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

Skip to content

Commit 45acd6d

Browse files
committed
[1.9.x] Fixed CVE-2016-9014 -- Validated Host header when DEBUG=True.
This is a security fix.
1 parent 4844d86 commit 45acd6d

File tree

5 files changed

+71
-21
lines changed

5 files changed

+71
-21
lines changed

django/http/request.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,13 @@ def get_host(self):
9292
"""Return the HTTP host using the environment or request headers."""
9393
host = self._get_raw_host()
9494

95-
# There is no hostname validation when DEBUG=True
96-
if settings.DEBUG:
97-
return host
95+
# Allow variants of localhost if ALLOWED_HOSTS is empty and DEBUG=True.
96+
allowed_hosts = settings.ALLOWED_HOSTS
97+
if settings.DEBUG and not allowed_hosts:
98+
allowed_hosts = ['localhost', '127.0.0.1', '[::1]']
9899

99100
domain, port = split_domain_port(host)
100-
if domain and validate_host(domain, settings.ALLOWED_HOSTS):
101+
if domain and validate_host(domain, allowed_hosts):
101102
return host
102103
else:
103104
msg = "Invalid HTTP_HOST header: %r." % host

docs/ref/settings.txt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,18 @@ If the ``Host`` header (or ``X-Forwarded-Host`` if
9090
list, the :meth:`django.http.HttpRequest.get_host()` method will raise
9191
:exc:`~django.core.exceptions.SuspiciousOperation`.
9292

93-
When :setting:`DEBUG` is ``True`` or when running tests, host validation is
94-
disabled; any host will be accepted. Thus it's usually only necessary to set it
95-
in production.
93+
When :setting:`DEBUG` is ``True`` and ``ALLOWED_HOSTS`` is empty, the host
94+
is validated against ``['localhost', '127.0.0.1', '[::1]']``.
9695

9796
This validation only applies via :meth:`~django.http.HttpRequest.get_host()`;
9897
if your code accesses the ``Host`` header directly from ``request.META`` you
9998
are bypassing this security protection.
10099

100+
.. versionchanged:: 1.9.11
101+
102+
In older versions, ``ALLOWED_HOSTS`` wasn't checked if ``DEBUG=True``.
103+
This was also changed in Django 1.8.16 to prevent a DNS rebinding attack.
104+
101105
.. setting:: ALLOWED_INCLUDE_ROOTS
102106

103107
ALLOWED_INCLUDE_ROOTS

docs/releases/1.8.16.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,25 @@ the ``manage.py test --keepdb`` option or if the user has an active session
1919
(such as an attacker's connection).
2020

2121
A randomly generated password is now used for each test run.
22+
23+
DNS rebinding vulnerability when ``DEBUG=True``
24+
===============================================
25+
26+
Older versions of Django don't validate the ``Host`` header against
27+
``settings.ALLOWED_HOSTS`` when ``settings.DEBUG=True``. This makes them
28+
vulnerable to a `DNS rebinding attack
29+
<http://benmmurphy.github.io/blog/2016/07/11/rails-webconsole-dns-rebinding/>`_.
30+
31+
While Django doesn't ship a module that allows remote code execution, this is
32+
at least a cross-site scripting vector, which could be quite serious if
33+
developers load a copy of the production database in development or connect to
34+
some production services for which there's no development instance, for
35+
example. If a project uses a package like the ``django-debug-toolbar``, then
36+
the attacker could execute arbitrary SQL, which could be especially bad if the
37+
developers connect to the database with a superuser account.
38+
39+
``settings.ALLOWED_HOSTS`` is now validated regardless of ``DEBUG``. For
40+
convenience, if ``ALLOWED_HOSTS`` is empty and ``DEBUG=True``, the following
41+
variations of localhost are allowed ``['localhost', '127.0.0.1', '::1']``. If
42+
your local settings file has your production ``ALLOWED_HOSTS`` value, you must
43+
now omit it to get those fallback values.

docs/releases/1.9.11.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,25 @@ the ``manage.py test --keepdb`` option or if the user has an active session
1919
(such as an attacker's connection).
2020

2121
A randomly generated password is now used for each test run.
22+
23+
DNS rebinding vulnerability when ``DEBUG=True``
24+
===============================================
25+
26+
Older versions of Django don't validate the ``Host`` header against
27+
``settings.ALLOWED_HOSTS`` when ``settings.DEBUG=True``. This makes them
28+
vulnerable to a `DNS rebinding attack
29+
<http://benmmurphy.github.io/blog/2016/07/11/rails-webconsole-dns-rebinding/>`_.
30+
31+
While Django doesn't ship a module that allows remote code execution, this is
32+
at least a cross-site scripting vector, which could be quite serious if
33+
developers load a copy of the production database in development or connect to
34+
some production services for which there's no development instance, for
35+
example. If a project uses a package like the ``django-debug-toolbar``, then
36+
the attacker could execute arbitrary SQL, which could be especially bad if the
37+
developers connect to the database with a superuser account.
38+
39+
``settings.ALLOWED_HOSTS`` is now validated regardless of ``DEBUG``. For
40+
convenience, if ``ALLOWED_HOSTS`` is empty and ``DEBUG=True``, the following
41+
variations of localhost are allowed ``['localhost', '127.0.0.1', '::1']``. If
42+
your local settings file has your production ``ALLOWED_HOSTS`` value, you must
43+
now omit it to get those fallback values.

tests/requests/tests.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -709,21 +709,22 @@ def test_get_port_with_x_forwarded_port(self):
709709
self.assertEqual(request.get_port(), '8080')
710710

711711
@override_settings(DEBUG=True, ALLOWED_HOSTS=[])
712-
def test_host_validation_disabled_in_debug_mode(self):
713-
"""If ALLOWED_HOSTS is empty and DEBUG is True, all hosts pass."""
714-
request = HttpRequest()
715-
request.META = {
716-
'HTTP_HOST': 'example.com',
717-
}
718-
self.assertEqual(request.get_host(), 'example.com')
712+
def test_host_validation_in_debug_mode(self):
713+
"""
714+
If ALLOWED_HOSTS is empty and DEBUG is True, variants of localhost are
715+
allowed.
716+
"""
717+
valid_hosts = ['localhost', '127.0.0.1', '[::1]']
718+
for host in valid_hosts:
719+
request = HttpRequest()
720+
request.META = {'HTTP_HOST': host}
721+
self.assertEqual(request.get_host(), host)
719722

720-
# Invalid hostnames would normally raise a SuspiciousOperation,
721-
# but we have DEBUG=True, so this check is disabled.
722-
request = HttpRequest()
723-
request.META = {
724-
'HTTP_HOST': "invalid_hostname.com",
725-
}
726-
self.assertEqual(request.get_host(), "invalid_hostname.com")
723+
# Other hostnames raise a SuspiciousOperation.
724+
with self.assertRaises(SuspiciousOperation):
725+
request = HttpRequest()
726+
request.META = {'HTTP_HOST': 'example.com'}
727+
request.get_host()
727728

728729
@override_settings(ALLOWED_HOSTS=[])
729730
def test_get_host_suggestion_of_allowed_host(self):

0 commit comments

Comments
 (0)