commit 4418a0f56539cba537e98273f0a56f64d6920af2
Author: Carlton Gibson <carlton.gibson@noumenal.es>
Date:   Thu, 13 Jun 2019 10:57:29 +0200

    [1.8.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 32124fc41e75074141b05f10fc55a4f01ff7f050 from 1.11.x.

Index: b/django/http/request.py
===================================================================
--- a/django/http/request.py
+++ b/django/http/request.py
@@ -165,13 +165,14 @@ class HttpRequest(object):
         # First, check the SECURE_PROXY_SSL_HEADER setting.
         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, None) == value:
-                return 'https'
+            header_value = self.META.get(header)
+            if header_value is not None:
+                return 'https' if header_value == secure_value else 'http'
         # Failing that, fall back to _get_scheme(), which is a hook for
         # subclasses to implement.
         return self._get_scheme()
Index: b/docs/ref/settings.txt
===================================================================
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -2166,10 +2166,13 @@ whether a request is secure by looking a
 "https://". This is important for Django's CSRF protection, and 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, you'll want to configure your proxy to set a custom HTTP
 header that tells Django whether the request came in via HTTPS, and you'll want
Index: b/docs/releases/1.8.19.txt
===================================================================
--- a/docs/releases/1.8.19.txt
+++ b/docs/releases/1.8.19.txt
@@ -78,3 +78,23 @@ invalid UTF-8 octet sequences.
 
 ``uri_to_iri()`` now avoids recursion when re-percent-encoding invalid UTF-8
 octet sequences.
+
+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``.
Index: b/tests/settings_tests/tests.py
===================================================================
--- a/tests/settings_tests/tests.py
+++ b/tests/settings_tests/tests.py
@@ -417,6 +417,18 @@ class SecureProxySslHeaderTest(TestCase)
         req.META['HTTP_X_FORWARDED_PROTOCOL'] = 'https'
         self.assertEqual(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(TestCase):
     def test_configure(self):
