From 3626cf164dd785625a5f8402c621019707094782 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 10 Sep 2025 09:53:52 +0200 Subject: [PATCH 2/2] [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. CVE: CVE-2025-59681 Upstream-Status: Backport [https://github.com/django/django/commit/38d9ef8c7b5cb6ef51b933e51a20e0e0063f33d5] Signed-off-by: Haixiao Yan --- django/db/models/sql/query.py | 8 ++++---- tests/aggregation/tests.py | 4 ++-- tests/annotations/tests.py | 23 ++++++++++++----------- tests/expressions/test_queryset_values.py | 8 ++++---- tests/queries/tests.py | 4 ++-- 5 files changed, 24 insertions(+), 23 deletions(-) diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 6a86a184d8b4..aa348ddf5ff8 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -47,9 +47,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 @@ -1188,8 +1188,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 48266d97746b..277c0507f7d9 100644 --- a/tests/aggregation/tests.py +++ b/tests/aggregation/tests.py @@ -2090,8 +2090,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 01fa6958db7b..ac40408977ae 100644 --- a/tests/annotations/tests.py +++ b/tests/annotations/tests.py @@ -1127,8 +1127,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)}) @@ -1136,8 +1136,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")}) @@ -1154,13 +1154,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): @@ -1439,8 +1440,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)}) @@ -1448,8 +1449,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 47bd1358de54..080ee06183dc 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 5df231949194..91dce6170361 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -1942,8 +1942,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.34.1