Hi Moritz et al.,

> > > > > > 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)?
> > […]
> > > I just realised that there's a 1.11.23 (thanks Salvatore!), given that
> > > we agreed to follow 1.11.x in buster, shouldn't we rather use that one?
> > 
> > D'oh, that makes more sense. Okay, I can prepare a debdiff for that --
> > however, can you just confirm the version we should use?
> > 1:1.11.23-1~deb10u1?
> 
> Looks good!

Updated debdiff attached.


Regards,

-- 
      ,''`.
     : :'  :     Chris Lamb
     `. `'`      la...@debian.org 🍥 chris-lamb.co.uk
       `-
diff --git a/Django.egg-info/PKG-INFO b/Django.egg-info/PKG-INFO
index 75a27527c..f6cdde7db 100644
--- a/Django.egg-info/PKG-INFO
+++ b/Django.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: Django
-Version: 1.11.22
+Version: 1.11.23
 Summary: A high-level Python Web framework that encourages rapid development 
and clean, pragmatic design.
 Home-page: https://www.djangoproject.com/
 Author: Django Software Foundation
diff --git a/Django.egg-info/SOURCES.txt b/Django.egg-info/SOURCES.txt
index 4343c1389..f31a9c2f9 100644
--- a/Django.egg-info/SOURCES.txt
+++ b/Django.egg-info/SOURCES.txt
@@ -3550,6 +3550,7 @@ docs/releases/1.11.2.txt
 docs/releases/1.11.20.txt
 docs/releases/1.11.21.txt
 docs/releases/1.11.22.txt
+docs/releases/1.11.23.txt
 docs/releases/1.11.3.txt
 docs/releases/1.11.4.txt
 docs/releases/1.11.5.txt
diff --git a/PKG-INFO b/PKG-INFO
index 75a27527c..f6cdde7db 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: Django
-Version: 1.11.22
+Version: 1.11.23
 Summary: A high-level Python Web framework that encourages rapid development 
and clean, pragmatic design.
 Home-page: https://www.djangoproject.com/
 Author: Django Software Foundation
diff --git a/debian/changelog b/debian/changelog
index b048bd0ec..cf382c3cf 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,59 @@
+python-django (1:1.11.23-1~deb10u1) buster-security; urgency=high
+
+  * New upstream security release.
+    <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 <la...@debian.org>  Thu, 08 Aug 2019 16:00:04 +0100
+
 python-django (1:1.11.22-1~deb10u1) buster-security; urgency=high
 
   * No-change update for buster-security.
diff --git a/django/__init__.py b/django/__init__.py
index 90ca62a28..c622e303c 100644
--- a/django/__init__.py
+++ b/django/__init__.py
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
 
 from django.utils.version import get_version
 
-VERSION = (1, 11, 22, 'final', 0)
+VERSION = (1, 11, 23, 'final', 0)
 
 __version__ = get_version(VERSION)
 
diff --git a/django/contrib/postgres/fields/hstore.py 
b/django/contrib/postgres/fields/hstore.py
index 605deaf62..b77d1b195 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 0722a05a6..d7a22591e 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/django/utils/encoding.py b/django/utils/encoding.py
index 999ffae19..a29ef2be5 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/django/utils/html.py b/django/utils/html.py
index 9c38cde55..30a6a2f0c 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/django/utils/text.py b/django/utils/text.py
index a6172c41b..f221747f6 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/docs/releases/1.11.23.txt b/docs/releases/1.11.23.txt
new file mode 100644
index 000000000..04acca90f
--- /dev/null
+++ b/docs/releases/1.11.23.txt
@@ -0,0 +1,57 @@
+============================
+Django 1.11.23 release notes
+============================
+
+*August 1, 2019*
+
+Django 1.11.23 fixes security issues in 1.11.22.
+
+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
+:tfilter:`truncatechars_html` and :tfilter:`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``,
+:func:`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
+:tfilter:`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
+:func:`django.utils.html.escape`.
+
+CVE-2019-14234: SQL injection possibility in key and index lookups for 
``JSONField``/``HStoreField``
+====================================================================================================
+
+:lookup:`Key and index lookups <jsonfield.key>` for
+:class:`~django.contrib.postgres.fields.JSONField` and :lookup:`key lookups
+<hstorefield.key>` for :class:`~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, :func:`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.
diff --git a/docs/releases/index.txt b/docs/releases/index.txt
index e00804d65..f6b0dccb7 100644
--- a/docs/releases/index.txt
+++ b/docs/releases/index.txt
@@ -26,6 +26,7 @@ versions of the documentation contain the release notes for 
any later releases.
 .. toctree::
    :maxdepth: 1
 
+   1.11.23
    1.11.22
    1.11.21
    1.11.20
diff --git a/docs/releases/security.txt b/docs/releases/security.txt
index d26669d63..2e1e94198 100644
--- a/docs/releases/security.txt
+++ b/docs/releases/security.txt
@@ -961,3 +961,16 @@ Versions affected
 * Django 2.2 :commit:`(patch) <afddabf8428ddc89a332f7a78d0d21eaf2b5a673>`
 * Django 2.1 :commit:`(patch) <09186a13d975de6d049f8b3e05484f66b01ece62>`
 * Django 1.11 :commit:`(patch) <c238701859a52d584f349cce15d56c8e8137c52b>`
+
+July 1, 2019 - :cve:`2019-12781`
+--------------------------------
+
+Incorrect HTTP detection with reverse-proxy connecting via HTTPS. `Full
+description 
<https://www.djangoproject.com/weblog/2019/jul/01/security-releases/>`__
+
+Versions affected
+~~~~~~~~~~~~~~~~~
+
+* Django 2.2 :commit:`(patch) <77706a3e4766da5d5fb75c4db22a0a59a28e6cd6>`
+* Django 2.1 :commit:`(patch) <1e40f427bb8d0fb37cc9f830096a97c36c97af6f>`
+* Django 1.11 :commit:`(patch) <32124fc41e75074141b05f10fc55a4f01ff7f050>`
diff --git a/tests/postgres_tests/test_hstore.py 
b/tests/postgres_tests/test_hstore.py
index 0fc427f67..dd8e642ed 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 4e8851d48..925e80013 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/tests/template_tests/filter_tests/test_truncatewords_html.py 
b/tests/template_tests/filter_tests/test_truncatewords_html.py
index aec2abf2d..3c73442fb 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_encoding.py 
b/tests/utils_tests/test_encoding.py
index 688b46194..2b4bcff87 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/tests/utils_tests/test_html.py b/tests/utils_tests/test_html.py
index 1bebe9452..6122b695f 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/tests/utils_tests/test_text.py b/tests/utils_tests/test_text.py
index 50d1805e8..bfc1b4efc 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'

Reply via email to