summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorJake Howard <git@theorangeone.net>2026-01-21 11:14:48 +0000
committerJacob Walls <jacobtylerwalls@gmail.com>2026-02-03 08:03:39 -0500
commit0c0f5c2178c01ada5410cd53b4b207bf7858b952 (patch)
tree835593f167090d10c90e03c0c576246c40967135 /tests
parent4b86ba51e486530db982341a23e53c7a1e1e6e71 (diff)
[6.0.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 e891a84c7ef9962bfcc3b4685690219542f86a22 from main.
Diffstat (limited to 'tests')
-rw-r--r--tests/aggregation/tests.py16
-rw-r--r--tests/annotations/tests.py66
-rw-r--r--tests/expressions/test_queryset_values.py36
-rw-r--r--tests/queries/tests.py16
4 files changed, 90 insertions, 44 deletions
diff --git a/tests/aggregation/tests.py b/tests/aggregation/tests.py
index f2ec4bd343..bf6bf27031 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 NotSupportedError, connection
@@ -2242,13 +2243,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."
+ "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")})
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 cde6d4cf2b..139abadf79 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 unittest import skipUnless
from django.core.exceptions import FieldDoesNotExist, FieldError
@@ -1158,32 +1159,42 @@ class NonAggregateAnnotationTestCase(TestCase):
)
def test_alias_sql_injection(self):
- crafted_alias = """injected_name" from "annotations_book"; --"""
# RemovedInDjango70Warning: When the deprecation ends, replace with:
# msg = (
# "Column aliases cannot contain whitespace characters, hashes, "
# "quotation marks, semicolons, percent signs, or SQL comments."
# )
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"; --"""
# RemovedInDjango70Warning: When the deprecation ends, replace with:
# msg = (
# "Column aliases cannot contain whitespace characters, hashes, "
# "quotation marks, semicolons, percent signs, or SQL comments."
# )
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 = [
@@ -1203,6 +1214,7 @@ class NonAggregateAnnotationTestCase(TestCase):
"alias[",
"alias]",
"ali#as",
+ "ali\0as",
]
# RemovedInDjango70Warning: When the deprecation ends, replace with:
# msg = (
@@ -1210,8 +1222,8 @@ class NonAggregateAnnotationTestCase(TestCase):
# "quotation marks, semicolons, percent signs, or SQL comments."
# )
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):
@@ -1514,32 +1526,42 @@ class AliasTests(TestCase):
self.assertEqual(qs.get(pk=self.b1.pk), (self.b1.pk,))
def test_alias_sql_injection(self):
- crafted_alias = """injected_name" from "annotations_book"; --"""
# RemovedInDjango70Warning: When the deprecation ends, replace with:
# msg = (
# "Column aliases cannot contain whitespace characters, hashes, "
# "quotation marks, semicolons, percent signs, or SQL comments."
# )
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.alias(**{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.alias(**{crafted_alias: Value(1)})
def test_alias_filtered_relation_sql_injection(self):
- crafted_alias = """injected_name" from "annotations_book"; --"""
# RemovedInDjango70Warning: When the deprecation ends, replace with:
# msg = (
# "Column aliases cannot contain whitespace characters, hashes, "
# "quotation marks, semicolons, percent signs, or SQL comments."
# )
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.alias(**{crafted_alias: FilteredRelation("authors")})
+ 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 24f22e8187..6264d02594 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
from django.utils.deprecation import RemovedInDjango70Warning
@@ -42,26 +44,36 @@ class ValuesExpressionsTests(TestCase):
self.assertEqual(len(cm.warnings), 1)
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 51d1915c97..74929e4944 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 django.core.exceptions import EmptyResultSet, FieldError, FullResultSet
@@ -1965,13 +1966,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"})
def test_queryset_reuse(self):
# Using querysets doesn't mutate aliases.