Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib/gitlab/middleware/path_traversal_check_spec.rb')
-rw-r--r--spec/lib/gitlab/middleware/path_traversal_check_spec.rb197
1 files changed, 197 insertions, 0 deletions
diff --git a/spec/lib/gitlab/middleware/path_traversal_check_spec.rb b/spec/lib/gitlab/middleware/path_traversal_check_spec.rb
new file mode 100644
index 00000000000..3d334a60c49
--- /dev/null
+++ b/spec/lib/gitlab/middleware/path_traversal_check_spec.rb
@@ -0,0 +1,197 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Gitlab::Middleware::PathTraversalCheck, feature_category: :shared do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:fake_response) { [200, { 'Content-Type' => 'text/plain' }, ['OK']] }
+ let(:fake_app) { ->(_) { fake_response } }
+ let(:middleware) { described_class.new(fake_app) }
+
+ describe '#call' do
+ let(:fullpath) { ::Rack::Request.new(env).fullpath }
+ let(:decoded_fullpath) { CGI.unescape(fullpath) }
+
+ let(:env) do
+ Rack::MockRequest.env_for(
+ path,
+ method: method,
+ params: query_params
+ )
+ end
+
+ subject { middleware.call(env) }
+
+ shared_examples 'no issue' do
+ it 'measures and logs the execution time' do
+ expect(::Gitlab::PathTraversal)
+ .to receive(:check_path_traversal!)
+ .with(decoded_fullpath, skip_decoding: true)
+ .and_call_original
+ expect(::Gitlab::AppLogger)
+ .to receive(:warn)
+ .with({ class_name: described_class.name, duration_ms: instance_of(Float) })
+ .and_call_original
+
+ expect(subject).to eq(fake_response)
+ end
+
+ context 'with log_execution_time_path_traversal_middleware disabled' do
+ before do
+ stub_feature_flags(log_execution_time_path_traversal_middleware: false)
+ end
+
+ it 'does nothing' do
+ expect(::Gitlab::PathTraversal)
+ .to receive(:check_path_traversal!)
+ .with(decoded_fullpath, skip_decoding: true)
+ .and_call_original
+ expect(::Gitlab::AppLogger)
+ .not_to receive(:warn)
+
+ expect(subject).to eq(fake_response)
+ end
+ end
+ end
+
+ shared_examples 'path traversal' do
+ it 'logs the problem and measures the execution time' do
+ expect(::Gitlab::PathTraversal)
+ .to receive(:check_path_traversal!)
+ .with(decoded_fullpath, skip_decoding: true)
+ .and_call_original
+ expect(::Gitlab::AppLogger)
+ .to receive(:warn)
+ .with({ message: described_class::PATH_TRAVERSAL_MESSAGE, path: instance_of(String) })
+ expect(::Gitlab::AppLogger)
+ .to receive(:warn)
+ .with({
+ class_name: described_class.name,
+ duration_ms: instance_of(Float),
+ message: described_class::PATH_TRAVERSAL_MESSAGE,
+ fullpath: fullpath
+ }).and_call_original
+
+ expect(subject).to eq(fake_response)
+ end
+
+ context 'with log_execution_time_path_traversal_middleware disabled' do
+ before do
+ stub_feature_flags(log_execution_time_path_traversal_middleware: false)
+ end
+
+ it 'logs the problem without the execution time' do
+ expect(::Gitlab::PathTraversal)
+ .to receive(:check_path_traversal!)
+ .with(decoded_fullpath, skip_decoding: true)
+ .and_call_original
+ expect(::Gitlab::AppLogger)
+ .to receive(:warn)
+ .with({ message: described_class::PATH_TRAVERSAL_MESSAGE, path: instance_of(String) })
+ expect(::Gitlab::AppLogger)
+ .to receive(:warn)
+ .with({
+ class_name: described_class.name,
+ message: described_class::PATH_TRAVERSAL_MESSAGE,
+ fullpath: fullpath
+ }).and_call_original
+
+ expect(subject).to eq(fake_response)
+ end
+ end
+ end
+
+ # we use Rack request.full_path, this will dump the accessed path and
+ # the query string. The query string is only for GETs requests.
+ # Hence different expectation (when params are set) for GETs and
+ # the other methods (see below)
+ context 'when using get' do
+ let(:method) { 'get' }
+
+ where(:path, :query_params, :shared_example_name) do
+ '/foo/bar' | {} | 'no issue'
+ '/foo/../bar' | {} | 'path traversal'
+ '/foo%2Fbar' | {} | 'no issue'
+ '/foo%2F..%2Fbar' | {} | 'path traversal'
+ '/foo%252F..%252Fbar' | {} | 'no issue'
+ '/foo/bar' | { x: 'foo' } | 'no issue'
+ '/foo/bar' | { x: 'foo/../bar' } | 'path traversal'
+ '/foo/bar' | { x: 'foo%2Fbar' } | 'no issue'
+ '/foo/bar' | { x: 'foo%2F..%2Fbar' } | 'no issue'
+ '/foo/bar' | { x: 'foo%252F..%252Fbar' } | 'no issue'
+ '/foo%2F..%2Fbar' | { x: 'foo%252F..%252Fbar' } | 'path traversal'
+ end
+
+ with_them do
+ it_behaves_like params[:shared_example_name]
+ end
+
+ context 'with a issues search path' do
+ let(:query_params) { {} }
+ let(:path) do
+ 'project/-/issues/?sort=updated_desc&milestone_title=16.0&search=Release%20%252525&first_page_size=20'
+ end
+
+ it_behaves_like 'no issue'
+ end
+ end
+
+ %w[post put post delete patch].each do |http_method|
+ context "when using #{http_method}" do
+ let(:method) { http_method }
+
+ where(:path, :query_params, :shared_example_name) do
+ '/foo/bar' | {} | 'no issue'
+ '/foo/../bar' | {} | 'path traversal'
+ '/foo%2Fbar' | {} | 'no issue'
+ '/foo%2F..%2Fbar' | {} | 'path traversal'
+ '/foo%252F..%252Fbar' | {} | 'no issue'
+ '/foo/bar' | { x: 'foo' } | 'no issue'
+ '/foo/bar' | { x: 'foo/../bar' } | 'no issue'
+ '/foo/bar' | { x: 'foo%2Fbar' } | 'no issue'
+ '/foo/bar' | { x: 'foo%2F..%2Fbar' } | 'no issue'
+ '/foo/bar' | { x: 'foo%252F..%252Fbar' } | 'no issue'
+ '/foo%2F..%2Fbar' | { x: 'foo%252F..%252Fbar' } | 'path traversal'
+ end
+
+ with_them do
+ it_behaves_like params[:shared_example_name]
+ end
+ end
+ end
+
+ context 'with check_path_traversal_middleware disabled' do
+ before do
+ stub_feature_flags(check_path_traversal_middleware: false)
+ end
+
+ where(:path, :query_params) do
+ '/foo/bar' | {}
+ '/foo/../bar' | {}
+ '/foo%2Fbar' | {}
+ '/foo%2F..%2Fbar' | {}
+ '/foo%252F..%252Fbar' | {}
+ '/foo/bar' | { x: 'foo' }
+ '/foo/bar' | { x: 'foo/../bar' }
+ '/foo/bar' | { x: 'foo%2Fbar' }
+ '/foo/bar' | { x: 'foo%2F..%2Fbar' }
+ '/foo/bar' | { x: 'foo%252F..%252Fbar' }
+ end
+
+ with_them do
+ %w[get post put post delete patch].each do |http_method|
+ context "when using #{http_method}" do
+ let(:method) { http_method }
+
+ it 'does not check for path traversals' do
+ expect(::Gitlab::PathTraversal).not_to receive(:check_path_traversal!)
+
+ subject
+ end
+ end
+ end
+ end
+ end
+ end
+end