File CVE-2026-1287.patch of Package python-Django.42494

From bf215d6ae89c888a111c876a22291f2fdabd691c Mon Sep 17 00:00:00 2001
From: Jake Howard <git@theorangeone.net>
Date: Wed, 21 Jan 2026 11:14:48 +0000
Subject: [PATCH 5/7] [4.2.x] Fixed CVE-2026-1287 -- Protected against SQL
 injection in column aliases via control characters.

Control characters in FilteredRelation column aliases could be used for
SQL injection attacks. This affected QuerySet.annotate(), aggregate(),
extra(), values(), values_list(), and alias() when using dictionary
expansion with **kwargs.

Thanks Solomon Kebede for the report, and Simon Charette, Jacob Walls,
and Natalia Bidart for reviews.

Backport of 5020880b2e64bc00360ee04534a75d2575e501e0 from main.
---
 django/db/models/sql/query.py             | 10 +--
 docs/releases/4.2.28.txt                  | 15 +++++
 tests/aggregation/tests.py                | 18 ++++--
 tests/annotations/tests.py                | 74 +++++++++++++++--------
 tests/expressions/test_queryset_values.py | 36 +++++++----
 tests/queries/tests.py                    | 18 ++++--
 6 files changed, 117 insertions(+), 54 deletions(-)

diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 3b8071eab4..a0d2d028cd 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -46,9 +46,11 @@ from django.utils.tree import Node
 
 __all__ = ["Query", "RawQuery"]
 
-# Quotation marks ('"`[]), whitespace characters, semicolons, hashes, or inline
-# SQL comments are forbidden in column aliases.
-FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile(r"['`\"\]\[;\s]|#|--|/\*|\*/")
+# Quotation marks ('"`[]), whitespace characters, control characters,
+# semicolons, hashes, or inline SQL comments are forbidden in column aliases.
+FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile(
+    r"['`\"\]\[;\s\x00-\x1F\x7F-\x9F]|#|--|/\*|\*/"
+)
 
 # Inspired from
 # https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
@@ -1124,7 +1126,7 @@ class Query(BaseExpression):
         if FORBIDDEN_ALIAS_PATTERN.search(alias):
             raise ValueError(
                 "Column aliases cannot contain whitespace characters, hashes, "
-                "quotation marks, semicolons, or SQL comments."
+                "control characters, 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 277c0507f7..abd43fc210 100644
--- a/tests/aggregation/tests.py
+++ b/tests/aggregation/tests.py
@@ -2,6 +2,7 @@ import datetime
 import math
 import re
 from decimal import Decimal
+from itertools import chain
 
 from django.core.exceptions import FieldError
 from django.db import connection
@@ -2088,13 +2089,18 @@ class AggregateTestCase(TestCase):
         self.assertEqual(len(qs), 6)
 
     def test_alias_sql_injection(self):
-        crafted_alias = """injected_name" from "aggregation_author"; --"""
         msg = (
-            "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")})
+            "Column aliases cannot contain whitespace characters, hashes, "
+            "control characters, quotation marks, semicolons, or SQL comments."
+        )
+        for crafted_alias in [
+            """injected_name" from "aggregation_author"; --""",
+            # Control characters.
+            *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))),
+        ]:
+            with self.subTest(crafted_alias):
+                with self.assertRaisesMessage(ValueError, msg):
+                    Author.objects.aggregate(**{crafted_alias: Avg("age")})
 
     def test_exists_extra_where_with_aggregate(self):
         qs = Book.objects.annotate(
diff --git a/tests/annotations/tests.py b/tests/annotations/tests.py
index d876e3a6f5..9356917986 100644
--- a/tests/annotations/tests.py
+++ b/tests/annotations/tests.py
@@ -1,5 +1,6 @@
 import datetime
 from decimal import Decimal
+from itertools import chain
 
 from django.core.exceptions import FieldDoesNotExist, FieldError
 from django.db import connection
@@ -1115,22 +1116,32 @@ class NonAggregateAnnotationTestCase(TestCase):
         )
 
     def test_alias_sql_injection(self):
-        crafted_alias = """injected_name" from "annotations_book"; --"""
         msg = (
-            "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)})
+            "Column aliases cannot contain whitespace characters, hashes, "
+            "control characters, quotation marks, semicolons, or SQL comments."
+        )
+        for crafted_alias in [
+            """injected_name" from "annotations_book"; --""",
+            # Control characters.
+            *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))),
+        ]:
+            with self.subTest(crafted_alias):
+                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, hashes, quotation "
-            "marks, semicolons, or SQL comments."
-        )
-        with self.assertRaisesMessage(ValueError, msg):
-            Book.objects.annotate(**{crafted_alias: FilteredRelation("author")})
+            "Column aliases cannot contain whitespace characters, hashes, "
+            "control characters, quotation marks, semicolons, or SQL comments."
+        )
+        for crafted_alias in [
+            """injected_name" from "annotations_book"; --""",
+            # Control characters.
+            *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))),
+        ]:
+            with self.subTest(crafted_alias):
+                with self.assertRaisesMessage(ValueError, msg):
+                    Book.objects.annotate(**{crafted_alias: FilteredRelation("author")})
 
     def test_alias_forbidden_chars(self):
         tests = [
@@ -1148,10 +1159,11 @@ class NonAggregateAnnotationTestCase(TestCase):
             "alias[",
             "alias]",
             "ali#as",
+            "ali\0as",
         ]
         msg = (
-            "Column aliases cannot contain whitespace characters, hashes, quotation "
-            "marks, semicolons, or SQL comments."
+            "Column aliases cannot contain whitespace characters, hashes, "
+            "control characters, quotation marks, semicolons, or SQL comments."
         )
         for crafted_alias in tests:
             with self.subTest(crafted_alias):
