diff options
Diffstat (limited to 'actionpack')
-rw-r--r-- | actionpack/CHANGELOG.md | 16 | ||||
-rw-r--r-- | actionpack/lib/abstract_controller/base.rb | 4 | ||||
-rw-r--r-- | actionpack/lib/abstract_controller/caching/fragments.rb | 2 | ||||
-rw-r--r-- | actionpack/lib/action_controller/metal/request_forgery_protection.rb | 2 | ||||
-rw-r--r-- | actionpack/lib/action_controller/renderer.rb | 2 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/http/parameter_filter.rb | 17 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/request/utils.rb | 2 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/routing/inspector.rb | 2 | ||||
-rw-r--r-- | actionpack/test/dispatch/cookies_test.rb | 12 | ||||
-rw-r--r-- | actionpack/test/dispatch/exception_wrapper_test.rb | 1 | ||||
-rw-r--r-- | actionpack/test/dispatch/request_test.rb | 7 |
11 files changed, 44 insertions, 23 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index a5497aa055..a30f178190 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,19 @@ +* Purpose metadata for signed/encrypted cookies. + + Rails can now thwart attacks that attempt to copy signed/encrypted value + of a cookie and use it as the value of another cookie. + + It does so by stashing the cookie-name in the purpose field which is + then signed/encrypted along with the cookie value. Then, on a server-side + read, we verify the cookie-names and discard any attacked cookies. + + Enable `action_dispatch.use_cookies_with_metadata` to use this feature, which + writes cookies with the new purpose and expiry metadata embedded. + + Pull Request: #32937 + + *Assain Jaleel* + * Raises `ActionController::RespondToMismatchError` with confliciting `respond_to` invocations. `respond_to` can match multiple types and lead to undefined behavior when diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index a312af6715..6e6786d0be 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -78,7 +78,9 @@ module AbstractController # Except for public instance methods of Base and its ancestors internal_methods + # Be sure to include shadowed public instance methods of this class - public_instance_methods(false)).uniq.map(&:to_s) + public_instance_methods(false)) + + methods.map!(&:to_s) methods.to_set end diff --git a/actionpack/lib/abstract_controller/caching/fragments.rb b/actionpack/lib/abstract_controller/caching/fragments.rb index f99b0830b2..febd8a67a6 100644 --- a/actionpack/lib/abstract_controller/caching/fragments.rb +++ b/actionpack/lib/abstract_controller/caching/fragments.rb @@ -82,7 +82,7 @@ module AbstractController # Given a key (as described in +expire_fragment+), returns # a key array suitable for use in reading, writing, or expiring a # cached fragment. All keys begin with <tt>:views</tt>, - # followed by ENV["RAILS_CACHE_ID"] or ENV["RAILS_APP_VERSION"] if set, + # followed by <tt>ENV["RAILS_CACHE_ID"]</tt> or <tt>ENV["RAILS_APP_VERSION"]</tt> if set, # followed by any controller-wide key prefix values, ending # with the specified +key+ value. def combined_fragment_cache_key(key) diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index ea637c8150..7ed7b9d546 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -46,7 +46,7 @@ module ActionController #:nodoc: # allowed via {CORS}[https://en.wikipedia.org/wiki/Cross-origin_resource_sharing] # will also be able to create XHR requests. Be sure to check your # CORS whitelist before disabling forgery protection for XHR. - # + # # CSRF protection is turned on with the <tt>protect_from_forgery</tt> method. # By default <tt>protect_from_forgery</tt> protects your session with # <tt>:null_session</tt> method, which provides an empty session diff --git a/actionpack/lib/action_controller/renderer.rb b/actionpack/lib/action_controller/renderer.rb index 2d1523f0fc..2b4559c760 100644 --- a/actionpack/lib/action_controller/renderer.rb +++ b/actionpack/lib/action_controller/renderer.rb @@ -81,7 +81,7 @@ module ActionController # * <tt>:html</tt> - Renders the provided HTML safe string, otherwise # performs HTML escape on the string first. Sets the content type as <tt>text/html</tt>. # * <tt>:json</tt> - Renders the provided hash or object in JSON. You don't - # need to call <tt>.to_json<tt> on the object you want to render. + # need to call <tt>.to_json</tt> on the object you want to render. # * <tt>:body</tt> - Renders provided text and sets content type of <tt>text/plain</tt>. # # If no <tt>options</tt> hash is passed or if <tt>:update</tt> is specified, the default is diff --git a/actionpack/lib/action_dispatch/http/parameter_filter.rb b/actionpack/lib/action_dispatch/http/parameter_filter.rb index 1d58964862..09aab631ed 100644 --- a/actionpack/lib/action_dispatch/http/parameter_filter.rb +++ b/actionpack/lib/action_dispatch/http/parameter_filter.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "active_support/core_ext/object/duplicable" +require "active_support/core_ext/array/extract" module ActionDispatch module Http @@ -38,8 +39,8 @@ module ActionDispatch end end - deep_regexps, regexps = regexps.partition { |r| r.to_s.include?("\\.".freeze) } - deep_strings, strings = strings.partition { |s| s.include?("\\.".freeze) } + deep_regexps = regexps.extract! { |r| r.to_s.include?("\\.".freeze) } + deep_strings = strings.extract! { |s| s.include?("\\.".freeze) } regexps << Regexp.new(strings.join("|".freeze), true) unless strings.empty? deep_regexps << Regexp.new(deep_strings.join("|".freeze), true) unless deep_strings.empty? @@ -55,23 +56,23 @@ module ActionDispatch @blocks = blocks end - def call(original_params, parents = []) - filtered_params = original_params.class.new + def call(params, parents = [], original_params = params) + filtered_params = params.class.new - original_params.each do |key, value| + params.each do |key, value| parents.push(key) if deep_regexps if regexps.any? { |r| key =~ r } value = FILTERED elsif deep_regexps && (joined = parents.join(".")) && deep_regexps.any? { |r| joined =~ r } value = FILTERED elsif value.is_a?(Hash) - value = call(value, parents) + value = call(value, parents, original_params) elsif value.is_a?(Array) - value = value.map { |v| v.is_a?(Hash) ? call(v, parents) : v } + value = value.map { |v| v.is_a?(Hash) ? call(v, parents, original_params) : v } elsif blocks.any? key = key.dup if key.duplicable? value = value.dup if value.duplicable? - blocks.each { |b| b.call(key, value) } + blocks.each { |b| b.arity == 2 ? b.call(key, value) : b.call(key, value, original_params) } end parents.pop if deep_regexps diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb index 0ae464082d..fb0efb9a58 100644 --- a/actionpack/lib/action_dispatch/request/utils.rb +++ b/actionpack/lib/action_dispatch/request/utils.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "active_support/core_ext/hash/indifferent_access" + module ActionDispatch class Request class Utils # :nodoc: diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index cba49d1a0b..413e524ef6 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -83,7 +83,7 @@ module ActionDispatch private def normalize_filter(filter) if filter[:controller] - { controller: /#{filter[:controller].downcase.sub(/_?controller\z/, '').sub('::', '/')}/ } + { controller: /#{filter[:controller].underscore.sub(/_?controller\z/, "")}/ } elsif filter[:grep] { controller: /#{filter[:grep]}/, action: /#{filter[:grep]}/, verb: /#{filter[:grep]}/, name: /#{filter[:grep]}/, path: /#{filter[:grep]}/ } diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index 34ead0a4c0..6637c2cae9 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -1405,8 +1405,7 @@ class CookiesTest < ActionController::TestCase assert_equal "5-2-Stable Chocolate Cookies", cookies.encrypted[:favorite] - freeze_time do - travel 1001.years + travel 1001.years do assert_nil cookies.encrypted[:favorite] end @@ -1422,8 +1421,7 @@ class CookiesTest < ActionController::TestCase assert_equal "5-2-Stable Choco Chip Cookie", cookies.signed[:favorite] - freeze_time do - travel 1001.years + travel 1001.years do assert_nil cookies.signed[:favorite] end @@ -1439,8 +1437,7 @@ class CookiesTest < ActionController::TestCase assert_equal "5-2-Stable Chocolate Cookies", cookies.encrypted[:favorite] - freeze_time do - travel 1001.years + travel 1001.years do assert_nil cookies.encrypted[:favorite] end @@ -1456,8 +1453,7 @@ class CookiesTest < ActionController::TestCase assert_equal "5-2-Stable Choco Chip Cookie", cookies.signed[:favorite] - freeze_time do - travel 1001.years + travel 1001.years do assert_nil cookies.signed[:favorite] end diff --git a/actionpack/test/dispatch/exception_wrapper_test.rb b/actionpack/test/dispatch/exception_wrapper_test.rb index 600280d6b3..668469a01d 100644 --- a/actionpack/test/dispatch/exception_wrapper_test.rb +++ b/actionpack/test/dispatch/exception_wrapper_test.rb @@ -20,6 +20,7 @@ module ActionDispatch setup do @cleaner = ActiveSupport::BacktraceCleaner.new + @cleaner.remove_filters! @cleaner.add_silencer { |line| line !~ /^lib/ } end diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 84a2d1f69e..0ac8713527 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -1078,10 +1078,13 @@ class RequestParameterFilter < BaseRequestTest filter_words << lambda { |key, value| value.reverse! if key =~ /bargain/ } + filter_words << lambda { |key, value, original_params| + value.replace("world!") if original_params["barg"]["blah"] == "bar" && key == "hello" + } parameter_filter = ActionDispatch::Http::ParameterFilter.new(filter_words) - before_filter["barg"] = { :bargain => "gain", "blah" => "bar", "bar" => { "bargain" => { "blah" => "foo" } } } - after_filter["barg"] = { :bargain => "niag", "blah" => "[FILTERED]", "bar" => { "bargain" => { "blah" => "[FILTERED]" } } } + before_filter["barg"] = { :bargain => "gain", "blah" => "bar", "bar" => { "bargain" => { "blah" => "foo", "hello" => "world" } } } + after_filter["barg"] = { :bargain => "niag", "blah" => "[FILTERED]", "bar" => { "bargain" => { "blah" => "[FILTERED]", "hello" => "world!" } } } assert_equal after_filter, parameter_filter.filter(before_filter) end |