Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9936fdb

Browse files
committed
[1.4.x] Added ALLOWED_HOSTS setting for HTTP host header validation.
This is a security fix; disclosure and advisory coming shortly.
1 parent 57b62a7 commit 9936fdb

File tree

13 files changed

+314
-175
lines changed

13 files changed

+314
-175
lines changed

django/conf/global_settings.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
# * Receive x-headers
3030
INTERNAL_IPS = ()
3131

32+
# Hosts/domain names that are valid for this site.
33+
# "*" matches anything, ".example.com" matches example.com and all subdomains
34+
ALLOWED_HOSTS = ['*']
35+
3236
# Local time zone for this installation. All choices can be found here:
3337
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name (although not all
3438
# systems may support all possibilities). When USE_TZ is True, this is

django/conf/project_template/project_name/settings.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
}
2121
}
2222

23+
# Hosts/domain names that are valid for this site; required if DEBUG is False
24+
# See https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/#allowed-hosts
25+
ALLOWED_HOSTS = []
26+
2327
# Local time zone for this installation. Choices can be found here:
2428
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
2529
# although not all choices may be available on all operating systems.

django/contrib/auth/tests/views.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ def test_email_found_custom_from(self):
107107
self.assertEqual(len(mail.outbox), 1)
108108
self.assertEqual("[email protected]", mail.outbox[0].from_email)
109109

110+
@override_settings(ALLOWED_HOSTS=['adminsite.com'])
110111
def test_admin_reset(self):
111112
"If the reset view is marked as being for admin, the HTTP_HOST header is used for a domain override."
112113
response = self.client.post('/admin_password_reset/',

django/contrib/contenttypes/tests.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from django.http import HttpRequest, Http404
1010
from django.test import TestCase
1111
from django.utils.encoding import smart_str
12+
from django.test.utils import override_settings
1213

1314

1415
class FooWithoutUrl(models.Model):
@@ -114,6 +115,7 @@ def test_get_for_models_full_cache(self):
114115
FooWithUrl: ContentType.objects.get_for_model(FooWithUrl),
115116
})
116117

