Compare commits

...

10 Commits

Author SHA1 Message Date
openeuler-ci-bot
12458c174c
!137 Fix CVE-2024-53907 CVE-2024-53908
From: @wk333 
Reviewed-by: @cherry530 
Signed-off-by: @cherry530
2024-12-09 02:58:24 +00:00
wk333
66ebce5491 Fix CVE-2024-53907 CVE-2024-53908 2024-12-09 10:24:52 +08:00
openeuler-ci-bot
d6c8c5f278
!131 [sync] PR-129: Fix CVE-2024-45230 CVE-2024-45231
From: @openeuler-sync-bot 
Reviewed-by: @cherry530 
Signed-off-by: @cherry530
2024-10-11 06:09:30 +00:00
zhangxianting
eaee11c046 Fix CVE-2024-45230 CVE-2024-45231
(cherry picked from commit 2f48f14416679bfd07bd526077f002e97ed67b76)
2024-10-11 10:41:19 +08:00
openeuler-ci-bot
c6fd153566
!120 [sync] PR-116: Update to 4.2.15 for fix CVE-2024-41989,CVE-2024-41990,CVE-2024-41991 and CVE-2024-42005
From: @openeuler-sync-bot 
Reviewed-by: @cherry530 
Signed-off-by: @cherry530
2024-08-13 06:22:16 +00:00
starlet-dx
f15296ef33 Update to 4.2.15 for fix CVE-2024-41989,CVE-2024-41990,CVE-2024-41991 and CVE-2024-42005
(cherry picked from commit 2b335764a62585dcfb77c3fdf6110c7cb3b69b11)
2024-08-13 09:41:56 +08:00
openeuler-ci-bot
5b51e71d77
!114 [sync] PR-113: Update to 4.2.14 for fix CVE-2024-38875,CVE-2024-39329,CVE-2024-39330 and CVE-2024-39614
From: @openeuler-sync-bot 
Reviewed-by: @cherry530 
Signed-off-by: @cherry530
2024-07-15 08:46:05 +00:00
starlet-dx
e44a0bfbf7 Update to 4.2.14 for fix CVE-2024-38875,CVE-2024-39329,CVE-2024-39330 and CVE-2024-39614
(cherry picked from commit 3de1f69ae7f0e5ee077c370770e49a13fe903b43)
2024-07-12 17:32:37 +08:00
openeuler-ci-bot
4278eb93ea
!99 Fix CVE-2024-27351
From: @starlet-dx 
Reviewed-by: @cherry530 
Signed-off-by: @cherry530
2024-03-05 08:49:38 +00:00
starlet-dx
1c322af085 Fix CVE-2024-27351 2024-03-05 15:27:21 +08:00
10 changed files with 558 additions and 514 deletions

View File

