Skip to content

Commit

Permalink
[1.2.X] Added protection against spoofing of X_FORWARDED_HOST headers…
Browse files Browse the repository at this point in the history
…. A security announcement will be made shortly.

Backport of r16758 from trunk.

git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@16764 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information
freakboy3742 committed Sep 10, 2011
1 parent 285b464 commit c613af4
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 5 deletions.
2 changes: 2 additions & 0 deletions django/conf/global_settings.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -390,6 +390,8 @@
DEFAULT_TABLESPACE = '' DEFAULT_TABLESPACE = ''
DEFAULT_INDEX_TABLESPACE = '' DEFAULT_INDEX_TABLESPACE = ''


USE_X_FORWARDED_HOST = False

############## ##############
# MIDDLEWARE # # MIDDLEWARE #
############## ##############
Expand Down
3 changes: 2 additions & 1 deletion django/http/__init__.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ def __repr__(self):
def get_host(self): def get_host(self):
"""Returns the HTTP host using the environment or request headers.""" """Returns the HTTP host using the environment or request headers."""
# We try three options, in order of decreasing preference. # We try three options, in order of decreasing preference.
if 'HTTP_X_FORWARDED_HOST' in self.META: if settings.USE_X_FORWARDED_HOST and (
'HTTP_X_FORWARDED_HOST' in self.META):
host = self.META['HTTP_X_FORWARDED_HOST'] host = self.META['HTTP_X_FORWARDED_HOST']
elif 'HTTP_HOST' in self.META: elif 'HTTP_HOST' in self.META:
host = self.META['HTTP_HOST'] host = self.META['HTTP_HOST']
Expand Down
9 changes: 5 additions & 4 deletions docs/ref/request-response.txt
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -185,10 +185,11 @@ Methods


.. method:: HttpRequest.get_host() .. method:: HttpRequest.get_host()


Returns the originating host of the request using information from the Returns the originating host of the request using information from
``HTTP_X_FORWARDED_HOST`` and ``HTTP_HOST`` headers (in that order). If the ``HTTP_X_FORWARDED_HOST`` (if enabled in the settings) and ``HTTP_HOST``
they don't provide a value, the method uses a combination of headers (in that order). If they don't provide a value, the method
``SERVER_NAME`` and ``SERVER_PORT`` as detailed in `PEP 333`_. uses a combination of ``SERVER_NAME`` and ``SERVER_PORT`` as
detailed in :pep:`3333`.


.. _PEP 333: http://www.python.org/dev/peps/pep-0333/ .. _PEP 333: http://www.python.org/dev/peps/pep-0333/


Expand Down
13 changes: 13 additions & 0 deletions docs/ref/settings.txt
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -1698,6 +1698,19 @@ and ``NUMBER_GROUPING`` from current locale, to format the number.


See also ``THOUSAND_SEPARATOR`` and ``NUMBER_GROUPING``. See also ``THOUSAND_SEPARATOR`` and ``NUMBER_GROUPING``.


.. setting:: USE_X_FORWARDED_HOST

USE_X_FORWARDED_HOST
--------------------

.. versionadded:: 1.3.1

Default: ``False``

A boolean that specifies whether to use the X-Forwarded-Host header in
preference to the Host header. This should only be enabled if a proxy
which sets this header is in use.

.. setting:: YEAR_MONTH_FORMAT .. setting:: YEAR_MONTH_FORMAT


YEAR_MONTH_FORMAT YEAR_MONTH_FORMAT
Expand Down
90 changes: 90 additions & 0 deletions tests/regressiontests/requests/tests.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
import time import time
import unittest import unittest


from django.conf import settings
from django.http import HttpRequest, HttpResponse, parse_cookie from django.http import HttpRequest, HttpResponse, parse_cookie
from django.core.handlers.wsgi import WSGIRequest from django.core.handlers.wsgi import WSGIRequest
from django.core.handlers.modpython import ModPythonRequest from django.core.handlers.modpython import ModPythonRequest
from django.utils.http import cookie_date from django.utils.http import cookie_date



class RequestsTests(unittest.TestCase): class RequestsTests(unittest.TestCase):


def test_httprequest(self): def test_httprequest(self):
Expand Down Expand Up @@ -57,3 +59,91 @@ def test_httprequest_location(self):
request.path = '' request.path = ''
self.assertEqual(request.build_absolute_uri(location="/path/with:colons"), self.assertEqual(request.build_absolute_uri(location="/path/with:colons"),
'http://www.example.com/path/with:colons') 'http://www.example.com/path/with:colons')

def test_http_get_host(self):
old_USE_X_FORWARDED_HOST = settings.USE_X_FORWARDED_HOST
try:
settings.USE_X_FORWARDED_HOST = False

# Check if X_FORWARDED_HOST is provided.
request = HttpRequest()
request.META = {
u'HTTP_X_FORWARDED_HOST': u'forward.com',
u'HTTP_HOST': u'example.com',
u'SERVER_NAME': u'internal.com',
u'SERVER_PORT': 80,
}
# X_FORWARDED_HOST is ignored.
self.assertEqual(request.get_host(), 'example.com')

# Check if X_FORWARDED_HOST isn't provided.
request = HttpRequest()
request.META = {
u'HTTP_HOST': u'example.com',
u'SERVER_NAME': u'internal.com',
u'SERVER_PORT': 80,
}
self.assertEqual(request.get_host(), 'example.com')

# Check if HTTP_HOST isn't provided.
request = HttpRequest()
request.META = {
u'SERVER_NAME': u'internal.com',
u'SERVER_PORT': 80,
}
self.assertEqual(request.get_host(), 'internal.com')

# Check if HTTP_HOST isn't provided, and we're on a nonstandard port
request = HttpRequest()
request.META = {
u'SERVER_NAME': u'internal.com',
u'SERVER_PORT': 8042,
}
self.assertEqual(request.get_host(), 'internal.com:8042')

finally:
settings.USE_X_FORWARDED_HOST = old_USE_X_FORWARDED_HOST

def test_http_get_host_with_x_forwarded_host(self):
old_USE_X_FORWARDED_HOST = settings.USE_X_FORWARDED_HOST
try:
settings.USE_X_FORWARDED_HOST = True

# Check if X_FORWARDED_HOST is provided.
request = HttpRequest()
request.META = {
u'HTTP_X_FORWARDED_HOST': u'forward.com',
u'HTTP_HOST': u'example.com',
u'SERVER_NAME': u'internal.com',
u'SERVER_PORT': 80,
}
# X_FORWARDED_HOST is obeyed.
self.assertEqual(request.get_host(), 'forward.com')

# Check if X_FORWARDED_HOST isn't provided.
request = HttpRequest()
request.META = {
u'HTTP_HOST': u'example.com',
u'SERVER_NAME': u'internal.com',
u'SERVER_PORT': 80,
}
self.assertEqual(request.get_host(), 'example.com')

# Check if HTTP_HOST isn't provided.
request = HttpRequest()
request.META = {
u'SERVER_NAME': u'internal.com',
u'SERVER_PORT': 80,
}
self.assertEqual(request.get_host(), 'internal.com')

# Check if HTTP_HOST isn't provided, and we're on a nonstandard port
request = HttpRequest()
request.META = {
u'SERVER_NAME': u'internal.com',
u'SERVER_PORT': 8042,
}
self.assertEqual(request.get_host(), 'internal.com:8042')

finally:
settings.USE_X_FORWARDED_HOST = old_USE_X_FORWARDED_HOST

0 comments on commit c613af4

Please sign in to comment.