summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorick Peterse <yorickpeterse@gmail.com>2019-02-27 14:20:44 +0000
committerYorick Peterse <yorickpeterse@gmail.com>2019-02-27 14:20:44 +0000
commit95f17f79dbf9179685c8fe08183dd6c75e944509 (patch)
tree3aa73b58cad71fc3cf514a538a6eea30c08069c0
parentf066bbec73af3fc34dfd81f4180ba75e91ea6cab (diff)
parent7fa334135f3afe82bd9ec7855cbc8bc7e92912dd (diff)
downloadgitlab-ce-95f17f79dbf9179685c8fe08183dd6c75e944509.tar.gz
Merge branch 'security-56348-11-7' into '11-7-stable'
Check snippet attached file to be moved is within designated directory See merge request gitlab/gitlabhq!2942
-rw-r--r--app/uploaders/file_mover.rb8
-rw-r--r--changelogs/unreleased/security-56348.yml5
-rw-r--r--spec/controllers/snippets_controller_spec.rb4
-rw-r--r--spec/support/helpers/file_mover_helpers.rb12
-rw-r--r--spec/uploaders/file_mover_spec.rb33
5 files changed, 60 insertions, 2 deletions
diff --git a/app/uploaders/file_mover.rb b/app/uploaders/file_mover.rb
index a7f8615e9ba..236b7ed2b3d 100644
--- a/app/uploaders/file_mover.rb
+++ b/app/uploaders/file_mover.rb
@@ -11,6 +11,8 @@ class FileMover
end
def execute
+ return unless valid?
+
move
if update_markdown
@@ -21,6 +23,12 @@ class FileMover
private
+ def valid?
+ Pathname.new(temp_file_path).realpath.to_path.start_with?(
+ (Pathname(temp_file_uploader.root) + temp_file_uploader.base_dir).to_path
+ )
+ end
+
def move
FileUtils.mkdir_p(File.dirname(file_path))
FileUtils.move(temp_file_path, file_path)
diff --git a/changelogs/unreleased/security-56348.yml b/changelogs/unreleased/security-56348.yml
new file mode 100644
index 00000000000..a289e4e9077
--- /dev/null
+++ b/changelogs/unreleased/security-56348.yml
@@ -0,0 +1,5 @@
+---
+title: Check snippet attached file to be moved is within designated directory
+merge_request:
+author:
+type: security
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index 5c6858dc7b2..77a94f26d8c 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -205,6 +205,8 @@ describe SnippetsController do
end
context 'when the snippet description contains a file' do
+ include FileMoverHelpers
+
let(:picture_file) { '/-/system/temp/secret56/picture.jpg' }
let(:text_file) { '/-/system/temp/secret78/text.txt' }
let(:description) do
@@ -215,6 +217,8 @@ describe SnippetsController do
before do
allow(FileUtils).to receive(:mkdir_p)
allow(FileUtils).to receive(:move)
+ stub_file_mover(text_file)
+ stub_file_mover(picture_file)
end
subject { create_snippet({ description: description }, { files: [picture_file, text_file] }) }
diff --git a/spec/support/helpers/file_mover_helpers.rb b/spec/support/helpers/file_mover_helpers.rb
new file mode 100644
index 00000000000..1ba7cc03354
--- /dev/null
+++ b/spec/support/helpers/file_mover_helpers.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module FileMoverHelpers
+ def stub_file_mover(file_path, stub_real_path: nil)
+ file_name = File.basename(file_path)
+ allow(Pathname).to receive(:new).and_call_original
+
+ expect_next_instance_of(Pathname, a_string_including(file_name)) do |pathname|
+ allow(pathname).to receive(:realpath) { stub_real_path || pathname.cleanpath }
+ end
+ end
+end
diff --git a/spec/uploaders/file_mover_spec.rb b/spec/uploaders/file_mover_spec.rb
index de29d0c943f..e474a714b10 100644
--- a/spec/uploaders/file_mover_spec.rb
+++ b/spec/uploaders/file_mover_spec.rb
@@ -1,8 +1,9 @@
require 'spec_helper'
describe FileMover do
+ include FileMoverHelpers
+
let(:filename) { 'banana_sample.gif' }
- let(:file) { fixture_file_upload(File.join('spec', 'fixtures', filename)) }
let(:temp_file_path) { File.join('uploads/-/system/temp', 'secret55', filename) }
let(:temp_description) do
@@ -12,7 +13,7 @@ describe FileMover do
let(:file_path) { File.join('uploads/-/system/personal_snippet', snippet.id.to_s, 'secret55', filename) }
let(:snippet) { create(:personal_snippet, description: temp_description) }
- subject { described_class.new(file_path, snippet).execute }
+ subject { described_class.new(temp_file_path, snippet).execute }
describe '#execute' do
before do
@@ -20,6 +21,8 @@ describe FileMover do
expect(FileUtils).to receive(:move).with(a_string_including(temp_file_path), a_string_including(file_path))
allow_any_instance_of(CarrierWave::SanitizedFile).to receive(:exists?).and_return(true)
allow_any_instance_of(CarrierWave::SanitizedFile).to receive(:size).and_return(10)
+
+ stub_file_mover(temp_file_path)
end
context 'when move and field update successful' do
@@ -66,4 +69,30 @@ describe FileMover do
end
end
end
+
+ context 'security' do
+ context 'when relative path is involved' do
+ let(:temp_file_path) { File.join('uploads/-/system/temp', '..', 'another_subdir_of_temp') }
+
+ it 'does not trigger move if path is outside designated directory' do
+ stub_file_mover('uploads/-/system/another_subdir_of_temp')
+ expect(FileUtils).not_to receive(:move)
+
+ subject
+
+ expect(snippet.reload.description).to eq(temp_description)
+ end
+ end
+
+ context 'when symlink is involved' do
+ it 'does not trigger move if path is outside designated directory' do
+ stub_file_mover(temp_file_path, stub_real_path: Pathname('/etc'))
+ expect(FileUtils).not_to receive(:move)
+
+ subject
+
+ expect(snippet.reload.description).to eq(temp_description)
+ end
+ end
+ end
end