[1.3.X] Added protection against spoofing of X_FORWARDED_HOST headers. A security announcement will be made shortly.

Backport of r16758 from trunk.

git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16761 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Russell Keith-Magee 2011-09-10 01:07:50 +00:00
parent afe47636f7
commit 2f7fadc38e
5 changed files with 112 additions and 5 deletions

View File

@ -399,6 +399,8 @@ URL_VALIDATOR_USER_AGENT = "Django/%s (http://www.djangoproject.com)" % get_vers
DEFAULT_TABLESPACE = '' DEFAULT_TABLESPACE = ''
DEFAULT_INDEX_TABLESPACE = '' DEFAULT_INDEX_TABLESPACE = ''
USE_X_FORWARDED_HOST = False
############## ##############
# MIDDLEWARE # # MIDDLEWARE #
############## ##############

View File

@ -153,7 +153,8 @@ class HttpRequest(object):
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']

View File

@ -191,10 +191,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/

View File

@ -1960,6 +1960,19 @@ in order to format numbers.
See also :setting:`THOUSAND_SEPARATOR` and :setting:`NUMBER_GROUPING`. See also :setting:`THOUSAND_SEPARATOR` and :setting:`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

View File

@ -2,12 +2,14 @@ import time
from datetime import datetime, timedelta from datetime import datetime, timedelta
from StringIO import StringIO from StringIO import StringIO
from django.conf import settings
from django.core.handlers.modpython import ModPythonRequest from django.core.handlers.modpython import ModPythonRequest
from django.core.handlers.wsgi import WSGIRequest, LimitedStream from django.core.handlers.wsgi import WSGIRequest, LimitedStream
from django.http import HttpRequest, HttpResponse, parse_cookie from django.http import HttpRequest, HttpResponse, parse_cookie
from django.utils import unittest from django.utils import unittest
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):
request = HttpRequest() request = HttpRequest()
@ -58,6 +60,94 @@ class RequestsTests(unittest.TestCase):
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
def test_near_expiration(self): def test_near_expiration(self):
"Cookie will expire when an near expiration time is provided" "Cookie will expire when an near expiration time is provided"
response = HttpResponse() response = HttpResponse()