@ -1,85 +0,0 @@
From 9c51b4dcfa0cefcb48231f4d71cafa80821f87b9 Mon Sep 17 00:00:00 2001
From: Mariusz Felisiak <felisiak.mariusz@gmail.com>
Date: Tue, 22 Aug 2023 08:53:03 +0200
Subject: [PATCH] [4.2.x] Fixed CVE-2023-41164 -- Fixed potential DoS in
django.utils.encoding.uri_to_iri().
Thanks MProgrammer (https://hackerone.com/mprogrammer) for the report.
Origin: https://github.com/django/django/commit/9c51b4dcfa0cefcb48231f4d71cafa80821f87b9
Co-authored-by: nessita <124304+nessita@users.noreply.github.com>
---
django/utils/encoding.py | 6 ++++--
docs/releases/3.2.21.txt | 7 ++++++-
docs/releases/4.1.11.txt | 7 ++++++-
docs/releases/4.2.5.txt | 7 +++++++
tests/utils_tests/test_encoding.py | 21 ++++++++++++++++++++-
5 files changed, 43 insertions(+), 5 deletions(-)
diff --git a/django/utils/encoding.py b/django/utils/encoding.py
index 43847b538510..23473930fd96 100644
--- a/django/utils/encoding.py
+++ b/django/utils/encoding.py
@@ -219,6 +219,7 @@ def repercent_broken_unicode(path):
repercent-encode any octet produced that is not part of a strictly legal
UTF-8 octet sequence.
"""
+ changed_parts = []
while True:
try:
path.decode()
@@ -226,9 +227,10 @@ def repercent_broken_unicode(path):
# 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] + repercent.encode() + path[e.end :]
+ changed_parts.append(path[: e.start] + repercent.encode())
+ path = path[e.end :]
else:
- return path
+ return b"".join(changed_parts) + path
def filepath_to_uri(path):
diff --git a/tests/utils_tests/test_encoding.py b/tests/utils_tests/test_encoding.py
index 6dea260b841b..2b52b1607c97 100644
--- a/tests/utils_tests/test_encoding.py
+++ b/tests/utils_tests/test_encoding.py
@@ -1,9 +1,10 @@
import datetime
+import inspect
import sys
import unittest
from pathlib import Path
from unittest import mock
-from urllib.parse import quote_plus
+from urllib.parse import quote, quote_plus
from django.test import SimpleTestCase
from django.utils.encoding import (
@@ -120,6 +121,24 @@ def test_repercent_broken_unicode_recursion_error(self):
except RecursionError:
self.fail("Unexpected RecursionError raised.")
+ def test_repercent_broken_unicode_small_fragments(self):
+ data = b"test\xfctest\xfctest\xfc"
+ decoded_paths = []
+
+ def mock_quote(*args, **kwargs):
+ # The second frame is the call to repercent_broken_unicode().
+ decoded_paths.append(inspect.currentframe().f_back.f_locals["path"])
+ return quote(*args, **kwargs)
+
+ with mock.patch("django.utils.encoding.quote", mock_quote):
+ self.assertEqual(repercent_broken_unicode(data), b"test%FCtest%FCtest%FC")
+
+ # decode() is called on smaller fragment of the path each time.
+ self.assertEqual(
+ decoded_paths,
+ [b"test\xfctest\xfctest\xfc", b"test\xfctest\xfc", b"test\xfc"],
+ )
+
class TestRFC3987IEncodingUtils(unittest.TestCase):
def test_filepath_to_uri(self):

View File

@ -1,167 +0,0 @@
From be9c27c4d18c2e6a5be8af4e53c0797440794473 Mon Sep 17 00:00:00 2001
From: Natalia <124304+nessita@users.noreply.github.com>
Date: Tue, 19 Sep 2023 09:51:48 -0300
Subject: [PATCH] [4.2.x] Fixed CVE-2023-43665 -- Mitigated potential DoS in
django.utils.text.Truncator when truncating HTML text.
Thanks Wenchao Li of Alibaba Group for the report.
Origin:
https://github.com/django/django/commit/be9c27c4d18c2e6a5be8af4e53c0797440794473
---
django/utils/text.py | 17 ++++++++++++++++-
docs/ref/templates/builtins.txt | 20 ++++++++++++++++++++
tests/utils_tests/test_text.py | 33 +++++++++++++++++++++++++--------
3 files changed, 61 insertions(+), 9 deletions(-)
diff --git a/django/utils/text.py b/django/utils/text.py
index 86d3b52..2663164 100644
--- a/django/utils/text.py
+++ b/django/utils/text.py
@@ -67,8 +67,14 @@ def wrap(text, width):
class Truncator(SimpleLazyObject):
"""
An object used to truncate text, either by characters or words.
+
+ When truncating HTML text (either chars or words), input will be limited to
+ at most `MAX_LENGTH_HTML` characters.
"""
+ # 5 million characters are approximately 4000 text pages or 3 web pages.
+ MAX_LENGTH_HTML = 5_000_000
+
def __init__(self, text):
super().__init__(lambda: str(text))
@@ -164,6 +170,11 @@ class Truncator(SimpleLazyObject):
if words and length <= 0:
return ""
+ size_limited = False
+ if len(text) > self.MAX_LENGTH_HTML:
+ text = text[: self.MAX_LENGTH_HTML]
+ size_limited = True
+
html4_singlets = (
"br",
"col",
@@ -220,10 +231,14 @@ class Truncator(SimpleLazyObject):
# Add it to the start of the open tags list
open_tags.insert(0, tagname)
+ truncate_text = self.add_truncation_text("", truncate)
+
if current_len <= length:
+ if size_limited and truncate_text:
+ text += truncate_text
return text
+
out = text[:end_text_pos]
- truncate_text = self.add_truncation_text("", truncate)
if truncate_text:
out += truncate_text
# Close any tags still open
diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt
index bee7807..02d6431 100644
--- a/docs/ref/templates/builtins.txt
+++ b/docs/ref/templates/builtins.txt
@@ -2651,6 +2651,16 @@ If ``value`` is ``"<p>Joel is a slug</p>"``, the output will be
Newlines in the HTML content will be preserved.
+.. admonition:: Size of input string
+
+ Processing large, potentially malformed HTML strings can be
+ resource-intensive and impact service performance. ``truncatechars_html``
+ limits input to the first five million characters.
+
+.. versionchanged:: 3.2.22
+
+ In older versions, strings over five million characters were processed.
+
.. templatefilter:: truncatewords
``truncatewords``
@@ -2693,6 +2703,16 @@ If ``value`` is ``"<p>Joel is a slug</p>"``, the output will be
Newlines in the HTML content will be preserved.
+.. admonition:: Size of input string
+
+ Processing large, potentially malformed HTML strings can be
+ resource-intensive and impact service performance. ``truncatewords_html``
+ limits input to the first five million characters.
+
+.. versionchanged:: 3.2.22
+
+ In older versions, strings over five million characters were processed.
+
.. templatefilter:: unordered_list
``unordered_list``
diff --git a/tests/utils_tests/test_text.py b/tests/utils_tests/test_text.py
index cb2959f..7d20445 100644
--- a/tests/utils_tests/test_text.py
+++ b/tests/utils_tests/test_text.py
@@ -1,5 +1,6 @@
import json
import sys
+from unittest.mock import patch
from django.core.exceptions import SuspiciousFileOperation
from django.test import SimpleTestCase
@@ -94,11 +95,17 @@ class TestUtilsText(SimpleTestCase):
text.Truncator(lazystr("The quick brown fox")).chars(10), "The quick…"
)
- def test_truncate_chars_html(self):
+ @patch("django.utils.text.Truncator.MAX_LENGTH_HTML", 10_000)
+ def test_truncate_chars_html_size_limit(self):
+ max_len = text.Truncator.MAX_LENGTH_HTML
+ bigger_len = text.Truncator.MAX_LENGTH_HTML + 1
+ valid_html = "<p>Joel is a slug</p>" # 14 chars
perf_test_values = [
- (("</a" + "\t" * 50000) + "//>", None),
- ("&" * 50000, "&" * 9 + "…"),
+ ("</a" + "\t" * (max_len - 6) + "//>", None),
+ ("</p" + "\t" * bigger_len + "//>", "</p" + "\t" * 6 + "…"),
+ ("&" * bigger_len, "&" * 9 + "…"),
("_X<<<<<<<<<<<>", None),
+ (valid_html * bigger_len, "<p>Joel is a…</p>"), # 10 chars
]
for value, expected in perf_test_values:
with self.subTest(value=value):
@@ -176,15 +183,25 @@ class TestUtilsText(SimpleTestCase):
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))
+ @patch("django.utils.text.Truncator.MAX_LENGTH_HTML", 10_000)
+ def test_truncate_words_html_size_limit(self):
+ max_len = text.Truncator.MAX_LENGTH_HTML
+ bigger_len = text.Truncator.MAX_LENGTH_HTML + 1
+ valid_html = "<p>Joel is a slug</p>" # 4 words
perf_test_values = [
- ("</a" + "\t" * 50000) + "//>",
- "&" * 50000,
- "_X<<<<<<<<<<<>",
+ ("</a" + "\t" * (max_len - 6) + "//>", None),
+ ("</p" + "\t" * bigger_len + "//>", "</p" + "\t" * (max_len - 3) + "…"),
+ ("&" * max_len, None), # no change
+ ("&" * bigger_len, "&" * max_len + "…"),
+ ("_X<<<<<<<<<<<>", None),
+ (valid_html * bigger_len, valid_html * 12 + "<p>Joel is…</p>"), # 50 words
]
- for value in perf_test_values:
+ for value, expected in perf_test_values:
with self.subTest(value=value):
truncator = text.Truncator(value)
- self.assertEqual(value, truncator.words(50, html=True))
+ self.assertEqual(
+ expected if expected else value, truncator.words(50, html=True)
+ )
def test_wrap(self):
digits = "1234 67 9"
--
2.30.0

View File

@ -1,61 +0,0 @@
From 048a9ebb6ea468426cb4e57c71572cbbd975517f Mon Sep 17 00:00:00 2001
From: Mariusz Felisiak <felisiak.mariusz@gmail.com>
Date: Tue, 17 Oct 2023 11:48:32 +0200
Subject: [PATCH] [4.2.x] Fixed CVE-2023-46695 -- Fixed potential DoS in
UsernameField on Windows.
Thanks MProgrammer (https://hackerone.com/mprogrammer) for the report.
---
django/contrib/auth/forms.py | 10 +++++++++-
tests/auth_tests/test_forms.py | 7 +++++++
2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index eaae0bf..061dc81 100644
--- a/django/contrib/auth/forms.py
+++ b/django/contrib/auth/forms.py
@@ -71,7 +71,15 @@ class ReadOnlyPasswordHashField(forms.Field):
class UsernameField(forms.CharField):
def to_python(self, value):
- return unicodedata.normalize("NFKC", super().to_python(value))
+ value = super().to_python(value)
+ if self.max_length is not None and len(value) > self.max_length:
+ # Normalization can increase the string length (e.g.
+ # "ff" -> "ff", "½" -> "12") but cannot reduce it, so there is no
+ # point in normalizing invalid data. Moreover, Unicode
+ # normalization is very slow on Windows and can be a DoS attack
+ # vector.
+ return value
+ return unicodedata.normalize("NFKC", value)
def widget_attrs(self, widget):
return {
diff --git a/tests/auth_tests/test_forms.py b/tests/auth_tests/test_forms.py
index 7a80adb..81c56a4 100644
--- a/tests/auth_tests/test_forms.py
+++ b/tests/auth_tests/test_forms.py
@@ -14,6 +14,7 @@ from django.contrib.auth.forms import (
SetPasswordForm,
UserChangeForm,
UserCreationForm,
+ UsernameField,
)
from django.contrib.auth.models import User
from django.contrib.auth.signals import user_login_failed
@@ -154,6 +155,12 @@ class BaseUserCreationFormTest(TestDataMixin, TestCase):
self.assertNotEqual(user.username, ohm_username)
self.assertEqual(user.username, "testΩ") # U+03A9 GREEK CAPITAL LETTER OMEGA
+ def test_invalid_username_no_normalize(self):
+ field = UsernameField(max_length=254)
+ # Usernames are not normalized if they are too long.
+ self.assertEqual(field.to_python("½" * 255), "½" * 255)
+ self.assertEqual(field.to_python("ff" * 254), "ff" * 254)
+
def test_duplicate_normalized_unicode(self):
"""
To prevent almost identical usernames, visually identical but differing
--
2.30.0

View File

@ -1,191 +0,0 @@
From 572ea07e84b38ea8de0551f4b4eda685d91d09d2 Mon Sep 17 00:00:00 2001
From: Adam Johnson <me@adamj.eu>
Date: Mon, 22 Jan 2024 13:21:13 +0000
Subject: [PATCH] [4.2.x] Fixed CVE-2024-24680 -- Mitigated potential DoS in
intcomma template filter.
Thanks Seokchan Yoon for the report.
Co-authored-by: Mariusz Felisiak <felisiak.mariusz@gmail.com>
Co-authored-by: Natalia <124304+nessita@users.noreply.github.com>
Co-authored-by: Shai Berger <shai@platonix.com>
---
.../contrib/humanize/templatetags/humanize.py | 13 ++--
tests/humanize_tests/tests.py | 64 +++++++++++++++++++
2 files changed, 71 insertions(+), 6 deletions(-)
diff --git a/django/contrib/humanize/templatetags/humanize.py b/django/contrib/humanize/templatetags/humanize.py
index 2322477..2c26f89 100644
--- a/django/contrib/humanize/templatetags/humanize.py
+++ b/django/contrib/humanize/templatetags/humanize.py
@@ -75,12 +75,13 @@ def intcomma(value, use_l10n=True):
return intcomma(value, False)
else:
return number_format(value, use_l10n=True, force_grouping=True)
- orig = str(value)
- new = re.sub(r"^(-?\d+)(\d{3})", r"\g<1>,\g<2>", orig)
- if orig == new:
- return new
- else:
- return intcomma(new, use_l10n)
+ result = str(value)
+ match = re.match(r"-?\d+", result)
+ if match:
+ prefix = match[0]
+ prefix_with_commas = re.sub(r"\d{3}", r"\g<0>,", prefix[::-1])[::-1]
+ result = prefix_with_commas + result[len(prefix) :]
+ return result
# A tuple of standard large number to their converters
diff --git a/tests/humanize_tests/tests.py b/tests/humanize_tests/tests.py
index cf29f58..a78bbad 100644
--- a/tests/humanize_tests/tests.py
+++ b/tests/humanize_tests/tests.py
@@ -116,39 +116,71 @@ class HumanizeTests(SimpleTestCase):
def test_intcomma(self):
test_list = (
100,
+ -100,
1000,
+ -1000,
10123,
+ -10123,
10311,
+ -10311,
1000000,
+ -1000000,
1234567.25,
+ -1234567.25,
"100",
+ "-100",
"1000",
+ "-1000",
"10123",
+ "-10123",
"10311",
+ "-10311",
"1000000",
+ "-1000000",
"1234567.1234567",
+ "-1234567.1234567",
Decimal("1234567.1234567"),
+ Decimal("-1234567.1234567"),
None,
"",
+ "-",
".",
+ "-.",
+ "the quick brown fox jumped over the lazy dog",
)
result_list = (
"100",
+ "-100",
"1,000",
+ "-1,000",
"10,123",
+ "-10,123",
"10,311",
+ "-10,311",
"1,000,000",
+ "-1,000,000",
"1,234,567.25",
+ "-1,234,567.25",
"100",
+ "-100",
"1,000",
+ "-1,000",
"10,123",
+ "-10,123",
"10,311",
+ "-10,311",
"1,000,000",
+ "-1,000,000",
"1,234,567.1234567",
+ "-1,234,567.1234567",
"1,234,567.1234567",
+ "-1,234,567.1234567",
None,
"1,234,567",
+ "-1,234,567",
",,.",
+ "-,,.",
+ "the quick brown fox jumped over the lazy dog",
)
with translation.override("en"):
self.humanize_tester(test_list, result_list, "intcomma")
@@ -156,39 +188,71 @@ class HumanizeTests(SimpleTestCase):
def test_l10n_intcomma(self):
test_list = (
100,
+ -100,
1000,
+ -1000,
10123,
+ -10123,
10311,
+ -10311,
1000000,
+ -1000000,
1234567.25,
+ -1234567.25,
"100",
+ "-100",
"1000",
+ "-1000",
"10123",
+ "-10123",
"10311",
+ "-10311",
"1000000",
+ "-1000000",
"1234567.1234567",
+ "-1234567.1234567",
Decimal("1234567.1234567"),
+ -Decimal("1234567.1234567"),
None,
"",
+ "-",
".",
+ "-.",
+ "the quick brown fox jumped over the lazy dog",
)
result_list = (
"100",
+ "-100",
"1,000",
+ "-1,000",
"10,123",
+ "-10,123",
"10,311",
+ "-10,311",
"1,000,000",
+ "-1,000,000",
"1,234,567.25",
+ "-1,234,567.25",
"100",
+ "-100",
"1,000",
+ "-1,000",
"10,123",
+ "-10,123",
"10,311",
+ "-10,311",
"1,000,000",
+ "-1,000,000",
"1,234,567.1234567",
+ "-1,234,567.1234567",
"1,234,567.1234567",
+ "-1,234,567.1234567",
None,
"1,234,567",
+ "-1,234,567",
",,.",
+ "-,,.",
+ "the quick brown fox jumped over the lazy dog",
)
with self.settings(USE_THOUSAND_SEPARATOR=False):
with translation.override("en"):
--
2.33.0

135
CVE-2024-45230.patch Normal file
View File

@ -0,0 +1,135 @@
From d147a8ebbdf28c17cafbbe2884f0bc57e2bf82e2 Mon Sep 17 00:00:00 2001
From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com>
Date: Mon, 12 Aug 2024 15:17:57 +0200
Subject: [PATCH] [4.2.x] Fixed CVE-2024-45230 -- Mitigated potential DoS in
urlize and urlizetrunc template filters.
Thanks MProgrammer (https://hackerone.com/mprogrammer) for the report.
---
django/utils/html.py | 17 ++++++++------
docs/ref/templates/builtins.txt | 11 ++++++++++
docs/releases/4.2.16.txt | 14 ++++++++++++
.../filter_tests/test_urlize.py | 22 +++++++++++++++++++
tests/utils_tests/test_html.py | 1 +
5 files changed, 58 insertions(+), 7 deletions(-)
create mode 100644 docs/releases/4.2.16.txt
diff --git a/django/utils/html.py b/django/utils/html.py
index 23575d3..df38c20 100644
--- a/django/utils/html.py
+++ b/django/utils/html.py
@@ -395,14 +395,17 @@ class Urlizer:
potential_entity = middle[amp:]
escaped = html.unescape(potential_entity)
if escaped == potential_entity or escaped.endswith(";"):
- rstripped = middle.rstrip(";")
- amount_stripped = len(middle) - len(rstripped)
- if amp > -1 and amount_stripped > 1:
- # Leave a trailing semicolon as might be an entity.
- trail = middle[len(rstripped) + 1 :] + trail
- middle = rstripped + ";"
+ rstripped = middle.rstrip(self.trailing_punctuation_chars)
+ trail_start = len(rstripped)
+ amount_trailing_semicolons = len(middle) - len(middle.rstrip(";"))
+ if amp > -1 and amount_trailing_semicolons > 1:
+ # Leave up to most recent semicolon as might be an entity.
+ recent_semicolon = middle[trail_start:].index(";")
+ middle_semicolon_index = recent_semicolon + trail_start + 1
+ trail = middle[middle_semicolon_index:] + trail
+ middle = rstripped + middle[trail_start:middle_semicolon_index]
else:
- trail = middle[len(rstripped) :] + trail
+ trail = middle[trail_start:] + trail
middle = rstripped
trimmed_something = True
diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt
index 39aa398..dda5b42 100644
--- a/docs/ref/templates/builtins.txt
+++ b/docs/ref/templates/builtins.txt
@@ -2831,6 +2831,17 @@ Django's built-in :tfilter:`escape` filter. The default value for
email addresses that contain single quotes (``'``), things won't work as
expected. Apply this filter only to plain text.
+.. warning::
+
+ Using ``urlize`` or ``urlizetrunc`` can incur a performance penalty, which
+ can become severe when applied to user controlled values such as content
+ stored in a :class:`~django.db.models.TextField`. You can use
+ :tfilter:`truncatechars` to add a limit to such inputs:
+
+ .. code-block:: html+django
+
+ {{ value|truncatechars:500|urlize }}
+
.. templatefilter:: urlizetrunc
``urlizetrunc``
diff --git a/docs/releases/4.2.16.txt b/docs/releases/4.2.16.txt
new file mode 100644
index 0000000..b624d5c
--- /dev/null
+++ b/docs/releases/4.2.16.txt
@@ -0,0 +1,14 @@
+===========================
+Django 4.2.16 release notes
+===========================
+*September 3, 2024*
+Django 4.2.16 fixes one security issue with severity "moderate" and one
+security issue with severity "low" in 4.2.15.
+
+...
+CVE-2024-45230: Potential denial-of-service vulnerability in ``django.utils.html.urlize()``
+===========================================================================================
+
+:tfilter:`urlize` and :tfilter:`urlizetrunc` were subject to a potential
+denial-of-service attack via very large inputs with a specific sequence of
+characters.
diff --git a/tests/template_tests/filter_tests/test_urlize.py b/tests/template_tests/filter_tests/test_urlize.py
index abc227b..e542802 100644
--- a/tests/template_tests/filter_tests/test_urlize.py
+++ b/tests/template_tests/filter_tests/test_urlize.py
@@ -305,6 +305,28 @@ class FunctionTests(SimpleTestCase):
"http://testing.com/example</a>.,:;)&quot;!",
)
+ def test_trailing_semicolon(self):
+ self.assertEqual(
+ urlize("http://example.com?x=&amp;", autoescape=False),
+ '<a href="http://example.com?x=" rel="nofollow">'
+ "http://example.com?x=&amp;</a>",
+ )
+ self.assertEqual(
+ urlize("http://example.com?x=&amp;;", autoescape=False),
+ '<a href="http://example.com?x=" rel="nofollow">'
+ "http://example.com?x=&amp;</a>;",
+ )
+ self.assertEqual(
+ urlize("http://example.com?x=&amp;;;", autoescape=False),
+ '<a href="http://example.com?x=" rel="nofollow">'
+ "http://example.com?x=&amp;</a>;;",
+ )
+ self.assertEqual(
+ urlize("http://example.com?x=&amp.;...;", autoescape=False),
+ '<a href="http://example.com?x=" rel="nofollow">'
+ "http://example.com?x=&amp</a>.;...;",
+ )
+
def test_brackets(self):
"""
#19070 - Check urlize handles brackets properly
diff --git a/tests/utils_tests/test_html.py b/tests/utils_tests/test_html.py
index 83ebe43..7ff5020 100644
--- a/tests/utils_tests/test_html.py
+++ b/tests/utils_tests/test_html.py
@@ -364,6 +364,7 @@ class TestUtilsHtml(SimpleTestCase):
"&:" + ";" * 100_000,
"&.;" * 100_000,
".;" * 100_000,
+ "&" + ";:" * 100_000,
)
for value in tests:
with self.subTest(value=value):
--
2.43.0

159
CVE-2024-45231.patch Normal file
View File

@ -0,0 +1,159 @@
From bf4888d317ba4506d091eeac6e8b4f1fcc731199 Mon Sep 17 00:00:00 2001
From: Natalia <124304+nessita@users.noreply.github.com>
Date: Mon, 19 Aug 2024 14:47:38 -0300
Subject: [PATCH] [4.2.x] Fixed CVE-2024-45231 -- Avoided server error on
password reset when email sending fails.
On successful submission of a password reset request, an email is sent
to the accounts known to the system. If sending this email fails (due to
email backend misconfiguration, service provider outage, network issues,
etc.), an attacker might exploit this by detecting which password reset
requests succeed and which ones generate a 500 error response.
Thanks to Thibaut Spriet for the report, and to Mariusz Felisiak, Adam
Johnson, and Sarah Boyce for the reviews.
---
django/contrib/auth/forms.py | 9 ++++++++-
docs/ref/logging.txt | 12 ++++++++++++
docs/releases/4.2.16.txt | 11 +++++++++++
docs/topics/auth/default.txt | 4 +++-
tests/auth_tests/test_forms.py | 21 +++++++++++++++++++++
tests/mail/custombackend.py | 5 +++++
6 files changed, 60 insertions(+), 2 deletions(-)
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index 061dc81b42..7f85787f03 100644
--- a/django/contrib/auth/forms.py
+++ b/django/contrib/auth/forms.py
@@ -1,3 +1,4 @@
+import logging
import unicodedata
from django import forms
@@ -16,6 +17,7 @@ from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
UserModel = get_user_model()
+logger = logging.getLogger("django.contrib.auth")
def _unicode_ci_compare(s1, s2):
@@ -314,7 +316,12 @@ class PasswordResetForm(forms.Form):
html_email = loader.render_to_string(html_email_template_name, context)
email_message.attach_alternative(html_email, "text/html")
- email_message.send()
+ try:
+ email_message.send()
+ except Exception:
+ logger.exception(
+ "Failed to send password reset email to %s", context["user"].pk
+ )
def get_users(self, email):
"""Given an email, return matching user(s) who should receive a reset.
diff --git a/docs/ref/logging.txt b/docs/ref/logging.txt
index b11fb752f7..3d33e0af63 100644
--- a/docs/ref/logging.txt
+++ b/docs/ref/logging.txt
@@ -204,6 +204,18 @@ all database queries.
Support for logging transaction management queries (``BEGIN``, ``COMMIT``,
and ``ROLLBACK``) was added.
+.. _django-contrib-auth-logger:
+
+``django.contrib.auth``
+~~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 4.2.16
+
+Log messages related to :doc:`contrib/auth`, particularly ``ERROR`` messages
+are generated when a :class:`~django.contrib.auth.forms.PasswordResetForm` is
+successfully submitted but the password reset email cannot be delivered due to
+a mail sending exception.
+
.. _django-security-logger:
``django.security.*``
diff --git a/docs/releases/4.2.16.txt b/docs/releases/4.2.16.txt
index 2a84186867..963036345c 100644
--- a/docs/releases/4.2.16.txt
+++ b/docs/releases/4.2.16.txt
@@ -13,3 +13,14 @@ CVE-2024-45230: Potential denial-of-service vulnerability in ``django.utils.html
:tfilter:`urlize` and :tfilter:`urlizetrunc` were subject to a potential
denial-of-service attack via very large inputs with a specific sequence of
characters.
+
+CVE-2024-45231: Potential user email enumeration via response status on password reset
+======================================================================================
+
+Due to unhandled email sending failures, the
+:class:`~django.contrib.auth.forms.PasswordResetForm` class allowed remote
+attackers to enumerate user emails by issuing password reset requests and
+observing the outcomes.
+
+To mitigate this risk, exceptions occurring during password reset email sending
+are now handled and logged using the :ref:`django-contrib-auth-logger` logger.
diff --git a/docs/topics/auth/default.txt b/docs/topics/auth/default.txt
index 528902416d..ad840c5e57 100644
--- a/docs/topics/auth/default.txt
+++ b/docs/topics/auth/default.txt
@@ -1661,7 +1661,9 @@ provides several built-in forms located in :mod:`django.contrib.auth.forms`:
.. method:: send_mail(subject_template_name, email_template_name, context, from_email, to_email, html_email_template_name=None)
Uses the arguments to send an ``EmailMultiAlternatives``.
- Can be overridden to customize how the email is sent to the user.
+ Can be overridden to customize how the email is sent to the user. If
+ you choose to override this method, be mindful of handling potential
+ exceptions raised due to email sending failures.
:param subject_template_name: the template for the subject.
:param email_template_name: the template for the email body.
diff --git a/tests/auth_tests/test_forms.py b/tests/auth_tests/test_forms.py
index 81c56a428e..f068d347a9 100644
--- a/tests/auth_tests/test_forms.py
+++ b/tests/auth_tests/test_forms.py
@@ -1245,6 +1245,27 @@ class PasswordResetFormTest(TestDataMixin, TestCase):
)
)
+ @override_settings(EMAIL_BACKEND="mail.custombackend.FailingEmailBackend")
+ def test_save_send_email_exceptions_are_catched_and_logged(self):
+ (user, username, email) = self.create_dummy_user()
+ form = PasswordResetForm({"email": email})
+ self.assertTrue(form.is_valid())
+
+ with self.assertLogs("django.contrib.auth", level=0) as cm:
+ form.save()
+
+ self.assertEqual(len(mail.outbox), 0)
+ self.assertEqual(len(cm.output), 1)
+ errors = cm.output[0].split("\n")
+ pk = user.pk
+ self.assertEqual(
+ errors[0],
+ f"ERROR:django.contrib.auth:Failed to send password reset email to {pk}",
+ )
+ self.assertEqual(
+ errors[-1], "ValueError: FailingEmailBackend is doomed to fail."
+ )
+
@override_settings(AUTH_USER_MODEL="auth_tests.CustomEmailField")
def test_custom_email_field(self):
email = "test@mail.com"
diff --git a/tests/mail/custombackend.py b/tests/mail/custombackend.py
index 14e7f077ba..c6c567b642 100644
--- a/tests/mail/custombackend.py
+++ b/tests/mail/custombackend.py
@@ -12,3 +12,8 @@ class EmailBackend(BaseEmailBackend):
# Messages are stored in an instance variable for testing.
self.test_outbox.extend(email_messages)
return len(email_messages)
+
+
+class FailingEmailBackend(BaseEmailBackend):
+ def send_messages(self, email_messages):
+ raise ValueError("FailingEmailBackend is doomed to fail.")
--
2.20.1

