High severityNVD Advisory· Published Oct 19, 2011· Updated Apr 29, 2026
CVE-2011-4139
CVE-2011-4139
Description
Django before 1.2.7 and 1.3.x before 1.3.1 uses a request's HTTP Host header to construct a full URL in certain circumstances, which allows remote attackers to conduct cache poisoning attacks via a crafted request.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
DjangoPyPI | < 1.2.7 | 1.2.7 |
DjangoPyPI | >= 1.3, < 1.3.1 | 1.3.1 |
Affected products
22cpe:2.3:a:djangoproject:django:*:*:*:*:*:*:*:*+ 21 more
- cpe:2.3:a:djangoproject:django:*:*:*:*:*:*:*:*range: <=1.2.6
- cpe:2.3:a:djangoproject:django:0.91:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:0.95:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:0.95.1:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:0.96:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.0:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.0.1:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.0.2:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.1:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.1.0:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.1.2:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.1.3:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.2:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.2.1:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.2.1:2:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.2.2:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.2.3:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.2.4:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.2.5:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.3:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.3:alpha1:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.3:alpha2:*:*:*:*:*:*
Patches
2c613af4d6485[1.2.X] Added protection against spoofing of X_FORWARDED_HOST headers. A security announcement will be made shortly.
5 files changed · +112 −5
django/conf/global_settings.py+2 −0 modified@@ -390,6 +390,8 @@ DEFAULT_TABLESPACE = '' DEFAULT_INDEX_TABLESPACE = '' +USE_X_FORWARDED_HOST = False + ############## # MIDDLEWARE # ##############
django/http/__init__.py+2 −1 modified@@ -45,7 +45,8 @@ def __repr__(self): def get_host(self): """Returns the HTTP host using the environment or request headers.""" # 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'] elif 'HTTP_HOST' in self.META: host = self.META['HTTP_HOST']
docs/ref/request-response.txt+5 −4 modified@@ -185,10 +185,11 @@ Methods .. method:: HttpRequest.get_host() - Returns the originating host of the request using information from the - ``HTTP_X_FORWARDED_HOST`` and ``HTTP_HOST`` headers (in that order). If - they don't provide a value, the method uses a combination of - ``SERVER_NAME`` and ``SERVER_PORT`` as detailed in `PEP 333`_. + Returns the originating host of the request using information from + the ``HTTP_X_FORWARDED_HOST`` (if enabled in the settings) and ``HTTP_HOST`` + headers (in that order). If they don't provide a value, the method + uses a combination of ``SERVER_NAME`` and ``SERVER_PORT`` as + detailed in :pep:`3333`. .. _PEP 333: http://www.python.org/dev/peps/pep-0333/
docs/ref/settings.txt+13 −0 modified@@ -1698,6 +1698,19 @@ and ``NUMBER_GROUPING`` from current locale, to format the number. 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 YEAR_MONTH_FORMAT
tests/regressiontests/requests/tests.py+90 −0 modified@@ -2,11 +2,13 @@ import time import unittest +from django.conf import settings from django.http import HttpRequest, HttpResponse, parse_cookie from django.core.handlers.wsgi import WSGIRequest from django.core.handlers.modpython import ModPythonRequest from django.utils.http import cookie_date + class RequestsTests(unittest.TestCase): def test_httprequest(self): @@ -57,3 +59,91 @@ def test_httprequest_location(self): request.path = '' self.assertEqual(request.build_absolute_uri(location="/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
2f7fadc38efa[1.3.X] Added protection against spoofing of X_FORWARDED_HOST headers. A security announcement will be made shortly.
5 files changed · +112 −5
django/conf/global_settings.py+2 −0 modified@@ -399,6 +399,8 @@ DEFAULT_TABLESPACE = '' DEFAULT_INDEX_TABLESPACE = '' +USE_X_FORWARDED_HOST = False + ############## # MIDDLEWARE # ##############
django/http/__init__.py+2 −1 modified@@ -153,7 +153,8 @@ def __repr__(self): def get_host(self): """Returns the HTTP host using the environment or request headers.""" # 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'] elif 'HTTP_HOST' in self.META: host = self.META['HTTP_HOST']
docs/ref/request-response.txt+5 −4 modified@@ -191,10 +191,11 @@ Methods .. method:: HttpRequest.get_host() - Returns the originating host of the request using information from the - ``HTTP_X_FORWARDED_HOST`` and ``HTTP_HOST`` headers (in that order). If - they don't provide a value, the method uses a combination of - ``SERVER_NAME`` and ``SERVER_PORT`` as detailed in `PEP 333`_. + Returns the originating host of the request using information from + the ``HTTP_X_FORWARDED_HOST`` (if enabled in the settings) and ``HTTP_HOST`` + headers (in that order). If they don't provide a value, the method + uses a combination of ``SERVER_NAME`` and ``SERVER_PORT`` as + detailed in :pep:`3333`. .. _PEP 333: http://www.python.org/dev/peps/pep-0333/
docs/ref/settings.txt+13 −0 modified@@ -1960,6 +1960,19 @@ in order to format numbers. 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 YEAR_MONTH_FORMAT
tests/regressiontests/requests/tests.py+90 −0 modified@@ -2,12 +2,14 @@ from datetime import datetime, timedelta from StringIO import StringIO +from django.conf import settings from django.core.handlers.modpython import ModPythonRequest from django.core.handlers.wsgi import WSGIRequest, LimitedStream from django.http import HttpRequest, HttpResponse, parse_cookie from django.utils import unittest from django.utils.http import cookie_date + class RequestsTests(unittest.TestCase): def test_httprequest(self): request = HttpRequest() @@ -58,6 +60,94 @@ def test_httprequest_location(self): self.assertEqual(request.build_absolute_uri(location="/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): "Cookie will expire when an near expiration time is provided" response = HttpResponse()
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
15- openwall.com/lists/oss-security/2011/09/11/1nvdPatchWEB
- openwall.com/lists/oss-security/2011/09/13/2nvdPatchWEB
- bugzilla.redhat.com/show_bug.cginvdPatchWEB
- www.djangoproject.com/weblog/2011/sep/09/nvdPatchVendor Advisory
- www.djangoproject.com/weblog/2011/sep/10/127/nvdPatch
- github.com/advisories/GHSA-rm2j-x595-q9cjghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2011-4139ghsaADVISORY
- www.debian.org/security/2011/dsa-2332nvdWEB
- github.com/django/django/commit/2f7fadc38efa58ac0a8f93f936b82332a199f396ghsaWEB
- github.com/django/django/commit/c613af4d6485586c79d692b70a9acac429f3ca9dghsaWEB
- github.com/pypa/advisory-database/tree/main/vulns/django/PYSEC-2011-4.yamlghsaWEB
- hermes.opensuse.org/messages/14700881nvdWEB
- www.djangoproject.com/weblog/2011/sep/09ghsaWEB
- www.djangoproject.com/weblog/2011/sep/10/127ghsaWEB
- secunia.com/advisories/46614nvd
News mentions
0No linked articles in our index yet.