diff --git a/CVE-2025-59681.patch b/CVE-2025-59681.patch new file mode 100644 index 0000000000000000000000000000000000000000..45b90ed13bfb2a06f25bb2f9e6bd36dba8b493e9 --- /dev/null +++ b/CVE-2025-59681.patch @@ -0,0 +1,143 @@ +From: Mariusz Felisiak +Date: Wed, 10 Sep 2025 09:53:52 +0200 +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. + +Thanks sw0rd1ight for the report. + +Follow up to 93cae5cb2f9a4ef1514cf1a41f714fef08005200. + +Backport of 41b43c74bda19753c757036673ea9db74acf494a from main. +--- + django/db/models/sql/query.py | 9 +++++---- + tests/aggregation/tests.py | 4 ++-- + tests/annotations/tests.py | 15 ++++++++------- + tests/expressions/test_queryset_values.py | 4 ++-- + tests/queries/tests.py | 4 ++-- + 5 files changed, 19 insertions(+), 17 deletions(-) + +diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py +index 48852532f13f..5bf9a5374073 100644 +--- a/django/db/models/sql/query.py ++++ b/django/db/models/sql/query.py +@@ -41,9 +41,10 @@ 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 = re.compile(r"['`\"\]\[;\s]|--|/\*|\*/") ++FORBIDDEN_ALIAS_PATTERN = re.compile(r"['`\"\]\[;\s]|#|--|/\*|\*/") ++ + + # Inspired from + # https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS +@@ -1012,8 +1013,8 @@ class Query: + 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, is_summary=False): +diff --git a/tests/aggregation/tests.py b/tests/aggregation/tests.py +index 501a18700baf..4acddf4b1d8c 100644 +--- a/tests/aggregation/tests.py ++++ b/tests/aggregation/tests.py +@@ -1118,8 +1118,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 14d8d2ca9384..055aaa334bd9 100644 +--- a/tests/annotations/tests.py ++++ b/tests/annotations/tests.py +@@ -602,8 +602,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)}) +@@ -611,7 +611,7 @@ 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, " ++ "Column aliases cannot contain whitespace characters, hashes, quotation marks, " + "semicolons, or SQL comments." + ) + with self.assertRaisesMessage(ValueError, msg): +@@ -620,7 +620,7 @@ 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, " ++ "Column aliases cannot contain whitespace characters, hashes, quotation marks, " + "semicolons, or SQL comments." + ) + with self.assertRaisesMessage(ValueError, msg): +@@ -638,13 +638,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): +diff --git a/tests/expressions/test_queryset_values.py b/tests/expressions/test_queryset_values.py +index 0804531869d9..2d9a3bf4f290 100644 +--- a/tests/expressions/test_queryset_values.py ++++ b/tests/expressions/test_queryset_values.py +@@ -30,8 +30,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")}) +diff --git a/tests/queries/tests.py b/tests/queries/tests.py +index 99ab57f4fc2e..d9d418033dd4 100644 +--- a/tests/queries/tests.py ++++ b/tests/queries/tests.py +@@ -1740,8 +1740,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"}) diff --git a/CVE-2025-59682.patch b/CVE-2025-59682.patch new file mode 100644 index 0000000000000000000000000000000000000000..0e3ae9b577ea8ea0b5b53b97577ab1845810ea41 --- /dev/null +++ b/CVE-2025-59682.patch @@ -0,0 +1,66 @@ +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. +--- + 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 f2f153a1fc3d..c6da1bd55e35 100644 +--- a/django/utils/archive.py ++++ b/django/utils/archive.py +@@ -138,7 +138,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 ed7908df8295..cffac5fcb15d 100644 +--- a/tests/utils_tests/test_archive.py ++++ b/tests/utils_tests/test_archive.py +@@ -4,6 +4,7 @@ import stat + import sys + import tempfile + import unittest ++import zipfile + + from django.core.exceptions import SuspiciousOperation + from django.test import SimpleTestCase +@@ -108,3 +109,21 @@ class TestArchiveInvalid(SimpleTestCase): + with self.subTest(entry), tempfile.TemporaryDirectory() as tmpdir: + with self.assertRaisesMessage(SuspiciousOperation, msg % invalid_path): + 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" ++ ): ++ extract(tar_path, base) diff --git a/python-django.spec b/python-django.spec index 2c886aea8733b750063e0fea3b0214fc4822780b..953bd478cb863e720db56ae3fd23c13afd665e8d 100644 --- a/python-django.spec +++ b/python-django.spec @@ -1,7 +1,7 @@ %global _empty_manifest_terminate_build 0 Name: python-django Version: 2.2.27 -Release: 18 +Release: 19 Summary: A high-level Python Web framework that encourages rapid development and clean, pragmatic design. License: Apache-2.0 and Python-2.0 and OFL-1.1 and MIT URL: https://www.djangoproject.com/ @@ -42,6 +42,8 @@ Patch23: CVE-2025-26699.patch Patch24: CVE-2025-32873.patch Patch25: CVE-2025-48432.patch Patch26: CVE-2025-57833.patch +Patch27: CVE-2025-59681.patch +Patch28: CVE-2025-59682.patch BuildArch: noarch %description @@ -108,6 +110,9 @@ mv %{buildroot}/doclist.lst . %{_docdir}/* %changelog +* Thu Oct 09 2025 yaoxin <1024769339@qq.com> - 2.2.27-19 +- Fix CVE-2025-59681 and CVE-2025-59682 + * Thu Sep 18 2025 yaoxin <1024769339@qq.com> - 2.2.27-18 - Fix CVE-2025-57833