88
CVE-2024-53907.patch Normal file
View File

@ -0,0 +1,88 @@
From 790eb058b0716c536a2f2e8d1c6d5079d776c22b Mon Sep 17 00:00:00 2001
From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com>
Date: Wed, 13 Nov 2024 15:06:23 +0100
Subject: [PATCH] [4.2.x] Fixed CVE-2024-53907 -- Mitigated potential DoS in
strip_tags().
Origin: https://github.com/django/django/commit/790eb058b0716c536a2f2e8d1c6d5079d776c22b
Thanks to jiangniao for the report, and Shai Berger and Natalia Bidart
for the reviews.
---
django/utils/html.py | 10 ++++++++--
tests/utils_tests/test_html.py | 7 +++++++
3 files changed, 31 insertions(+), 2 deletions(-)
diff --git a/django/utils/html.py b/django/utils/html.py
index df38c2051994..a3a7238cba44 100644
--- a/django/utils/html.py
+++ b/django/utils/html.py
@@ -6,6 +6,7 @@
from html.parser import HTMLParser
from urllib.parse import parse_qsl, quote, unquote, urlencode, urlsplit, urlunsplit
+from django.core.exceptions import SuspiciousOperation
from django.utils.encoding import punycode
from django.utils.functional import Promise, cached_property, keep_lazy, keep_lazy_text
from django.utils.http import RFC3986_GENDELIMS, RFC3986_SUBDELIMS
@@ -14,6 +15,7 @@
from django.utils.text import normalize_newlines
MAX_URL_LENGTH = 2048
+MAX_STRIP_TAGS_DEPTH = 50
@keep_lazy(SafeString)
@@ -172,15 +174,19 @@ def _strip_once(value):
@keep_lazy_text
def strip_tags(value):
"""Return the given HTML with all tags stripped."""
- # Note: in typical case this loop executes _strip_once once. Loop condition
- # is redundant, but helps to reduce number of executions of _strip_once.
value = str(value)
+ # Note: in typical case this loop executes _strip_once twice (the second
+ # execution does not remove any more tags).
+ strip_tags_depth = 0
while "<" in value and ">" in value:
+ if strip_tags_depth >= MAX_STRIP_TAGS_DEPTH:
+ raise SuspiciousOperation
new_value = _strip_once(value)
if value.count("<") == new_value.count("<"):
# _strip_once wasn't able to detect more tags.
break
value = new_value
+ strip_tags_depth += 1
return value
diff --git a/tests/utils_tests/test_html.py b/tests/utils_tests/test_html.py
index 7ff5020fb6d3..579bb2a1e359 100644
--- a/tests/utils_tests/test_html.py
+++ b/tests/utils_tests/test_html.py
@@ -1,6 +1,7 @@
import os
from datetime import datetime
+from django.core.exceptions import SuspiciousOperation
from django.core.serializers.json import DjangoJSONEncoder
from django.test import SimpleTestCase
from django.utils.functional import lazystr
@@ -113,12 +114,18 @@ def test_strip_tags(self):
("<script>alert()</script>&h", "alert()h"),
("><!" + ("&" * 16000) + "D", "><!" + ("&" * 16000) + "D"),
("X<<<<br>br>br>br>X", "XX"),
+ ("<" * 50 + "a>" * 50, ""),
)
for value, output in items:
with self.subTest(value=value, output=output):
self.check_output(strip_tags, value, output)
self.check_output(strip_tags, lazystr(value), output)
+ def test_strip_tags_suspicious_operation(self):
+ value = "<" * 51 + "a>" * 51, "<a>"
+ with self.assertRaises(SuspiciousOperation):
+ strip_tags(value)
+
def test_strip_tags_files(self):
# Test with more lengthy content (also catching performance regressions)
for filename in ("strip_tags1.html", "strip_tags2.txt"):

