From b33fe6176cd5c8efd93eaaff01256a1b9669a33e Mon Sep 17 00:00:00 2001 From: sa-buc Date: Fri, 14 Nov 2025 17:34:25 +0800 Subject: [PATCH] fix cves --- 0002-fix-CVE-2025-59682.patch | 73 +++++++++++++++ 0003-fix-CVE-2025-64459.patch | 54 +++++++++++ 1001-fix-CVE-2025-59681.patch | 168 ++++++++++++++++++++++++++++++++++ 1002-fix-CVE-2025-64458.patch | 91 ++++++++++++++++++ python-django.spec | 13 ++- 5 files changed, 398 insertions(+), 1 deletion(-) create mode 100644 0002-fix-CVE-2025-59682.patch create mode 100644 0003-fix-CVE-2025-64459.patch create mode 100644 1001-fix-CVE-2025-59681.patch create mode 100644 1002-fix-CVE-2025-64458.patch diff --git a/0002-fix-CVE-2025-59682.patch b/0002-fix-CVE-2025-59682.patch new file mode 100644 index 0000000..c45e2e1 --- /dev/null +++ b/0002-fix-CVE-2025-59682.patch @@ -0,0 +1,73 @@ +From 9504bbaa392c9fe37eee9291f5b4c29eb6037619 Mon Sep 17 00:00:00 2001 +From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> +Date: Tue, 16 Sep 2025 17:13:36 +0200 +Subject: [PATCH] [4.2.x] Fixed CVE-2025-59682 -- Fixed potential partial + directory-traversal via archive.extract(). + +Thanks stackered for the report. + +Follow up to 05413afa8c18cdb978fcdf470e09f7a12b234a23. + +Backport of 924a0c092e65fa2d0953fd1855d2dc8786d94de2 from main. + +Origin: https://github.com/django/django/commit/9504bbaa392c9fe37eee9291f5b4c29eb6037619 +--- + django/utils/archive.py | 6 +++++- + tests/utils_tests/test_archive.py | 19 +++++++++++++++++++ + 2 files changed, 24 insertions(+), 1 deletion(-) + +diff --git a/django/utils/archive.py b/django/utils/archive.py +index 71ec2d0..e8af690 100644 +--- a/django/utils/archive.py ++++ b/django/utils/archive.py +@@ -144,7 +144,11 @@ class BaseArchive: + def target_filename(self, to_path, name): + target_path = os.path.abspath(to_path) + filename = os.path.abspath(os.path.join(target_path, name)) +- if not filename.startswith(target_path): ++ try: ++ if os.path.commonpath([target_path, filename]) != target_path: ++ raise SuspiciousOperation("Archive contains invalid path: '%s'" % name) ++ except ValueError: ++ # Different drives on Windows raises ValueError. + raise SuspiciousOperation("Archive contains invalid path: '%s'" % name) + return filename + +diff --git a/tests/utils_tests/test_archive.py b/tests/utils_tests/test_archive.py +index 8cd1070..8063daf 100644 +--- a/tests/utils_tests/test_archive.py ++++ b/tests/utils_tests/test_archive.py +@@ -3,6 +3,7 @@ import stat + import sys + import tempfile + import unittest ++import zipfile + + from django.core.exceptions import SuspiciousOperation + from django.test import SimpleTestCase +@@ -96,3 +97,21 @@ class TestArchiveInvalid(SimpleTestCase): + with self.subTest(entry), tempfile.TemporaryDirectory() as tmpdir: + with self.assertRaisesMessage(SuspiciousOperation, msg % invalid_path): + archive.extract(os.path.join(archives_dir, entry), tmpdir) ++ ++ def test_extract_function_traversal_startswith(self): ++ with tempfile.TemporaryDirectory() as tmpdir: ++ base = os.path.abspath(tmpdir) ++ tarfile_handle = tempfile.NamedTemporaryFile(suffix=".zip", delete=False) ++ tar_path = tarfile_handle.name ++ tarfile_handle.close() ++ self.addCleanup(os.remove, tar_path) ++ ++ malicious_member = os.path.join(base + "abc", "evil.txt") ++ with zipfile.ZipFile(tar_path, "w") as zf: ++ zf.writestr(malicious_member, "evil\n") ++ zf.writestr("test.txt", "data\n") ++ ++ with self.assertRaisesMessage( ++ SuspiciousOperation, "Archive contains invalid path" ++ ): ++ archive.extract(tar_path, base) +-- +2.33.0 + + diff --git a/0003-fix-CVE-2025-64459.patch b/0003-fix-CVE-2025-64459.patch new file mode 100644 index 0000000..b345890 --- /dev/null +++ b/0003-fix-CVE-2025-64459.patch @@ -0,0 +1,54 @@ +From 59ae82e67053d281ff4562a24bbba21299f0a7d4 Mon Sep 17 00:00:00 2001 +From: Jacob Walls +Date: Wed, 24 Sep 2025 15:54:51 -0400 +Subject: [PATCH] [4.2.x] Fixed CVE-2025-64459 -- Prevented SQL injections in + Q/QuerySet via the _connector kwarg. + +Thanks cyberstan for the report, Sarah Boyce, Adam Johnson, Simon +Charette, and Jake Howard for the reviews. + +Backport of c880530ddd4fabd5939bab0e148bebe36699432a from main. + +Origin: https://github.com/django/django/commit/59ae82e67053d281ff4562a24bbba21299f0a7d4 +--- + django/db/models/query_utils.py | 4 ++++ + tests/queries/test_q.py | 5 +++++ + 2 files changed, 9 insertions(+) + +diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py +index 5c5644c..a85a682 100644 +--- a/django/db/models/query_utils.py ++++ b/django/db/models/query_utils.py +@@ -44,8 +44,12 @@ class Q(tree.Node): + XOR = "XOR" + default = AND + conditional = True ++ connectors = (None, AND, OR, XOR) + + def __init__(self, *args, _connector=None, _negated=False, **kwargs): ++ if _connector not in self.connectors: ++ connector_reprs = ", ".join(f"{conn!r}" for conn in self.connectors[1:]) ++ raise ValueError(f"_connector must be one of {connector_reprs}, or None.") + super().__init__( + children=[*args, *sorted(kwargs.items())], + connector=_connector, +diff --git a/tests/queries/test_q.py b/tests/queries/test_q.py +index cdf4029..5f20a41 100644 +--- a/tests/queries/test_q.py ++++ b/tests/queries/test_q.py +@@ -225,6 +225,11 @@ class QTests(SimpleTestCase): + Q(*items, _connector=connector), + ) + ++ def test_connector_validation(self): ++ msg = f"_connector must be one of {Q.AND!r}, {Q.OR!r}, {Q.XOR!r}, or None." ++ with self.assertRaisesMessage(ValueError, msg): ++ Q(_connector="evil") ++ + + class QCheckTests(TestCase): + def test_basic(self): +-- +2.51.1 + + diff --git a/1001-fix-CVE-2025-59681.patch b/1001-fix-CVE-2025-59681.patch new file mode 100644 index 0000000..0ec44c1 --- /dev/null +++ b/1001-fix-CVE-2025-59681.patch @@ -0,0 +1,168 @@ +From a050b315d6f70b4112a228de62e90bd2174ca62d Mon Sep 17 00:00:00 2001 +From: WB02254423 +Date: Mon, 17 Nov 2025 03:33:31 -0500 +Subject: [PATCH] [4.2.x] Fixed CVE-2025-59681 -- Protected + QuerySet.annotate(), alias(), aggregate(), and extra() against SQL injection + in column aliases on MySQL/MariaDB. + +--- + django/db/models/sql/query.py | 8 ++++---- + tests/aggregation/tests.py | 6 +++--- + tests/annotations/tests.py | 23 ++++++++++++----------- + tests/expressions/test_queryset_values.py | 8 ++++---- + tests/queries/tests.py | 4 ++-- + 5 files changed, 25 insertions(+), 24 deletions(-) + +diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py +index 5a1b685..c2c04df 100644 +--- a/django/db/models/sql/query.py ++++ b/django/db/models/sql/query.py +@@ -46,9 +46,9 @@ from django.utils.tree import Node + + __all__ = ["Query", "RawQuery"] + +-# Quotation marks ('"`[]), whitespace characters, semicolons, or inline ++# Quotation marks ('"`[]), whitespace characters, semicolons, hashes, or inline + # SQL comments are forbidden in column aliases. +-FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile(r"['`\"\]\[;\s]|--|/\*|\*/") ++FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile(r"['`\"\]\[;\s]|#|--|/\*|\*/") + + # Inspired from + # https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS +@@ -1123,8 +1123,8 @@ class Query(BaseExpression): + def check_alias(self, alias): + if FORBIDDEN_ALIAS_PATTERN.search(alias): + raise ValueError( +- "Column aliases cannot contain whitespace characters, quotation marks, " +- "semicolons, or SQL comments." ++ "Column aliases cannot contain whitespace characters, hashes, " ++ "quotation marks, semicolons, or SQL comments." + ) + + def add_annotation(self, annotation, alias, select=True): +diff --git a/tests/aggregation/tests.py b/tests/aggregation/tests.py +index 48266d9..84bc9b4 100644 +--- a/tests/aggregation/tests.py ++++ b/tests/aggregation/tests.py +@@ -2090,9 +2090,9 @@ class AggregateTestCase(TestCase): + def test_alias_sql_injection(self): + crafted_alias = """injected_name" from "aggregation_author"; --""" + msg = ( +- "Column aliases cannot contain whitespace characters, quotation marks, " +- "semicolons, or SQL comments." +- ) ++ "Column aliases cannot contain whitespace characters, hashes, quotation " ++ "marks, semicolons, or SQL comments." ++ ) + with self.assertRaisesMessage(ValueError, msg): + Author.objects.aggregate(**{crafted_alias: Avg("age")}) + +diff --git a/tests/annotations/tests.py b/tests/annotations/tests.py +index 1a07cd5..88c7cd9 100644 +--- a/tests/annotations/tests.py ++++ b/tests/annotations/tests.py +@@ -1116,16 +1116,16 @@ class NonAggregateAnnotationTestCase(TestCase): + def test_alias_sql_injection(self): + crafted_alias = """injected_name" from "annotations_book"; --""" + msg = ( +- "Column aliases cannot contain whitespace characters, quotation marks, " +- "semicolons, or SQL comments." ++ "Column aliases cannot contain whitespace characters, hashes, quotation " ++ "marks, semicolons, or SQL comments." + ) + with self.assertRaisesMessage(ValueError, msg): + Book.objects.annotate(**{crafted_alias: Value(1)}) + def test_alias_filtered_relation_sql_injection(self): + crafted_alias = """injected_name" from "annotations_book"; --""" + msg = ( +- "Column aliases cannot contain whitespace characters, quotation marks, " +- "semicolons, or SQL comments." ++ "Column aliases cannot contain whitespace characters, hashes, quotation " ++ "marks, semicolons, or SQL comments." + ) + with self.assertRaisesMessage(ValueError, msg): + Book.objects.alias(**{crafted_alias: FilteredRelation("authors")}) +@@ -1133,8 +1133,8 @@ class NonAggregateAnnotationTestCase(TestCase): + def test_alias_filtered_relation_sql_injection(self): + crafted_alias = """injected_name" from "annotations_book"; --""" + msg = ( +- "Column aliases cannot contain whitespace characters, quotation marks, " +- "semicolons, or SQL comments." ++ "Column aliases cannot contain whitespace characters, hashes, quotation " ++ "marks, semicolons, or SQL comments." + ) + with self.assertRaisesMessage(ValueError, msg): + Book.objects.annotate(**{crafted_alias: FilteredRelation("author")}) +@@ -1151,13 +1151,14 @@ class NonAggregateAnnotationTestCase(TestCase): + "ali/*as", + "alias*/", + "alias;", +- # [] are used by MSSQL. ++ # [] and # are used by MSSQL. + "alias[", + "alias]", ++ "ali#as", + ] + msg = ( +- "Column aliases cannot contain whitespace characters, quotation marks, " +- "semicolons, or SQL comments." ++ "Column aliases cannot contain whitespace characters, hashes, quotation " ++ "marks, semicolons, or SQL comments." + ) + for crafted_alias in tests: + with self.subTest(crafted_alias): +@@ -1436,8 +1437,8 @@ class AliasTests(TestCase): + def test_alias_sql_injection(self): + crafted_alias = """injected_name" from "annotations_book"; --""" + msg = ( +- "Column aliases cannot contain whitespace characters, quotation marks, " +- "semicolons, or SQL comments." ++ "Column aliases cannot contain whitespace characters, hashes, quotation " ++ "marks, semicolons, or SQL comments." + ) + with self.assertRaisesMessage(ValueError, msg): + Book.objects.alias(**{crafted_alias: Value(1)}) +diff --git a/tests/expressions/test_queryset_values.py b/tests/expressions/test_queryset_values.py +index 47bd135..13ce2bf 100644 +--- a/tests/expressions/test_queryset_values.py ++++ b/tests/expressions/test_queryset_values.py +@@ -37,8 +37,8 @@ class ValuesExpressionsTests(TestCase): + def test_values_expression_alias_sql_injection(self): + crafted_alias = """injected_name" from "expressions_company"; --""" + msg = ( +- "Column aliases cannot contain whitespace characters, quotation marks, " +- "semicolons, or SQL comments." ++ "Column aliases cannot contain whitespace characters, hashes, quotation " ++ "marks, semicolons, or SQL comments." + ) + with self.assertRaisesMessage(ValueError, msg): + Company.objects.values(**{crafted_alias: F("ceo__salary")}) +@@ -47,8 +47,8 @@ class ValuesExpressionsTests(TestCase): + def test_values_expression_alias_sql_injection_json_field(self): + crafted_alias = """injected_name" from "expressions_company"; --""" + msg = ( +- "Column aliases cannot contain whitespace characters, quotation marks, " +- "semicolons, or SQL comments." ++ "Column aliases cannot contain whitespace characters, hashes, quotation " ++ "marks, semicolons, or SQL comments." + ) + with self.assertRaisesMessage(ValueError, msg): + JSONFieldModel.objects.values(f"data__{crafted_alias}") +diff --git a/tests/queries/tests.py b/tests/queries/tests.py +index a6a2b25..d52f3be 100644 +--- a/tests/queries/tests.py ++++ b/tests/queries/tests.py +@@ -1943,8 +1943,8 @@ class Queries5Tests(TestCase): + def test_extra_select_alias_sql_injection(self): + crafted_alias = """injected_name" from "queries_note"; --""" + msg = ( +- "Column aliases cannot contain whitespace characters, quotation marks, " +- "semicolons, or SQL comments." ++ "Column aliases cannot contain whitespace characters, hashes, quotation " ++ "marks, semicolons, or SQL comments." + ) + with self.assertRaisesMessage(ValueError, msg): + Note.objects.extra(select={crafted_alias: "1"}) +-- +2.47.3 + diff --git a/1002-fix-CVE-2025-64458.patch b/1002-fix-CVE-2025-64458.patch new file mode 100644 index 0000000..6eabfad --- /dev/null +++ b/1002-fix-CVE-2025-64458.patch @@ -0,0 +1,91 @@ +From d7341854bc6062f80c497b1d87d4b082deb74aca Mon Sep 17 00:00:00 2001 +From: WB02254423 +Date: Mon, 17 Nov 2025 03:44:10 -0500 +Subject: [PATCH] [4.2.x] Fixed CVE-2025-64458 -- Mitigated potential DoS in + +--- + django/http/response.py | 9 +++++++-- + django/utils/html.py | 3 +-- + django/utils/http.py | 1 + + tests/httpwrappers/tests.py | 2 ++ + 4 files changed, 11 insertions(+), 4 deletions(-) + +diff --git a/django/http/response.py b/django/http/response.py +index ea31141..763fd2a 100644 +--- a/django/http/response.py ++++ b/django/http/response.py +@@ -21,7 +21,7 @@ from django.http.cookie import SimpleCookie + from django.utils import timezone + from django.utils.datastructures import CaseInsensitiveMapping + from django.utils.encoding import iri_to_uri +-from django.utils.http import content_disposition_header, http_date ++from django.utils.http import MAX_URL_LENGTH, content_disposition_header, http_date + from django.utils.regex_helper import _lazy_re_compile + + _charset_from_content_type_re = _lazy_re_compile( +@@ -614,7 +614,12 @@ class HttpResponseRedirectBase(HttpResponse): + def __init__(self, redirect_to, *args, **kwargs): + super().__init__(*args, **kwargs) + self["Location"] = iri_to_uri(redirect_to) +- parsed = urlparse(str(redirect_to)) ++ redirect_to_str = str(redirect_to) ++ if len(redirect_to_str) > MAX_URL_LENGTH: ++ raise DisallowedRedirect( ++ f"Unsafe redirect exceeding {MAX_URL_LENGTH} characters" ++ ) ++ parsed = urlparse(redirect_to_str) + if parsed.scheme and parsed.scheme not in self.allowed_schemes: + raise DisallowedRedirect( + "Unsafe redirect to URL with protocol '%s'" % parsed.scheme +diff --git a/django/utils/html.py b/django/utils/html.py +index a3a7238..3f07ae1 100644 +--- a/django/utils/html.py ++++ b/django/utils/html.py +@@ -9,12 +9,11 @@ from urllib.parse import parse_qsl, quote, unquote, urlencode, urlsplit, urlunsp + 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 ++from django.utils.http import MAX_URL_LENGTH, RFC3986_GENDELIMS, RFC3986_SUBDELIMS + from django.utils.regex_helper import _lazy_re_compile + from django.utils.safestring import SafeData, SafeString, mark_safe + from django.utils.text import normalize_newlines + +-MAX_URL_LENGTH = 2048 + MAX_STRIP_TAGS_DEPTH = 50 + + +diff --git a/django/utils/http.py b/django/utils/http.py +index 3e7acb5..5c1c6b8 100644 +--- a/django/utils/http.py ++++ b/django/utils/http.py +@@ -46,6 +46,7 @@ ASCTIME_DATE = _lazy_re_compile(r"^\w{3} %s %s %s %s$" % (__M, __D2, __T, __Y)) + + RFC3986_GENDELIMS = ":/?#[]@" + RFC3986_SUBDELIMS = "!$&'()*+,;=" ++MAX_URL_LENGTH = 2048 + + # TODO: Remove when dropping support for PY38. + # Unsafe bytes to be removed per WHATWG spec. +diff --git a/tests/httpwrappers/tests.py b/tests/httpwrappers/tests.py +index fa2c8fd..b20d9a1 100644 +--- a/tests/httpwrappers/tests.py ++++ b/tests/httpwrappers/tests.py +@@ -24,6 +24,7 @@ from django.http import ( + ) + from django.test import SimpleTestCase + from django.utils.functional import lazystr ++from django.utils.http import MAX_URL_LENGTH + + + class QueryDictTests(SimpleTestCase): +@@ -490,6 +491,7 @@ class HttpResponseTests(SimpleTestCase): + 'data:text/html,', + "mailto:test@example.com", + "file:///etc/passwd", ++ "é" * (MAX_URL_LENGTH + 1), + ] + for url in bad_urls: + with self.assertRaises(DisallowedRedirect): +-- +2.47.3 diff --git a/python-django.spec b/python-django.spec index 999e532..55f4d5a 100644 --- a/python-django.spec +++ b/python-django.spec @@ -1,4 +1,4 @@ -%define anolis_release 3 +%define anolis_release 4 %global pkgname Django Name: python-django @@ -17,6 +17,14 @@ BuildArch: noarch Patch0001: 0001-fix-CVE-2024-53907.patch # https://github.com/django/django/commit/31334e6965ad136a5e369993b01721499c5d1a92 Patch0002: 1000-add-patch-to-fix-CVE-2025-57833.patch +# https://github.com/django/django/commit/38d9ef8c7b5cb6ef51b933e51a20e0e0063f33d5 +Patch0003: 1001-fix-CVE-2025-59681.patch +# https://github.com/django/django/commit/9504bbaa392c9fe37eee9291f5b4c29eb6037619 +Patch0004: 0002-fix-CVE-2025-59682.patch +# https://github.com/django/django/commit/770eea38d7a0e9ba9455140b5a9a9e33618226a7 +Patch0005: 1002-fix-CVE-2025-64458.patch +# https://github.com/django/django/commit/59ae82e67053d281ff4562a24bbba21299f0a7d4 +Patch0006: 0003-fix-CVE-2025-64459.patch %global _description %{expand: Django is a high-level Python Web framework that encourages rapid @@ -181,6 +189,9 @@ cd tests %changelog +* Mon Nov 17 2025 zhoujiajia111 - 4.2.16-4 +- Add patches to fix CVE-2025-59681,CVE-2025-59682,CVE-2025-64458,CVE-2025-64459 + * Wed Nov 12 2025 lzq11122 - 4.2.16-3 - Add patch to fix CVE-2025-57833 -- Gitee