Fixed CVE-2016-9014 -- Validated Host header when DEBUG=True.
This is a security fix.
This commit is contained in:
parent
da7910d483
commit
7fe2d8d940
|
@ -96,12 +96,13 @@ class HttpRequest(object):
|
||||||
"""Return the HTTP host using the environment or request headers."""
|
"""Return the HTTP host using the environment or request headers."""
|
||||||
host = self._get_raw_host()
|
host = self._get_raw_host()
|
||||||
|
|
||||||
# There is no hostname validation when DEBUG=True
|
# Allow variants of localhost if ALLOWED_HOSTS is empty and DEBUG=True.
|
||||||
if settings.DEBUG:
|
allowed_hosts = settings.ALLOWED_HOSTS
|
||||||
return host
|
if settings.DEBUG and not allowed_hosts:
|
||||||
|
allowed_hosts = ['localhost', '127.0.0.1', '[::1]']
|
||||||
|
|
||||||
domain, port = split_domain_port(host)
|
domain, port = split_domain_port(host)
|
||||||
if domain and validate_host(domain, settings.ALLOWED_HOSTS):
|
if domain and validate_host(domain, allowed_hosts):
|
||||||
return host
|
return host
|
||||||
else:
|
else:
|
||||||
msg = "Invalid HTTP_HOST header: %r." % host
|
msg = "Invalid HTTP_HOST header: %r." % host
|
||||||
|
|
|
@ -90,8 +90,10 @@ If the ``Host`` header (or ``X-Forwarded-Host`` if
|
||||||
list, the :meth:`django.http.HttpRequest.get_host()` method will raise
|
list, the :meth:`django.http.HttpRequest.get_host()` method will raise
|
||||||
:exc:`~django.core.exceptions.SuspiciousOperation`.
|
:exc:`~django.core.exceptions.SuspiciousOperation`.
|
||||||
|
|
||||||
When :setting:`DEBUG` is ``True``, host validation is disabled; any host will
|
When :setting:`DEBUG` is ``True`` and ``ALLOWED_HOSTS`` is empty, the host
|
||||||
be accepted. ``ALLOWED_HOSTS`` is :ref:`checked when running tests
|
is validated against ``['localhost', '127.0.0.1', '[::1]']``.
|
||||||
|
|
||||||
|
``ALLOWED_HOSTS`` is also :ref:`checked when running tests
|
||||||
<topics-testing-advanced-multiple-hosts>`.
|
<topics-testing-advanced-multiple-hosts>`.
|
||||||
|
|
||||||
This validation only applies via :meth:`~django.http.HttpRequest.get_host()`;
|
This validation only applies via :meth:`~django.http.HttpRequest.get_host()`;
|
||||||
|
@ -102,6 +104,10 @@ are bypassing this security protection.
|
||||||
|
|
||||||
In older versions, ``ALLOWED_HOSTS`` wasn't checked when running tests.
|
In older versions, ``ALLOWED_HOSTS`` wasn't checked when running tests.
|
||||||
|
|
||||||
|
In older versions, ``ALLOWED_HOSTS`` wasn't checked if ``DEBUG=True``.
|
||||||
|
This was also changed in Django 1.10.3, 1.9.11, and 1.8.16 to prevent a
|
||||||
|
DNS rebinding attack.
|
||||||
|
|
||||||
.. setting:: APPEND_SLASH
|
.. setting:: APPEND_SLASH
|
||||||
|
|
||||||
``APPEND_SLASH``
|
``APPEND_SLASH``
|
||||||
|
|
|
@ -20,6 +20,28 @@ the ``manage.py test --keepdb`` option or if the user has an active session
|
||||||
|
|
||||||
A randomly generated password is now used for each test run.
|
A randomly generated password is now used for each test run.
|
||||||
|
|
||||||
|
DNS rebinding vulnerability when ``DEBUG=True``
|
||||||
|
===============================================
|
||||||
|
|
||||||
|
Older versions of Django don't validate the ``Host`` header against
|
||||||
|
``settings.ALLOWED_HOSTS`` when ``settings.DEBUG=True``. This makes them
|
||||||
|
vulnerable to a `DNS rebinding attack
|
||||||
|
<http://benmmurphy.github.io/blog/2016/07/11/rails-webconsole-dns-rebinding/>`_.
|
||||||
|
|
||||||
|
While Django doesn't ship a module that allows remote code execution, this is
|
||||||
|
at least a cross-site scripting vector, which could be quite serious if
|
||||||
|
developers load a copy of the production database in development or connect to
|
||||||
|
some production services for which there's no development instance, for
|
||||||
|
example. If a project uses a package like the ``django-debug-toolbar``, then
|
||||||
|
the attacker could execute arbitrary SQL, which could be especially bad if the
|
||||||
|
developers connect to the database with a superuser account.
|
||||||
|
|
||||||
|
``settings.ALLOWED_HOSTS`` is now validated regardless of ``DEBUG``. For
|
||||||
|
convenience, if ``ALLOWED_HOSTS`` is empty and ``DEBUG=True``, the following
|
||||||
|
variations of localhost are allowed ``['localhost', '127.0.0.1', '::1']``. If
|
||||||
|
your local settings file has your production ``ALLOWED_HOSTS`` value, you must
|
||||||
|
now omit it to get those fallback values.
|
||||||
|
|
||||||
Bugfixes
|
Bugfixes
|
||||||
========
|
========
|
||||||
|
|
||||||
|
|
|
@ -19,3 +19,25 @@ the ``manage.py test --keepdb`` option or if the user has an active session
|
||||||
(such as an attacker's connection).
|
(such as an attacker's connection).
|
||||||
|
|
||||||
A randomly generated password is now used for each test run.
|
A randomly generated password is now used for each test run.
|
||||||
|
|
||||||
|
DNS rebinding vulnerability when ``DEBUG=True``
|
||||||
|
===============================================
|
||||||
|
|
||||||
|
Older versions of Django don't validate the ``Host`` header against
|
||||||
|
``settings.ALLOWED_HOSTS`` when ``settings.DEBUG=True``. This makes them
|
||||||
|
vulnerable to a `DNS rebinding attack
|
||||||
|
<http://benmmurphy.github.io/blog/2016/07/11/rails-webconsole-dns-rebinding/>`_.
|
||||||
|
|
||||||
|
While Django doesn't ship a module that allows remote code execution, this is
|
||||||
|
at least a cross-site scripting vector, which could be quite serious if
|
||||||
|
developers load a copy of the production database in development or connect to
|
||||||
|
some production services for which there's no development instance, for
|
||||||
|
example. If a project uses a package like the ``django-debug-toolbar``, then
|
||||||
|
the attacker could execute arbitrary SQL, which could be especially bad if the
|
||||||
|
developers connect to the database with a superuser account.
|
||||||
|
|
||||||
|
``settings.ALLOWED_HOSTS`` is now validated regardless of ``DEBUG``. For
|
||||||
|
convenience, if ``ALLOWED_HOSTS`` is empty and ``DEBUG=True``, the following
|
||||||
|
variations of localhost are allowed ``['localhost', '127.0.0.1', '::1']``. If
|
||||||
|
your local settings file has your production ``ALLOWED_HOSTS`` value, you must
|
||||||
|
now omit it to get those fallback values.
|
||||||
|
|
|
@ -19,3 +19,25 @@ the ``manage.py test --keepdb`` option or if the user has an active session
|
||||||
(such as an attacker's connection).
|
(such as an attacker's connection).
|
||||||
|
|
||||||
A randomly generated password is now used for each test run.
|
A randomly generated password is now used for each test run.
|
||||||
|
|
||||||
|
DNS rebinding vulnerability when ``DEBUG=True``
|
||||||
|
===============================================
|
||||||
|
|
||||||
|
Older versions of Django don't validate the ``Host`` header against
|
||||||
|
``settings.ALLOWED_HOSTS`` when ``settings.DEBUG=True``. This makes them
|
||||||
|
vulnerable to a `DNS rebinding attack
|
||||||
|
<http://benmmurphy.github.io/blog/2016/07/11/rails-webconsole-dns-rebinding/>`_.
|
||||||
|
|
||||||
|
While Django doesn't ship a module that allows remote code execution, this is
|
||||||
|
at least a cross-site scripting vector, which could be quite serious if
|
||||||
|
developers load a copy of the production database in development or connect to
|
||||||
|
some production services for which there's no development instance, for
|
||||||
|
example. If a project uses a package like the ``django-debug-toolbar``, then
|
||||||
|
the attacker could execute arbitrary SQL, which could be especially bad if the
|
||||||
|
developers connect to the database with a superuser account.
|
||||||
|
|
||||||
|
``settings.ALLOWED_HOSTS`` is now validated regardless of ``DEBUG``. For
|
||||||
|
convenience, if ``ALLOWED_HOSTS`` is empty and ``DEBUG=True``, the following
|
||||||
|
variations of localhost are allowed ``['localhost', '127.0.0.1', '::1']``. If
|
||||||
|
your local settings file has your production ``ALLOWED_HOSTS`` value, you must
|
||||||
|
now omit it to get those fallback values.
|
||||||
|
|
|
@ -386,7 +386,7 @@ class CsrfViewMiddlewareTest(SimpleTestCase):
|
||||||
self.assertEqual(len(csrf_cookie.value), CSRF_TOKEN_LENGTH)
|
self.assertEqual(len(csrf_cookie.value), CSRF_TOKEN_LENGTH)
|
||||||
self._check_token_present(resp, csrf_id=csrf_cookie.value)
|
self._check_token_present(resp, csrf_id=csrf_cookie.value)
|
||||||
|
|
||||||
@override_settings(DEBUG=True)
|
@override_settings(DEBUG=True, ALLOWED_HOSTS=['www.example.com'])
|
||||||
def test_https_bad_referer(self):
|
def test_https_bad_referer(self):
|
||||||
"""
|
"""
|
||||||
Test that a POST HTTPS request with a bad referer is rejected
|
Test that a POST HTTPS request with a bad referer is rejected
|
||||||
|
|
|
@ -779,21 +779,22 @@ class HostValidationTests(SimpleTestCase):
|
||||||
self.assertEqual(request.get_port(), '8080')
|
self.assertEqual(request.get_port(), '8080')
|
||||||
|
|
||||||
@override_settings(DEBUG=True, ALLOWED_HOSTS=[])
|
@override_settings(DEBUG=True, ALLOWED_HOSTS=[])
|
||||||
def test_host_validation_disabled_in_debug_mode(self):
|
def test_host_validation_in_debug_mode(self):
|
||||||
"""If ALLOWED_HOSTS is empty and DEBUG is True, all hosts pass."""
|
"""
|
||||||
|
If ALLOWED_HOSTS is empty and DEBUG is True, variants of localhost are
|
||||||
|
allowed.
|
||||||
|
"""
|
||||||
|
valid_hosts = ['localhost', '127.0.0.1', '[::1]']
|
||||||
|
for host in valid_hosts:
|
||||||
request = HttpRequest()
|
request = HttpRequest()
|
||||||
request.META = {
|
request.META = {'HTTP_HOST': host}
|
||||||
'HTTP_HOST': 'example.com',
|
self.assertEqual(request.get_host(), host)
|
||||||
}
|
|
||||||
self.assertEqual(request.get_host(), 'example.com')
|
|
||||||
|
|
||||||
# Invalid hostnames would normally raise a SuspiciousOperation,
|
# Other hostnames raise a SuspiciousOperation.
|
||||||
# but we have DEBUG=True, so this check is disabled.
|
with self.assertRaises(SuspiciousOperation):
|
||||||
request = HttpRequest()
|
request = HttpRequest()
|
||||||
request.META = {
|
request.META = {'HTTP_HOST': 'example.com'}
|
||||||
'HTTP_HOST': "invalid_hostname.com",
|
request.get_host()
|
||||||
}
|
|
||||||
self.assertEqual(request.get_host(), "invalid_hostname.com")
|
|
||||||
|
|
||||||
@override_settings(ALLOWED_HOSTS=[])
|
@override_settings(ALLOWED_HOSTS=[])
|
||||||
def test_get_host_suggestion_of_allowed_host(self):
|
def test_get_host_suggestion_of_allowed_host(self):
|
||||||
|
|
Loading…
Reference in New Issue