From e2ec97a92e6393dd0adeed39c77ff2b4eba0aed9 Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Sat, 7 Jul 2018 19:30:16 +0200 Subject: Add FileUploader.root to allowed upload paths Currently we check if uploaded file is under `Gitlab.config.uploads.storage_path`, the problem is that uploads are placed in `uploads` subdirectory which is symlink. In allow_path? method we check real (expanded) paths, which causes that `Gitlab.config.uploads.storage_path` is expaned into symlink path and there is a mismatch with upload file path. By adding `Gitlab.config.uploads.storage_path/uploads` into allowed paths, this path is expaned during path check. `Gitlab.config.uploads.storage_path` is left there intentionally in case some uploader wouldn't use `uploads` subdir. --- changelogs/unreleased/jprovazn-upload-symlink.yml | 5 +++++ lib/gitlab/middleware/multipart.rb | 2 +- lib/uploaded_file.rb | 5 +++-- 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/jprovazn-upload-symlink.yml diff --git a/changelogs/unreleased/jprovazn-upload-symlink.yml b/changelogs/unreleased/jprovazn-upload-symlink.yml new file mode 100644 index 00000000000..265791d332f --- /dev/null +++ b/changelogs/unreleased/jprovazn-upload-symlink.yml @@ -0,0 +1,5 @@ +--- +title: Add /uploads subdirectory to allowed upload paths. +merge_request: +author: +type: fixed diff --git a/lib/gitlab/middleware/multipart.rb b/lib/gitlab/middleware/multipart.rb index 9753be6d5c3..18f91db98fc 100644 --- a/lib/gitlab/middleware/multipart.rb +++ b/lib/gitlab/middleware/multipart.rb @@ -84,7 +84,7 @@ module Gitlab def open_file(params, key) ::UploadedFile.from_params( params, key, - Gitlab.config.uploads.storage_path) + [FileUploader.root, Gitlab.config.uploads.storage_path]) end end diff --git a/lib/uploaded_file.rb b/lib/uploaded_file.rb index 5dc85b2baea..0172461670b 100644 --- a/lib/uploaded_file.rb +++ b/lib/uploaded_file.rb @@ -28,7 +28,7 @@ class UploadedFile @tempfile = File.new(path, 'rb') end - def self.from_params(params, field, upload_path) + def self.from_params(params, field, upload_paths) unless params["#{field}.path"] raise InvalidPathError, "file is invalid" if params["#{field}.remote_id"] @@ -37,7 +37,8 @@ class UploadedFile file_path = File.realpath(params["#{field}.path"]) - unless self.allowed_path?(file_path, [upload_path, Dir.tmpdir].compact) + paths = Array.wrap(upload_paths) << Dir.tmpdir + unless self.allowed_path?(file_path, paths.compact) raise InvalidPathError, "insecure path used '#{file_path}'" end -- cgit v1.2.1 From 6b2ebea7dc036c2b21bd47f2955639f9b257b568 Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Mon, 9 Jul 2018 13:06:12 +0200 Subject: Added test and used Array() instead of .wrap --- lib/uploaded_file.rb | 2 +- spec/lib/gitlab/middleware/multipart_spec.rb | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/lib/uploaded_file.rb b/lib/uploaded_file.rb index 0172461670b..4b9cb59eab5 100644 --- a/lib/uploaded_file.rb +++ b/lib/uploaded_file.rb @@ -37,7 +37,7 @@ class UploadedFile file_path = File.realpath(params["#{field}.path"]) - paths = Array.wrap(upload_paths) << Dir.tmpdir + paths = Array(upload_paths) << Dir.tmpdir unless self.allowed_path?(file_path, paths.compact) raise InvalidPathError, "insecure path used '#{file_path}'" end diff --git a/spec/lib/gitlab/middleware/multipart_spec.rb b/spec/lib/gitlab/middleware/multipart_spec.rb index b4837a1689a..f788f8ee276 100644 --- a/spec/lib/gitlab/middleware/multipart_spec.rb +++ b/spec/lib/gitlab/middleware/multipart_spec.rb @@ -75,6 +75,33 @@ describe Gitlab::Middleware::Multipart do it_behaves_like 'multipart upload files' end + it 'allows symlinks for uploads dir' do + Tempfile.open('two-levels') do |tempfile| + symlinked_dir = '/some/dir/uploads' + symlinked_path = File.join(symlinked_dir, File.basename(tempfile.path)) + env = post_env({ 'file' => symlinked_path }, { 'file.name' => original_filename, 'file.path' => symlinked_path }, Gitlab::Workhorse.secret, 'gitlab-workhorse') + + allow(FileUploader).to receive(:root).and_return(symlinked_dir) + allow(UploadedFile).to receive(:allowed_paths).and_return([symlinked_dir, Gitlab.config.uploads.storage_path]) + allow(File).to receive(:realpath).and_call_original + allow(File).to receive(:realpath).with(symlinked_dir).and_return(Dir.tmpdir) + allow(File).to receive(:realpath).with(symlinked_path).and_return(tempfile.path) + allow(File).to receive(:exist?).and_call_original + allow(File).to receive(:exist?).with(symlinked_dir).and_return(true) + + # override Dir.tmpdir because this dir is in the list of allowed paths + # and it would match FileUploader.root path (which in this test is linked + # to /tmp too) + allow(Dir).to receive(:tmpdir).and_return(File.join(Dir.tmpdir, 'tmpsubdir')) + + expect(app).to receive(:call) do |env| + expect(Rack::Request.new(env).params['file']).to be_a(::UploadedFile) + end + + middleware.call(env) + end + end + def post_env(rewritten_fields, params, secret, issuer) token = JWT.encode({ 'iss' => issuer, 'rewritten_fields' => rewritten_fields }, secret, 'HS256') Rack::MockRequest.env_for( -- cgit v1.2.1