Skip to content

Commit 4f13ea5

Browse files
hsbtclaude
andcommitted
Reject control characters and colon in header field names
Field values and the request line are already validated against CR/LF, but field names were interpolated into the request as-is, allowing header injection via the key. Validate names in set_field and initialize_http_header, which cover all paths into @Header with a user-supplied key. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 0c045fa commit 4f13ea5

2 files changed

Lines changed: 21 additions & 0 deletions

File tree

lib/net/http/header.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ def initialize_http_header(initheader) #:nodoc:
196196
if key.to_s.bytesize > MAX_KEY_LENGTH
197197
raise ArgumentError, "too long (#{key.bytesize} bytes) header: #{key[0, 30].inspect}..."
198198
end
199+
validate_field_name(key)
199200
if value.to_s.bytesize > MAX_FIELD_LENGTH
200201
raise ArgumentError, "header #{key} has too long field value: #{value.bytesize}"
201202
end
@@ -270,7 +271,14 @@ def add_field(key, val)
270271
end
271272

272273
# :stopdoc:
274+
private def validate_field_name(key)
275+
if /[\x00-\x1f\x7f:]/n.match?(key.to_s.b)
276+
raise ArgumentError, "header field name cannot include control characters or colon: #{key.to_s[0, 30].inspect}"
277+
end
278+
end
279+
273280
private def set_field(key, val)
281+
validate_field_name(key)
274282
case val
275283
when Enumerable
276284
ary = []

test/net/http/test_httpheader.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,19 @@ def test_initialize
3030
assert_raise(ArgumentError){ @c.initialize_http_header("foo"=>"a\rb") }
3131
end
3232

33+
def test_invalid_field_name
34+
assert_raise(ArgumentError){ @c.initialize_http_header("foo\nbar"=>"abc") }
35+
assert_raise(ArgumentError){ @c.initialize_http_header("foo\rbar"=>"abc") }
36+
assert_raise(ArgumentError){ @c.initialize_http_header("foo:bar"=>"abc") }
37+
assert_raise(ArgumentError){ @c.initialize_http_header("foo\x00bar"=>"abc") }
38+
assert_raise(ArgumentError){ @c['foo'.b << 0x0a << 'bar'] = 'abc' }
39+
assert_raise(ArgumentError){ @c["foo\rbar"] = 'abc' }
40+
assert_raise(ArgumentError){ @c["foo:bar"] = 'abc' }
41+
assert_raise(ArgumentError){ @c["foo\x7fbar"] = 'abc' }
42+
assert_raise(ArgumentError){ @c.add_field "foo\nbar", 'abc' }
43+
assert_raise(ArgumentError){ @c.add_field "foo\nbar", ['abc'] }
44+
end
45+
3346
def test_initialize_with_broken_coderange
3447
error = RUBY_VERSION >= "3.2" ? Encoding::CompatibilityError : ArgumentError
3548
assert_raise(error){ @c.initialize_http_header("foo"=>"a\xff") }

0 commit comments

Comments
 (0)