VYPR
Critical severityNVD Advisory· Published Apr 12, 2022· Updated Feb 13, 2025

CVE-2022-28346

CVE-2022-28346

Description

An issue was discovered in Django 2.2 before 2.2.28, 3.2 before 3.2.13, and 4.0 before 4.0.4. QuerySet.annotate(), aggregate(), and extra() methods are subject to SQL injection in column aliases via a crafted dictionary (with dictionary expansion) as the passed **kwargs.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Django QuerySet.annotate(), aggregate(), and extra() methods are vulnerable to SQL injection via crafted dictionary column aliases, affecting versions before 2.2.28, 3.2.13, and 4.0.4.

Vulnerability

An SQL injection vulnerability exists in Django's QuerySet.annotate(), QuerySet.aggregate(), and QuerySet.extra() methods. When these methods are called with a crafted dictionary (using dictionary expansion) as the passed **kwargs, column aliases are not properly sanitized, allowing an attacker to inject arbitrary SQL. This affects Django 2.2 before 2.2.28, 3.2 before 3.2.13, and 4.0 before 4.0.4 [1][2][3].

Exploitation

An attacker requires network access to an application that uses Django's ORM and accepts user-controllable dictionary keys or keyword argument names passed to these QuerySet methods. By providing a malicious dictionary key (i.e., a column alias) containing SQL syntax, the attacker can inject arbitrary SQL into the generated query. No authentication is explicitly required if the vulnerable endpoint is publicly accessible; the attack can be performed remotely without user interaction [1][2][3].

Impact

Successful exploitation allows an attacker to execute arbitrary SQL statements on the database server. This can lead to unauthorized access to data, modification of data, or potential database-level compromise, depending on the database user permissions. The impact is a full compromise of confidentiality, integrity, and availability of the database [1][2][3].

Mitigation

The Django project released fixed versions: Django 2.2.28, 3.2.13, and 4.0.4 on April 11, 2022 [3][4]. All users are strongly advised to upgrade immediately. No workarounds have been published aside from upgrading to the patched versions. This CVE is not listed in the Known Exploited Vulnerabilities Catalog (KEV) as of the date of publication.

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
DjangoPyPI
>= 2.2, < 2.2.282.2.28
DjangoPyPI
>= 3.2, < 3.2.133.2.13
DjangoPyPI
>= 4.0, < 4.0.44.0.4

Affected products

60

Patches

4
2044dac5c696

[3.2.x] Fixed CVE-2022-28346 -- Protected QuerySet.annotate(), aggregate(), and extra() against SQL injection in column aliases.

