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&iacute;as! 
&#x00bf;C&oacute;mo est&aacute;?</i>')
+         self.assertEqual('<i>Buenos d&iacute;as! &#x00bf;C&oacute;mo...</i>', 
truncator.words(3, '...', html=True))
+         truncator = text.Truncator('<p>I &lt;3 python, what about you?</p>')
+-        self.assertEqual('<p>I &lt;3 python...</p>', truncator.words(3, 
'...', html=True))
++        self.assertEqual('<p>I &lt;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&iacute;as! 
&#x00bf;C&oacute;mo est&aacute;?</i>')
+         self.assertEqual('<i>Buenos d&iacute;as! &#x00bf;C&oacute;mo...</i>', 
truncator.words(3, '...', html=True))
+         truncator = text.Truncator('<p>I &lt;3 python, what about you?</p>')
+-        self.assertEqual('<p>I &lt;3 python...</p>', truncator.words(3, 
'...', html=True))
++        self.assertEqual('<p>I &lt;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

Reply via email to