VYPR
Moderate severityNVD Advisory· Published Jan 7, 2022· Updated Aug 4, 2024

CVE-2021-44528

CVE-2021-44528

Description

A open redirect vulnerability exists in Action Pack >= 6.0.0 that could allow an attacker to craft a "X-Forwarded-Host" headers in combination with certain "allowed host" formats can cause the Host Authorization middleware in Action Pack to redirect users to a malicious website.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Open redirect in Rails Action Pack Host Authorization middleware allows attackers to redirect users to malicious sites via crafted X-Forwarded-Host header.

Vulnerability

The vulnerability is an open redirect in the Host Authorization middleware of Action Pack (part of Ruby on Rails) affecting versions >= 6.0.0. It allows an attacker to craft a X-Forwarded-Host header that, in combination with certain formats of allowed host configurations (e.g., using wildcards or specific patterns), can bypass host validation and redirect users to an arbitrary external domain. [1][2]

Exploitation

An attacker must be able to send HTTP requests to the target application with a specially crafted X-Forwarded-Host header. The application must be using Action Pack's Host Authorization middleware (common in production) and have a permissive allowed host configuration, such as a wildcard pattern. The attacker can then trigger a redirect to a malicious site by exploiting the middleware's handling of the forwarded host. [1][4]

Impact

Successful exploitation allows an attacker to redirect users from the legitimate application to an arbitrary external website, enabling phishing attacks or other social engineering schemes. The redirect is performed by the application itself, so users may trust the destination. This is a confidentiality and integrity impact, as user trust and data could be compromised. [2]

Mitigation

The vulnerability is fixed in Rails versions 6.1.4.2 and 6.0.4.2 (patched releases). Users should upgrade to these versions or later. If immediate upgrade is not possible, ensure that the allowed_hosts configuration is restrictive and avoid using wildcard patterns that could be exploited. [1][4]

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
actionpackRubyGems
>= 6.0.0, < 6.0.4.26.0.4.2
actionpackRubyGems
>= 6.1.0, < 6.1.4.26.1.4.2

Affected products

4

Patches

2
0fccfb9a3097

Fix invalid forwarded host vulnerability

