File CVE-2026-1287.patch of Package python-Django.openSUSE_Backports_SLE-15-SP6_Update
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(-)
Index: Django-2.2.28/django/db/models/sql/query.py
===================================================================
--- Django-2.2.28.orig/django/db/models/sql/query.py
+++ Django-2.2.28/django/db/models/sql/query.py
@@ -41,9 +41,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 = re.compile(r"['`\"\]\[;\s]|#|--|/\*|\*/")
+# Quotation marks ('"`[]), whitespace characters, control characters,
+# semicolons, hashes, or inline SQL comments are forbidden in column aliases.
+FORBIDDEN_ALIAS_PATTERN = re.compile(
+ r"['`\"\]\[;\s\x00-\x1F\x7F-\x9F]|#|--|/\*|\*/"
+)
# Inspired from
# https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
@@ -1013,7 +1015,7 @@ class Query:
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, is_summary=False):
Index: Django-2.2.28/tests/aggregation/tests.py
===================================================================
--- Django-2.2.28.orig/tests/aggregation/tests.py
+++ Django-2.2.28/tests/aggregation/tests.py
@@ -1,6 +1,7 @@
import datetime
import re
from decimal import Decimal
+from itertools import chain
from django.core.exceptions import FieldError
from django.db import connection
@@ -1116,10 +1117,15 @@ class AggregateTestCase(TestCase):
Book.objects.aggregate(FloatField(), Avg('price'), is_book=True)
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."
+ "Column aliases cannot contain whitespace characters, hashes, "
+ "control characters, quotation marks, semicolons, or SQL comments."
)
- with self.assertRaisesMessage(ValueError, msg):
- Author.objects.aggregate(**{crafted_alias: Avg("age")})
+ 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")})
Index: Django-2.2.28/tests/annotations/tests.py
===================================================================
--- Django-2.2.28.orig/tests/annotations/tests.py
+++ Django-2.2.28/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
@@ -601,22 +602,32 @@ class NonAggregateAnnotationTestCase(Tes
self.assertCountEqual(publisher_books_qs, [{'name': 'Sams'}, {'name': 'Morgan Kaufmann'}])
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."
+ "Column aliases cannot contain whitespace characters, hashes, "
+ "control characters, quotation marks, semicolons, or SQL comments."
)
- with self.assertRaisesMessage(ValueError, msg):
- Book.objects.annotate(**{crafted_alias: Value(1)})
+ 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."
+ "Column aliases cannot contain whitespace characters, hashes, "
+ "control characters, quotation marks, semicolons, or SQL comments."
)
- with self.assertRaisesMessage(ValueError, msg):
- Book.objects.annotate(**{crafted_alias: FilteredRelation("author")})
+ 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 = [
@@ -634,10 +645,11 @@ class NonAggregateAnnotationTestCase(Tes
"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):
Index: Django-2.2.28/tests/expressions/test_queryset_values.py
===================================================================
--- Django-2.2.28.orig/tests/expressions/test_queryset_values.py
+++ Django-2.2.28/tests/expressions/test_queryset_values.py
@@ -1,3 +1,4 @@
+from itertools import chain
from django.db.models.aggregates import Sum
from django.db.models.expressions import F
from django.test import TestCase, skipUnlessDBFeature
@@ -28,26 +29,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
Index: Django-2.2.28/tests/queries/tests.py
===================================================================
--- Django-2.2.28.orig/tests/queries/tests.py
+++ Django-2.2.28/tests/queries/tests.py
@@ -1,6 +1,7 @@
import datetime
import pickle
import unittest
+from itertools import chain
from collections import OrderedDict
from operator import attrgetter
@@ -1738,13 +1739,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."
+ "Column aliases cannot contain whitespace characters, hashes, "
+ "control characters, quotation marks, semicolons, or SQL comments."
)
- with self.assertRaisesMessage(ValueError, msg):
- Note.objects.extra(select={crafted_alias: "1"})
+ 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"})
class SelectRelatedTests(TestCase):