From d01f57b970016d4fd4a83a92c4153485e018a841 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Tue, 18 Mar 2025 05:31:37 -0500 Subject: [PATCH] Protect static file routes from directory traversal attacks. Currently using a URL like `http://baseURL/../../../../../../../../../../../../../../../../etc/passwd` returns the file `/etc/passwd`. That of course is a security vulnerability that should not happen. This was recently discovered and fixed for webwork2, and basically the same solution is used to fix it here. Below is a minimal script that can be used to test this: ```perl use Mojo::Base -strict; use Mojo::UserAgent; my $server = 'http://localhost:3001'; my $ua = Mojo::UserAgent->new; my $res = $ua->get("$server/../../../../../../../../../../../../../../../../etc/passwd")->result; if ($res->is_success) { say 'success'; say $res->body } elsif ($res->is_error) { say 'error'; say $res->message } elsif ($res->code == 301) { say '301'; say $res->headers->location } else { say 'Whatever...' } Mojo::IOLoop->start unless Mojo::IOLoop->is_running; ``` Set `$server` to the URL of your renderer including the `$baseURL`. With the develop branch the above script will output "success" followed by the contents of the `/etc/passwd` file on your server. With this branch it will output "error" followed by "Not found". --- lib/RenderApp/Controller/StaticFiles.pm | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/RenderApp/Controller/StaticFiles.pm b/lib/RenderApp/Controller/StaticFiles.pm index 7c1149185..2481d849b 100644 --- a/lib/RenderApp/Controller/StaticFiles.pm +++ b/lib/RenderApp/Controller/StaticFiles.pm @@ -3,9 +3,10 @@ use Mojo::Base 'Mojolicious::Controller', -signatures; use Mojo::File qw(path); -sub reply_with_file_if_readable ($c, $file) { - if (-r $file) { - return $c->reply->file($file); +sub reply_with_file_if_readable ($c, $directory, $file) { + my $filePath = $directory->child($file); + if (-r $filePath && $filePath->realpath =~ /^$directory/) { + return $c->reply->file($filePath); } else { return $c->render(data => 'File not found', status => 404); } @@ -13,23 +14,23 @@ sub reply_with_file_if_readable ($c, $file) { # Route requests for pg_files/CAPA_Graphics to render root Contrib/CAPA/CAPA_Graphics sub CAPA_graphics_file ($c) { - return $c->reply_with_file_if_readable($c->app->home->child('Contrib/CAPA/CAPA_Graphics', $c->stash('static'))); + return $c->reply_with_file_if_readable($c->app->home->child('Contrib/CAPA/CAPA_Graphics'), $c->stash('static')); } # Route requests for pg_files to the render root tmp. The # only requests should be for files in the temporary directory. # FIXME: Perhaps this directory should be configurable. sub temp_file ($c) { - $c->reply_with_file_if_readable($c->app->home->child('tmp', $c->stash('static'))); + return $c->reply_with_file_if_readable($c->app->home->child('tmp'), $c->stash('static')); } # Route request to pg_files to lib/PG/htdocs. sub pg_file ($c) { - $c->reply_with_file_if_readable(path($ENV{PG_ROOT}, 'htdocs', $c->stash('static'))); + return $c->reply_with_file_if_readable(path($ENV{PG_ROOT}, 'htdocs'), $c->stash('static')); } sub public_file ($c) { - $c->reply_with_file_if_readable($c->app->home->child('public', $c->stash('static'))); + $c->reply_with_file_if_readable($c->app->home->child('public'), $c->stash('static')); } 1;