From b37ddb2a21ffd199f2879701e7bf68979bd88435 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 10 Feb 2021 11:38:17 -0800 Subject: [PATCH] Prevent open redirect when allowed host starts with a dot [CVE-2021-22881] Thanks to @tktech (https://hackerone.com/tktech) for reporting this issue and the patch! --- .../middleware/host_authorization.rb | 17 +++++++++++++---- .../test/dispatch/host_authorization_test.rb | 11 +++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/host_authorization.rb b/actionpack/lib/action_dispatch/middleware/host_authorization.rb index de7739b9b6..ebfaa61e38 100644 --- a/actionpack/lib/action_dispatch/middleware/host_authorization.rb +++ b/actionpack/lib/action_dispatch/middleware/host_authorization.rb @@ -87,11 +87,20 @@ def call(env) private def authorized?(request) - origin_host = request.get_header("HTTP_HOST").to_s.sub(/:\d+\z/, "") - forwarded_host = request.x_forwarded_host.to_s.split(/,\s?/).last.to_s.sub(/:\d+\z/, "") + valid_host = / + \A + (?[a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9\.:]+\]) + (:\d+)? + \z + /x - @permissions.allows?(origin_host) && - (forwarded_host.blank? || @permissions.allows?(forwarded_host)) + origin_host = valid_host.match( + request.get_header("HTTP_HOST").to_s.downcase) + forwarded_host = valid_host.match( + request.x_forwarded_host.to_s.split(/,\s?/).last) + + origin_host && @permissions.allows?(origin_host[:host]) && ( + forwarded_host.nil? || @permissions.allows?(forwarded_host[:host])) end def mark_as_authorized(request) diff --git a/actionpack/test/dispatch/host_authorization_test.rb b/actionpack/test/dispatch/host_authorization_test.rb index 5263dd2597..027a6749f0 100644 --- a/actionpack/test/dispatch/host_authorization_test.rb +++ b/actionpack/test/dispatch/host_authorization_test.rb @@ -158,4 +158,15 @@ class HostAuthorizationTest < ActionDispatch::IntegrationTest assert_response :ok assert_equal "Success", body end + + test "only compare to valid hostnames" do + @app = ActionDispatch::HostAuthorization.new(App, ".example.com") + + get "/", env: { + "HOST" => "example.com#sub.example.com", + } + + assert_response :forbidden + assert_match "Blocked host: example.com#sub.example.com", response.body + end end -- 2.26.2