https://github.com/django/djangoMariusz FelisiakApr 1, 2022via ghsa
7 files changed · +100 0
  • django/db/models/sql/query.py+14 0 modified
    @@ -41,10 +41,15 @@
     )
     from django.utils.deprecation import RemovedInDjango40Warning
     from django.utils.functional import cached_property
    +from django.utils.regex_helper import _lazy_re_compile
     from django.utils.tree import Node
     
     __all__ = ['Query', 'RawQuery']
     
    +# Quotation marks ('"`[]), whitespace characters, semicolons, or inline
    +# SQL comments are forbidden in column aliases.
    +FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile(r"['`\"\]\[;\s]|--|/\*|\*/")
    +
     
     def get_field_names_from_opts(opts):
         return set(chain.from_iterable(
    @@ -1034,8 +1039,16 @@ def join_parent_model(self, opts, model, alias, seen):
                 alias = seen[int_model] = join_info.joins[-1]
             return alias or seen[None]
     
    +    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."
    +            )
    +
         def add_annotation(self, annotation, alias, is_summary=False, select=True):
             """Add a single annotation expression to the Query."""
    +        self.check_alias(alias)
             annotation = annotation.resolve_expression(self, allow_joins=True, reuse=None,
                                                        summarize=is_summary)
             if select:
    @@ -2088,6 +2101,7 @@ def add_extra(self, select, select_params, where, params, tables, order_by):
                 else:
                     param_iter = iter([])
                 for name, entry in select.items():
    +                self.check_alias(name)
                     entry = str(entry)
                     entry_params = []
                     pos = entry.find("%s")
    
  • docs/releases/2.2.28.txt+8 0 modified
    @@ -5,3 +5,11 @@ Django 2.2.28 release notes
     *April 11, 2022*
     
     Django 2.2.28 fixes two security issues with severity "high" in 2.2.27.
    +
    +CVE-2022-28346: Potential SQL injection in ``QuerySet.annotate()``, ``aggregate()``, and ``extra()``
    +====================================================================================================
    +
    +:meth:`.QuerySet.annotate`, :meth:`~.QuerySet.aggregate`, and
    +:meth:`~.QuerySet.extra` methods were subject to SQL injection in column
    +aliases, using a suitably crafted dictionary, with dictionary expansion, as the
    +``**kwargs`` passed to these methods.
    
  • docs/releases/3.2.13.txt+8 0 modified
    @@ -7,6 +7,14 @@ Django 3.2.13 release notes
     Django 3.2.13 fixes two security issues with severity "high" in
     3.2.12 and a regression in 3.2.4.
     
    +CVE-2022-28346: Potential SQL injection in ``QuerySet.annotate()``, ``aggregate()``, and ``extra()``
    +====================================================================================================
    +
    +:meth:`.QuerySet.annotate`, :meth:`~.QuerySet.aggregate`, and
    +:meth:`~.QuerySet.extra` methods were subject to SQL injection in column
    +aliases, using a suitably crafted dictionary, with dictionary expansion, as the
    +``**kwargs`` passed to these methods.
    +
     Bugfixes
     ========
     
    
  • tests/aggregation/tests.py+9 0 modified
    @@ -1340,3 +1340,12 @@ def test_aggregation_random_ordering(self):
                 ('Stuart Russell', 1),
                 ('Peter Norvig', 2),
             ], lambda a: (a.name, a.contact_count), ordered=False)
    +
    +    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."
    +        )
    +        with self.assertRaisesMessage(ValueError, msg):
    +            Author.objects.aggregate(**{crafted_alias: Avg("age")})
    
  • tests/annotations/tests.py+43 0 modified
    @@ -766,6 +766,40 @@ def test_annotation_aggregate_with_m2o(self):
                 {'name': 'Wesley J. Chun', 'max_pages': 0},
             ])
     
    +    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."
    +        )
    +        with self.assertRaisesMessage(ValueError, msg):
    +            Book.objects.annotate(**{crafted_alias: Value(1)})
    +
    +    def test_alias_forbidden_chars(self):
    +        tests = [
    +            'al"ias',
    +            "a'lias",
    +            "ali`as",
    +            "alia s",
    +            "alias\t",
    +            "ali\nas",
    +            "alias--",
    +            "ali/*as",
    +            "alias*/",
    +            "alias;",
    +            # [] are used by MSSQL.
    +            "alias[",
    +            "alias]",
    +        ]
    +        msg = (
    +            "Column aliases cannot contain whitespace characters, quotation marks, "
    +            "semicolons, or SQL comments."
    +        )
    +        for crafted_alias in tests:
    +            with self.subTest(crafted_alias):
    +                with self.assertRaisesMessage(ValueError, msg):
    +                    Book.objects.annotate(**{crafted_alias: Value(1)})
    +
     
     class AliasTests(TestCase):
         @classmethod
    @@ -996,3 +1030,12 @@ def test_values_alias(self):
                 with self.subTest(operation=operation):
                     with self.assertRaisesMessage(FieldError, msg):
                         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, quotation marks, "
    +            "semicolons, or SQL comments."
    +        )
    +        with self.assertRaisesMessage(ValueError, msg):
    +            Book.objects.alias(**{crafted_alias: Value(1)})
    
  • tests/expressions/test_queryset_values.py+9 0 modified
    @@ -26,6 +26,15 @@ def test_values_expression(self):
                 [{'salary': 10}, {'salary': 20}, {'salary': 30}],
             )
     
    +    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."
    +        )
    +        with self.assertRaisesMessage(ValueError, msg):
    +            Company.objects.values(**{crafted_alias: F("ceo__salary")})
    +
         def test_values_expression_group_by(self):
             # values() applies annotate() first, so values selected are grouped by
             # id, not firstname.
    
  • tests/queries/tests.py+9 0 modified
    @@ -1677,6 +1677,15 @@ def test_extra_select_literal_percent_s(self):
                 'bar %s'
             )
     
    +    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."
    +        )
    +        with self.assertRaisesMessage(ValueError, msg):
    +            Note.objects.extra(select={crafted_alias: "1"})
    +
     
     class SelectRelatedTests(TestCase):
         def test_tickets_3045_3288(self):
    
