[ruby/cgi] Prevent CRLF injection
Throw a RuntimeError if the HTTP response header contains CR or LF to prevent HTTP response splitting. https://hackerone.com/reports/1204695 https://github.com/ruby/cgi/commit/64c5045c0a
This commit is contained in:
parent
c05f85f373
commit
0e75b2f2e6
@ -188,17 +188,28 @@ class CGI
|
|||||||
# Using #header with the HTML5 tag maker will create a <header> element.
|
# Using #header with the HTML5 tag maker will create a <header> element.
|
||||||
alias :header :http_header
|
alias :header :http_header
|
||||||
|
|
||||||
|
def _no_crlf_check(str)
|
||||||
|
if str
|
||||||
|
str = str.to_s
|
||||||
|
raise "A HTTP status or header field must not include CR and LF" if str =~ /[\r\n]/
|
||||||
|
str
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
private :_no_crlf_check
|
||||||
|
|
||||||
def _header_for_string(content_type) #:nodoc:
|
def _header_for_string(content_type) #:nodoc:
|
||||||
buf = ''.dup
|
buf = ''.dup
|
||||||
if nph?()
|
if nph?()
|
||||||
buf << "#{$CGI_ENV['SERVER_PROTOCOL'] || 'HTTP/1.0'} 200 OK#{EOL}"
|
buf << "#{_no_crlf_check($CGI_ENV['SERVER_PROTOCOL']) || 'HTTP/1.0'} 200 OK#{EOL}"
|
||||||
buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}"
|
buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}"
|
||||||
buf << "Server: #{$CGI_ENV['SERVER_SOFTWARE']}#{EOL}"
|
buf << "Server: #{_no_crlf_check($CGI_ENV['SERVER_SOFTWARE'])}#{EOL}"
|
||||||
buf << "Connection: close#{EOL}"
|
buf << "Connection: close#{EOL}"
|
||||||
end
|
end
|
||||||
buf << "Content-Type: #{content_type}#{EOL}"
|
buf << "Content-Type: #{_no_crlf_check(content_type)}#{EOL}"
|
||||||
if @output_cookies
|
if @output_cookies
|
||||||
@output_cookies.each {|cookie| buf << "Set-Cookie: #{cookie}#{EOL}" }
|
@output_cookies.each {|cookie| buf << "Set-Cookie: #{_no_crlf_check(cookie)}#{EOL}" }
|
||||||
end
|
end
|
||||||
return buf
|
return buf
|
||||||
end # _header_for_string
|
end # _header_for_string
|
||||||
@ -213,9 +224,9 @@ class CGI
|
|||||||
## NPH
|
## NPH
|
||||||
options.delete('nph') if defined?(MOD_RUBY)
|
options.delete('nph') if defined?(MOD_RUBY)
|
||||||
if options.delete('nph') || nph?()
|
if options.delete('nph') || nph?()
|
||||||
protocol = $CGI_ENV['SERVER_PROTOCOL'] || 'HTTP/1.0'
|
protocol = _no_crlf_check($CGI_ENV['SERVER_PROTOCOL']) || 'HTTP/1.0'
|
||||||
status = options.delete('status')
|
status = options.delete('status')
|
||||||
status = HTTP_STATUS[status] || status || '200 OK'
|
status = HTTP_STATUS[status] || _no_crlf_check(status) || '200 OK'
|
||||||
buf << "#{protocol} #{status}#{EOL}"
|
buf << "#{protocol} #{status}#{EOL}"
|
||||||
buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}"
|
buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}"
|
||||||
options['server'] ||= $CGI_ENV['SERVER_SOFTWARE'] || ''
|
options['server'] ||= $CGI_ENV['SERVER_SOFTWARE'] || ''
|
||||||
@ -223,38 +234,38 @@ class CGI
|
|||||||
end
|
end
|
||||||
## common headers
|
## common headers
|
||||||
status = options.delete('status')
|
status = options.delete('status')
|
||||||
buf << "Status: #{HTTP_STATUS[status] || status}#{EOL}" if status
|
buf << "Status: #{HTTP_STATUS[status] || _no_crlf_check(status)}#{EOL}" if status
|
||||||
server = options.delete('server')
|
server = options.delete('server')
|
||||||
buf << "Server: #{server}#{EOL}" if server
|
buf << "Server: #{_no_crlf_check(server)}#{EOL}" if server
|
||||||
connection = options.delete('connection')
|
connection = options.delete('connection')
|
||||||
buf << "Connection: #{connection}#{EOL}" if connection
|
buf << "Connection: #{_no_crlf_check(connection)}#{EOL}" if connection
|
||||||
type = options.delete('type')
|
type = options.delete('type')
|
||||||
buf << "Content-Type: #{type}#{EOL}" #if type
|
buf << "Content-Type: #{_no_crlf_check(type)}#{EOL}" #if type
|
||||||
length = options.delete('length')
|
length = options.delete('length')
|
||||||
buf << "Content-Length: #{length}#{EOL}" if length
|
buf << "Content-Length: #{_no_crlf_check(length)}#{EOL}" if length
|
||||||
language = options.delete('language')
|
language = options.delete('language')
|
||||||
buf << "Content-Language: #{language}#{EOL}" if language
|
buf << "Content-Language: #{_no_crlf_check(language)}#{EOL}" if language
|
||||||
expires = options.delete('expires')
|
expires = options.delete('expires')
|
||||||
buf << "Expires: #{CGI.rfc1123_date(expires)}#{EOL}" if expires
|
buf << "Expires: #{CGI.rfc1123_date(expires)}#{EOL}" if expires
|
||||||
## cookie
|
## cookie
|
||||||
if cookie = options.delete('cookie')
|
if cookie = options.delete('cookie')
|
||||||
case cookie
|
case cookie
|
||||||
when String, Cookie
|
when String, Cookie
|
||||||
buf << "Set-Cookie: #{cookie}#{EOL}"
|
buf << "Set-Cookie: #{_no_crlf_check(cookie)}#{EOL}"
|
||||||
when Array
|
when Array
|
||||||
arr = cookie
|
arr = cookie
|
||||||
arr.each {|c| buf << "Set-Cookie: #{c}#{EOL}" }
|
arr.each {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" }
|
||||||
when Hash
|
when Hash
|
||||||
hash = cookie
|
hash = cookie
|
||||||
hash.each_value {|c| buf << "Set-Cookie: #{c}#{EOL}" }
|
hash.each_value {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if @output_cookies
|
if @output_cookies
|
||||||
@output_cookies.each {|c| buf << "Set-Cookie: #{c}#{EOL}" }
|
@output_cookies.each {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" }
|
||||||
end
|
end
|
||||||
## other headers
|
## other headers
|
||||||
options.each do |key, value|
|
options.each do |key, value|
|
||||||
buf << "#{key}: #{value}#{EOL}"
|
buf << "#{_no_crlf_check(key)}: #{_no_crlf_check(value)}#{EOL}"
|
||||||
end
|
end
|
||||||
return buf
|
return buf
|
||||||
end # _header_for_hash
|
end # _header_for_hash
|
||||||
|
@ -176,6 +176,14 @@ class CGIHeaderTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def test_cgi_http_header_crlf_injection
|
||||||
|
cgi = CGI.new
|
||||||
|
assert_raise(RuntimeError) { cgi.http_header("text/xhtml\r\nBOO") }
|
||||||
|
assert_raise(RuntimeError) { cgi.http_header("type" => "text/xhtml\r\nBOO") }
|
||||||
|
assert_raise(RuntimeError) { cgi.http_header("status" => "200 OK\r\nBOO") }
|
||||||
|
assert_raise(RuntimeError) { cgi.http_header("location" => "text/xhtml\r\nBOO") }
|
||||||
|
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