Hi Sébastien,
> > Security team (added to CC), would you be interested in uploads for
> > buster (currently 1:1.11.22-1~deb10u1) and stretch (currently
> > 1:1.10.7-2+deb9u5)?
[…]
> yes, thank you. Can you email us debdiffs ? I'll then take care of the
> review and DSAs. I've attached these and the testsuites (etc.) are
all green on my test machines.
Note that the previous changelog entry in buster was:
python-django (1:1.11.22-1~deb10u1) buster-security; urgency=high
* No-change update for buster-security.
* Update debian/gbp.conf for new debian/buster branch.
-- Chris Lamb <[email protected]> Wed, 03 Jul 2019 15:18:13 -0300
… and that I've tentatively versioned the updated version to address
these new CVEs as 1:1.11.22-1+deb10u1 (ie. with a plus, not a tilde).
I mention it specifically as I'm not 100% confident this is correct
and Lintian somewhat-correctly complained about a "missing" version
(to wit, 1:1.11.22-1 its technically missing).
Regards,
--
,''`.
: :' : Chris Lamb
`. `'` [email protected] 🍥 chris-lamb.co.uk
`-diff --git a/debian/changelog b/debian/changelog
index fa89c8b21..47e10adb4 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,59 @@
+python-django (1:1.10.7-2+deb9u5) stretch-security; urgency=high
+
+ * Backport four security patches from upstream. (Closes: #934026)
+ <https://www.djangoproject.com/weblog/2019/aug/01/security-releases/>
+
+ - CVE-2019-14232: Denial-of-service possibility in
+ django.utils.text.Truncator
+
+ If django.utils.text.Truncator's chars() and words() methods were passed
+ the html=True argument, they were extremely slow to evaluate certain
+ inputs due to a catastrophic backtracking vulnerability in a regular
+ expression. The chars() and words() methods are used to implement the
+ truncatechars_html and truncatewords_html template filters, which were
+ thus vulnerable.
+
+ The regular expressions used by Truncator have been simplified in order
+ to avoid potential backtracking issues. As a consequence, trailing
+ punctuation may now at times be included in the truncated output.
+
+ - CVE-2019-14233: Denial-of-service possibility in strip_tags()
+
+ Due to the behavior of the underlying HTMLParser,
+ django.utils.html.strip_tags() would be extremely slow to evaluate
+ certain inputs containing large sequences of nested incomplete HTML
+ entities. The strip_tags() method is used to implement the corresponding
+ striptags template filter, which was thus also vulnerable.
+
+ strip_tags() now avoids recursive calls to HTMLParser when progress
+ removing tags, but necessarily incomplete HTML entities, stops being
+ made.
+
+ Remember that absolutely NO guarantee is provided about the results of
+ strip_tags() being HTML safe. So NEVER mark safe the result of a
+ strip_tags() call without escaping it first, for example with
+ django.utils.html.escape().
+
+ - CVE-2019-14234: SQL injection possibility in key and index lookups for
+ JSONField/HStoreField
+
+ Key and index lookups for django.contrib.postgres.fields.JSONField and
+ key lookups for django.contrib.postgres.fields.HStoreField were subject
+ to SQL injection, using a suitably crafted dictionary, with dictionary
+ expansion, as the **kwargs passed to QuerySet.filter().
+
+ - CVE-2019-14235: Potential memory exhaustion in
+ django.utils.encoding.uri_to_iri()
+
+ If passed certain inputs, django.utils.encoding.uri_to_iri could lead to
+ significant memory usage due to excessive recursion when
+ re-percent-encoding invalid UTF-8 octet sequences.
+
+ uri_to_iri() now avoids recursion when re-percent-encoding invalid UTF-8
+ octet sequences.
+
+ -- Chris Lamb <[email protected]> Thu, 08 Aug 2019 10:42:49 +0100
+
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/0019-CVE-2019-14232.patch
b/debian/patches/0019-CVE-2019-14232.patch
new file mode 100644
index 000000000..3bccb924e
--- /dev/null
+++ b/debian/patches/0019-CVE-2019-14232.patch
@@ -0,0 +1,89 @@
+From: Chris Lamb <[email protected]>
+Date: Thu, 8 Aug 2019 10:30:35 +0100
+Subject: CVE-2019-14232
+
+Backported from
+<https://github.com/django/django/commit/42a66e969023c00536256469f0e8b8a099ef109d>
+---
+ django/utils/text.py | 4 ++--
+ .../filter_tests/test_truncatewords_html.py | 4 ++--
+ tests/utils_tests/test_text.py | 23 ++++++++++++++++++----
+ 3 files changed, 23 insertions(+), 8 deletions(-)
+
+diff --git a/django/utils/text.py b/django/utils/text.py
+index 5e4dd3d..a69cf7a 100644
+--- a/django/utils/text.py
++++ b/django/utils/text.py
+@@ -24,8 +24,8 @@ def capfirst(x):
+ capfirst = keep_lazy_text(capfirst)
+
+ # Set up regular expressions
+-re_words = re.compile(r'<.*?>|((?:\w[-\w]*|&.*?;)+)', re.U | re.S)
+-re_chars = re.compile(r'<.*?>|(.)', re.U | re.S)
++re_words = re.compile(r'<[^>]+?>|([^<>\s]+)', re.S)
++re_chars = re.compile(r'<[^>]+?>|(.)', re.S)
+ re_tag = re.compile(r'<(/)?(\S+?)(?:(\s*/)|\s.*?)?>', re.S)
+ re_newlines = re.compile(r'\r\n|\r') # Used in normalize_newlines
+ re_camel_case = re.compile(r'(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))')
+diff --git a/tests/template_tests/filter_tests/test_truncatewords_html.py
b/tests/template_tests/filter_tests/test_truncatewords_html.py
+index aec2abf..3c73442 100644
+--- a/tests/template_tests/filter_tests/test_truncatewords_html.py
++++ b/tests/template_tests/filter_tests/test_truncatewords_html.py
+@@ -19,13 +19,13 @@ class FunctionTests(SimpleTestCase):
+ def test_truncate2(self):
+ self.assertEqual(
+ truncatewords_html('<p>one <a href="#">two - three <br>four</a>
five</p>', 4),
+- '<p>one <a href="#">two - three <br>four ...</a></p>',
++ '<p>one <a href="#">two - three ...</a></p>',
+ )
+
+ def test_truncate3(self):
+ self.assertEqual(
+ truncatewords_html('<p>one <a href="#">two - three <br>four</a>
five</p>', 5),
+- '<p>one <a href="#">two - three <br>four</a> five</p>',
++ '<p>one <a href="#">two - three <br>four ...</a></p>',
+ )
+
+ def test_truncate4(self):
+diff --git a/tests/utils_tests/test_text.py b/tests/utils_tests/test_text.py
+index 2e1bc70..7e56bbf 100644
+--- a/tests/utils_tests/test_text.py
++++ b/tests/utils_tests/test_text.py
+@@ -90,6 +90,16 @@ class TestUtilsText(SimpleTestCase):
+ # Ensure that lazy strings are handled correctly
+ self.assertEqual(text.Truncator(lazystr('The quick brown
fox')).chars(12), 'The quick...')
+
++ def test_truncate_chars_html(self):
++ perf_test_values = [
++ (('</a' + '\t' * 50000) + '//>', None),
++ ('&' * 50000, '&' * 7 + '...'),
++ ('_X<<<<<<<<<<<>', None),
++ ]
++ for value, expected in perf_test_values:
++ truncator = text.Truncator(value)
++ self.assertEqual(expected if expected else value,
truncator.chars(10, html=True))
++
+ def test_truncate_words(self):
+ truncator = text.Truncator('The quick brown fox jumped over the lazy
dog.')
+ self.assertEqual('The quick brown fox jumped over the lazy dog.',
truncator.words(10))
+@@ -139,11 +149,16 @@ class TestUtilsText(SimpleTestCase):
+ truncator = text.Truncator('<i>Buenos días!
¿Cómo está?</i>')
+ self.assertEqual('<i>Buenos días! ¿Cómo...</i>',
truncator.words(3, '...', html=True))
+ truncator = text.Truncator('<p>I <3 python, what about you?</p>')
+- self.assertEqual('<p>I <3 python...</p>', truncator.words(3,
'...', html=True))
++ self.assertEqual('<p>I <3 python,...</p>', truncator.words(3,
'...', html=True))
+
+- re_tag_catastrophic_test = ('</a' + '\t' * 50000) + '//>'
+- truncator = text.Truncator(re_tag_catastrophic_test)
+- self.assertEqual(re_tag_catastrophic_test, truncator.words(500,
html=True))
++ perf_test_values = [
++ ('</a' + '\t' * 50000) + '//>',
++ '&' * 50000,
++ '_X<<<<<<<<<<<>',
++ ]
++ for value in perf_test_values:
++ truncator = text.Truncator(value)
++ self.assertEqual(value, truncator.words(50, html=True))
+
+ def test_wrap(self):
+ digits = '1234 67 9'
diff --git a/debian/patches/0020-CVE-2019-14233.patch
b/debian/patches/0020-CVE-2019-14233.patch
new file mode 100644
index 000000000..9fff26ef0
--- /dev/null
+++ b/debian/patches/0020-CVE-2019-14233.patch
@@ -0,0 +1,39 @@
+From: Chris Lamb <[email protected]>
+Date: Thu, 8 Aug 2019 10:31:08 +0100
+Subject: CVE-2019-14233
+
+Backported from
+<https://github.com/django/django/commit/52479acce792ad80bb0f915f20b835f919993c72>
+---
+ django/utils/html.py | 4 ++--
+ tests/utils_tests/test_html.py | 2 ++
+ 2 files changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/django/utils/html.py b/django/utils/html.py
+index 5a9f735..3fb791c 100644
+--- a/django/utils/html.py
++++ b/django/utils/html.py
+@@ -175,8 +175,8 @@ def strip_tags(value):
+ value = force_text(value)
+ while '<' in value and '>' in value:
+ new_value = _strip_once(value)
+- if len(new_value) >= len(value):
+- # _strip_once was not able to detect more tags or length increased
++ if len(new_value) >= len(value) or value.count('<') ==
new_value.count('<'):
++ # _strip_once wasn't able to detect more tags, or line length
increased.
+ # due to http://bugs.python.org/issue20288
+ # (affects Python 2 < 2.7.7 and Python 3 < 3.3.5)
+ break
+diff --git a/tests/utils_tests/test_html.py b/tests/utils_tests/test_html.py
+index 8b683c1..56c380c 100644
+--- a/tests/utils_tests/test_html.py
++++ b/tests/utils_tests/test_html.py
+@@ -86,6 +86,8 @@ class TestUtilsHtml(SimpleTestCase):
+ # caused infinite loop on Pythons not patched with
+ # http://bugs.python.org/issue20288
+ ('&gotcha&#;<>', '&gotcha&#;<>'),
++ ('><!' + ('&' * 16000) + 'D', '><!' + ('&' * 16000) + 'D'),
++ ('X<<<<br>br>br>br>X', 'XX'),
+ )
+ for value, output in items:
+ self.check_output(f, value, output)
diff --git a/debian/patches/0021-CVE-2019-14234.patch
b/debian/patches/0021-CVE-2019-14234.patch
new file mode 100644
index 000000000..c7096f37a
--- /dev/null
+++ b/debian/patches/0021-CVE-2019-14234.patch
@@ -0,0 +1,116 @@
+From: Chris Lamb <[email protected]>
+Date: Thu, 8 Aug 2019 10:35:56 +0100
+Subject: CVE-2019-14234
+
+Backported from
+<https://github.com/django/django/commit/ed682a24fca774818542757651bfba576c3fc3ef>
+---
+ django/contrib/postgres/fields/hstore.py | 2 +-
+ django/contrib/postgres/fields/jsonb.py | 8 +++-----
+ tests/postgres_tests/test_hstore.py | 14 ++++++++++++++
+ tests/postgres_tests/test_json.py | 15 ++++++++++++++-
+ 4 files changed, 32 insertions(+), 7 deletions(-)
+
+diff --git a/django/contrib/postgres/fields/hstore.py
b/django/contrib/postgres/fields/hstore.py
+index 8322d81..6d6f542 100644
+--- a/django/contrib/postgres/fields/hstore.py
++++ b/django/contrib/postgres/fields/hstore.py
+@@ -85,7 +85,7 @@ class KeyTransform(Transform):
+
+ def as_sql(self, compiler, connection):
+ lhs, params = compiler.compile(self.lhs)
+- return "(%s -> '%s')" % (lhs, self.key_name), params
++ return '(%s -> %%s)' % lhs, [self.key_name] + params
+
+
+ class KeyTransformFactory(object):
+diff --git a/django/contrib/postgres/fields/jsonb.py
b/django/contrib/postgres/fields/jsonb.py
+index ae83d9e..6d30b7a 100644
+--- a/django/contrib/postgres/fields/jsonb.py
++++ b/django/contrib/postgres/fields/jsonb.py
+@@ -75,12 +75,10 @@ class KeyTransform(Transform):
+ if len(key_transforms) > 1:
+ return "{} #> %s".format(lhs), [key_transforms] + params
+ try:
+- int(self.key_name)
++ lookup = int(self.key_name)
+ except ValueError:
+- lookup = "'%s'" % self.key_name
+- else:
+- lookup = "%s" % self.key_name
+- return "%s -> %s" % (lhs, lookup), params
++ lookup = self.key_name
++ return '(%s %s %%s)' % (lhs, self.operator), [lookup] + params
+
+
+ class KeyTransformFactory(object):
+diff --git a/tests/postgres_tests/test_hstore.py
b/tests/postgres_tests/test_hstore.py
+index 0afa630..fbcd32c 100644
+--- a/tests/postgres_tests/test_hstore.py
++++ b/tests/postgres_tests/test_hstore.py
+@@ -3,8 +3,10 @@ from __future__ import unicode_literals
+
+ import json
+
++from django.db import connection
+ from django.core import exceptions, serializers
+ from django.forms import Form
++from django.test.utils import CaptureQueriesContext
+
+ from . import PostgreSQLTestCase
+ from .models import HStoreModel
+@@ -163,6 +165,18 @@ class TestQuerying(PostgreSQLTestCase):
+ self.objs[:2]
+ )
+
++ def test_key_sql_injection(self):
++ with CaptureQueriesContext(connection) as queries:
++ self.assertFalse(
++ HStoreModel.objects.filter(**{
++ "field__test' = 'a') OR 1 = 1 OR ('d": 'x',
++ }).exists()
++ )
++ self.assertIn(
++ """."field" -> 'test'' = ''a'') OR 1 = 1 OR (''d') = 'x' """,
++ queries[0]['sql'],
++ )
++
+
+ class TestSerialization(PostgreSQLTestCase):
+ test_data = ('[{"fields": {"field": "{\\"a\\": \\"b\\"}"}, '
+diff --git a/tests/postgres_tests/test_json.py
b/tests/postgres_tests/test_json.py
+index 1978552..216fe77 100644
+--- a/tests/postgres_tests/test_json.py
++++ b/tests/postgres_tests/test_json.py
+@@ -1,10 +1,11 @@
+ import datetime
+ import unittest
+
+-from django.core import exceptions, serializers
+ from django.db import connection
++from django.core import exceptions, serializers
+ from django.forms import CharField, Form
+ from django.test import TestCase
++from django.test.utils import CaptureQueriesContext
+ from django.utils.html import escape
+
+ from . import PostgreSQLTestCase
+@@ -236,6 +237,18 @@ class TestValidation(PostgreSQLTestCase):
+ self.assertEqual(cm.exception.code, 'invalid')
+ self.assertEqual(cm.exception.message % cm.exception.params, "Value
must be valid JSON.")
+
++ def test_key_sql_injection(self):
++ with CaptureQueriesContext(connection) as queries:
++ self.assertFalse(
++ JSONModel.objects.filter(**{
++ """field__test' = '"a"') OR 1 = 1 OR ('d""": 'x',
++ }).exists()
++ )
++ self.assertIn(
++ """."field" -> 'test'' = ''"a"'') OR 1 = 1 OR (''d') = '"x"' """,
++ queries[0]['sql'],
++ )
++
+
+ class TestFormField(PostgreSQLTestCase):
+
diff --git a/debian/patches/0022-CVE-2019-14235.patch
b/debian/patches/0022-CVE-2019-14235.patch
new file mode 100644
index 000000000..eba780db4
--- /dev/null
+++ b/debian/patches/0022-CVE-2019-14235.patch
@@ -0,0 +1,74 @@
+From: Chris Lamb <[email protected]>
+Date: Thu, 8 Aug 2019 10:36:25 +0100
+Subject: CVE-2019-14235
+
+Backported from
+<https://github.com/django/django/commit/869b34e9b3be3a4cfcb3a145f218ffd3f5e3fd79>
+---
+ django/utils/encoding.py | 17 ++++++++++-------
+ tests/utils_tests/test_encoding.py | 12 +++++++++++-
+ 2 files changed, 21 insertions(+), 8 deletions(-)
+
+diff --git a/django/utils/encoding.py b/django/utils/encoding.py
+index 66077e2..2a03d10 100644
+--- a/django/utils/encoding.py
++++ b/django/utils/encoding.py
+@@ -236,13 +236,16 @@ def repercent_broken_unicode(path):
+ we need to re-percent-encode any octet produced that is not part of a
+ strictly legal UTF-8 octet sequence.
+ """
+- try:
+- path.decode('utf-8')
+- except UnicodeDecodeError as e:
+- repercent = quote(path[e.start:e.end], safe=b"/#%[]=:;$&()+,!?*@'~")
+- path = repercent_broken_unicode(
+- path[:e.start] + force_bytes(repercent) + path[e.end:])
+- return path
++ while True:
++ try:
++ path.decode('utf-8')
++ except UnicodeDecodeError as e:
++ # CVE-2019-14235: A recursion shouldn't be used since the
exception
++ # handling uses massive amounts of memory
++ repercent = quote(path[e.start:e.end],
safe=b"/#%[]=:;$&()+,!?*@'~")
++ path = path[:e.start] + force_bytes(repercent) + path[e.end:]
++ else:
++ return path
+
+
+ def filepath_to_uri(path):
+diff --git a/tests/utils_tests/test_encoding.py
b/tests/utils_tests/test_encoding.py
+index 5ddb18d..df4cc9d 100644
+--- a/tests/utils_tests/test_encoding.py
++++ b/tests/utils_tests/test_encoding.py
+@@ -2,12 +2,13 @@
+ from __future__ import unicode_literals
+
+ import datetime
++import sys
+ import unittest
+
+ from django.utils import six
+ from django.utils.encoding import (
+ escape_uri_path, filepath_to_uri, force_bytes, force_text, iri_to_uri,
+- smart_text, uri_to_iri,
++ repercent_broken_unicode, smart_text, uri_to_iri,
+ )
+ from django.utils.functional import SimpleLazyObject
+ from django.utils.http import urlquote_plus
+@@ -76,6 +77,15 @@ class TestEncodingUtils(unittest.TestCase):
+ self.assertEqual(smart_text(1), '1')
+ self.assertEqual(smart_text('foo'), 'foo')
+
++ def test_repercent_broken_unicode_recursion_error(self):
++ # Prepare a string long enough to force a recursion error if the
tested
++ # function uses recursion.
++ data = b'\xfc' * sys.getrecursionlimit()
++ try:
++ self.assertEqual(repercent_broken_unicode(data), b'%FC' *
sys.getrecursionlimit())
++ except RecursionError:
++ self.fail('Unexpected RecursionError raised.')
++
+
+ class TestRFC3987IEncodingUtils(unittest.TestCase):
+
diff --git a/debian/patches/series b/debian/patches/series
index ad6685673..38e455694 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -10,3 +10,7 @@ fix-test-middleware-classes-headers.patch
0006-Default-to-supporting-Spatialite-4.2.patch
0017-CVE-2019-3498.patch
0018-CVE-2019-6975.patch
+0019-CVE-2019-14232.patch
+0020-CVE-2019-14233.patch
+0021-CVE-2019-14234.patch
+0022-CVE-2019-14235.patch
diff --git a/debian/changelog b/debian/changelog
index b048bd0ec..e16ca1821 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,59 @@
+python-django (1:1.11.22-1+deb10u1) buster-security; urgency=high
+
+ * Backport four security patches from upstream. (Closes: #934026)
+ <https://www.djangoproject.com/weblog/2019/aug/01/security-releases/>
+
+ - CVE-2019-14232: Denial-of-service possibility in
+ django.utils.text.Truncator
+
+ If django.utils.text.Truncator's chars() and words() methods were passed
+ the html=True argument, they were extremely slow to evaluate certain
+ inputs due to a catastrophic backtracking vulnerability in a regular
+ expression. The chars() and words() methods are used to implement the
+ truncatechars_html and truncatewords_html template filters, which were
+ thus vulnerable.
+
+ The regular expressions used by Truncator have been simplified in order
+ to avoid potential backtracking issues. As a consequence, trailing
+ punctuation may now at times be included in the truncated output.
+
+ - CVE-2019-14233: Denial-of-service possibility in strip_tags()
+
+ Due to the behavior of the underlying HTMLParser,
+ django.utils.html.strip_tags() would be extremely slow to evaluate
+ certain inputs containing large sequences of nested incomplete HTML
+ entities. The strip_tags() method is used to implement the corresponding
+ striptags template filter, which was thus also vulnerable.
+
+ strip_tags() now avoids recursive calls to HTMLParser when progress
+ removing tags, but necessarily incomplete HTML entities, stops being
+ made.
+
+ Remember that absolutely NO guarantee is provided about the results of
+ strip_tags() being HTML safe. So NEVER mark safe the result of a
+ strip_tags() call without escaping it first, for example with
+ django.utils.html.escape().
+
+ - CVE-2019-14234: SQL injection possibility in key and index lookups for
+ JSONField/HStoreField
+
+ Key and index lookups for django.contrib.postgres.fields.JSONField and
+ key lookups for django.contrib.postgres.fields.HStoreField were subject
+ to SQL injection, using a suitably crafted dictionary, with dictionary
+ expansion, as the **kwargs passed to QuerySet.filter().
+
+ - CVE-2019-14235: Potential memory exhaustion in
+ django.utils.encoding.uri_to_iri()
+
+ If passed certain inputs, django.utils.encoding.uri_to_iri could lead to
+ significant memory usage due to excessive recursion when
+ re-percent-encoding invalid UTF-8 octet sequences.
+
+ uri_to_iri() now avoids recursion when re-percent-encoding invalid UTF-8
+ octet sequences.
+
+ -- Chris Lamb <[email protected]> Thu, 08 Aug 2019 10:11:42 +0100
+
python-django (1:1.11.22-1~deb10u1) buster-security; urgency=high
* No-change update for buster-security.
diff --git a/debian/patches/0007-CVE-2019-14232.patch
b/debian/patches/0007-CVE-2019-14232.patch
new file mode 100644
index 000000000..729f775fa
--- /dev/null
+++ b/debian/patches/0007-CVE-2019-14232.patch
@@ -0,0 +1,90 @@
+From: Chris Lamb <[email protected]>
+Date: Thu, 8 Aug 2019 10:05:28 +0100
+Subject: CVE-2019-14232
+
+Backported from
+<https://github.com/django/django/commit/42a66e969023c00536256469f0e8b8a099ef109d>
+
+---
+ django/utils/text.py | 4 ++--
+ .../filter_tests/test_truncatewords_html.py | 4 ++--
+ tests/utils_tests/test_text.py | 23 ++++++++++++++++++----
+ 3 files changed, 23 insertions(+), 8 deletions(-)
+
+diff --git a/django/utils/text.py b/django/utils/text.py
+index a6172c4..f221747 100644
+--- a/django/utils/text.py
++++ b/django/utils/text.py
+@@ -27,8 +27,8 @@ def capfirst(x):
+
+
+ # Set up regular expressions
+-re_words = re.compile(r'<.*?>|((?:\w[-\w]*|&.*?;)+)', re.U | re.S)
+-re_chars = re.compile(r'<.*?>|(.)', re.U | re.S)
++re_words = re.compile(r'<[^>]+?>|([^<>\s]+)', re.S)
++re_chars = re.compile(r'<[^>]+?>|(.)', re.S)
+ re_tag = re.compile(r'<(/)?(\S+?)(?:(\s*/)|\s.*?)?>', re.S)
+ re_newlines = re.compile(r'\r\n|\r') # Used in normalize_newlines
+ re_camel_case = re.compile(r'(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))')
+diff --git a/tests/template_tests/filter_tests/test_truncatewords_html.py
b/tests/template_tests/filter_tests/test_truncatewords_html.py
+index aec2abf..3c73442 100644
+--- a/tests/template_tests/filter_tests/test_truncatewords_html.py
++++ b/tests/template_tests/filter_tests/test_truncatewords_html.py
+@@ -19,13 +19,13 @@ class FunctionTests(SimpleTestCase):
+ def test_truncate2(self):
+ self.assertEqual(
+ truncatewords_html('<p>one <a href="#">two - three <br>four</a>
five</p>', 4),
+- '<p>one <a href="#">two - three <br>four ...</a></p>',
++ '<p>one <a href="#">two - three ...</a></p>',
+ )
+
+ def test_truncate3(self):
+ self.assertEqual(
+ truncatewords_html('<p>one <a href="#">two - three <br>four</a>
five</p>', 5),
+- '<p>one <a href="#">two - three <br>four</a> five</p>',
++ '<p>one <a href="#">two - three <br>four ...</a></p>',
+ )
+
+ def test_truncate4(self):
+diff --git a/tests/utils_tests/test_text.py b/tests/utils_tests/test_text.py
+index 50d1805..bfc1b4e 100644
+--- a/tests/utils_tests/test_text.py
++++ b/tests/utils_tests/test_text.py
+@@ -88,6 +88,16 @@ class TestUtilsText(SimpleTestCase):
+ # lazy strings are handled correctly
+ self.assertEqual(text.Truncator(lazystr('The quick brown
fox')).chars(12), 'The quick...')
+
++ def test_truncate_chars_html(self):
++ perf_test_values = [
++ (('</a' + '\t' * 50000) + '//>', None),
++ ('&' * 50000, '&' * 7 + '...'),
++ ('_X<<<<<<<<<<<>', None),
++ ]
++ for value, expected in perf_test_values:
++ truncator = text.Truncator(value)
++ self.assertEqual(expected if expected else value,
truncator.chars(10, html=True))
++
+ def test_truncate_words(self):
+ truncator = text.Truncator('The quick brown fox jumped over the lazy
dog.')
+ self.assertEqual('The quick brown fox jumped over the lazy dog.',
truncator.words(10))
+@@ -137,11 +147,16 @@ class TestUtilsText(SimpleTestCase):
+ truncator = text.Truncator('<i>Buenos días!
¿Cómo está?</i>')
+ self.assertEqual('<i>Buenos días! ¿Cómo...</i>',
truncator.words(3, '...', html=True))
+ truncator = text.Truncator('<p>I <3 python, what about you?</p>')
+- self.assertEqual('<p>I <3 python...</p>', truncator.words(3,
'...', html=True))
++ self.assertEqual('<p>I <3 python,...</p>', truncator.words(3,
'...', html=True))
+
+- re_tag_catastrophic_test = ('</a' + '\t' * 50000) + '//>'
+- truncator = text.Truncator(re_tag_catastrophic_test)
+- self.assertEqual(re_tag_catastrophic_test, truncator.words(500,
html=True))
++ perf_test_values = [
++ ('</a' + '\t' * 50000) + '//>',
++ '&' * 50000,
++ '_X<<<<<<<<<<<>',
++ ]
++ for value in perf_test_values:
++ truncator = text.Truncator(value)
++ self.assertEqual(value, truncator.words(50, html=True))
+
+ def test_wrap(self):
+ digits = '1234 67 9'
diff --git a/debian/patches/0008-CVE-2019-14233.patch
b/debian/patches/0008-CVE-2019-14233.patch
new file mode 100644
index 000000000..43c140100
--- /dev/null
+++ b/debian/patches/0008-CVE-2019-14233.patch
@@ -0,0 +1,40 @@
+From: Chris Lamb <[email protected]>
+Date: Thu, 8 Aug 2019 10:05:56 +0100
+Subject: CVE-2019-14233
+
+Backported from
+<https://github.com/django/django/commit/52479acce792ad80bb0f915f20b835f919993c72>
+
+---
+ django/utils/html.py | 4 ++--
+ tests/utils_tests/test_html.py | 2 ++
+ 2 files changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/django/utils/html.py b/django/utils/html.py
+index 9c38cde..30a6a2f 100644
+--- a/django/utils/html.py
++++ b/django/utils/html.py
+@@ -169,8 +169,8 @@ def strip_tags(value):
+ value = force_text(value)
+ while '<' in value and '>' in value:
+ new_value = _strip_once(value)
+- if len(new_value) >= len(value):
+- # _strip_once was not able to detect more tags or length increased
++ if len(new_value) >= len(value) or value.count('<') ==
new_value.count('<'):
++ # _strip_once wasn't able to detect more tags, or line length
increased.
+ # due to http://bugs.python.org/issue20288
+ # (affects Python 2 < 2.7.7 and Python 3 < 3.3.5)
+ break
+diff --git a/tests/utils_tests/test_html.py b/tests/utils_tests/test_html.py
+index 1bebe94..6122b69 100644
+--- a/tests/utils_tests/test_html.py
++++ b/tests/utils_tests/test_html.py
+@@ -86,6 +86,8 @@ class TestUtilsHtml(SimpleTestCase):
+ # caused infinite loop on Pythons not patched with
+ # http://bugs.python.org/issue20288
+ ('&gotcha&#;<>', '&gotcha&#;<>'),
++ ('><!' + ('&' * 16000) + 'D', '><!' + ('&' * 16000) + 'D'),
++ ('X<<<<br>br>br>br>X', 'XX'),
+ )
+ for value, output in items:
+ self.check_output(f, value, output)
diff --git a/debian/patches/0009-CVE-2019-14234.patch
b/debian/patches/0009-CVE-2019-14234.patch
new file mode 100644
index 000000000..8b3db95d2
--- /dev/null
+++ b/debian/patches/0009-CVE-2019-14234.patch
@@ -0,0 +1,115 @@
+From: Chris Lamb <[email protected]>
+Date: Thu, 8 Aug 2019 10:06:24 +0100
+Subject: CVE-2019-14234
+
+Backported from
+<https://github.com/django/django/commit/ed682a24fca774818542757651bfba576c3fc3ef>
+
+---
+ django/contrib/postgres/fields/hstore.py | 2 +-
+ django/contrib/postgres/fields/jsonb.py | 8 +++-----
+ tests/postgres_tests/test_hstore.py | 15 ++++++++++++++-
+ tests/postgres_tests/test_json.py | 14 ++++++++++++++
+ 4 files changed, 32 insertions(+), 7 deletions(-)
+
+diff --git a/django/contrib/postgres/fields/hstore.py
b/django/contrib/postgres/fields/hstore.py
+index 605deaf..b77d1b1 100644
+--- a/django/contrib/postgres/fields/hstore.py
++++ b/django/contrib/postgres/fields/hstore.py
+@@ -86,7 +86,7 @@ class KeyTransform(Transform):
+
+ def as_sql(self, compiler, connection):
+ lhs, params = compiler.compile(self.lhs)
+- return "(%s -> '%s')" % (lhs, self.key_name), params
++ return '(%s -> %%s)' % lhs, [self.key_name] + params
+
+
+ class KeyTransformFactory(object):
+diff --git a/django/contrib/postgres/fields/jsonb.py
b/django/contrib/postgres/fields/jsonb.py
+index 0722a05..d7a2259 100644
+--- a/django/contrib/postgres/fields/jsonb.py
++++ b/django/contrib/postgres/fields/jsonb.py
+@@ -104,12 +104,10 @@ class KeyTransform(Transform):
+ if len(key_transforms) > 1:
+ return "(%s %s %%s)" % (lhs, self.nested_operator),
[key_transforms] + params
+ try:
+- int(self.key_name)
++ lookup = int(self.key_name)
+ except ValueError:
+- lookup = "'%s'" % self.key_name
+- else:
+- lookup = "%s" % self.key_name
+- return "(%s %s %s)" % (lhs, self.operator, lookup), params
++ lookup = self.key_name
++ return '(%s %s %%s)' % (lhs, self.operator), [lookup] + params
+
+
+ class KeyTextTransform(KeyTransform):
+diff --git a/tests/postgres_tests/test_hstore.py
b/tests/postgres_tests/test_hstore.py
+index 0fc427f..dd8e642 100644
+--- a/tests/postgres_tests/test_hstore.py
++++ b/tests/postgres_tests/test_hstore.py
+@@ -4,8 +4,9 @@ from __future__ import unicode_literals
+ import json
+
+ from django.core import exceptions, serializers
++from django.db import connection
+ from django.forms import Form
+-from django.test.utils import modify_settings
++from django.test.utils import CaptureQueriesContext, modify_settings
+
+ from . import PostgreSQLTestCase
+ from .models import HStoreModel
+@@ -167,6 +168,18 @@ class TestQuerying(HStoreTestCase):
+ self.objs[:2]
+ )
+
++ def test_key_sql_injection(self):
++ with CaptureQueriesContext(connection) as queries:
++ self.assertFalse(
++ HStoreModel.objects.filter(**{
++ "field__test' = 'a') OR 1 = 1 OR ('d": 'x',
++ }).exists()
++ )
++ self.assertIn(
++ """."field" -> 'test'' = ''a'') OR 1 = 1 OR (''d') = 'x' """,
++ queries[0]['sql'],
++ )
++
+
+ class TestSerialization(HStoreTestCase):
+ test_data = ('[{"fields": {"field": "{\\"a\\": \\"b\\"}"}, '
+diff --git a/tests/postgres_tests/test_json.py
b/tests/postgres_tests/test_json.py
+index 4e8851d..925e800 100644
+--- a/tests/postgres_tests/test_json.py
++++ b/tests/postgres_tests/test_json.py
+@@ -6,8 +6,10 @@ from decimal import Decimal
+
+ from django.core import exceptions, serializers
+ from django.core.serializers.json import DjangoJSONEncoder
++from django.db import connection
+ from django.forms import CharField, Form, widgets
+ from django.test import skipUnlessDBFeature
++from django.test.utils import CaptureQueriesContext
+ from django.utils.html import escape
+
+ from . import PostgreSQLTestCase
+@@ -263,6 +265,18 @@ class TestQuerying(PostgreSQLTestCase):
+ def test_iregex(self):
+
self.assertTrue(JSONModel.objects.filter(field__foo__iregex=r'^bAr$').exists())
+
++ def test_key_sql_injection(self):
++ with CaptureQueriesContext(connection) as queries:
++ self.assertFalse(
++ JSONModel.objects.filter(**{
++ """field__test' = '"a"') OR 1 = 1 OR ('d""": 'x',
++ }).exists()
++ )
++ self.assertIn(
++ """."field" -> 'test'' = ''"a"'') OR 1 = 1 OR (''d') = '"x"' """,
++ queries[0]['sql'],
++ )
++
+
+ @skipUnlessDBFeature('has_jsonb_datatype')
+ class TestSerialization(PostgreSQLTestCase):
diff --git a/debian/patches/0010-CVE-2019-14235.patch
b/debian/patches/0010-CVE-2019-14235.patch
new file mode 100644
index 000000000..efd5be152
--- /dev/null
+++ b/debian/patches/0010-CVE-2019-14235.patch
@@ -0,0 +1,75 @@
+From: Chris Lamb <[email protected]>
+Date: Thu, 8 Aug 2019 10:06:48 +0100
+Subject: CVE-2019-14235
+
+Backported from
+<https://github.com/django/django/commit/869b34e9b3be3a4cfcb3a145f218ffd3f5e3fd79>
+
+---
+ django/utils/encoding.py | 17 ++++++++++-------
+ tests/utils_tests/test_encoding.py | 12 +++++++++++-
+ 2 files changed, 21 insertions(+), 8 deletions(-)
+
+diff --git a/django/utils/encoding.py b/django/utils/encoding.py
+index 999ffae..a29ef2b 100644
+--- a/django/utils/encoding.py
++++ b/django/utils/encoding.py
+@@ -237,13 +237,16 @@ def repercent_broken_unicode(path):
+ we need to re-percent-encode any octet produced that is not part of a
+ strictly legal UTF-8 octet sequence.
+ """
+- try:
+- path.decode('utf-8')
+- except UnicodeDecodeError as e:
+- repercent = quote(path[e.start:e.end], safe=b"/#%[]=:;$&()+,!?*@'~")
+- path = repercent_broken_unicode(
+- path[:e.start] + force_bytes(repercent) + path[e.end:])
+- return path
++ while True:
++ try:
++ path.decode('utf-8')
++ except UnicodeDecodeError as e:
++ # CVE-2019-14235: A recursion shouldn't be used since the
exception
++ # handling uses massive amounts of memory
++ repercent = quote(path[e.start:e.end],
safe=b"/#%[]=:;$&()+,!?*@'~")
++ path = path[:e.start] + force_bytes(repercent) + path[e.end:]
++ else:
++ return path
+
+
+ def filepath_to_uri(path):
+diff --git a/tests/utils_tests/test_encoding.py
b/tests/utils_tests/test_encoding.py
+index 688b461..2b4bcff 100644
+--- a/tests/utils_tests/test_encoding.py
++++ b/tests/utils_tests/test_encoding.py
+@@ -2,12 +2,13 @@
+ from __future__ import unicode_literals
+
+ import datetime
++import sys
+ import unittest
+
+ from django.utils import six
+ from django.utils.encoding import (
+ escape_uri_path, filepath_to_uri, force_bytes, force_text, iri_to_uri,
+- smart_text, uri_to_iri,
++ repercent_broken_unicode, smart_text, uri_to_iri,
+ )
+ from django.utils.functional import SimpleLazyObject
+ from django.utils.http import urlquote_plus
+@@ -76,6 +77,15 @@ class TestEncodingUtils(unittest.TestCase):
+ self.assertEqual(smart_text(1), '1')
+ self.assertEqual(smart_text('foo'), 'foo')
+
++ def test_repercent_broken_unicode_recursion_error(self):
++ # Prepare a string long enough to force a recursion error if the
tested
++ # function uses recursion.
++ data = b'\xfc' * sys.getrecursionlimit()
++ try:
++ self.assertEqual(repercent_broken_unicode(data), b'%FC' *
sys.getrecursionlimit())
++ except RecursionError:
++ self.fail('Unexpected RecursionError raised.')
++
+
+ class TestRFC3987IEncodingUtils(unittest.TestCase):
+
diff --git a/debian/patches/series b/debian/patches/series
index 59611e9f1..df4527b6e 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -4,3 +4,7 @@
0004-Fix-QuerySet.defer-with-super-and-subclass-fields.patch
0006-Default-to-supporting-Spatialite-4.2.patch
0007-Fixed-29182-Adjusted-SQLite-schema-table-alteration-.patch
+0007-CVE-2019-14232.patch
+0008-CVE-2019-14233.patch
+0009-CVE-2019-14234.patch
+0010-CVE-2019-14235.patch
_______________________________________________
Python-modules-team mailing list
[email protected]
https://alioth-lists.debian.net/cgi-bin/mailman/listinfo/python-modules-team