diff --git a/lib/Dancer2/Core/App.pm b/lib/Dancer2/Core/App.pm index 4c94160b3..f90601275 100644 --- a/lib/Dancer2/Core/App.pm +++ b/lib/Dancer2/Core/App.pm @@ -1044,8 +1044,14 @@ sub send_file { $self->with_return->( $self->response ); }; - $file_path = Path::Tiny::path( $dir, $path ); + # resolve relative paths (with '../') as much as possible + $file_path = Path::Tiny::path( $dir, $path )->realpath; + # We need to check whether they are trying to access + # a directory outside their scope + $err_response->(403) if !Path::Tiny::path($dir)->realpath->subsumes($file_path); + + # other error checks $err_response->(403) if !$file_path->exists; $err_response->(404) if !$file_path->is_file; $err_response->(403) if !-r $file_path; diff --git a/t/dsl/send_file.t b/t/dsl/send_file.t index 0ea2594ba..48cd3cd7d 100644 --- a/t/dsl/send_file.t +++ b/t/dsl/send_file.t @@ -23,6 +23,10 @@ use Ref::Util qw; send_file 'index.html'; }; + get '/illegal' => sub { + send_file '../index.html'; + }; + prefix '/some' => sub { get '/image' => sub { send_file '1x1.png'; @@ -144,6 +148,17 @@ test_psgi $app, sub { ok($r->is_success, 'send_file returns success'); is($r->header('Content-Disposition'), 'inline; filename="1x1.png"', 'send_file returns correct inline Content-Disposition'); }; + + subtest "Illegal path" => sub { + my $r = $cb->( GET '/illegal' ); + is( $r->code, 403, 'Illegal path returns 403' ); + is( + $r->content, + 'Forbidden', + 'Text content contains UTF-8 characters', + ); + }; + }; done_testing;