From b86d7612420f9518a5f5cd23db74a0bfc021c92e Mon Sep 17 00:00:00 2001 From: Alexander Reelsen Date: Mon, 29 Jul 2019 23:01:26 +0200 Subject: [PATCH 1/2] Only follow symlinks within configured static file handler directory This adds an additional check to only follow symlinks that are within the configured public directory of a static file handler. This ensures a malicious user cannot link to any files outside of the public directory to prevent reading arbitrary files. --- spec/static_file_handler_spec.cr | 30 ++++++++++++++++++++++++++++++ src/kemal/static_file_handler.cr | 7 +++++++ 2 files changed, 37 insertions(+) diff --git a/spec/static_file_handler_spec.cr b/spec/static_file_handler_spec.cr index 1aac161b..142d8a4c 100644 --- a/spec/static_file_handler_spec.cr +++ b/spec/static_file_handler_spec.cr @@ -150,4 +150,34 @@ describe Kemal::StaticFileHandler do response = handle HTTP::Request.new("GET", "/dir/index.html") response.headers["Access-Control-Allow-Origin"].should eq("*") end + + it "should not follow symlinks outside of the configured directory" do + tempfile = File.tempfile("symlink", "txt") + symlink_path = "#{__DIR__}/static/dir/symlink.txt" + File.write tempfile.path, "my_super_secret" + begin + File.symlink(tempfile.path, symlink_path) + + response = handle HTTP::Request.new("GET", "/dir/symlink.txt") + response.body.should_not contain("my_super_secret") + response.status_code.should eq(404) + ensure + File.delete symlink_path + tempfile.delete + end + end + + it "should follow symlinks inside of the configured directory" do + symlink_path = "#{__DIR__}/static/dir/symlink.txt" + begin + File.symlink("#{__DIR__}/static/dir/test.txt", symlink_path) + + response = handle HTTP::Request.new("GET", "/dir/symlink.txt") + response.status_code.should eq(200) + response.body.should eq(File.read("#{__DIR__}/static/dir/test.txt")) + ensure + File.delete symlink_path + end + end + end diff --git a/src/kemal/static_file_handler.cr b/src/kemal/static_file_handler.cr index 11ae98e5..6dbd36ab 100644 --- a/src/kemal/static_file_handler.cr +++ b/src/kemal/static_file_handler.cr @@ -39,6 +39,13 @@ module Kemal end file_path = File.join(@public_dir, expanded_path) + + # prevent symlinks out of the public dir + if File.symlink?(file_path) && !File.real_path(file_path).starts_with?(@public_dir) + call_next(context) + return + end + is_dir = Dir.exists? file_path if request_path != expanded_path From 64c8571535ead56044b3768e579db17f957b0a6a Mon Sep 17 00:00:00 2001 From: Alexander Reelsen Date: Mon, 29 Jul 2019 23:56:01 +0200 Subject: [PATCH 2/2] no need to return next handler, return 404 --- src/kemal/static_file_handler.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kemal/static_file_handler.cr b/src/kemal/static_file_handler.cr index 6dbd36ab..49779fca 100644 --- a/src/kemal/static_file_handler.cr +++ b/src/kemal/static_file_handler.cr @@ -42,7 +42,7 @@ module Kemal # prevent symlinks out of the public dir if File.symlink?(file_path) && !File.real_path(file_path).starts_with?(@public_dir) - call_next(context) + context.response.status_code = 404 return end