From a2220d5d79ae179e800d904c89434e76b27ec4418eb1be0a1d879cdd675b2e19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mark=C3=A9ta=20Cal=C3=A1bkov=C3=A1?= Date: Thu, 2 Oct 2025 12:08:09 +0200 Subject: [PATCH] CVE-2025-59681, CVE-2025-59682 --- CVE-2025-59681.patch | 173 ++++++++++++++++++++++++++++++++++++++++++ CVE-2025-59682.patch | 74 ++++++++++++++++++ python-Django.changes | 7 ++ python-Django.spec | 4 + 4 files changed, 258 insertions(+) create mode 100644 CVE-2025-59681.patch create mode 100644 CVE-2025-59682.patch diff --git a/CVE-2025-59681.patch b/CVE-2025-59681.patch new file mode 100644 index 0000000..40840fc --- /dev/null +++ b/CVE-2025-59681.patch @@ -0,0 +1,173 @@ +From b4d3036c04ae71d611edecf5cfc7d4e5b5927f81 Mon Sep 17 00:00:00 2001 +From: Mariusz Felisiak +Date: Wed, 10 Sep 2025 09:53:52 +0200 +Subject: [PATCH 1/2] [5.2.x] Fixed CVE-2025-59681 -- Protected + QuerySet.annotate(), alias(), aggregate(), and extra() against SQL injection + in column aliases on MySQL/MariaDB. + +Thanks sw0rd1ight for the report. + +Follow up to 93cae5cb2f9a4ef1514cf1a41f714fef08005200. +--- + django/db/models/sql/query.py | 8 ++++---- + docs/releases/4.2.25.txt | 9 ++++++++- + docs/releases/5.1.13.txt | 9 ++++++++- + docs/releases/5.2.7.txt | 9 +++++++++ + tests/aggregation/tests.py | 4 ++-- + tests/annotations/tests.py | 23 ++++++++++++----------- + tests/expressions/test_queryset_values.py | 8 ++++---- + tests/queries/tests.py | 4 ++-- + 8 files changed, 49 insertions(+), 25 deletions(-) + +diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py +index 5247616086..3a1cd73951 100644 +--- a/django/db/models/sql/query.py ++++ b/django/db/models/sql/query.py +@@ -48,9 +48,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 +@@ -1208,8 +1208,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 bf44c4d25f..2e41f19947 100644 +--- a/tests/aggregation/tests.py ++++ b/tests/aggregation/tests.py +@@ -2136,8 +2136,8 @@ 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 060d6324c7..7a12121224 100644 +--- a/tests/annotations/tests.py ++++ b/tests/annotations/tests.py +@@ -1159,8 +1159,8 @@ 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)}) +@@ -1168,8 +1168,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")}) +@@ -1186,13 +1186,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): +@@ -1492,8 +1493,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)}) +@@ -1501,8 +1502,8 @@ class AliasTests(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.alias(**{crafted_alias: FilteredRelation("authors")}) +diff --git a/tests/expressions/test_queryset_values.py b/tests/expressions/test_queryset_values.py +index 47bd1358de..080ee06183 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 38b0a5ddfa..ffaabf48a0 100644 +--- a/tests/queries/tests.py ++++ b/tests/queries/tests.py +@@ -1961,8 +1961,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.39.5 (Apple Git-154) + diff --git a/CVE-2025-59682.patch b/CVE-2025-59682.patch new file mode 100644 index 0000000..41f37ad --- /dev/null +++ b/CVE-2025-59682.patch @@ -0,0 +1,74 @@ +From 3a7091babcb19f213a25dd5bf8ad90fd63c3cba0 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 2/2] [5.2.x] Fixed CVE-2025-59682 -- Fixed potential partial + directory-traversal via archive.extract(). + +Thanks stackered for the report. + +Follow up to 05413afa8c18cdb978fcdf470e09f7a12b234a23. +--- + django/utils/archive.py | 6 +++++- + docs/releases/4.2.25.txt | 8 ++++++++ + docs/releases/5.1.13.txt | 8 ++++++++ + docs/releases/5.2.7.txt | 8 ++++++++ + tests/utils_tests/test_archive.py | 22 ++++++++++++++++++++++ + 5 files changed, 51 insertions(+), 1 deletion(-) + +diff --git a/django/utils/archive.py b/django/utils/archive.py +index 56f34c0038..c05fbcdc97 100644 +--- a/django/utils/archive.py ++++ b/django/utils/archive.py +@@ -145,7 +145,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 89a45bc072..24e60039a5 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 +@@ -94,3 +95,24 @@ 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 ++ ++ try: ++ tarfile_handle.close() ++ 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) ++ finally: ++ os.remove(tar_path) +-- +2.39.5 (Apple Git-154) + diff --git a/python-Django.changes b/python-Django.changes index 7e5e0c3..52f6bb5 100644 --- a/python-Django.changes +++ b/python-Django.changes @@ -1,3 +1,10 @@ +------------------------------------------------------------------- +Thu Oct 2 10:01:50 UTC 2025 - Markéta Machová + +- Add security patches: + * CVE-2025-59681.patch (bsc#1250485) + * CVE-2025-59682.patch (bsc#1250487) + ------------------------------------------------------------------- Thu Sep 4 13:14:40 UTC 2025 - Markéta Machová diff --git a/python-Django.spec b/python-Django.spec index 791aaa2..f3ab34e 100644 --- a/python-Django.spec +++ b/python-Django.spec @@ -34,6 +34,10 @@ Source99: python-Django-rpmlintrc Patch0: test_strip_tags.patch # PATCH-FIX-UPSTREAM CVE-2025-57833.patch bsc#1248810 Patch1: CVE-2025-57833.patch +# PATCH-FIX-UPSTREAM CVE-2025-59681.patch bsc#1250485 +Patch2: CVE-2025-59681.patch +# PATCH-FIX-UPSTREAM CVE-2025-59682.patch bsc#1250487 +Patch3: CVE-2025-59682.patch BuildRequires: %{python_module Jinja2 >= 2.9.2} BuildRequires: %{python_module Pillow >= 6.2.0} BuildRequires: %{python_module PyYAML} -- 2.51.1