blob: e806f6478b76848e928a103539a9e452b31552df (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
|
# frozen_string_literal: true
require 'spec_helper'
require "rack/test"
RSpec.describe Gitlab::Middleware::HandleMalformedStrings do
include GitHttpHelpers
let(:null_byte) { "\u0000" }
let(:escaped_null_byte) { "%00" }
let(:invalid_string) { "mal\xC0formed" }
let(:escaped_invalid_string) { "mal%c0formed" }
let(:error_400) { [400, { 'Content-Type' => 'text/plain' }, ['Bad Request']] }
let(:app) { double(:app) }
subject { described_class.new(app) }
before do
allow(app).to receive(:call) do |args|
args
end
end
def env_for(params = {})
Rack::MockRequest.env_for('/', { params: params })
end
context 'in the URL' do
it 'rejects null bytes' do
# We have to create the env separately or Rack::MockRequest complains about invalid URI
env = env_for
env['PATH_INFO'] = "/someplace/witha#{null_byte}nullbyte"
expect(subject.call(env)).to eq error_400
end
it 'rejects escaped null bytes' do
# We have to create the env separately or Rack::MockRequest complains about invalid URI
env = env_for
env['PATH_INFO'] = "/someplace/withan#{escaped_null_byte}escaped nullbyte"
expect(subject.call(env)).to eq error_400
end
it 'rejects malformed strings' do
# We have to create the env separately or Rack::MockRequest complains about invalid URI
env = env_for
env['PATH_INFO'] = "/someplace/with_an/#{invalid_string}"
expect(subject.call(env)).to eq error_400
end
it 'rejects escaped malformed strings' do
# We have to create the env separately or Rack::MockRequest complains about invalid URI
env = env_for
env['PATH_INFO'] = "/someplace/with_an/#{escaped_invalid_string}"
expect(subject.call(env)).to eq error_400
end
end
context 'in authorization headers' do
let(:problematic_input) { null_byte }
shared_examples 'rejecting invalid input' do
it 'rejects problematic input in the password' do
env = env_for.merge(auth_env("username", "password#{problematic_input}encoded", nil))
expect(subject.call(env)).to eq error_400
end
it 'rejects problematic input in the username' do
env = env_for.merge(auth_env("username#{problematic_input}", "passwordencoded", nil))
expect(subject.call(env)).to eq error_400
end
it 'rejects problematic input in non-basic-auth tokens' do
env = env_for.merge('HTTP_AUTHORIZATION' => "GL-Geo hello#{problematic_input}world")
expect(subject.call(env)).to eq error_400
end
end
it_behaves_like 'rejecting invalid input' do
let(:problematic_input) { null_byte }
end
it_behaves_like 'rejecting invalid input' do
let(:problematic_input) { invalid_string }
end
it_behaves_like 'rejecting invalid input' do
let(:problematic_input) { "\xC3" }
end
it 'does not reject correct non-basic-auth tokens' do
# This token is known to include a null-byte when we were to try to decode it
# as Base64, while it wasn't encoded at such.
special_token = 'GL-Geo ta8KakZWpu0AcledQ6n0:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoie1wic2NvcGVcIjpcImdlb19hcGlcIn0iLCJqdGkiOiIwYWFmNzVlYi1lNWRkLTRkZjEtODQzYi1lM2E5ODhhNDMwMzIiLCJpYXQiOjE2MDQ3MDI4NzUsIm5iZiI6MTYwNDcwMjg3MCwiZXhwIjoxNjA0NzAyOTM1fQ.NcgDipDyxSP5uSzxc01ylzH4GkTxJRflNNjT7U6fpg4'
expect(Base64.decode64(special_token)).to include(null_byte)
env = env_for.merge('HTTP_AUTHORIZATION' => special_token)
expect(subject.call(env)).not_to eq error_400
end
end
context 'in params' do
shared_examples_for 'checks params' do
it 'rejects bad params in a top level param' do
env = env_for(name: "null#{problematic_input}byte")
expect(subject.call(env)).to eq error_400
end
it "rejects bad params for hashes with strings" do
env = env_for(name: { inner_key: "I am #{problematic_input} bad" })
expect(subject.call(env)).to eq error_400
end
it "rejects bad params for arrays with strings" do
env = env_for(name: ["I am #{problematic_input} bad"])
expect(subject.call(env)).to eq error_400
end
it "rejects bad params for arrays containing hashes with string values" do
env = env_for(name: [
{
inner_key: "I am #{problematic_input} bad"
}
])
expect(subject.call(env)).to eq error_400
end
end
context 'with null byte' do
let(:problematic_input) { null_byte }
it_behaves_like 'checks params'
it "gives up and does not reject too deeply nested params" do
env = env_for(name: [
{
inner_key: { deeper_key: [{ hash_inside_array_key: "I am #{problematic_input} bad" }] }
}
])
expect(subject.call(env)).not_to eq error_400
end
end
context 'with malformed strings' do
it_behaves_like 'checks params' do
let(:problematic_input) { invalid_string }
end
end
end
context 'without problematic input' do
it "does not error for strings" do
env = env_for(name: "safe name")
expect(subject.call(env)).not_to eq error_400
end
it "does not error with no params" do
env = env_for
expect(subject.call(env)).not_to eq error_400
end
end
it 'does not modify the env' do
env = env_for
expect { subject.call(env) }.not_to change { env }
end
end
|