Hi Moritz,

> > Security team (added to CC), would you like an upload for stable?
> 
> Please do, if we do a DSA, let's also include the fixes for CVE-2019-6975
> and CVE-2019-12308 which were previously postponed due to low impact, ack?

Sure thing; my proposed diff is attached. It builds for me (with all
tests passing) in a stretch chroot.


Regards,

-- 
      ,''`.
     : :'  :     Chris Lamb
     `. `'`      la...@debian.org πŸ₯ chris-lamb.co.uk
       `-
diff --git a/debian/changelog b/debian/changelog
index fa89c8b21..5bb1d6625 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,14 @@
+python-django (1:1.10.7-2+deb9u5) stretch-security; urgency=high
+
+  * CVE-2019-6975: Fix memory exhaustion in utils.numberformat.format.
+    (Closes: #922027)
+  * CVE-2019-12308: Prevent a XSS vulnerability in the Django admin via the
+    AdminURLFieldWidget. (Closes: #929927)
+  * CVE-2019-12781: Prevent incorrect HTTPS detection with reverse-proxies
+    connecting via HTTPS. (Closes: #931316)
+
+ -- Chris Lamb <la...@debian.org>  Tue, 02 Jul 2019 23:07:21 -0300
+
 python-django (1:1.10.7-2+deb9u4) stretch-security; urgency=high
 
   * CVE-2019-3498: Prevent a content-spoofing vulnerability in the default
diff --git a/debian/patches/0018-CVE-2019-6975.patch 
b/debian/patches/0018-CVE-2019-6975.patch
new file mode 100644
index 000000000..39c2f864c
--- /dev/null
+++ b/debian/patches/0018-CVE-2019-6975.patch
@@ -0,0 +1,69 @@
+From: Carlton Gibson <carlton.gib...@noumenal.es>
+Date: Mon, 11 Feb 2019 11:15:45 +0100
+Subject: Fixed CVE-2019-6975 -- Fixed memory exhaustion in
+ utils.numberformat.format().
+
+Thanks Sjoerd Job Postmus for the report and initial patch.
+Thanks Michael Manfre, Tim Graham, and Florian Apolloner for review.
+
+Backport of 402c0caa851e265410fbcaa55318f22d2bf22ee2 from master.
+---
+ django/utils/numberformat.py           | 15 ++++++++++++++-
+ tests/utils_tests/test_numberformat.py | 18 ++++++++++++++++++
+ 2 files changed, 32 insertions(+), 1 deletion(-)
+
+diff --git a/django/utils/numberformat.py b/django/utils/numberformat.py
+index 6667d82..8b4d228 100644
+--- a/django/utils/numberformat.py
++++ b/django/utils/numberformat.py
+@@ -27,7 +27,20 @@ def format(number, decimal_sep, decimal_pos=None, 
grouping=0, thousand_sep='',
+     # sign
+     sign = ''
+     if isinstance(number, Decimal):
+-        str_number = '{:f}'.format(number)
++        # Format values with more than 200 digits (an arbitrary cutoff) using
++        # scientific notation to avoid high memory usage in {:f}'.format().
++        _, digits, exponent = number.as_tuple()
++        if abs(exponent) + len(digits) > 200:
++            number = '{:e}'.format(number)
++            coefficient, exponent = number.split('e')
++            # Format the coefficient.
++            coefficient = format(
++                coefficient, decimal_sep, decimal_pos, grouping,
++                thousand_sep, force_grouping,
++            )
++            return '{}e{}'.format(coefficient, exponent)
++        else:
++            str_number = '{:f}'.format(number)
+     else:
+         str_number = six.text_type(number)
+     if str_number[0] == '-':
+diff --git a/tests/utils_tests/test_numberformat.py 
b/tests/utils_tests/test_numberformat.py
+index 3dd1b06..769406c 100644
+--- a/tests/utils_tests/test_numberformat.py
++++ b/tests/utils_tests/test_numberformat.py
+@@ -60,6 +60,24 @@ class TestNumberFormat(TestCase):
+         self.assertEqual(nformat(Decimal('1234'), '.', grouping=2, 
thousand_sep=',', force_grouping=True), '12,34')
+         self.assertEqual(nformat(Decimal('-1234.33'), '.', decimal_pos=1), 
'-1234.3')
+         self.assertEqual(nformat(Decimal('0.00000001'), '.', decimal_pos=8), 
'0.00000001')
++        # Very large & small numbers.
++        tests = [
++            ('9e9999', None, '9e+9999'),
++            ('9e9999', 3, '9.000e+9999'),
++            ('9e201', None, '9e+201'),
++            ('9e200', None, '9e+200'),
++            ('1.2345e999', 2, '1.23e+999'),
++            ('9e-999', None, '9e-999'),
++            ('1e-7', 8, '0.00000010'),
++            ('1e-8', 8, '0.00000001'),
++            ('1e-9', 8, '0.00000000'),
++            ('1e-10', 8, '0.00000000'),
++            ('1e-11', 8, '0.00000000'),
++            ('1' + ('0' * 300), 3, '1.000e+300'),
++            ('0.{}1234'.format('0' * 299), 3, '1.234e-300'),
++        ]
++        for value, decimal_pos, expected_value in tests:
++            self.assertEqual(nformat(Decimal(value), '.', decimal_pos), 
expected_value)
+ 
+     def test_decimal_subclass(self):
+         class EuroDecimal(Decimal):
diff --git a/debian/patches/0019-CVE-2019-12308.patch 
b/debian/patches/0019-CVE-2019-12308.patch
new file mode 100644
index 000000000..d3e73f45d
--- /dev/null
+++ b/debian/patches/0019-CVE-2019-12308.patch
@@ -0,0 +1,77 @@
+From: Chris Lamb <la...@debian.org>
+Date: Tue, 2 Jul 2019 22:47:00 -0300
+Subject: CVE-2019-12308
+
+Backported from 
https://github.com/django/django/commit/c238701859a52d584f349cce15d56c8e8137c52b
+
+---
+ django/contrib/admin/widgets.py | 11 +++++++++--
+ tests/admin_widgets/tests.py    | 16 ++++++----------
+ 2 files changed, 15 insertions(+), 12 deletions(-)
+
+diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py
+index c228a3c..26de9c7 100644
+--- a/django/contrib/admin/widgets.py
++++ b/django/contrib/admin/widgets.py
+@@ -7,6 +7,8 @@ import copy
+ 
+ from django import forms
+ from django.db.models.deletion import CASCADE
++from django.core.exceptions import ValidationError
++from django.core.validators import URLValidator
+ from django.forms.utils import flatatt
+ from django.forms.widgets import RadioFieldRenderer
+ from django.template.loader import render_to_string
+@@ -369,15 +371,20 @@ class AdminEmailInputWidget(forms.EmailInput):
+ 
+ 
+ class AdminURLFieldWidget(forms.URLInput):
+-    def __init__(self, attrs=None):
++    def __init__(self, attrs=None, validator_class=URLValidator):
+         final_attrs = {'class': 'vURLField'}
+         if attrs is not None:
+             final_attrs.update(attrs)
+         super(AdminURLFieldWidget, self).__init__(attrs=final_attrs)
++        self.validator = validator_class()
+ 
+     def render(self, name, value, attrs=None):
+         html = super(AdminURLFieldWidget, self).render(name, value, attrs)
+-        if value:
++        try:
++            self.validator(value if value else '')
++        except ValidationError:
++            pass
++        else:
+             value = force_text(self.format_value(value))
+             final_attrs = {'href': smart_urlquote(value)}
+             html = format_html(
+diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py
+index 308ec17..5a75c67 100644
+--- a/tests/admin_widgets/tests.py
++++ b/tests/admin_widgets/tests.py
+@@ -377,19 +377,15 @@ class AdminURLWidgetTest(SimpleTestCase):
+         w = widgets.AdminURLFieldWidget()
+         self.assertEqual(
+             w.render('test', 'http://example.com/<sometag>some 
text</sometag>'),
+-            '<p class="url">Currently: '
+-            '<a 
href="http://example.com/%3Csometag%3Esome%20text%3C/sometag%3E";>'
+-            'http://example.com/&lt;sometag&gt;some 
text&lt;/sometag&gt;</a><br />'
+-            'Change: <input class="vURLField" name="test" type="url" '
+-            'value="http://example.com/&lt;sometag&gt;some 
text&lt;/sometag&gt;" /></p>'
++            '<input class="vURLField" name="test" type="url" '
++            'value="http://example.com/&lt;sometag&gt;some '
++            'text&lt;/sometag&gt;" />'
+         )
+         self.assertEqual(
+             w.render('test', 'http://example-Àüâ.com/<sometag>some 
text</sometag>'),
+-            '<p class="url">Currently: '
+-            '<a 
href="http://xn--example--7za4pnc.com/%3Csometag%3Esome%20text%3C/sometag%3E";>'
+-            'http://example-Àüâ.com/&lt;sometag&gt;some 
text&lt;/sometag&gt;</a><br />'
+-            'Change: <input class="vURLField" name="test" type="url" '
+-            'value="http://example-Àüâ.com/&lt;sometag&gt;some 
text&lt;/sometag&gt;" /></p>'
++            '<input class="vURLField" name="test" type="url" '
++            'value="http://example-Àüâ.com/&lt;sometag&gt;some '
++            'text&lt;/sometag&gt;" />'
+         )
+         self.assertEqual(
+             w.render('test', 
'http://www.example.com/%C3%A4";><script>alert("XSS!")</script>"'),
diff --git a/debian/patches/0020-CVE-2019-12781.patch 
b/debian/patches/0020-CVE-2019-12781.patch
new file mode 100644
index 000000000..2d3e22c26
--- /dev/null
+++ b/debian/patches/0020-CVE-2019-12781.patch
@@ -0,0 +1,55 @@
+From: Chris Lamb <la...@debian.org>
+Date: Tue, 2 Jul 2019 23:02:23 -0300
+Subject: CVE-2019-12781
+
+Backport of 
https://github.com/django/django/commit/32124fc41e75074141b05f10fc55a4f01ff7f050
+---
+ django/http/request.py        |  7 ++++---
+ tests/settings_tests/tests.py | 12 ++++++++++++
+ 2 files changed, 16 insertions(+), 3 deletions(-)
+
+diff --git a/django/http/request.py b/django/http/request.py
+index 8c32af5..87b4fca 100644
+--- a/django/http/request.py
++++ b/django/http/request.py
+@@ -199,13 +199,14 @@ class HttpRequest(object):
+     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/tests/settings_tests/tests.py b/tests/settings_tests/tests.py
+index 97d734e..f0f1fe5 100644
+--- a/tests/settings_tests/tests.py
++++ b/tests/settings_tests/tests.py
+@@ -419,6 +419,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):
diff --git a/debian/patches/series b/debian/patches/series
index 5bda383eb..96a095e53 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -9,3 +9,6 @@ fix-test-middleware-classes-headers.patch
 0016-CVE-2017-12794.patch
 0006-Default-to-supporting-Spatialite-4.2.patch
 0017-CVE-2019-3498.patch
+0018-CVE-2019-6975.patch
+0019-CVE-2019-12308.patch
+0020-CVE-2019-12781.patch

Reply via email to