https://github.com/rails/railsStef SchenkelaarsJul 7, 2021via ghsa
2 files changed · +91 8
  • actionpack/lib/action_dispatch/middleware/host_authorization.rb+3 7 modified
    @@ -52,7 +52,7 @@ def sanitize_regexp(host)
     
             def sanitize_string(host)
               if host.start_with?(".")
    -            /\A(.+\.)?#{Regexp.escape(host[1..-1])}\z/i
    +            /\A([a-z0-9-]+\.)?#{Regexp.escape(host[1..-1])}\z/i
               else
                 /\A#{Regexp.escape host}\z/i
               end
    @@ -120,13 +120,9 @@ def call(env)
         end
     
         private
    -      HOSTNAME = /[a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9.:]+\]/i
    -      VALID_ORIGIN_HOST = /\A(#{HOSTNAME})(?::\d+)?\z/
    -      VALID_FORWARDED_HOST = /(?:\A|,[ ]?)(#{HOSTNAME})(?::\d+)?\z/
    -
           def authorized?(request)
    -        origin_host = request.get_header("HTTP_HOST")&.slice(VALID_ORIGIN_HOST, 1) || ""
    -        forwarded_host = request.x_forwarded_host&.slice(VALID_FORWARDED_HOST, 1) || ""
    +        origin_host = request.get_header("HTTP_HOST")
    +        forwarded_host = request.x_forwarded_host&.split(/,\s?/)&.last
     
             @permissions.allows?(origin_host) && (forwarded_host.blank? || @permissions.allows?(forwarded_host))
           end
    
  • actionpack/test/dispatch/host_authorization_test.rb+88 1 modified
    @@ -167,6 +167,44 @@ class HostAuthorizationTest < ActionDispatch::IntegrationTest
         assert_match "Blocked host: 127.0.0.1", response.body
       end
     
    +  test "blocks requests with spoofed relative X-FORWARDED-HOST" do
    +    @app = ActionDispatch::HostAuthorization.new(App, ["www.example.com"])
    +
    +    get "/", env: {
    +      "HTTP_X_FORWARDED_HOST" => "//randomhost.com",
    +      "HOST" => "www.example.com",
    +      "action_dispatch.show_detailed_exceptions" => true
    +    }
    +
    +    assert_response :forbidden
    +    assert_match "Blocked host: //randomhost.com", response.body
    +  end
    +
    +  test "forwarded secondary hosts are allowed when permitted" do
    +    @app = ActionDispatch::HostAuthorization.new(App, ".domain.com")
    +
    +    get "/", env: {
    +      "HTTP_X_FORWARDED_HOST" => "example.com, my-sub.domain.com",
    +      "HOST" => "domain.com",
    +    }
    +
    +    assert_response :ok
    +    assert_equal "Success", body
    +  end
    +
    +  test "forwarded secondary hosts are blocked when mismatch" do
    +    @app = ActionDispatch::HostAuthorization.new(App, "domain.com")
    +
    +    get "/", env: {
    +      "HTTP_X_FORWARDED_HOST" => "domain.com, evil.com",
    +      "HOST" => "domain.com",
    +      "action_dispatch.show_detailed_exceptions" => true
    +    }
    +
    +    assert_response :forbidden
    +    assert_match "Blocked host: evil.com", response.body
    +  end
    +
       test "does not consider IP addresses in X-FORWARDED-HOST spoofed when disabled" do
         @app = ActionDispatch::HostAuthorization.new(App, nil)
     
    @@ -205,18 +243,67 @@ class HostAuthorizationTest < ActionDispatch::IntegrationTest
         assert_match "Blocked host: sub.domain.com", response.body
       end
     
    +  test "sub-sub domains should not be permitted" do
    +    @app = ActionDispatch::HostAuthorization.new(App, ".domain.com")
    +
    +    get "/", env: {
    +      "HOST" => "secondary.sub.domain.com",
    +      "action_dispatch.show_detailed_exceptions" => true
    +    }
    +
    +    assert_response :forbidden
    +    assert_match "Blocked host: secondary.sub.domain.com", response.body
    +  end
    +
       test "forwarded hosts are allowed when permitted" do
         @app = ActionDispatch::HostAuthorization.new(App, ".domain.com")
     
         get "/", env: {
    -      "HTTP_X_FORWARDED_HOST" => "sub.domain.com",
    +      "HTTP_X_FORWARDED_HOST" => "my-sub.domain.com",
           "HOST" => "domain.com",
         }
     
         assert_response :ok
         assert_equal "Success", body
       end
     
    +  test "lots of NG hosts" do
    +    ng_hosts = [
    +      "hacker%E3%80%82com",
    +      "hacker%00.com",
    +      "www.theirsite.com@yoursite.com",
    +      "hacker.com/test/",
    +      "hacker%252ecom",
    +      ".hacker.com",
    +      "/\/\/hacker.com/",
    +      "/hacker.com",
    +      "../hacker.com",
    +      ".hacker.com",
    +      "@hacker.com",
    +      "hacker.com",
    +      "hacker.com%23@example.com",
    +      "hacker.com/.jpg",
    +      "hacker.com\texample.com/",
    +      "hacker.com/example.com",
    +      "hacker.com\@example.com",
    +      "hacker.com/example.com",
    +      "hacker.com/"
    +    ]
    +
    +    @app = ActionDispatch::HostAuthorization.new(App, "example.com")
    +
    +    ng_hosts.each do |host|
    +      get "/", env: {
    +        "HTTP_X_FORWARDED_HOST" => host,
    +        "HOST" => "example.com",
    +        "action_dispatch.show_detailed_exceptions" => true
    +      }
    +
    +      assert_response :forbidden
    +      assert_match "Blocked host: #{host}", response.body
    +    end
    +  end
    +
       test "exclude matches allow any host" do
         @app = ActionDispatch::HostAuthorization.new(App, "only.com", exclude: ->(req) { req.path == "/foo" })
     
    
aecba3c301b8

Fix invalid forwarded host vulnerability

https://github.com/rails/railsStef SchenkelaarsJul 7, 2021via ghsa
2 files changed · +91 8
  • actionpack/lib/action_dispatch/middleware/host_authorization.rb+3 7 modified
    @@ -51,7 +51,7 @@ def sanitize_regexp(host)
     
             def sanitize_string(host)
               if host.start_with?(".")
    -            /\A(.+\.)?#{Regexp.escape(host[1..-1])}\z/i
    +            /\A([a-z0-9-]+\.)?#{Regexp.escape(host[1..-1])}\z/i
               else
                 /\A#{Regexp.escape host}\z/i
               end
    @@ -102,13 +102,9 @@ def call(env)
         end
     
         private
    -      HOSTNAME = /[a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9.:]+\]/i
    -      VALID_ORIGIN_HOST = /\A(#{HOSTNAME})(?::\d+)?\z/
    -      VALID_FORWARDED_HOST = /(?:\A|,[ ]?)(#{HOSTNAME})(?::\d+)?\z/
    -
           def authorized?(request)
    -        origin_host = request.get_header("HTTP_HOST")&.slice(VALID_ORIGIN_HOST, 1) || ""
    -        forwarded_host = request.x_forwarded_host&.slice(VALID_FORWARDED_HOST, 1) || ""
    +        origin_host = request.get_header("HTTP_HOST")
    +        forwarded_host = request.x_forwarded_host&.split(/,\s?/)&.last
     
             @permissions.allows?(origin_host) && (forwarded_host.blank? || @permissions.allows?(forwarded_host))
           end
    
  • actionpack/test/dispatch/host_authorization_test.rb+88 1 modified
    @@ -155,6 +155,44 @@ class HostAuthorizationTest < ActionDispatch::IntegrationTest
         assert_match "Blocked host: 127.0.0.1", response.body
       end
     
    +  test "blocks requests with spoofed relative X-FORWARDED-HOST" do
    +    @app = ActionDispatch::HostAuthorization.new(App, ["www.example.com"])
    +
    +    get "/", env: {
    +      "HTTP_X_FORWARDED_HOST" => "//randomhost.com",
    +      "HOST" => "www.example.com",
    +      "action_dispatch.show_detailed_exceptions" => true
    +    }
    +
    +    assert_response :forbidden
    +    assert_match "Blocked host: //randomhost.com", response.body
    +  end
    +
    +  test "forwarded secondary hosts are allowed when permitted" do
    +    @app = ActionDispatch::HostAuthorization.new(App, ".domain.com")
    +
    +    get "/", env: {
    +      "HTTP_X_FORWARDED_HOST" => "example.com, my-sub.domain.com",
    +      "HOST" => "domain.com",
    +    }
    +
    +    assert_response :ok
    +    assert_equal "Success", body
    +  end
    +
    +  test "forwarded secondary hosts are blocked when mismatch" do
    +    @app = ActionDispatch::HostAuthorization.new(App, "domain.com")
    +
    +    get "/", env: {
    +      "HTTP_X_FORWARDED_HOST" => "domain.com, evil.com",
    +      "HOST" => "domain.com",
    +      "action_dispatch.show_detailed_exceptions" => true
    +    }
    +
    +    assert_response :forbidden
    +    assert_match "Blocked host: evil.com", response.body
    +  end
    +
       test "does not consider IP addresses in X-FORWARDED-HOST spoofed when disabled" do
         @app = ActionDispatch::HostAuthorization.new(App, nil)
     
    @@ -191,18 +229,67 @@ class HostAuthorizationTest < ActionDispatch::IntegrationTest
         assert_match "Blocked host: sub.domain.com", response.body
       end
     
    +  test "sub-sub domains should not be permitted" do
    +    @app = ActionDispatch::HostAuthorization.new(App, ".domain.com")
    +
    +    get "/", env: {
    +      "HOST" => "secondary.sub.domain.com",
    +      "action_dispatch.show_detailed_exceptions" => true
    +    }
    +
    +    assert_response :forbidden
    +    assert_match "Blocked host: secondary.sub.domain.com", response.body
    +  end
    +
       test "forwarded hosts are allowed when permitted" do
         @app = ActionDispatch::HostAuthorization.new(App, ".domain.com")
     
         get "/", env: {
    -      "HTTP_X_FORWARDED_HOST" => "sub.domain.com",
    +      "HTTP_X_FORWARDED_HOST" => "my-sub.domain.com",
           "HOST" => "domain.com",
         }
     
         assert_response :ok
         assert_equal "Success", body
       end
     
    +  test "lots of NG hosts" do
    +    ng_hosts = [
    +      "hacker%E3%80%82com",
    +      "hacker%00.com",
    +      "www.theirsite.com@yoursite.com",
    +      "hacker.com/test/",
    +      "hacker%252ecom",
    +      ".hacker.com",
    +      "/\/\/hacker.com/",
    +      "/hacker.com",
    +      "../hacker.com",
    +      ".hacker.com",
    +      "@hacker.com",
    +      "hacker.com",
    +      "hacker.com%23@example.com",
    +      "hacker.com/.jpg",
    +      "hacker.com\texample.com/",
    +      "hacker.com/example.com",
    +      "hacker.com\@example.com",
    +      "hacker.com/example.com",
    +      "hacker.com/"
    +    ]
    +
    +    @app = ActionDispatch::HostAuthorization.new(App, "example.com")
    +
    +    ng_hosts.each do |host|
    +      get "/", env: {
    +        "HTTP_X_FORWARDED_HOST" => host,
    +        "HOST" => "example.com",
    +        "action_dispatch.show_detailed_exceptions" => true
    +      }
    +
    +      assert_response :forbidden
    +      assert_match "Blocked host: #{host}", response.body
    +    end
    +  end
    +
       test "exclude matches allow any host" do
         @app = ActionDispatch::HostAuthorization.new(App, "only.com", exclude: ->(req) { req.path == "/foo" })
     
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

11

News mentions

0

No linked articles in our index yet.