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:
parent
0516ac3d28
commit
893cea211a
|
@ -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 #
|
||||||
##############
|
##############
|
||||||
|
|
|
@ -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']
|
||||||
|
|
|
@ -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"``
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue