Package: rails / 2:6.0.3.7+dfsg-2+deb11u2

CVE-2021-44528.patch Patch series | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
From fd6a64fef1d0f7f40a8d4b046da882e83163299c Mon Sep 17 00:00:00 2001
From: Stef Schenkelaars <stef.schenkelaars@gmail.com>
Date: Wed, 7 Jul 2021 12:06:32 +0200
Subject: [PATCH] Fix invalid forwarded host vulnerability

Prior to this commit, it was possible to pass an unvalidated host
through the `X-Forwarded-Host` header. If the value of the header
was prefixed with a invalid domain character (for example a `/`),
it was always accepted as the actual host of that request.

Since this host is used for all url helpers, an attacker could change
generated links and redirects. If the header is set to
`X-Forwarded-Host: //evil.hacker`, a redirect will be send to
`https:////evil.hacker/`. Browsers will ignore these four slashes
and redirect the user.

[CVE-2021-44528]
---
 .../middleware/host_authorization.rb          | 10 +--
 .../test/dispatch/host_authorization_test.rb  | 89 ++++++++++++++++++-
 2 files changed, 91 insertions(+), 8 deletions(-)

Index: rails-6.0.3.7+dfsg/actionpack/lib/action_dispatch/middleware/host_authorization.rb
===================================================================
--- rails-6.0.3.7+dfsg.orig/actionpack/lib/action_dispatch/middleware/host_authorization.rb
+++ rails-6.0.3.7+dfsg/actionpack/lib/action_dispatch/middleware/host_authorization.rb
@@ -46,7 +46,7 @@ module ActionDispatch
 
         def sanitize_string(host)
           if host.start_with?(".")
-            /\A(.+\.)?#{Regexp.escape(host[1..-1])}\z/
+            /\A([a-z0-9-]+\.)?#{Regexp.escape(host[1..-1])}\z/i
           else
             host
           end
@@ -86,13 +86,9 @@ module ActionDispatch
     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
Index: rails-6.0.3.7+dfsg/actionpack/test/dispatch/host_authorization_test.rb
===================================================================
--- rails-6.0.3.7+dfsg.orig/actionpack/test/dispatch/host_authorization_test.rb
+++ rails-6.0.3.7+dfsg/actionpack/test/dispatch/host_authorization_test.rb
@@ -111,6 +111,44 @@ class HostAuthorizationTest < ActionDisp
     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)
 
@@ -147,11 +185,23 @@ class HostAuthorizationTest < ActionDisp
     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",
     }
 
@@ -169,4 +219,41 @@ class HostAuthorizationTest < ActionDisp
     assert_response :forbidden
     assert_match "Blocked host: attacker.com#x.example.com", response.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
 end