118+
@override_settings(ALLOWED_HOSTS=['example.com'])
117119
def test_shortcut_view(self):
118120
"""
119121
Check that the shortcut view (used for the admin "view on site"

django/contrib/sites/tests.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from django.core.exceptions import ObjectDoesNotExist
44
from django.http import HttpRequest
55
from django.test import TestCase
6+
from django.test.utils import override_settings
67

78

89
class SitesFrameworkTests(TestCase):
@@ -39,6 +40,7 @@ def test_site_cache(self):
3940
site = Site.objects.get_current()
4041
self.assertEqual(u"Example site", site.name)
4142

43+
@override_settings(ALLOWED_HOSTS=['example.com'])
4244
def test_get_current_site(self):
4345
# Test that the correct Site object is returned
4446
request = HttpRequest()

django/http/__init__.py

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -215,11 +215,12 @@ def get_host(self):
215215
if server_port != (self.is_secure() and '443' or '80'):
216216
host = '%s:%s' % (host, server_port)
217217

218-
# Disallow potentially poisoned hostnames.
219-
if not host_validation_re.match(host.lower()):
220-
raise SuspiciousOperation('Invalid HTTP_HOST header: %s' % host)
221-
222-
return host
218+
allowed_hosts = ['*'] if settings.DEBUG else settings.ALLOWED_HOSTS
219+
if validate_host(host, allowed_hosts):
220+
return host
221+
else:
222+
raise SuspiciousOperation(
223+
"Invalid HTTP_HOST header (you may need to set ALLOWED_HOSTS): %s" % host)
223224

224225
def get_full_path(self):
225226
# RFC 3986 requires query string arguments to be in the ASCII range.
@@ -799,3 +800,43 @@ def str_to_unicode(s, encoding):
799800
else:
800801
return s
801802

803+
def validate_host(host, allowed_hosts):
804+
"""
805+
Validate the given host header value for this site.
806+
807+
Check that the host looks valid and matches a host or host pattern in the
808+
given list of ``allowed_hosts``. Any pattern beginning with a period
809+
matches a domain and all its subdomains (e.g. ``.example.com`` matches
810+
``example.com`` and any subdomain), ``*`` matches anything, and anything
811+
else must match exactly.
812+
813+
Return ``True`` for a valid host, ``False`` otherwise.
814+
815+
"""
816+
# All validation is case-insensitive
817+
host = host.lower()
818+
819+
# Basic sanity check
820+
if not host_validation_re.match(host):
821+
return False
822+
823+
# Validate only the domain part.
824+
if host[-1] == ']':
825+
# It's an IPv6 address without a port.
826+
domain = host
827+
else:
828+
domain = host.rsplit(':', 1)[0]
829+
830+
for pattern in allowed_hosts:
831+
pattern = pattern.lower()
832+
match = (
833+
pattern == '*' or
834+
pattern.startswith('.') and (
835+
domain.endswith(pattern) or domain == pattern[1:]
836+
) or
837+
pattern == domain
838+
)
839+
if match:
840+
return True
841+
842+
return False

django/test/utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ def setup_test_environment():
7575
mail.original_email_backend = settings.EMAIL_BACKEND
7676
settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
7777

78+
settings._original_allowed_hosts = settings.ALLOWED_HOSTS
79+
settings.ALLOWED_HOSTS = ['*']
80+
7881
mail.outbox = []
7982

8083
deactivate()
@@ -93,6 +96,9 @@ def teardown_test_environment():
9396
settings.EMAIL_BACKEND = mail.original_email_backend
9497
del mail.original_email_backend
9598

99+
settings.ALLOWED_HOSTS = settings._original_allowed_hosts
100+
del settings._original_allowed_hosts
101+
96102
del mail.outbox
97103

98104

docs/ref/settings.txt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,42 @@ of (Full name, email address). Example::
6868
Note that Django will email *all* of these people whenever an error happens.
6969
See :doc:`/howto/error-reporting` for more information.
7070

71+
.. setting:: ALLOWED_HOSTS
72+
73+
ALLOWED_HOSTS
74+
-------------
75+
76+
Default: ``['*']``
77+
78+
A list of strings representing the host/domain names that this Django site can
79+
serve. This is a security measure to prevent an attacker from poisoning caches
80+
and password reset emails with links to malicious hosts by submitting requests
81+
with a fake HTTP ``Host`` header, which is possible even under many
82+
seemingly-safe webserver configurations.
83+
84+
Values in this list can be fully qualified names (e.g. ``'www.example.com'``),
85+
in which case they will be matched against the request's ``Host`` header
86+
exactly (case-insensitive, not including port). A value beginning with a period
87+
can be used as a subdomain wildcard: ``'.example.com'`` will match
88+
``example.com``, ``www.example.com``, and any other subdomain of
89+
``example.com``. A value of ``'*'`` will match anything; in this case you are
90+
responsible to provide your own validation of the ``Host`` header (perhaps in a
91+
middleware; if so this middleware must be listed first in
92+
:setting:`MIDDLEWARE_CLASSES`).
93+
94+
If the ``Host`` header (or ``X-Forwarded-Host`` if
95+
:setting:`USE_X_FORWARDED_HOST` is enabled) does not match any value in this
96+
list, the :meth:`django.http.HttpRequest.get_host()` method will raise
97+
:exc:`~django.core.exceptions.SuspiciousOperation`.
98+
99+
When :setting:`DEBUG` is ``True`` or when running tests, host validation is
100+
disabled; any host will be accepted. Thus it's usually only necessary to set it
101+
in production.
102+
103+
This validation only applies via :meth:`~django.http.HttpRequest.get_host()`;
104+
if your code accesses the ``Host`` header directly from ``request.META`` you
105+
are bypassing this security protection.
106+
71107
.. setting:: ALLOWED_INCLUDE_ROOTS
72108

73109
ALLOWED_INCLUDE_ROOTS

docs/releases/1.4.4.txt

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
==========================
2+
Django 1.4.4 release notes
3+
==========================
4+
5+
*February 19, 2013*
6+
7+
This is the fourth bugfix/security release in the Django 1.4 series.
8+
9+
Host header poisoning
10+
---------------------
11+
12+
Some parts of Django -- independent of end-user-written applications -- make
13+
use of full URLs, including domain name, which are generated from the HTTP Host
14+
header. Django's documentation has for some time contained notes advising users
15+
on how to configure webservers to ensure that only valid Host headers can reach
16+
the Django application. However, it has been reported to us that even with the
17+
recommended webserver configurations there are still techniques available for
18+
tricking many common webservers into supplying the application with an
19+
incorrect and possibly malicious Host header.
20+
21+
For this reason, Django 1.4.4 adds a new setting, ``ALLOWED_HOSTS``, containing
22+
an explicit list of valid host/domain names for this site. A request with a
23+
Host header not matching an entry in this list will raise
24+
``SuspiciousOperation`` if ``request.get_host()`` is called. For full details
25+
see the documentation for the :setting:`ALLOWED_HOSTS` setting.
26+
27+
The default value for this setting in Django 1.4.4 is `['*']` (matching any
28+
host), for backwards-compatibility, but we strongly encourage all sites to set
29+
a more restrictive value.
30+
31+
This host validation is disabled when ``DEBUG`` is ``True`` or when running tests.
32+
33+
34+
Other bugfixes and changes
35+
==========================
36+
37+
* Changed a SQL command syntax to be MySQL 4 compatible (#19702).
38+
* Added backwards-compatibility with old unsalted MD5 passwords (#18144).
39+
* Numerous documentation improvements and fixes.

docs/releases/index.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Final releases
2020
.. toctree::
2121
:maxdepth: 1
2222

23+
1.4.4
2324
1.4.2
2425
1.4.1
2526
1.4

0 commit comments

Comments
 (0)