@@ -1428,22 +1440,32 @@ class AliasTests(TestCase):
                     getattr(qs, operation)("rating_alias")
 
     def test_alias_sql_injection(self):
-        crafted_alias = """injected_name" from "annotations_book"; --"""
         msg = (
-            "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)})
+            "Column aliases cannot contain whitespace characters, hashes, "
+            "control characters, quotation marks, semicolons, or SQL comments."
+        )
+        for crafted_alias in [
+            """injected_name" from "annotations_book"; --""",
+            # Control characters.
+            *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))),
+        ]:
+            with self.subTest(crafted_alias):
+                with self.assertRaisesMessage(ValueError, msg):
+                    Book.objects.alias(**{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, hashes, quotation "
-            "marks, semicolons, or SQL comments."
-        )
-        with self.assertRaisesMessage(ValueError, msg):
-            Book.objects.alias(**{crafted_alias: FilteredRelation("authors")})
+            "Column aliases cannot contain whitespace characters, hashes, "
+            "control characters, quotation marks, semicolons, or SQL comments."
+        )
+        for crafted_alias in [
+            """injected_name" from "annotations_book"; --""",
+            # Control characters.
+            *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))),
+        ]:
+            with self.subTest(crafted_alias):
+                with self.assertRaisesMessage(ValueError, msg):
+                    Book.objects.alias(**{crafted_alias: FilteredRelation("authors")})
 
     def test_alias_filtered_relation_sql_injection_dollar_sign(self):
         qs = Book.objects.alias(
diff --git a/tests/expressions/test_queryset_values.py b/tests/expressions/test_queryset_values.py
index 080ee06183..afd8a51159 100644
--- a/tests/expressions/test_queryset_values.py
+++ b/tests/expressions/test_queryset_values.py
@@ -1,3 +1,5 @@
+from itertools import chain
+
 from django.db.models import F, Sum
 from django.test import TestCase, skipUnlessDBFeature
 
@@ -35,26 +37,36 @@ 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, hashes, quotation "
-            "marks, semicolons, or SQL comments."
+            "Column aliases cannot contain whitespace characters, hashes, "
+            "control characters, quotation marks, semicolons, or SQL comments."
         )
-        with self.assertRaisesMessage(ValueError, msg):
-            Company.objects.values(**{crafted_alias: F("ceo__salary")})
+        for crafted_alias in [
+            """injected_name" from "expressions_company"; --""",
+            # Control characters.
+            *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))),
+        ]:
+            with self.subTest(crafted_alias):
+                with self.assertRaisesMessage(ValueError, msg):
+                    Company.objects.values(**{crafted_alias: F("ceo__salary")})
 
     @skipUnlessDBFeature("supports_json_field")
     def test_values_expression_alias_sql_injection_json_field(self):
-        crafted_alias = """injected_name" from "expressions_company"; --"""
         msg = (
-            "Column aliases cannot contain whitespace characters, hashes, quotation "
-            "marks, semicolons, or SQL comments."
+            "Column aliases cannot contain whitespace characters, hashes, "
+            "control characters, quotation marks, semicolons, or SQL comments."
         )
-        with self.assertRaisesMessage(ValueError, msg):
-            JSONFieldModel.objects.values(f"data__{crafted_alias}")
+        for crafted_alias in [
+            """injected_name" from "expressions_company"; --""",
+            # Control characters.
+            *(chr(c) for c in chain(range(32), range(0x7F, 0xA0))),
+        ]:
+            with self.subTest(crafted_alias):
+                with self.assertRaisesMessage(ValueError, msg):
+                    JSONFieldModel.objects.values(f"data__{crafted_alias}")
 
-        with self.assertRaisesMessage(ValueError, msg):
-            JSONFieldModel.objects.values_list(f"data__{crafted_alias}")
+                with self.assertRaisesMessage(ValueError, msg):
+                    JSONFieldModel.objects.values_list(f"data__{crafted_alias}")
 
     def test_values_expression_group_by(self):
         # values() applies annotate() first, so values selected are grouped by
diff --git a/tests/queries/tests.py b/tests/queries/tests.py
index 2290ea29bc..d0af410eb3 100644
--- a/tests/queries/tests.py
+++ b/tests/queries/tests.py
@@ -2,6 +2,7 @@ import datetime
 import pickle
 import sys
 import unittest
+from itertools import chain
 from operator import attrgetter
 from threading import Lock
 
@@ -1941,13 +1942,18 @@ 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, hashes, quotation "
-            "marks, semicolons, or SQL comments."
-        )
-        with self.assertRaisesMessage(ValueError, msg):
-            Note.objects.extra(select={crafted_alias: "1"})
+            "Column aliases cannot contain whitespace characters, hashes, "
+            "control characters, quotation marks, semicolons, or SQL comments."
+        )
+        for crafted_alias in [
+            """injected_name" from "queries_note"; --""",
+            # Control characters.
+            *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))),
+        ]:
+            with self.subTest(crafted_alias):
+                with self.assertRaisesMessage(ValueError, msg):
+                    Note.objects.extra(select={crafted_alias: "1"})
 
     def test_queryset_reuse(self):
         # Using querysets doesn't mutate aliases.
-- 
2.50.1 (Apple Git-155)

openSUSE Build Service is sponsored by