2c09e68ec911

[2.2.x] Fixed CVE-2022-28346 -- Protected QuerySet.annotate(), aggregate(), and extra() against SQL injection in column aliases.

https://github.com/django/djangoMariusz FelisiakApr 1, 2022via ghsa
6 files changed · +83 0
  • django/db/models/sql/query.py+14 0 modified
    @@ -8,6 +8,7 @@
     """
     import difflib
     import functools
    +import re
     from collections import Counter, OrderedDict, namedtuple
     from collections.abc import Iterator, Mapping
     from itertools import chain, count, product
    @@ -40,6 +41,10 @@
     
     __all__ = ['Query', 'RawQuery']
     
    +# Quotation marks ('"`[]), whitespace characters, semicolons, or inline
    +# SQL comments are forbidden in column aliases.
    +FORBIDDEN_ALIAS_PATTERN = re.compile(r"['`\"\]\[;\s]|--|/\*|\*/")
    +
     
     def get_field_names_from_opts(opts):
         return set(chain.from_iterable(
    @@ -994,8 +999,16 @@ def join_parent_model(self, opts, model, alias, seen):
                 alias = seen[int_model] = join_info.joins[-1]
             return alias or seen[None]
     
    +    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."
    +            )
    +
         def add_annotation(self, annotation, alias, is_summary=False):
             """Add a single annotation expression to the Query."""
    +        self.check_alias(alias)
             annotation = annotation.resolve_expression(self, allow_joins=True, reuse=None,
                                                        summarize=is_summary)
             self.append_annotation_mask([alias])
    @@ -1873,6 +1886,7 @@ def add_extra(self, select, select_params, where, params, tables, order_by):
                 else:
                     param_iter = iter([])
                 for name, entry in select.items():
    +                self.check_alias(name)
                     entry = str(entry)
                     entry_params = []
                     pos = entry.find("%s")
    
  • docs/releases/2.2.28.txt+8 0 modified
    @@ -5,3 +5,11 @@ Django 2.2.28 release notes
     *April 11, 2022*
     
     Django 2.2.28 fixes two security issues with severity "high" in 2.2.27.
    +
    +CVE-2022-28346: Potential SQL injection in ``QuerySet.annotate()``, ``aggregate()``, and ``extra()``
    +====================================================================================================
    +
    +:meth:`.QuerySet.annotate`, :meth:`~.QuerySet.aggregate`, and
    +:meth:`~.QuerySet.extra` methods were subject to SQL injection in column
    +aliases, using a suitably crafted dictionary, with dictionary expansion, as the
    +``**kwargs`` passed to these methods.
    
  • tests/aggregation/tests.py+9 0 modified
    @@ -1114,3 +1114,12 @@ def test_arguments_must_be_expressions(self):
                 Book.objects.aggregate(is_book=True)
             with self.assertRaisesMessage(TypeError, msg % ', '.join([str(FloatField()), 'True'])):
                 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, quotation marks, "
    +            "semicolons, or SQL comments."
    +        )
    +        with self.assertRaisesMessage(ValueError, msg):
    +            Author.objects.aggregate(**{crafted_alias: Avg("age")})
    
  • tests/annotations/tests.py+34 0 modified
    @@ -598,3 +598,37 @@ def test_annotation_filter_with_subquery(self):
                 total_books=Subquery(long_books_qs, output_field=IntegerField()),
             ).values('name')
             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, quotation marks, "
    +            "semicolons, or SQL comments."
    +        )
    +        with self.assertRaisesMessage(ValueError, msg):
    +            Book.objects.annotate(**{crafted_alias: Value(1)})
    +
    +    def test_alias_forbidden_chars(self):
    +        tests = [
    +            'al"ias',
    +            "a'lias",
    +            "ali`as",
    +            "alia s",
    +            "alias\t",
    +            "ali\nas",
    +            "alias--",
    +            "ali/*as",
    +            "alias*/",
    +            "alias;",
    +            # [] are used by MSSQL.
    +            "alias[",
    +            "alias]",
    +        ]
    +        msg = (
    +            "Column aliases cannot contain whitespace characters, quotation marks, "
    +            "semicolons, or SQL comments."
    +        )
    +        for crafted_alias in tests:
    +            with self.subTest(crafted_alias):
    +                with self.assertRaisesMessage(ValueError, msg):
    +                    Book.objects.annotate(**{crafted_alias: Value(1)})
    
  • tests/expressions/test_queryset_values.py+9 0 modified
    @@ -27,6 +27,15 @@ def test_values_expression(self):
                 [{'salary': 10}, {'salary': 20}, {'salary': 30}],
             )
     
    +    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."
    +        )
    +        with self.assertRaisesMessage(ValueError, msg):
    +            Company.objects.values(**{crafted_alias: F("ceo__salary")})
    +
         def test_values_expression_group_by(self):
             # values() applies annotate() first, so values selected are grouped by
             # id, not firstname.
    
  • tests/queries/tests.py+9 0 modified
    @@ -1737,6 +1737,15 @@ def test_extra_select_literal_percent_s(self):
                 'bar %s'
             )
     
    +    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."
    +        )
    +        with self.assertRaisesMessage(ValueError, msg):
    +            Note.objects.extra(select={crafted_alias: "1"})
    +
     
     class SelectRelatedTests(TestCase):
         def test_tickets_3045_3288(self):
    
800828887a05

[4.0.x] Fixed CVE-2022-28346 -- Protected QuerySet.annotate(), aggregate(), and extra() against SQL injection in column aliases.

https://github.com/django/djangoMariusz FelisiakApr 1, 2022via ghsa
8 files changed · +108 0
  • django/db/models/sql/query.py+14 0 modified
    @@ -40,10 +40,15 @@
     from django.db.models.sql.datastructures import BaseTable, Empty, Join, MultiJoin
     from django.db.models.sql.where import AND, OR, ExtraWhere, NothingNode, WhereNode
     from django.utils.functional import cached_property
    +from django.utils.regex_helper import _lazy_re_compile
     from django.utils.tree import Node
     
     __all__ = ["Query", "RawQuery"]
     
    +# Quotation marks ('"`[]), whitespace characters, semicolons, or inline
    +# SQL comments are forbidden in column aliases.
    +FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile(r"['`\"\]\[;\s]|--|/\*|\*/")
    +
     
     def get_field_names_from_opts(opts):
         return set(
    @@ -1077,8 +1082,16 @@ def join_parent_model(self, opts, model, alias, seen):
                 alias = seen[int_model] = join_info.joins[-1]
             return alias or seen[None]
     
    +    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."
    +            )
    +
         def add_annotation(self, annotation, alias, is_summary=False, select=True):
             """Add a single annotation expression to the Query."""
    +        self.check_alias(alias)
             annotation = annotation.resolve_expression(
                 self, allow_joins=True, reuse=None, summarize=is_summary
             )
    @@ -2234,6 +2247,7 @@ def add_extra(self, select, select_params, where, params, tables, order_by):
                 else:
                     param_iter = iter([])
                 for name, entry in select.items():
    +                self.check_alias(name)
                     entry = str(entry)
                     entry_params = []
                     pos = entry.find("%s")
    
  • docs/releases/2.2.28.txt+8 0 modified
    @@ -5,3 +5,11 @@ Django 2.2.28 release notes
     *April 11, 2022*
     
     Django 2.2.28 fixes two security issues with severity "high" in 2.2.27.
    +
    +CVE-2022-28346: Potential SQL injection in ``QuerySet.annotate()``, ``aggregate()``, and ``extra()``
    +====================================================================================================
    +
    +:meth:`.QuerySet.annotate`, :meth:`~.QuerySet.aggregate`, and
    +:meth:`~.QuerySet.extra` methods were subject to SQL injection in column
    +aliases, using a suitably crafted dictionary, with dictionary expansion, as the
    +``**kwargs`` passed to these methods.
    
  • docs/releases/3.2.13.txt+8 0 modified
    @@ -7,6 +7,14 @@ Django 3.2.13 release notes
     Django 3.2.13 fixes two security issues with severity "high" in
     3.2.12 and a regression in 3.2.4.
     
    +CVE-2022-28346: Potential SQL injection in ``QuerySet.annotate()``, ``aggregate()``, and ``extra()``
    +====================================================================================================
    +
    +:meth:`.QuerySet.annotate`, :meth:`~.QuerySet.aggregate`, and
    +:meth:`~.QuerySet.extra` methods were subject to SQL injection in column
    +aliases, using a suitably crafted dictionary, with dictionary expansion, as the
    +``**kwargs`` passed to these methods.
    +
     Bugfixes
     ========
     
    
  • docs/releases/4.0.4.txt+8 0 modified
    @@ -7,6 +7,14 @@ Django 4.0.4 release notes
     Django 4.0.4 fixes two security issues with severity "high" and two bugs in
     4.0.3.
     
    +CVE-2022-28346: Potential SQL injection in ``QuerySet.annotate()``, ``aggregate()``, and ``extra()``
    +====================================================================================================
    +
    +:meth:`.QuerySet.annotate`, :meth:`~.QuerySet.aggregate`, and
    +:meth:`~.QuerySet.extra` methods were subject to SQL injection in column
    +aliases, using a suitably crafted dictionary, with dictionary expansion, as the
    +``**kwargs`` passed to these methods.
    +
     Bugfixes
     ========
     
    
  • tests/aggregation/tests.py+9 0 modified
    @@ -2029,6 +2029,15 @@ 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, quotation marks, "
    +            "semicolons, or SQL comments."
    +        )
    +        with self.assertRaisesMessage(ValueError, msg):
    +            Author.objects.aggregate(**{crafted_alias: Avg("age")})
    +
         def test_exists_extra_where_with_aggregate(self):
             qs = Book.objects.all().annotate(
                 count=Count("id"),
    
  • tests/annotations/tests.py+43 0 modified
    @@ -1055,6 +1055,40 @@ def test_annotation_aggregate_with_m2o(self):
                 ],
             )
     
    +    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."
    +        )
    +        with self.assertRaisesMessage(ValueError, msg):
    +            Book.objects.annotate(**{crafted_alias: Value(1)})
    +
    +    def test_alias_forbidden_chars(self):
    +        tests = [
    +            'al"ias',
    +            "a'lias",
    +            "ali`as",
    +            "alia s",
    +            "alias\t",
    +            "ali\nas",
    +            "alias--",
    +            "ali/*as",
    +            "alias*/",
    +            "alias;",
    +            # [] are used by MSSQL.
    +            "alias[",
    +            "alias]",
    +        ]
    +        msg = (
    +            "Column aliases cannot contain whitespace characters, quotation marks, "
    +            "semicolons, or SQL comments."
    +        )
    +        for crafted_alias in tests:
    +            with self.subTest(crafted_alias):
    +                with self.assertRaisesMessage(ValueError, msg):
    +                    Book.objects.annotate(**{crafted_alias: Value(1)})
    +
     
     class AliasTests(TestCase):
         @classmethod
    @@ -1318,3 +1352,12 @@ def test_values_alias(self):
                 with self.subTest(operation=operation):
                     with self.assertRaisesMessage(FieldError, msg):
                         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, quotation marks, "
    +            "semicolons, or SQL comments."
    +        )
    +        with self.assertRaisesMessage(ValueError, msg):
    +            Book.objects.alias(**{crafted_alias: Value(1)})
    
  • tests/expressions/test_queryset_values.py+9 0 modified
    @@ -34,6 +34,15 @@ def test_values_expression(self):
                 [{"salary": 10}, {"salary": 20}, {"salary": 30}],
             )
     
    +    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."
    +        )
    +        with self.assertRaisesMessage(ValueError, msg):
    +            Company.objects.values(**{crafted_alias: F("ceo__salary")})
    +
         def test_values_expression_group_by(self):
             # values() applies annotate() first, so values selected are grouped by
             # id, not firstname.
    
  • tests/queries/tests.py+9 0 modified
    @@ -1892,6 +1892,15 @@ def test_extra_select_literal_percent_s(self):
                 Note.objects.extra(select={"foo": "'bar %%s'"})[0].foo, "bar %s"
             )
     
    +    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."
    +        )
    +        with self.assertRaisesMessage(ValueError, msg):
    +            Note.objects.extra(select={crafted_alias: "1"})
    +
         def test_queryset_reuse(self):
             # Using querysets doesn't mutate aliases.
             authors = Author.objects.filter(Q(name="a1") | Q(name="nonexistent"))
    
93cae5cb2f9a

Fixed CVE-2022-28346 -- Protected QuerySet.annotate(), aggregate(), and extra() against SQL injection in column aliases.

https://github.com/django/djangoMariusz FelisiakApr 1, 2022via ghsa
8 files changed · +108 0
  • django/db/models/sql/query.py+14 0 modified
    @@ -40,10 +40,15 @@
     from django.db.models.sql.datastructures import BaseTable, Empty, Join, MultiJoin
     from django.db.models.sql.where import AND, OR, ExtraWhere, NothingNode, WhereNode
     from django.utils.functional import cached_property
    +from django.utils.regex_helper import _lazy_re_compile
     from django.utils.tree import Node
     
     __all__ = ["Query", "RawQuery"]
     
    +# Quotation marks ('"`[]), whitespace characters, semicolons, or inline
    +# SQL comments are forbidden in column aliases.
    +FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile(r"['`\"\]\[;\s]|--|/\*|\*/")
    +
     
     def get_field_names_from_opts(opts):
         if opts is None:
    @@ -1091,8 +1096,16 @@ def join_parent_model(self, opts, model, alias, seen):
                 alias = seen[int_model] = join_info.joins[-1]
             return alias or seen[None]
     
    +    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."
    +            )
    +
         def add_annotation(self, annotation, alias, is_summary=False, select=True):
             """Add a single annotation expression to the Query."""
    +        self.check_alias(alias)
             annotation = annotation.resolve_expression(
                 self, allow_joins=True, reuse=None, summarize=is_summary
             )
    @@ -2269,6 +2282,7 @@ def add_extra(self, select, select_params, where, params, tables, order_by):
                 else:
                     param_iter = iter([])
                 for name, entry in select.items():
    +                self.check_alias(name)
                     entry = str(entry)
                     entry_params = []
                     pos = entry.find("%s")
    
  • docs/releases/2.2.28.txt+8 0 modified
    @@ -5,3 +5,11 @@ Django 2.2.28 release notes
     *April 11, 2022*
     
     Django 2.2.28 fixes two security issues with severity "high" in 2.2.27.
    +
    +CVE-2022-28346: Potential SQL injection in ``QuerySet.annotate()``, ``aggregate()``, and ``extra()``
    +====================================================================================================
    +
    +:meth:`.QuerySet.annotate`, :meth:`~.QuerySet.aggregate`, and
    +:meth:`~.QuerySet.extra` methods were subject to SQL injection in column
    +aliases, using a suitably crafted dictionary, with dictionary expansion, as the
    +``**kwargs`` passed to these methods.
    
  • docs/releases/3.2.13.txt+8 0 modified
    @@ -7,6 +7,14 @@ Django 3.2.13 release notes
     Django 3.2.13 fixes two security issues with severity "high" in
     3.2.12 and a regression in 3.2.4.
     
    +CVE-2022-28346: Potential SQL injection in ``QuerySet.annotate()``, ``aggregate()``, and ``extra()``
    +====================================================================================================
    +
    +:meth:`.QuerySet.annotate`, :meth:`~.QuerySet.aggregate`, and
    +:meth:`~.QuerySet.extra` methods were subject to SQL injection in column
    +aliases, using a suitably crafted dictionary, with dictionary expansion, as the
    +``**kwargs`` passed to these methods.
    +
     Bugfixes
     ========
     
    
  • docs/releases/4.0.4.txt+8 0 modified
    @@ -7,6 +7,14 @@ Django 4.0.4 release notes
     Django 4.0.4 fixes two security issues with severity "high" and two bugs in
     4.0.3.
     
    +CVE-2022-28346: Potential SQL injection in ``QuerySet.annotate()``, ``aggregate()``, and ``extra()``
    +====================================================================================================
    +
    +:meth:`.QuerySet.annotate`, :meth:`~.QuerySet.aggregate`, and
    +:meth:`~.QuerySet.extra` methods were subject to SQL injection in column
    +aliases, using a suitably crafted dictionary, with dictionary expansion, as the
    +``**kwargs`` passed to these methods.
    +
     Bugfixes
     ========
     
    
  • tests/aggregation/tests.py+9 0 modified
    @@ -2048,6 +2048,15 @@ 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, quotation marks, "
    +            "semicolons, or SQL comments."
    +        )
    +        with self.assertRaisesMessage(ValueError, msg):
    +            Author.objects.aggregate(**{crafted_alias: Avg("age")})
    +
         def test_exists_extra_where_with_aggregate(self):
             qs = Book.objects.annotate(
                 count=Count("id"),
    
  • tests/annotations/tests.py+43 0 modified
    @@ -1076,6 +1076,40 @@ def test_annotation_aggregate_with_m2o(self):
                 ],
             )
     
    +    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."
    +        )
    +        with self.assertRaisesMessage(ValueError, msg):
    +            Book.objects.annotate(**{crafted_alias: Value(1)})
    +
    +    def test_alias_forbidden_chars(self):
    +        tests = [
    +            'al"ias',
    +            "a'lias",
    +            "ali`as",
    +            "alia s",
    +            "alias\t",
    +            "ali\nas",
    +            "alias--",
    +            "ali/*as",
    +            "alias*/",
    +            "alias;",
    +            # [] are used by MSSQL.
    +            "alias[",
    +            "alias]",
    +        ]
    +        msg = (
    +            "Column aliases cannot contain whitespace characters, quotation marks, "
    +            "semicolons, or SQL comments."
    +        )
    +        for crafted_alias in tests:
    +            with self.subTest(crafted_alias):
    +                with self.assertRaisesMessage(ValueError, msg):
    +                    Book.objects.annotate(**{crafted_alias: Value(1)})
    +
     
     class AliasTests(TestCase):
         @classmethod
    @@ -1339,3 +1373,12 @@ def test_values_alias(self):
                 with self.subTest(operation=operation):
                     with self.assertRaisesMessage(FieldError, msg):
                         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, quotation marks, "
    +            "semicolons, or SQL comments."
    +        )
    +        with self.assertRaisesMessage(ValueError, msg):
    +            Book.objects.alias(**{crafted_alias: Value(1)})
    
  • tests/expressions/test_queryset_values.py+9 0 modified
    @@ -34,6 +34,15 @@ def test_values_expression(self):
                 [{"salary": 10}, {"salary": 20}, {"salary": 30}],
             )
     
    +    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."
    +        )
    +        with self.assertRaisesMessage(ValueError, msg):
    +            Company.objects.values(**{crafted_alias: F("ceo__salary")})
    +
         def test_values_expression_group_by(self):
             # values() applies annotate() first, so values selected are grouped by
             # id, not firstname.
    
  • tests/queries/tests.py+9 0 modified
    @@ -1898,6 +1898,15 @@ def test_extra_select_literal_percent_s(self):
                 Note.objects.extra(select={"foo": "'bar %%s'"})[0].foo, "bar %s"
             )
     
    +    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."
    +        )
    +        with self.assertRaisesMessage(ValueError, msg):
    +            Note.objects.extra(select={crafted_alias: "1"})
    +
         def test_queryset_reuse(self):
             # Using querysets doesn't mutate aliases.
             authors = Author.objects.filter(Q(name="a1") | Q(name="nonexistent"))
    

Vulnerability mechanics

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

References

22

News mentions

0

No linked articles in our index yet.