diff options
| author | Carlton Gibson <carlton.gibson@noumenal.es> | 2019-06-13 10:57:29 +0200 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2019-07-01 08:24:47 +0200 |
| commit | 1e40f427bb8d0fb37cc9f830096a97c36c97af6f (patch) | |
| tree | 8f8525e01763ea03bfc8608c8d9475cfa8ab03cc | |
| parent | 87be9c9626014a2754729d6293cb4c86ba294354 (diff) | |
[2.1.x] Fixed CVE-2019-12781 -- Made HttpRequest always trust SECURE_PROXY_SSL_HEADER if set.
An HTTP request would not be redirected to HTTPS when the
SECURE_PROXY_SSL_HEADER and SECURE_SSL_REDIRECT settings were used if
the proxy connected to Django via HTTPS.
HttpRequest.scheme will now always trust the SECURE_PROXY_SSL_HEADER if
set, rather than falling back to the request scheme when the
SECURE_PROXY_SSL_HEADER did not have the secure value.
Thanks to Gavin Wahl for the report and initial patch suggestion, and
Shai Berger for review.
Backport of 54d0f5e62f54c29a12dd96f44bacd810cbe03ac8 from master
| -rw-r--r-- | django/http/request.py | 7 | ||||
| -rw-r--r-- | docs/ref/settings.txt | 11 | ||||
| -rw-r--r-- | docs/releases/1.11.22.txt | 20 | ||||
| -rw-r--r-- | docs/releases/2.1.10.txt | 20 | ||||
| -rw-r--r-- | tests/settings_tests/tests.py | 12 |
5 files changed, 63 insertions, 7 deletions
diff --git a/django/http/request.py b/django/http/request.py index 36d05baf4d..536638ca32 100644 --- a/django/http/request.py +++ b/django/http/request.py @@ -210,13 +210,14 @@ class HttpRequest: def scheme(self): if settings.SECURE_PROXY_SSL_HEADER: try: - header, value = settings.SECURE_PROXY_SSL_HEADER + header, secure_value = settings.SECURE_PROXY_SSL_HEADER except ValueError: raise ImproperlyConfigured( 'The SECURE_PROXY_SSL_HEADER setting must be a tuple containing two values.' ) - if self.META.get(header) == value: - return 'https' + header_value = self.META.get(header) + if header_value is not None: + return 'https' if header_value == secure_value else 'http' return self._get_scheme() def is_secure(self): diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 165b2dbeea..60d5a336db 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -2198,10 +2198,13 @@ By default, ``is_secure()`` determines if a request is secure by confirming that a requested URL uses ``https://``. This method is important for Django's CSRF protection, and it may be used by your own code or third-party apps. -If your Django app is behind a proxy, though, the proxy may be "swallowing" the -fact that a request is HTTPS, using a non-HTTPS connection between the proxy -and Django. In this case, ``is_secure()`` would always return ``False`` -- even -for requests that were made via HTTPS by the end user. +If your Django app is behind a proxy, though, the proxy may be "swallowing" +whether the original request uses HTTPS or not. If there is a non-HTTPS +connection between the proxy and Django then ``is_secure()`` would always +return ``False`` -- even for requests that were made via HTTPS by the end user. +In contrast, if there is an HTTPS connection between the proxy and Django then +``is_secure()`` would always return ``True`` -- even for requests that were +made originally via HTTP. In this situation, configure your proxy to set a custom HTTP header that tells Django whether the request came in via HTTPS, and set diff --git a/docs/releases/1.11.22.txt b/docs/releases/1.11.22.txt index 91d81890df..58ea68146e 100644 --- a/docs/releases/1.11.22.txt +++ b/docs/releases/1.11.22.txt @@ -5,3 +5,23 @@ Django 1.11.22 release notes *July 1, 2019* Django 1.11.22 fixes a security issue in 1.11.21. + +CVE-2019-12781: Incorrect HTTP detection with reverse-proxy connecting via HTTPS +-------------------------------------------------------------------------------- + +When deployed behind a reverse-proxy connecting to Django via HTTPS, +:attr:`django.http.HttpRequest.scheme` would incorrectly detect client +requests made via HTTP as using HTTPS. This entails incorrect results for +:meth:`~django.http.HttpRequest.is_secure`, and +:meth:`~django.http.HttpRequest.build_absolute_uri`, and that HTTP +requests would not be redirected to HTTPS in accordance with +:setting:`SECURE_SSL_REDIRECT`. + +``HttpRequest.scheme`` now respects :setting:`SECURE_PROXY_SSL_HEADER`, if it +is configured, and the appropriate header is set on the request, for both HTTP +and HTTPS requests. + +If you deploy Django behind a reverse-proxy that forwards HTTP requests, and +that connects to Django via HTTPS, be sure to verify that your application +correctly handles code paths relying on ``scheme``, ``is_secure()``, +``build_absolute_uri()``, and ``SECURE_SSL_REDIRECT``. diff --git a/docs/releases/2.1.10.txt b/docs/releases/2.1.10.txt index c572e42623..c5914c23c2 100644 --- a/docs/releases/2.1.10.txt +++ b/docs/releases/2.1.10.txt @@ -5,3 +5,23 @@ Django 2.1.10 release notes *July 1, 2019* Django 2.1.10 fixes a security issue in 2.1.9. + +CVE-2019-12781: Incorrect HTTP detection with reverse-proxy connecting via HTTPS +-------------------------------------------------------------------------------- + +When deployed behind a reverse-proxy connecting to Django via HTTPS, +:attr:`django.http.HttpRequest.scheme` would incorrectly detect client +requests made via HTTP as using HTTPS. This entails incorrect results for +:meth:`~django.http.HttpRequest.is_secure`, and +:meth:`~django.http.HttpRequest.build_absolute_uri`, and that HTTP +requests would not be redirected to HTTPS in accordance with +:setting:`SECURE_SSL_REDIRECT`. + +``HttpRequest.scheme`` now respects :setting:`SECURE_PROXY_SSL_HEADER`, if it +is configured, and the appropriate header is set on the request, for both HTTP +and HTTPS requests. + +If you deploy Django behind a reverse-proxy that forwards HTTP requests, and +that connects to Django via HTTPS, be sure to verify that your application +correctly handles code paths relying on ``scheme``, ``is_secure()``, +``build_absolute_uri()``, and ``SECURE_SSL_REDIRECT``. diff --git a/tests/settings_tests/tests.py b/tests/settings_tests/tests.py index 383345494d..8fb27a106f 100644 --- a/tests/settings_tests/tests.py +++ b/tests/settings_tests/tests.py @@ -367,6 +367,18 @@ class SecureProxySslHeaderTest(SimpleTestCase): req.META['HTTP_X_FORWARDED_PROTOCOL'] = 'https' self.assertIs(req.is_secure(), True) + @override_settings(SECURE_PROXY_SSL_HEADER=('HTTP_X_FORWARDED_PROTOCOL', 'https')) + def test_xheader_preferred_to_underlying_request(self): + class ProxyRequest(HttpRequest): + def _get_scheme(self): + """Proxy always connecting via HTTPS""" + return 'https' + + # Client connects via HTTP. + req = ProxyRequest() + req.META['HTTP_X_FORWARDED_PROTOCOL'] = 'http' + self.assertIs(req.is_secure(), False) + class IsOverriddenTest(SimpleTestCase): def test_configure(self): |
