VYPR
High severityOSV Advisory· Published Feb 3, 2026· Updated Feb 3, 2026

Potential SQL injection in column aliases via control characters

CVE-2026-1287

Description

An issue was discovered in 6.0 before 6.0.2, 5.2 before 5.2.11, and 4.2 before 4.2.28. FilteredRelation is subject to SQL injection in column aliases via control characters, using a suitably crafted dictionary, with dictionary expansion, as the **kwargs passed to QuerySet methods annotate(), aggregate(), extra(), values(), values_list(), and alias(). Earlier, unsupported Django series (such as 5.0.x, 4.1.x, and 3.2.x) were not evaluated and may also be affected. Django would like to thank Solomon Kebede for reporting this issue.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
DjangoPyPI
>= 6.0a1, < 6.0.26.0.2
DjangoPyPI
>= 5.2a1, < 5.2.115.2.11
DjangoPyPI
>= 4.2a1, < 4.2.284.2.28

Affected products

1

Patches

1
e891a84c7ef9

Fixed CVE-2026-1287 -- Protected against SQL injection in column aliases via control characters.

https://github.com/django/djangoJake HowardJan 21, 2026via ghsa
8 files changed · +149 59
  • django/db/models/sql/query.py+14 9 modified
    @@ -51,12 +51,17 @@
     __all__ = ["Query", "RawQuery"]
     
     # RemovedInDjango70Warning: When the deprecation ends, replace with:
    -# Quotation marks ('"`[]), whitespace characters, semicolons, percent signs,
    -# hashes, or inline SQL comments are forbidden in column aliases.
    -# FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile(r"['`\"\]\[;\s]|%|#|--|/\*|\*/")
    -# 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, percent signs, hashes, or inline SQL comments are
    +# forbidden in column aliases.
    +# FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile(
    +#   r"['`\"\]\[;\s\x00-\x1F\x7F-\x9F]|%|#|--|/\*|\*/"
    +# )
    +# 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
    @@ -1226,9 +1231,9 @@ def check_alias(self, alias):
                     "Column aliases cannot contain whitespace characters, hashes, "
                     # RemovedInDjango70Warning: When the deprecation ends, replace
                     # with:
    -                # "quotation marks, semicolons, percent signs, or SQL "
    -                # "comments."
    -                "quotation marks, semicolons, or SQL comments."
    +                # "control characters, quotation marks, semicolons, percent "
    +                # "signs, or SQL comments."
    +                "control characters, quotation marks, semicolons, or SQL comments."
                 )
     
         def add_annotation(self, annotation, alias, select=True):
    
  • docs/releases/4.2.28.txt+13 0 modified
    @@ -53,3 +53,16 @@ HTML end tags, which could cause quadratic time complexity during HTML parsing.
     
     This issue has severity "moderate" according to the :ref:`Django security
     policy <security-disclosure>`.
    +
    +CVE-2026-1287: Potential SQL injection in column aliases via control characters
    +===============================================================================
    +
    +:class:`.FilteredRelation` was subject to SQL injection in column aliases via
    +control characters, using a suitably crafted dictionary, with dictionary
    +expansion, as the ``**kwargs`` passed to :meth:`.QuerySet.annotate`,
    +:meth:`~.QuerySet.aggregate`, :meth:`~.QuerySet.extra`,
    +:meth:`~.QuerySet.values`, :meth:`~.QuerySet.values_list`, and
    +:meth:`~.QuerySet.alias`.
    +
    +This issue has severity "high" according to the :ref:`Django security policy
    +<security-disclosure>`.
    
  • docs/releases/5.2.11.txt+13 0 modified
    @@ -53,3 +53,16 @@ HTML end tags, which could cause quadratic time complexity during HTML parsing.
     
     This issue has severity "moderate" according to the :ref:`Django security
     policy <security-disclosure>`.
    +
    +CVE-2026-1287: Potential SQL injection in column aliases via control characters
    +===============================================================================
    +
    +:class:`.FilteredRelation` was subject to SQL injection in column aliases via
    +control characters, using a suitably crafted dictionary, with dictionary
    +expansion, as the ``**kwargs`` passed to :meth:`.QuerySet.annotate`,
    +:meth:`~.QuerySet.aggregate`, :meth:`~.QuerySet.extra`,
    +:meth:`~.QuerySet.values`, :meth:`~.QuerySet.values_list`, and
    +:meth:`~.QuerySet.alias`.
    +
    +This issue has severity "high" according to the :ref:`Django security policy
    +<security-disclosure>`.
    
  • docs/releases/6.0.2.txt+13 0 modified
    @@ -54,6 +54,19 @@ HTML end tags, which could cause quadratic time complexity during HTML parsing.
     This issue has severity "moderate" according to the :ref:`Django security
     policy <security-disclosure>`.
     
    +CVE-2026-1287: Potential SQL injection in column aliases via control characters
    +===============================================================================
    +
    +:class:`.FilteredRelation` was subject to SQL injection in column aliases via
    +control characters, using a suitably crafted dictionary, with dictionary
    +expansion, as the ``**kwargs`` passed to :meth:`.QuerySet.annotate`,
    +:meth:`~.QuerySet.aggregate`, :meth:`~.QuerySet.extra`,
    +:meth:`~.QuerySet.values`, :meth:`~.QuerySet.values_list`, and
    +:meth:`~.QuerySet.alias`.
    +
    +This issue has severity "high" according to the :ref:`Django security policy
    +<security-disclosure>`.
    +
     Bugfixes
     ========
     
    
  • tests/aggregation/tests.py+12 6 modified
    @@ -2,6 +2,7 @@
     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 @@ def test_exists_none_with_aggregate(self):
             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(
    
  • tests/annotations/tests.py+48 26 modified
    @@ -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
    @@ -1169,32 +1170,42 @@ def test_annotation_aggregate_with_m2o(self):
             )
     
         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."
    -        )
    -        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"; --"""
             # 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."
    -        )
    -        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 = [
    @@ -1214,15 +1225,16 @@ def test_alias_forbidden_chars(self):
                 "alias[",
                 "alias]",
                 "ali#as",
    +            "ali\0as",
             ]
             # 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."
             )
             for crafted_alias in tests:
                 with self.subTest(crafted_alias):
    @@ -1525,32 +1537,42 @@ def test_alias_after_values(self):
             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."
    -        )
    -        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"; --"""
             # 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."
    -        )
    -        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(
    
  • tests/expressions/test_queryset_values.py+24 12 modified
    @@ -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 @@ def test_values_expression_containing_percent_sign_deprecation_warns_once(self):
             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
    
  • tests/queries/tests.py+12 6 modified
    @@ -2,6 +2,7 @@
     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 @@ def test_extra_select_literal_percent_s(self):
             )
     
         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.
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

1