diff --git a/CHANGELOG.md b/CHANGELOG.md index 891a89f26..9c1b77fdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. For info on how to format all future additions to this file please reference [Keep A Changelog](https://keepachangelog.com/en/1.0.0/). +### Security + +- Do not use semicolon as GET parameter separator. ([#1733](https://github.com/rack/rack/pull/1733), [@jeremyevans](https://github.com/jeremyevans)) + ## [2.2.6.3] - 2023-03-02 - [CVE-2023-27530] Introduce multipart_total_part_limit to limit total parts @@ -134,7 +138,7 @@ All notable changes to this project will be documented in this file. For info on - Support for using `:SSLEnable` option when using WEBrick handler. (Gregor Melhorn) - Close response body after buffering it when buffering. ([@ioquatix](https://github.com/ioquatix)) - Only accept `;` as delimiter when parsing cookies. ([@mrageh](https://github.com/mrageh)) -- `Utils::HeaderHash#clear` clears the name mapping as well. ([@raxoft](https://github.com/raxoft)) +- `Utils::HeaderHash#clear` clears the name mapping as well. ([@raxoft](https://github.com/raxoft)) - Support for passing `nil` `Rack::Files.new`, which notably fixes Rails' current `ActiveStorage::FileServer` implementation. ([@ioquatix](https://github.com/ioquatix)) ### Documentation diff --git a/lib/rack/query_parser.rb b/lib/rack/query_parser.rb index 1c3923c32..803133f41 100644 --- a/lib/rack/query_parser.rb +++ b/lib/rack/query_parser.rb @@ -4,7 +4,7 @@ module Rack class QueryParser (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4' - DEFAULT_SEP = /[&;] */n + DEFAULT_SEP = /[&] */n COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n } # ParameterTypeError is the error that is raised when incoming structural @@ -33,16 +33,15 @@ def initialize(params_class, key_space_limit, param_depth_limit) end # Stolen from Mongrel, with some small modifications: - # Parses a query string by breaking it up at the '&' - # and ';' characters. You can also use this to parse - # cookies by changing the characters used in the second - # parameter (which defaults to '&;'). - def parse_query(qs, d = nil, &unescaper) + # Parses a query string by breaking it up at the '&'. You can also use this + # to parse cookies by changing the characters used in the second parameter + # (which defaults to '&'). + def parse_query(qs, separator = nil, &unescaper) unescaper ||= method(:unescape) params = make_params - (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p| + (qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p| next if p.empty? k, v = p.split('=', 2).map!(&unescaper) @@ -65,11 +64,11 @@ def parse_query(qs, d = nil, &unescaper) # query strings with parameters of conflicting types, in this case a # ParameterTypeError is raised. Users are encouraged to return a 400 in this # case. - def parse_nested_query(qs, d = nil) + def parse_nested_query(qs, separator = nil) params = make_params unless qs.nil? || qs.empty? - (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p| + (qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p| k, v = p.split('=', 2).map! { |s| unescape(s) } normalize_params(params, k, v, param_depth_limit) diff --git a/lib/rack/request.rb b/lib/rack/request.rb index 750a0dc44..922b7c4d7 100644 --- a/lib/rack/request.rb +++ b/lib/rack/request.rb @@ -427,7 +427,7 @@ def GET if get_header(RACK_REQUEST_QUERY_STRING) == query_string get_header(RACK_REQUEST_QUERY_HASH) else - query_hash = parse_query(query_string, '&;') + query_hash = parse_query(query_string, '&') set_header(RACK_REQUEST_QUERY_STRING, query_string) set_header(RACK_REQUEST_QUERY_HASH, query_hash) end diff --git a/test/spec_request.rb b/test/spec_request.rb index 51cfcdc88..57da4d091 100644 --- a/test/spec_request.rb +++ b/test/spec_request.rb @@ -278,12 +278,12 @@ def initialize(*) req.params[:quux].must_equal "bla" end - it "use semi-colons as separators for query strings in GET" do + it "does not use semi-colons as separators for query strings in GET" do req = make_request(Rack::MockRequest.env_for("/?foo=bar&quux=b;la;wun=duh")) req.query_string.must_equal "foo=bar&quux=b;la;wun=duh" - req.GET.must_equal "foo" => "bar", "quux" => "b", "la" => nil, "wun" => "duh" + req.GET.must_equal "foo" => "bar", "quux" => "b;la;wun=duh" req.POST.must_be :empty? - req.params.must_equal "foo" => "bar", "quux" => "b", "la" => nil, "wun" => "duh" + req.params.must_equal "foo" => "bar", "quux" => "b;la;wun=duh" end it "limit the keys from the GET query string" do