Added protection against spoofing of X_FORWARDED_HOST headers. A security announcement will be made shortly.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16758 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Russell Keith-Magee 2011-09-10 00:46:38 +00:00
parent 0516ac3d28
commit 893cea211a
6 changed files with 130 additions and 6 deletions

View File

@ -402,6 +402,8 @@ DEFAULT_INDEX_TABLESPACE = ''
# Default X-Frame-Options header value # Default X-Frame-Options header value
X_FRAME_OPTIONS = 'SAMEORIGIN' X_FRAME_OPTIONS = 'SAMEORIGIN'
USE_X_FORWARDED_HOST = False
############## ##############
# MIDDLEWARE # # MIDDLEWARE #
############## ##############

View File

@ -194,7 +194,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

@ -193,10 +193,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:`3333`. uses a combination of ``SERVER_NAME`` and ``SERVER_PORT`` as
detailed in :pep:`3333`.
Example: ``"127.0.0.1:8000"`` Example: ``"127.0.0.1:8000"``

View File

@ -2078,6 +2078,19 @@ When :setting:`USE_L10N` is set to ``True`` and if this is also set to
See also :setting:`DECIMAL_SEPARATOR`, :setting:`NUMBER_GROUPING` and See also :setting:`DECIMAL_SEPARATOR`, :setting:`NUMBER_GROUPING` and
:setting:`THOUSAND_SEPARATOR`. :setting:`THOUSAND_SEPARATOR`.
.. 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
@ -2135,4 +2148,4 @@ IGNORABLE_404_STARTS
-------------------- --------------------
.. deprecated:: 1.4 .. deprecated:: 1.4
This setting has been superseded by :setting:`IGNORABLE_404_URLS`. This setting has been superseded by :setting:`IGNORABLE_404_URLS`.

View File

@ -145,6 +145,23 @@ information is not leaked:
.. _additional-security-topics: .. _additional-security-topics:
Host Headers and Virtual Hosting
================================
Django uses the Host header provided by the client to construct URLs
in certain cases. While these values are sanitized to prevent Cross
Site Scripting attacks, they can be used for Cross-Site Request
Forgery and cache poisoning attacks in some circumstances. We
recommend that users of Django ensure their web-server configuration
always validates incoming HTTP Host headers against the expected host
name, disallows requests with no Host header, and that the web server
not be configured with a catch-all virtual host which forwards
requests to a Django application.
Additionally, as of 1.3.1, Django requires users to explicitly enable
support for the X-Forwarded-Host header if their configuration
requires it.
Additional security topics Additional security topics
========================== ==========================

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, build_request_repr from django.http import HttpRequest, HttpResponse, parse_cookie, build_request_repr
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()
@ -97,6 +99,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()