145
CVE-2024-53908.patch Normal file
View File

@ -0,0 +1,145 @@
From 7376bcbf508883282ffcc0f0fac5cf0ed2d6cbc5 Mon Sep 17 00:00:00 2001
From: Simon Charette <charette.s@gmail.com>
Date: Fri, 8 Nov 2024 21:27:31 -0500
Subject: [PATCH] [4.2.x] Fixed CVE-2024-53908 -- Prevented SQL injections in
direct HasKeyLookup usage on Oracle.
Origin: https://github.com/django/django/commit/7376bcbf508883282ffcc0f0fac5cf0ed2d6cbc5
Thanks Seokchan Yoon for the report, and Mariusz Felisiak and Sarah
Boyce for the reviews.
---
django/db/models/fields/json.py | 53 ++++++++++++++++++----------
tests/model_fields/test_jsonfield.py | 9 +++++
3 files changed, 53 insertions(+), 18 deletions(-)
diff --git a/django/db/models/fields/json.py b/django/db/models/fields/json.py
index b7cde157c4fa..b9c6ff1752b9 100644
--- a/django/db/models/fields/json.py
+++ b/django/db/models/fields/json.py
@@ -216,20 +216,18 @@ def compile_json_path_final_key(self, key_transform):
# Compile the final key without interpreting ints as array elements.
return ".%s" % json.dumps(key_transform)
- def as_sql(self, compiler, connection, template=None):
+ def _as_sql_parts(self, compiler, connection):
# Process JSON path from the left-hand side.
if isinstance(self.lhs, KeyTransform):
- lhs, lhs_params, lhs_key_transforms = self.lhs.preprocess_lhs(
+ lhs_sql, lhs_params, lhs_key_transforms = self.lhs.preprocess_lhs(
compiler, connection
)
lhs_json_path = compile_json_path(lhs_key_transforms)
else:
- lhs, lhs_params = self.process_lhs(compiler, connection)
+ lhs_sql, lhs_params = self.process_lhs(compiler, connection)
lhs_json_path = "$"
- sql = template % lhs
# Process JSON path from the right-hand side.
rhs = self.rhs
- rhs_params = []
if not isinstance(rhs, (list, tuple)):
rhs = [rhs]
for key in rhs:
@@ -240,24 +238,43 @@ def as_sql(self, compiler, connection, template=None):
*rhs_key_transforms, final_key = rhs_key_transforms
rhs_json_path = compile_json_path(rhs_key_transforms, include_root=False)
rhs_json_path += self.compile_json_path_final_key(final_key)
- rhs_params.append(lhs_json_path + rhs_json_path)
+ yield lhs_sql, lhs_params, lhs_json_path + rhs_json_path
+
+ def _combine_sql_parts(self, parts):
# Add condition for each key.
if self.logical_operator:
- sql = "(%s)" % self.logical_operator.join([sql] * len(rhs_params))
- return sql, tuple(lhs_params) + tuple(rhs_params)
+ return "(%s)" % self.logical_operator.join(parts)
+ return "".join(parts)
+
+ def as_sql(self, compiler, connection, template=None):
+ sql_parts = []
+ params = []
+ for lhs_sql, lhs_params, rhs_json_path in self._as_sql_parts(
+ compiler, connection
+ ):
+ sql_parts.append(template % (lhs_sql, "%s"))
+ params.extend(lhs_params + [rhs_json_path])
+ return self._combine_sql_parts(sql_parts), tuple(params)
def as_mysql(self, compiler, connection):
return self.as_sql(
- compiler, connection, template="JSON_CONTAINS_PATH(%s, 'one', %%s)"
+ compiler, connection, template="JSON_CONTAINS_PATH(%s, 'one', %s)"
)
def as_oracle(self, compiler, connection):
- sql, params = self.as_sql(
- compiler, connection, template="JSON_EXISTS(%s, '%%s')"
- )
- # Add paths directly into SQL because path expressions cannot be passed
- # as bind variables on Oracle.
- return sql % tuple(params), []
+ template = "JSON_EXISTS(%s, '%s')"
+ sql_parts = []
+ params = []
+ for lhs_sql, lhs_params, rhs_json_path in self._as_sql_parts(
+ compiler, connection
+ ):
+ # Add right-hand-side directly into SQL because it cannot be passed
+ # as bind variables to JSON_EXISTS. It might result in invalid
+ # queries but it is assumed that it cannot be evaded because the
+ # path is JSON serialized.
+ sql_parts.append(template % (lhs_sql, rhs_json_path))
+ params.extend(lhs_params)
+ return self._combine_sql_parts(sql_parts), tuple(params)
def as_postgresql(self, compiler, connection):
if isinstance(self.rhs, KeyTransform):
@@ -269,7 +286,7 @@ def as_postgresql(self, compiler, connection):
def as_sqlite(self, compiler, connection):
return self.as_sql(
- compiler, connection, template="JSON_TYPE(%s, %%s) IS NOT NULL"
+ compiler, connection, template="JSON_TYPE(%s, %s) IS NOT NULL"
)
@@ -467,9 +484,9 @@ def as_oracle(self, compiler, connection):
return "(NOT %s OR %s IS NULL)" % (sql, lhs), tuple(params) + tuple(lhs_params)
def as_sqlite(self, compiler, connection):
- template = "JSON_TYPE(%s, %%s) IS NULL"
+ template = "JSON_TYPE(%s, %s) IS NULL"
if not self.rhs:
- template = "JSON_TYPE(%s, %%s) IS NOT NULL"
+ template = "JSON_TYPE(%s, %s) IS NOT NULL"
return HasKeyOrArrayIndex(self.lhs.lhs, self.lhs.key_name).as_sql(
compiler,
connection,
diff --git a/tests/model_fields/test_jsonfield.py b/tests/model_fields/test_jsonfield.py
index 4a1cc075b4c4..4c8d14bf9a17 100644
--- a/tests/model_fields/test_jsonfield.py
+++ b/tests/model_fields/test_jsonfield.py
@@ -29,6 +29,7 @@
from django.db.models.expressions import RawSQL
from django.db.models.fields.json import (
KT,
+ HasKey,
KeyTextTransform,
KeyTransform,
KeyTransformFactory,
@@ -607,6 +608,14 @@ def test_has_key_deep(self):
[expected],
)
+ def test_has_key_literal_lookup(self):
+ self.assertSequenceEqual(
+ NullableJSONModel.objects.filter(
+ HasKey(Value({"foo": "bar"}, JSONField()), "foo")
+ ).order_by("id"),
+ self.objs,
+ )
+
def test_has_key_list(self):
obj = NullableJSONModel.objects.create(value=[{"a": 1}, {"b": "x"}])
tests = [

View File

@ -1,18 +1,15 @@
%global _empty_manifest_terminate_build 0 %global _empty_manifest_terminate_build 0
Name: python-django Name: python-django
Version: 4.2.3 Version: 4.2.15
Release: 6 Release: 3
Summary: A high-level Python Web framework that encourages rapid development and clean, pragmatic design. Summary: A high-level Python Web framework that encourages rapid development and clean, pragmatic design.
License: Apache-2.0 and Python-2.0 and BSD-3-Clause License: Apache-2.0 and Python-2.0 and BSD-3-Clause
URL: https://www.djangoproject.com/ URL: https://www.djangoproject.com/
Source0: https://files.pythonhosted.org/packages/36/24/d0e78e667f98efcca76c8b670ef247583349a8f5241cdb3c98eeb92726ff/Django-4.2.3.tar.gz Source0: https://files.pythonhosted.org/packages/source/d/Django/Django-%{version}.tar.gz
Patch0: CVE-2023-41164.patch Patch0: CVE-2024-45230.patch
# https://github.com/django/django/commit/be9c27c4d18c2e6a5be8af4e53c0797440794473 Patch1: CVE-2024-45231.patch
Patch1: CVE-2023-43665.patch Patch2: CVE-2024-53907.patch
# https://github.com/django/django/commit/048a9ebb6ea468426cb4e57c71572cbbd975517f Patch3: CVE-2024-53908.patch
Patch2: CVE-2023-46695.patch
# https://github.com/django/django/commit/572ea07e84b38ea8de0551f4b4eda685d91d09d2
Patch3: CVE-2024-24680.patch
BuildArch: noarch BuildArch: noarch
%description %description
@ -79,6 +76,30 @@ mv %{buildroot}/doclist.lst .
%{_docdir}/* %{_docdir}/*
%changelog %changelog
* Mon Dec 09 2024 wangkai <13474090681@163.com> - 4.2.15-3
- Fix CVE-2024-53907 CVE-2024-53908
* Thu Oct 10 2024 zhangxianting <zhangxianting@uniontech.com> - 4.2.15-2
- Fix CVE-2024-45230 CVE-2024-45231
* Thu Aug 08 2024 yaoxin <yao_xin001@hoperun.com> - 4.2.15-1
- Update to 4.2.15
* CVE-2024-41989: Memory exhaustion in ``django.utils.numberformat.floatformat()``
* CVE-2024-41990: Potential denial-of-service vulnerability in ``django.utils.html.urlize()``
* CVE-2024-41991: Potential denial-of-service vulnerability in ``django.utils.html.urlize()`` and ``AdminURLFieldWidget``
* CVE-2024-42005: Potential SQL injection in ``QuerySet.values()`` and ``values_list()``
* Fixed a regression in Django 4.2.14 that caused a crash in ``LocaleMiddleware`` when processing a language code over 500 characters
* Fri Jul 12 2024 yaoxin <yao_xin001@hoperun.com> - 4.2.14-1
- Update to 4.2.14
* CVE-2024-38875: Potential denial-of-service vulnerability in django.utils.html.urlize()
* CVE-2024-39329: Username enumeration through timing difference for users with unusable passwords
* CVE-2024-39330: Potential directory-traversal via Storage.save()
* CVE-2024-39614: Potential denial-of-service vulnerability in get_supported_language_variant()
* Tue Mar 05 2024 yaoxin <yao_xin001@hoperun.com> - 4.2.3-7
- Fix CVE-2024-27351
* Wed Feb 07 2024 yaoxin <yao_xin001@hoperun.com> - 4.2.3-6 * Wed Feb 07 2024 yaoxin <yao_xin001@hoperun.com> - 4.2.3-6
- Fix CVE-2024-24680 - Fix CVE-2024-24680