[2.2.x] Fixed CVE-2021-23336 -- Fixed web cache poisoning via django.utils.http.limited_parse_qsl().
This commit is contained in:
parent
226d831918
commit
fd6b6afd59
@ -41,7 +41,7 @@ ASCTIME_DATE = re.compile(r'^\w{3} %s %s %s %s$' % (__M, __D2, __T, __Y))
|
|||||||
RFC3986_GENDELIMS = ":/?#[]@"
|
RFC3986_GENDELIMS = ":/?#[]@"
|
||||||
RFC3986_SUBDELIMS = "!$&'()*+,;="
|
RFC3986_SUBDELIMS = "!$&'()*+,;="
|
||||||
|
|
||||||
FIELDS_MATCH = re.compile('[&;]')
|
FIELDS_MATCH = re.compile('&')
|
||||||
|
|
||||||
|
|
||||||
@keep_lazy_text
|
@keep_lazy_text
|
||||||
|
16
docs/releases/2.2.19.txt
Normal file
16
docs/releases/2.2.19.txt
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
===========================
|
||||||
|
Django 2.2.19 release notes
|
||||||
|
===========================
|
||||||
|
|
||||||
|
*February 19, 2021*
|
||||||
|
|
||||||
|
Django 2.2.19 fixes a security issue in 2.2.18.
|
||||||
|
|
||||||
|
CVE-2021-23336: Web cache poisoning via ``django.utils.http.limited_parse_qsl()``
|
||||||
|
=================================================================================
|
||||||
|
|
||||||
|
Django contains a copy of :func:`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. Django now includes this fix. See :bpo:`42967` for
|
||||||
|
further details.
|
@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases.
|
|||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
|
2.2.19
|
||||||
2.2.18
|
2.2.18
|
||||||
2.2.17
|
2.2.17
|
||||||
2.2.16
|
2.2.16
|
||||||
|
@ -6,7 +6,7 @@ from django.test.client import FakePayload
|
|||||||
class ExceptionHandlerTests(SimpleTestCase):
|
class ExceptionHandlerTests(SimpleTestCase):
|
||||||
|
|
||||||
def get_suspicious_environ(self):
|
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 {
|
return {
|
||||||
'REQUEST_METHOD': 'POST',
|
'REQUEST_METHOD': 'POST',
|
||||||
'CONTENT_TYPE': 'application/x-www-form-urlencoded',
|
'CONTENT_TYPE': 'application/x-www-form-urlencoded',
|
||||||
|
@ -11,7 +11,7 @@ TOO_MUCH_DATA_MSG = 'Request body exceeded settings.DATA_UPLOAD_MAX_MEMORY_SIZE.
|
|||||||
|
|
||||||
class DataUploadMaxMemorySizeFormPostTests(SimpleTestCase):
|
class DataUploadMaxMemorySizeFormPostTests(SimpleTestCase):
|
||||||
def setUp(self):
|
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({
|
self.request = WSGIRequest({
|
||||||
'REQUEST_METHOD': 'POST',
|
'REQUEST_METHOD': 'POST',
|
||||||
'CONTENT_TYPE': 'application/x-www-form-urlencoded',
|
'CONTENT_TYPE': 'application/x-www-form-urlencoded',
|
||||||
@ -117,7 +117,7 @@ class DataUploadMaxNumberOfFieldsGet(SimpleTestCase):
|
|||||||
request = WSGIRequest({
|
request = WSGIRequest({
|
||||||
'REQUEST_METHOD': 'GET',
|
'REQUEST_METHOD': 'GET',
|
||||||
'wsgi.input': BytesIO(b''),
|
'wsgi.input': BytesIO(b''),
|
||||||
'QUERY_STRING': 'a=1&a=2;a=3',
|
'QUERY_STRING': 'a=1&a=2&a=3',
|
||||||
})
|
})
|
||||||
request.GET['a']
|
request.GET['a']
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ class DataUploadMaxNumberOfFieldsGet(SimpleTestCase):
|
|||||||
request = WSGIRequest({
|
request = WSGIRequest({
|
||||||
'REQUEST_METHOD': 'GET',
|
'REQUEST_METHOD': 'GET',
|
||||||
'wsgi.input': BytesIO(b''),
|
'wsgi.input': BytesIO(b''),
|
||||||
'QUERY_STRING': 'a=1&a=2;a=3',
|
'QUERY_STRING': 'a=1&a=2&a=3',
|
||||||
})
|
})
|
||||||
request.GET['a']
|
request.GET['a']
|
||||||
|
|
||||||
@ -168,7 +168,7 @@ class DataUploadMaxNumberOfFieldsMultipartPost(SimpleTestCase):
|
|||||||
|
|
||||||
class DataUploadMaxNumberOfFieldsFormPost(SimpleTestCase):
|
class DataUploadMaxNumberOfFieldsFormPost(SimpleTestCase):
|
||||||
def setUp(self):
|
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({
|
self.request = WSGIRequest({
|
||||||
'REQUEST_METHOD': 'POST',
|
'REQUEST_METHOD': 'POST',
|
||||||
'CONTENT_TYPE': 'application/x-www-form-urlencoded',
|
'CONTENT_TYPE': 'application/x-www-form-urlencoded',
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
import unittest
|
import unittest
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
from django.core.exceptions import TooManyFieldsSent
|
||||||
from django.test import SimpleTestCase, ignore_warnings
|
from django.test import SimpleTestCase, ignore_warnings
|
||||||
from django.utils.datastructures import MultiValueDict
|
from django.utils.datastructures import MultiValueDict
|
||||||
from django.utils.deprecation import RemovedInDjango30Warning
|
from django.utils.deprecation import RemovedInDjango30Warning
|
||||||
from django.utils.http import (
|
from django.utils.http import (
|
||||||
base36_to_int, cookie_date, escape_leading_slashes, http_date,
|
base36_to_int, cookie_date, escape_leading_slashes, http_date,
|
||||||
int_to_base36, is_safe_url, is_same_domain, parse_etags, parse_http_date,
|
int_to_base36, is_safe_url, is_same_domain, limited_parse_qsl, parse_etags,
|
||||||
quote_etag, urlencode, urlquote, urlquote_plus, urlsafe_base64_decode,
|
parse_http_date, quote_etag, urlencode, urlquote, urlquote_plus,
|
||||||
urlsafe_base64_encode, urlunquote, urlunquote_plus,
|
urlsafe_base64_decode, urlsafe_base64_encode, urlunquote, urlunquote_plus,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -310,3 +311,47 @@ class EscapeLeadingSlashesTests(unittest.TestCase):
|
|||||||
for url, expected in tests:
|
for url, expected in tests:
|
||||||
with self.subTest(url=url):
|
with self.subTest(url=url):
|
||||||
self.assertEqual(escape_leading_slashes(url), expected)
|
self.assertEqual(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:
|
||||||
|
with self.subTest(original):
|
||||||
|
result = 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 = 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 = limited_parse_qsl('key=\u0141%E9', encoding='latin-1')
|
||||||
|
self.assertEqual(result, [('key', '\u0141\xE9')])
|
||||||
|
result = limited_parse_qsl('key=\u0141%C3%A9', encoding='utf-8')
|
||||||
|
self.assertEqual(result, [('key', '\u0141\xE9')])
|
||||||
|
result = limited_parse_qsl('key=\u0141%C3%A9', encoding='ascii')
|
||||||
|
self.assertEqual(result, [('key', '\u0141\ufffd\ufffd')])
|
||||||
|
result = limited_parse_qsl('key=\u0141%E9-', encoding='ascii')
|
||||||
|
self.assertEqual(result, [('key', '\u0141\ufffd-')])
|
||||||
|
result = 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):
|
||||||
|
limited_parse_qsl('&'.join(['a=a'] * 11), fields_limit=10)
|
||||||
|
limited_parse_qsl('&'.join(['a=a'] * 10), fields_limit=10)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user