[ruby/cgi] Check cookie name/path/domain characters
https://hackerone.com/reports/1204977 https://github.com/ruby/cgi/commit/30107a4797
This commit is contained in:
parent
cf05c202ce
commit
c05f85f373
@ -40,6 +40,10 @@ class CGI
|
|||||||
class Cookie < Array
|
class Cookie < Array
|
||||||
@@accept_charset="UTF-8" unless defined?(@@accept_charset)
|
@@accept_charset="UTF-8" unless defined?(@@accept_charset)
|
||||||
|
|
||||||
|
TOKEN_RE = %r"\A[[!-~]&&[^()<>@,;:\\\"/?=\[\]{}]]+\z"
|
||||||
|
PATH_VALUE_RE = %r"\A[[ -~]&&[^;]]*\z"
|
||||||
|
DOMAIN_VALUE_RE = %r"\A(?<label>[A-Za-z][-A-Za-z0-9]*[A-Za-z0-9])(?:\.\g<label>)*\z"
|
||||||
|
|
||||||
# Create a new CGI::Cookie object.
|
# Create a new CGI::Cookie object.
|
||||||
#
|
#
|
||||||
# :call-seq:
|
# :call-seq:
|
||||||
@ -72,8 +76,8 @@ class CGI
|
|||||||
@domain = nil
|
@domain = nil
|
||||||
@expires = nil
|
@expires = nil
|
||||||
if name.kind_of?(String)
|
if name.kind_of?(String)
|
||||||
@name = name
|
self.name = name
|
||||||
@path = (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "")
|
self.path = (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "")
|
||||||
@secure = false
|
@secure = false
|
||||||
@httponly = false
|
@httponly = false
|
||||||
return super(value)
|
return super(value)
|
||||||
@ -84,11 +88,11 @@ class CGI
|
|||||||
raise ArgumentError, "`name' required"
|
raise ArgumentError, "`name' required"
|
||||||
end
|
end
|
||||||
|
|
||||||
@name = options["name"]
|
self.name = options["name"]
|
||||||
value = Array(options["value"])
|
value = Array(options["value"])
|
||||||
# simple support for IE
|
# simple support for IE
|
||||||
@path = options["path"] || (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "")
|
self.path = options["path"] || (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "")
|
||||||
@domain = options["domain"]
|
self.domain = options["domain"]
|
||||||
@expires = options["expires"]
|
@expires = options["expires"]
|
||||||
@secure = options["secure"] == true
|
@secure = options["secure"] == true
|
||||||
@httponly = options["httponly"] == true
|
@httponly = options["httponly"] == true
|
||||||
@ -97,11 +101,35 @@ class CGI
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Name of this cookie, as a +String+
|
# Name of this cookie, as a +String+
|
||||||
attr_accessor :name
|
attr_reader :name
|
||||||
|
# Set name of this cookie
|
||||||
|
def name=(str)
|
||||||
|
if str and !TOKEN_RE.match?(str)
|
||||||
|
raise ArgumentError, "invalid name: #{str.dump}"
|
||||||
|
end
|
||||||
|
@name = str
|
||||||
|
end
|
||||||
|
|
||||||
# Path for which this cookie applies, as a +String+
|
# Path for which this cookie applies, as a +String+
|
||||||
attr_accessor :path
|
attr_reader :path
|
||||||
|
# Set path for which this cookie applies
|
||||||
|
def path=(str)
|
||||||
|
if str and !PATH_VALUE_RE.match?(str)
|
||||||
|
raise ArgumentError, "invalid path: #{str.dump}"
|
||||||
|
end
|
||||||
|
@path = str
|
||||||
|
end
|
||||||
|
|
||||||
# Domain for which this cookie applies, as a +String+
|
# Domain for which this cookie applies, as a +String+
|
||||||
attr_accessor :domain
|
attr_reader :domain
|
||||||
|
# Set domain for which this cookie applies
|
||||||
|
def domain=(str)
|
||||||
|
if str and ((str = str.b).bytesize > 255 or !DOMAIN_VALUE_RE.match?(str))
|
||||||
|
raise ArgumentError, "invalid domain: #{str.dump}"
|
||||||
|
end
|
||||||
|
@domain = str
|
||||||
|
end
|
||||||
|
|
||||||
# Time at which this cookie expires, as a +Time+
|
# Time at which this cookie expires, as a +Time+
|
||||||
attr_accessor :expires
|
attr_accessor :expires
|
||||||
# True if this cookie is secure; false otherwise
|
# True if this cookie is secure; false otherwise
|
||||||
|
@ -118,6 +118,70 @@ class CGICookieTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def test_cgi_cookie_domain_injection_into_name
|
||||||
|
name = "a=b; domain=example.com;"
|
||||||
|
path = "/"
|
||||||
|
domain = "example.jp"
|
||||||
|
assert_raise(ArgumentError) do
|
||||||
|
CGI::Cookie.new('name' => name,
|
||||||
|
'value' => "value",
|
||||||
|
'domain' => domain,
|
||||||
|
'path' => path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def test_cgi_cookie_newline_injection_into_name
|
||||||
|
name = "a=b;\r\nLocation: http://example.com#"
|
||||||
|
path = "/"
|
||||||
|
domain = "example.jp"
|
||||||
|
assert_raise(ArgumentError) do
|
||||||
|
CGI::Cookie.new('name' => name,
|
||||||
|
'value' => "value",
|
||||||
|
'domain' => domain,
|
||||||
|
'path' => path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def test_cgi_cookie_multibyte_injection_into_name
|
||||||
|
name = "a=b;\u3042"
|
||||||
|
path = "/"
|
||||||
|
domain = "example.jp"
|
||||||
|
assert_raise(ArgumentError) do
|
||||||
|
CGI::Cookie.new('name' => name,
|
||||||
|
'value' => "value",
|
||||||
|
'domain' => domain,
|
||||||
|
'path' => path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def test_cgi_cookie_injection_into_path
|
||||||
|
name = "name"
|
||||||
|
path = "/; samesite=none"
|
||||||
|
domain = "example.jp"
|
||||||
|
assert_raise(ArgumentError) do
|
||||||
|
CGI::Cookie.new('name' => name,
|
||||||
|
'value' => "value",
|
||||||
|
'domain' => domain,
|
||||||
|
'path' => path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def test_cgi_cookie_injection_into_domain
|
||||||
|
name = "name"
|
||||||
|
path = "/"
|
||||||
|
domain = "example.jp; samesite=none"
|
||||||
|
assert_raise(ArgumentError) do
|
||||||
|
CGI::Cookie.new('name' => name,
|
||||||
|
'value' => "value",
|
||||||
|
'domain' => domain,
|
||||||
|
'path' => path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
instance_methods.each do |method|
|
instance_methods.each do |method|
|
||||||
private method if method =~ /^test_(.*)/ && $1 != ENV['TEST']
|
private method if method =~ /^test_(.*)/ && $1 != ENV['TEST']
|
||||||
|
Loading…
x
Reference in New Issue
Block a user