diff --git a/CHANGELOG.md b/CHANGELOG.md index b7ccdf964..df1b3ac05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. For info on ## [3.0.0] - Unreleased +### Security + +- Do not use semicolon as GET parameter separator. ([#1733](https://github.com/rack/rack/pull/1733), [@jeremyevans](https://github.com/jeremyevans)) + ### Added - `Rack::RewindableInput` supports size. ([@ahorek](https://github.com/ahorek)) diff --git a/lib/rack/query_parser.rb b/lib/rack/query_parser.rb index dbbb18e5a..cea6b9b09 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 @@ -29,16 +29,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) @@ -61,11 +60,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 74377ede3..541c3801b 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 6b73cd5b5..6bc77b01a 100644 --- a/test/spec_request.rb +++ b/test/spec_request.rb @@ -308,12 +308,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