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/<sometag>some
text</sometag></a><br />'
+- 'Change: <input class="vURLField" name="test" type="url" '
+- 'value="http://example.com/<sometag>some
text</sometag>" /></p>'
++ '<input class="vURLField" name="test" type="url" '
++ 'value="http://example.com/<sometag>some '
++ 'text</sometag>" />'
+ )
+ 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/<sometag>some
text</sometag></a><br />'
+- 'Change: <input class="vURLField" name="test" type="url" '
+- 'value="http://example-ΓΒ€ΓΒΌΓΒΆ.com/<sometag>some
text</sometag>" /></p>'
++ '<input class="vURLField" name="test" type="url" '
++ 'value="http://example-ΓΒ€ΓΒΌΓΒΆ.com/<sometag>some '
++ 'text</sometag>" />'
+ )
+ 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