Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Added protection against spoofing of X_FORWARDED_HOST headers. A secu…

…rity announcement will be made shortly.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16758 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 893cea211ae88c6f68a6c2c281890d6f63541286 1 parent 0516ac3
Russell Keith-Magee authored September 10, 2011
2  django/conf/global_settings.py
@@ -402,6 +402,8 @@
402 402
 # Default X-Frame-Options header value
403 403
 X_FRAME_OPTIONS = 'SAMEORIGIN'
404 404
 
  405
+USE_X_FORWARDED_HOST = False
  406
+
405 407
 ##############
406 408
 # MIDDLEWARE #
407 409
 ##############
3  django/http/__init__.py
@@ -194,7 +194,8 @@ def __repr__(self):
194 194
     def get_host(self):
195 195
         """Returns the HTTP host using the environment or request headers."""
196 196
         # We try three options, in order of decreasing preference.
197  
-        if 'HTTP_X_FORWARDED_HOST' in self.META:
  197
+        if settings.USE_X_FORWARDED_HOST and (
  198
+            'HTTP_X_FORWARDED_HOST' in self.META):
198 199
             host = self.META['HTTP_X_FORWARDED_HOST']
199 200
         elif 'HTTP_HOST' in self.META:
200 201
             host = self.META['HTTP_HOST']
9  docs/ref/request-response.txt
@@ -193,10 +193,11 @@ Methods
193 193
 
194 194
 .. method:: HttpRequest.get_host()
195 195
 
196  
-    Returns the originating host of the request using information from the
197  
-    ``HTTP_X_FORWARDED_HOST`` and ``HTTP_HOST`` headers (in that order). If
198  
-    they don't provide a value, the method uses a combination of
199  
-    ``SERVER_NAME`` and ``SERVER_PORT`` as detailed in :pep:`3333`.
  196
+    Returns the originating host of the request using information from
  197
+    the ``HTTP_X_FORWARDED_HOST`` (if enabled in the settings) and ``HTTP_HOST``
  198
+    headers (in that order). If they don't provide a value, the method
  199
+    uses a combination of ``SERVER_NAME`` and ``SERVER_PORT`` as
  200
+    detailed in :pep:`3333`.
200 201
 
201 202
     Example: ``"127.0.0.1:8000"``
202 203
 
15  docs/ref/settings.txt
@@ -2078,6 +2078,19 @@ When :setting:`USE_L10N` is set to ``True`` and if this is also set to
2078 2078
 See also :setting:`DECIMAL_SEPARATOR`, :setting:`NUMBER_GROUPING` and
2079 2079
 :setting:`THOUSAND_SEPARATOR`.
2080 2080
 
  2081
+.. setting:: USE_X_FORWARDED_HOST
  2082
+
  2083
+USE_X_FORWARDED_HOST
  2084
+--------------------
  2085
+
  2086
+.. versionadded:: 1.3.1
  2087
+
  2088
+Default: ``False``
  2089
+
  2090
+A boolean that specifies whether to use the X-Forwarded-Host header in
  2091
+preference to the Host header. This should only be enabled if a proxy
  2092
+which sets this header is in use.
  2093
+
2081 2094
 .. setting:: YEAR_MONTH_FORMAT
2082 2095
 
2083 2096
 YEAR_MONTH_FORMAT
@@ -2135,4 +2148,4 @@ IGNORABLE_404_STARTS
2135 2148
 --------------------
2136 2149
 
2137 2150
 .. deprecated:: 1.4
2138  
-   This setting has been superseded by :setting:`IGNORABLE_404_URLS`.
  2151
+   This setting has been superseded by :setting:`IGNORABLE_404_URLS`.
17  docs/topics/security.txt
@@ -145,6 +145,23 @@ information is not leaked:
145 145
 
146 146
 .. _additional-security-topics:
147 147
 
  148
+Host Headers and Virtual Hosting
  149
+================================
  150
+
  151
+Django uses the Host header provided by the client to construct URLs
  152
+in certain cases. While these values are sanitized to prevent Cross
  153
+Site Scripting attacks, they can be used for Cross-Site Request
  154
+Forgery and cache poisoning attacks in some circumstances. We
  155
+recommend that users of Django ensure their web-server configuration
  156
+always validates incoming HTTP Host headers against the expected host
  157
+name, disallows requests with no Host header, and that the web server
  158
+not be configured with a catch-all virtual host which forwards
  159
+requests to a Django application.
  160
+
  161
+Additionally, as of 1.3.1, Django requires users to explicitly enable
  162
+support for the X-Forwarded-Host header if their configuration
  163
+requires it.
  164
+
148 165
 Additional security topics
149 166
 ==========================
150 167
 
90  tests/regressiontests/requests/tests.py
@@ -2,12 +2,14 @@
2 2
 from datetime import datetime, timedelta
3 3
 from StringIO import StringIO
4 4
 
  5
+from django.conf import settings
5 6
 from django.core.handlers.modpython import ModPythonRequest
