[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#983526: marked as done (buster-pu: package python-django/1:1.11.29-1~deb10u2)



Your message dated Sat, 10 Sep 2022 19:32:03 +0100
with message-id <45618403a51dc0ee6a08fc3ca1f8e81c0489ccca.camel@adam-barratt.org.uk>
and subject line Re: Bug#983526: buster-pu: package python-django/1:1.11.29-1+deb10u1
has caused the Debian Bug report #983526,
regarding buster-pu: package python-django/1:1.11.29-1~deb10u2
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
983526: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=983526
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
Package: release.debian.org
Severity: normal
Tags: buster
User: release.debian.org@packages.debian.org
Usertags: pu

Dear stable release managers,

Please consider python-django (1:1.11.29-1+deb10u1) for buster:
  
  python-django (1:1.11.29-1+deb10u1) buster; urgency=high
  .
    * CVE-2021-23336: Prevent a web cache poisoning attack via "parameter
      cloaking". Django contains a copy of urllib.parse.parse_qsl() which was
      added to backport some security fixes. A further security fix has been
      issued recently such that parse_qsl() no longer allows using ";" as a
      query parameter separator by default. (Closes: #983090)
  .
      For more information, please see:
  .
        https://www.djangoproject.com/weblog/2021/feb/19/security-releases/

The full diff is attached. The security team believe this should go
via s-p-u rather than via a DLA (if at all):

   https://bugs.debian.org/983090#27

Please double-check the version number for me. The current version in
buster-security is 1:1.11.29-1~deb10u1 (with a tilde).


Regards,

-- 
      ,''`.
     : :'  :     Chris Lamb
     `. `'`      lamby@debian.org / chris-lamb.co.uk
       `-
diff --git a/debian/changelog b/debian/changelog
index 00bbc0532..2683a597b 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,17 @@
+python-django (1:1.11.29-1+deb10u1) buster; urgency=high
+
+  * CVE-2021-23336: Prevent a web cache poisoning attack via "parameter
+    cloaking". Django contains a copy of urllib.parse.parse_qsl() which was
+    added to backport some security fixes. A further security fix has been
+    issued recently such that parse_qsl() no longer allows using ";" as a
+    query parameter separator by default. (Closes: #983090)
+
+    For more information, please see:
+
+      https://www.djangoproject.com/weblog/2021/feb/19/security-releases/
+
+ -- Chris Lamb <lamby@debian.org>  Thu, 25 Feb 2021 16:37:19 +0000
+
 python-django (1:1.11.29-1~deb10u1) buster-security; urgency=high
 
   * New upstream security release (postponed from March 2020):
diff --git a/debian/patches/0010-CVE-2021-23336.patch b/debian/patches/0010-CVE-2021-23336.patch
new file mode 100644
index 000000000..192ac7251
--- /dev/null
+++ b/debian/patches/0010-CVE-2021-23336.patch
@@ -0,0 +1,147 @@
+From: Chris Lamb <lamby@debian.org>
+Date: Thu, 25 Feb 2021 16:27:58 +0000
+Subject: CVE-2021-23336
+
+---
+ django/utils/http.py                        |  2 +-
+ tests/handlers/test_exception.py            |  2 +-
+ tests/requests/test_data_upload_settings.py |  8 ++---
+ tests/utils_tests/test_http.py              | 55 +++++++++++++++++++++++++++++
+ 4 files changed, 61 insertions(+), 6 deletions(-)
+
+diff --git a/django/utils/http.py b/django/utils/http.py
+index 644d4d0..adeabe9 100644
+--- a/django/utils/http.py
++++ b/django/utils/http.py
+@@ -56,7 +56,7 @@ ASCTIME_DATE = re.compile(r'^\w{3} %s %s %s %s$' % (__M, __D2, __T, __Y))
+ RFC3986_GENDELIMS = str(":/?#[]@")
+ RFC3986_SUBDELIMS = str("!$&'()*+,;=")
+ 
+-FIELDS_MATCH = re.compile('[&;]')
++FIELDS_MATCH = re.compile('&')
+ 
+ 
+ @keep_lazy_text
+diff --git a/tests/handlers/test_exception.py b/tests/handlers/test_exception.py
+index 7afd4ac..0c1e763 100644
+--- a/tests/handlers/test_exception.py
++++ b/tests/handlers/test_exception.py
+@@ -6,7 +6,7 @@ from django.test.client import FakePayload
+ class ExceptionHandlerTests(SimpleTestCase):
+ 
+     def get_suspicious_environ(self):
+-        payload = FakePayload('a=1&a=2;a=3\r\n')
++        payload = FakePayload('a=1&a=2&a=3\r\n')
+         return {
+             'REQUEST_METHOD': 'POST',
+             'CONTENT_TYPE': 'application/x-www-form-urlencoded',
+diff --git a/tests/requests/test_data_upload_settings.py b/tests/requests/test_data_upload_settings.py
+index f60f185..44897cc 100644
+--- a/tests/requests/test_data_upload_settings.py
++++ b/tests/requests/test_data_upload_settings.py
+@@ -11,7 +11,7 @@ TOO_MUCH_DATA_MSG = 'Request body exceeded settings.DATA_UPLOAD_MAX_MEMORY_SIZE.
+ 
+ class DataUploadMaxMemorySizeFormPostTests(SimpleTestCase):
+     def setUp(self):
+-        payload = FakePayload('a=1&a=2;a=3\r\n')
++        payload = FakePayload('a=1&a=2&a=3\r\n')
+         self.request = WSGIRequest({
+             'REQUEST_METHOD': 'POST',
+             'CONTENT_TYPE': 'application/x-www-form-urlencoded',
+@@ -117,7 +117,7 @@ class DataUploadMaxNumberOfFieldsGet(SimpleTestCase):
+                 request = WSGIRequest({
+                     'REQUEST_METHOD': 'GET',
+                     'wsgi.input': BytesIO(b''),
+-                    'QUERY_STRING': 'a=1&a=2;a=3',
++                    'QUERY_STRING': 'a=1&a=2&a=3',
+                 })
+                 request.GET['a']
+ 
+@@ -126,7 +126,7 @@ class DataUploadMaxNumberOfFieldsGet(SimpleTestCase):
+             request = WSGIRequest({
+                 'REQUEST_METHOD': 'GET',
+                 'wsgi.input': BytesIO(b''),
+-                'QUERY_STRING': 'a=1&a=2;a=3',
++                'QUERY_STRING': 'a=1&a=2&a=3',
+             })
+             request.GET['a']
+ 
+@@ -168,7 +168,7 @@ class DataUploadMaxNumberOfFieldsMultipartPost(SimpleTestCase):
+ 
+ class DataUploadMaxNumberOfFieldsFormPost(SimpleTestCase):
+     def setUp(self):
+-        payload = FakePayload("\r\n".join(['a=1&a=2;a=3', '']))
++        payload = FakePayload("\r\n".join(['a=1&a=2&a=3', '']))
+         self.request = WSGIRequest({
+             'REQUEST_METHOD': 'POST',
+             'CONTENT_TYPE': 'application/x-www-form-urlencoded',
+diff --git a/tests/utils_tests/test_http.py b/tests/utils_tests/test_http.py
+index d339e8a..b1184c2 100644
+--- a/tests/utils_tests/test_http.py
++++ b/tests/utils_tests/test_http.py
+@@ -5,6 +5,7 @@ import sys
+ import unittest
+ from datetime import datetime
+ 
++from django.core.exceptions import TooManyFieldsSent
+ from django.test import ignore_warnings
+ from django.utils import http, six
+ from django.utils.datastructures import MultiValueDict
+@@ -258,3 +259,57 @@ class EscapeLeadingSlashesTests(unittest.TestCase):
+         )
+         for url, expected in tests:
+             self.assertEqual(http.escape_leading_slashes(url), expected)
++
++
++# Backport of unit tests for urllib.parse.parse_qsl() from Python 3.8.8.
++# Copyright (C) 2021 Python Software Foundation (see LICENSE.python).
++class ParseQSLBackportTests(unittest.TestCase):
++    def test_parse_qsl(self):
++        tests = [
++            ('', []),
++            ('&', []),
++            ('&&', []),
++            ('=', [('', '')]),
++            ('=a', [('', 'a')]),
++            ('a', [('a', '')]),
++            ('a=', [('a', '')]),
++            ('&a=b', [('a', 'b')]),
++            ('a=a+b&b=b+c', [('a', 'a b'), ('b', 'b c')]),
++            ('a=1&a=2', [('a', '1'), ('a', '2')]),
++            (';a=b', [(';a', 'b')]),
++            ('a=a+b;b=b+c', [('a', 'a b;b=b c')]),
++        ]
++        for original, expected in tests:
++            result = http.limited_parse_qsl(original, keep_blank_values=True)
++            self.assertEqual(result, expected, 'Error parsing %r' % original)
++            expect_without_blanks = [v for v in expected if len(v[1])]
++            result = http.limited_parse_qsl(original, keep_blank_values=False)
++            self.assertEqual(result, expect_without_blanks, 'Error parsing %r' % original)
++
++    def test_parse_qsl_encoding(self):
++        result = http.limited_parse_qsl('key=\u0141%E9', encoding='latin-1')
++        self.assertEqual(result, [('key', '\u0141\xE9')])
++
++        if sys.version_info[0] == 2:
++            result = http.limited_parse_qsl('key=\u0141%C3%A9', encoding='utf-8')
++            self.assertEqual(result, [('key', '\u0141\xc3\xa9')])
++            result = http.limited_parse_qsl('key=\u0141%C3%A9', encoding='ascii')
++            self.assertEqual(result, [('key', '\u0141\xc3\xa9')])
++            result = http.limited_parse_qsl('key=\u0141%E9-', encoding='ascii')
++            self.assertEqual(result, [('key', '\u0141\xe9-')])
++            result = http.limited_parse_qsl('key=\u0141%E9-', encoding='ascii', errors='ignore')
++            self.assertEqual(result, [('key', '\u0141\xe9-')])
++        else:
++            result = http.limited_parse_qsl('key=\u0141%C3%A9', encoding='utf-8')
++            self.assertEqual(result, [('key', '\u0141\xE9')])
++            result = http.limited_parse_qsl('key=\u0141%C3%A9', encoding='ascii')
++            self.assertEqual(result, [('key', '\u0141\ufffd\ufffd')])
++            result = http.limited_parse_qsl('key=\u0141%E9-', encoding='ascii')
++            self.assertEqual(result, [('key', '\u0141\ufffd-')])
++            result = http.limited_parse_qsl('key=\u0141%E9-', encoding='ascii', errors='ignore')
++            self.assertEqual(result, [('key', '\u0141-')])
++
++    def test_parse_qsl_field_limit(self):
++        with self.assertRaises(TooManyFieldsSent):
++            http.limited_parse_qsl('&'.join(['a=a'] * 11), fields_limit=10)
++        http.limited_parse_qsl('&'.join(['a=a'] * 10), fields_limit=10)
diff --git a/debian/patches/series b/debian/patches/series
index 296032c78..5e4af2ec9 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -6,3 +6,4 @@
 0007-Fixed-29182-Adjusted-SQLite-schema-table-alteration-.patch
 0008-CVE-2020-13254.patch
 0009-CVE-2020-13596.patch
+0010-CVE-2021-23336.patch

--- End Message ---
--- Begin Message ---
On Wed, 2021-03-17 at 20:10 +0100, Julien Cristau wrote:
> Control: tag -1 moreinfo
> 
> On Thu, Feb 25, 2021 at 04:42:55PM +0000, Chris Lamb wrote:
> > Please consider python-django (1:1.11.29-1+deb10u1) for buster:
> >   
> >   python-django (1:1.11.29-1+deb10u1) buster; urgency=high
> >   .
> >     * CVE-2021-23336: Prevent a web cache poisoning attack via
> > "parameter
> >       cloaking". Django contains a copy of urllib.parse.parse_qsl()
> > which was
> >       added to backport some security fixes. A further security fix
> > has been
> >       issued recently such that parse_qsl() no longer allows using
> > ";" as a
> >       query parameter separator by default. (Closes: #983090)
> >   .
> >       For more information, please see:
> >   .
> >         
> > https://www.djangoproject.com/weblog/2021/feb/19/security-releases/
> > 
> Hi Chris,
> 
> I'm not convinced the regression risk here, of changing the
> longstanding
> behaviour, is worth it.  People using a caching reverse proxy with a
> different config wrt query strings can just as well fix the issue on
> that end.
> 
> 

In either case, it's now too late to handle this via buster-pu, as the
final point release happened earlier today. I'm therefore closing the
request now.

Regards,

Adam

--- End Message ---

Reply to: