summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlton Gibson <carlton.gibson@noumenal.es>2019-06-13 10:57:29 +0200
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2019-07-01 07:50:48 +0200
commit77706a3e4766da5d5fb75c4db22a0a59a28e6cd6 (patch)
treee2f3db1e846eb120c839d57872f46f90ef15e1f0
parentdb9f7b44fcdca18ef26dafaa87575a0745bc86cf (diff)
[2.2.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.py7
-rw-r--r--docs/ref/settings.txt11
-rw-r--r--docs/releases/1.11.22.txt20
-rw-r--r--docs/releases/2.1.10.txt20
-rw-r--r--docs/releases/2.2.3.txt24
-rw-r--r--tests/settings_tests/tests.py12
6 files changed, 85 insertions, 9 deletions
diff --git a/django/http/request.py b/django/http/request.py
index 02a127d664..39298aac77 100644
--- a/django/http/request.py
+++ b/django/http/request.py
@@ -215,13 +215,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 d203728e08..9618f1f039 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -2224,10 +2224,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/docs/releases/2.2.3.txt b/docs/releases/2.2.3.txt
index 2bb8736192..1dd20d3bfd 100644
--- a/docs/releases/2.2.3.txt
+++ b/docs/releases/2.2.3.txt
@@ -4,8 +4,28 @@ Django 2.2.3 release notes
*Expected July 1, 2019*
-Django 2.2.3 fixes several bugs in 2.2.2. Also, the latest string translations
-from Transifex are incorporated.
+Django 2.2.3 fixes a security issue and several bugs in 2.2.2. Also, the latest
+string translations from Transifex are incorporated.
+
+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``.
Bugfixes
========
diff --git a/tests/settings_tests/tests.py b/tests/settings_tests/tests.py
index bfd580b600..d0127db427 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):