6 7
 from django.core.handlers.wsgi import WSGIRequest, LimitedStream
7 8
 from django.http import HttpRequest, HttpResponse, parse_cookie, build_request_repr
8 9
 from django.utils import unittest
9 10
 from django.utils.http import cookie_date
10 11
 
  12
+
11 13
 class RequestsTests(unittest.TestCase):
12 14
     def test_httprequest(self):
13 15
         request = HttpRequest()
@@ -97,6 +99,94 @@ def test_httprequest_location(self):
97 99
         self.assertEqual(request.build_absolute_uri(location="/path/with:colons"),
98 100
             'http://www.example.com/path/with:colons')
99 101
 
  102
+    def test_http_get_host(self):
  103
+        old_USE_X_FORWARDED_HOST = settings.USE_X_FORWARDED_HOST
  104
+        try:
  105
+            settings.USE_X_FORWARDED_HOST = False
  106
+
  107
+            # Check if X_FORWARDED_HOST is provided.
  108
+            request = HttpRequest()
  109
+            request.META = {
  110
+                u'HTTP_X_FORWARDED_HOST': u'forward.com',
  111
+                u'HTTP_HOST': u'example.com',
  112
+                u'SERVER_NAME': u'internal.com',
  113
+                u'SERVER_PORT': 80,
  114
+            }
  115
+            # X_FORWARDED_HOST is ignored.
  116
+            self.assertEqual(request.get_host(), 'example.com')
  117
+
  118
+            # Check if X_FORWARDED_HOST isn't provided.
  119
+            request = HttpRequest()
  120
+            request.META = {
  121
+                u'HTTP_HOST': u'example.com',
  122
+                u'SERVER_NAME': u'internal.com',
  123
+                u'SERVER_PORT': 80,
  124
+            }
  125
+            self.assertEqual(request.get_host(), 'example.com')
  126
+
  127
+            # Check if HTTP_HOST isn't provided.
  128
+            request = HttpRequest()
  129
+            request.META = {
  130
+                u'SERVER_NAME': u'internal.com',
  131
+                u'SERVER_PORT': 80,
  132
+            }
  133
+            self.assertEqual(request.get_host(), 'internal.com')
  134
+
  135
+            # Check if HTTP_HOST isn't provided, and we're on a nonstandard port
  136
+            request = HttpRequest()
  137
+            request.META = {
  138
+                u'SERVER_NAME': u'internal.com',
  139
+                u'SERVER_PORT': 8042,
  140
+            }
  141
+            self.assertEqual(request.get_host(), 'internal.com:8042')
  142
+
  143
+        finally:
  144
+            settings.USE_X_FORWARDED_HOST = old_USE_X_FORWARDED_HOST
  145
+
  146
+    def test_http_get_host_with_x_forwarded_host(self):
  147
+        old_USE_X_FORWARDED_HOST = settings.USE_X_FORWARDED_HOST
  148
+        try:
  149
+            settings.USE_X_FORWARDED_HOST = True
  150
+
  151
+            # Check if X_FORWARDED_HOST is provided.
  152
+            request = HttpRequest()
  153
+            request.META = {
  154
+                u'HTTP_X_FORWARDED_HOST': u'forward.com',
  155
+                u'HTTP_HOST': u'example.com',
  156
+                u'SERVER_NAME': u'internal.com',
  157
+                u'SERVER_PORT': 80,
  158
+            }
  159
+            # X_FORWARDED_HOST is obeyed.
  160
+            self.assertEqual(request.get_host(), 'forward.com')
  161
+
  162
+            # Check if X_FORWARDED_HOST isn't provided.
  163
+            request = HttpRequest()
  164
+            request.META = {
  165
+                u'HTTP_HOST': u'example.com',
  166
+                u'SERVER_NAME': u'internal.com',
  167
+                u'SERVER_PORT': 80,
  168
+            }
  169
+            self.assertEqual(request.get_host(), 'example.com')
  170
+
  171
+            # Check if HTTP_HOST isn't provided.
  172
+            request = HttpRequest()
  173
+            request.META = {
  174
+                u'SERVER_NAME': u'internal.com',
  175
+                u'SERVER_PORT': 80,
  176
+            }
  177
+            self.assertEqual(request.get_host(), 'internal.com')
  178
+
  179
+            # Check if HTTP_HOST isn't provided, and we're on a nonstandard port
  180
+            request = HttpRequest()
  181
+            request.META = {
  182
+                u'SERVER_NAME': u'internal.com',
  183
+                u'SERVER_PORT': 8042,
  184
+            }
  185
+            self.assertEqual(request.get_host(), 'internal.com:8042')
  186
+
  187
+        finally:
  188
+            settings.USE_X_FORWARDED_HOST = old_USE_X_FORWARDED_HOST
  189
+
100 190
     def test_near_expiration(self):
101 191
         "Cookie will expire when an near expiration time is provided"
102 192
         response = HttpResponse()

0 notes on commit 893cea2

Please sign in to comment.
Something went wrong with that request. Please try again.