Skip to content

Commit 30107a4

Browse files
nobuhsbt
authored andcommitted
Check cookie name/path/domain characters
https://hackerone.com/reports/1204977
1 parent c1ffa3a commit 30107a4

File tree

2 files changed

+100
-8
lines changed

2 files changed

+100
-8
lines changed

lib/cgi/cookie.rb

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ class CGI
4040
class Cookie < Array
4141
@@accept_charset="UTF-8" unless defined?(@@accept_charset)
4242

43+
TOKEN_RE = %r"\A[[!-~]&&[^()<>@,;:\\\"/?=\[\]{}]]+\z"
44+
PATH_VALUE_RE = %r"\A[[ -~]&&[^;]]*\z"
45+
DOMAIN_VALUE_RE = %r"\A(?<label>[A-Za-z][-A-Za-z0-9]*[A-Za-z0-9])(?:\.\g<label>)*\z"
46+
4347
# Create a new CGI::Cookie object.
4448
#
4549
# :call-seq:
@@ -72,8 +76,8 @@ def initialize(name = "", *value)
7276
@domain = nil
7377
@expires = nil
7478
if name.kind_of?(String)
75-
@name = name
76-
@path = (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "")
79+
self.name = name
80+
self.path = (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "")
7781
@secure = false
7882
@httponly = false
7983
return super(value)
@@ -84,11 +88,11 @@ def initialize(name = "", *value)
8488
raise ArgumentError, "`name' required"
8589
end
8690

87-
@name = options["name"]
91+
self.name = options["name"]
8892
value = Array(options["value"])
8993
# simple support for IE
90-
@path = options["path"] || (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "")
91-
@domain = options["domain"]
94+
self.path = options["path"] || (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "")
95+
self.domain = options["domain"]
9296
@expires = options["expires"]
9397
@secure = options["secure"] == true
9498
@httponly = options["httponly"] == true
@@ -97,11 +101,35 @@ def initialize(name = "", *value)
97101
end
98102

99103
# Name of this cookie, as a +String+
100-
attr_accessor :name
104+
attr_reader :name
105+
# Set name of this cookie
106+
def name=(str)
107+
if str and !TOKEN_RE.match?(str)
108+
raise ArgumentError, "invalid name: #{str.dump}"
109+
end
110+
@name = str
111+
end
112+
101113
# Path for which this cookie applies, as a +String+
102-
attr_accessor :path
114+
attr_reader :path
115+
# Set path for which this cookie applies
116+
def path=(str)
117+
if str and !PATH_VALUE_RE.match?(str)
118+
raise ArgumentError, "invalid path: #{str.dump}"
119+
end
120+
@path = str
121+
end
122+
103123
# Domain for which this cookie applies, as a +String+
104-
attr_accessor :domain
124+
attr_reader :domain
125+
# Set domain for which this cookie applies
126+
def domain=(str)
127+
if str and ((str = str.b).bytesize > 255 or !DOMAIN_VALUE_RE.match?(str))
128+
raise ArgumentError, "invalid domain: #{str.dump}"
129+
end
130+
@domain = str
131+
end
132+
105133
# Time at which this cookie expires, as a +Time+
106134
attr_accessor :expires
107135
# True if this cookie is secure; false otherwise

test/cgi/test_cgi_cookie.rb

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,70 @@ def test_cgi_cookie_arrayinterface
118118
end
119119

120120

121+
def test_cgi_cookie_domain_injection_into_name
122+
name = "a=b; domain=example.com;"
123+
path = "/"
124+
domain = "example.jp"
125+
assert_raise(ArgumentError) do
126+
CGI::Cookie.new('name' => name,
127+
'value' => "value",
128+
'domain' => domain,
129+
'path' => path)
130+
end
131+
end
132+
133+
134+
def test_cgi_cookie_newline_injection_into_name
135+
name = "a=b;\r\nLocation: http://example.com#"
136+
path = "/"
137+
domain = "example.jp"
138+
assert_raise(ArgumentError) do
139+
CGI::Cookie.new('name' => name,
140+
'value' => "value",
141+
'domain' => domain,
142+
'path' => path)
143+
end
144+
end
145+
146+
147+
def test_cgi_cookie_multibyte_injection_into_name
148+
name = "a=b;\u3042"
149+
path = "/"
150+
domain = "example.jp"
151+
assert_raise(ArgumentError) do
152+
CGI::Cookie.new('name' => name,
153+
'value' => "value",
154+
'domain' => domain,
155+
'path' => path)
156+
end
157+
end
158+
159+
160+
def test_cgi_cookie_injection_into_path
161+
name = "name"
162+
path = "/; samesite=none"
163+
domain = "example.jp"
164+
assert_raise(ArgumentError) do
165+
CGI::Cookie.new('name' => name,
166+
'value' => "value",
167+
'domain' => domain,
168+
'path' => path)
169+
end
170+
end
171+
172+
173+
def test_cgi_cookie_injection_into_domain
174+
name = "name"
175+
path = "/"
176+
domain = "example.jp; samesite=none"
177+
assert_raise(ArgumentError) do
178+
CGI::Cookie.new('name' => name,
179+
'value' => "value",
180+
'domain' => domain,
181+
'path' => path)
182+
end
183+
end
184+
121185

122186
instance_methods.each do |method|
123187
private method if method =~ /^test_(.*)/ && $1 != ENV['TEST']

0 commit comments

Comments
 (0)