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.
| Package | Affected versions | Patched versions |
|---|---|---|
DjangoPyPI | >= 2.2, < 2.2.28 | 2.2.28 |
DjangoPyPI | >= 3.2, < 3.2.13 | 3.2.13 |
DjangoPyPI | >= 4.0, < 4.0.4 | 4.0.4 |
Affected products
60- Django/Djangodescription
- osv-coords59 versionspkg:bitnami/djangopkg:pypi/djangopkg:rpm/opensuse/python-Django4&distro=openSUSE%20Tumbleweedpkg:rpm/opensuse/python-Django6&distro=openSUSE%20Tumbleweedpkg:rpm/opensuse/python-Django&distro=openSUSE%20Leap%2015.3pkg:rpm/opensuse/python-Django&distro=openSUSE%20Leap%2015.5pkg:rpm/opensuse/python-Django&distro=openSUSE%20Tumbleweedpkg:rpm/suse/ardana-ansible&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/ardana-ansible&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/ardana-ansible&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/ardana-cobbler&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/ardana-cobbler&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/ardana-cobbler&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/ardana-tempest&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/grafana&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/grafana&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/grafana&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/grafana&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/grafana&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/openstack-heat-templates&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/openstack-heat-templates&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/openstack-heat-templates&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/openstack-heat-templates&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/openstack-heat-templates&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/openstack-horizon-plugin-gbp-ui&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/openstack-horizon-plugin-gbp-ui&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/openstack-murano&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/openstack-murano&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/openstack-murano&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/openstack-murano-doc&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/openstack-murano-doc&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/openstack-murano-doc&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/openstack-neutron-gbp&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/openstack-neutron-gbp&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/openstack-nova&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/openstack-nova&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/python-Django1&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/python-Django1&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/python-Django&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/python-Django&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/python-Django&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/python-Django&distro=SUSE%20Package%20Hub%2015%20SP3pkg:rpm/suse/rabbitmq-server&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/rabbitmq-server&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/rabbitmq-server&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/rabbitmq-server&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/rabbitmq-server&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/rubygem-puma&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/rubygem-puma&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/venv-openstack-heat&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-heat&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-heat&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/venv-openstack-horizon&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-horizon&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/venv-openstack-horizon-hpe&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-murano&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-murano&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-neutron&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/venv-openstack-nova&distro=SUSE%20OpenStack%20Cloud%209
>= 2.2.0, < 2.2.28+ 58 more
- (no CPE)range: >= 2.2.0, < 2.2.28
- (no CPE)range: >= 2.2, < 2.2.28
- (no CPE)range: < 4.2.14-1.1
- (no CPE)range: < 6.0-1.1
- (no CPE)range: < 2.2.28-bp153.2.3.1
- (no CPE)range: < 2.0.7-150000.1.27.1
- (no CPE)range: < 4.0.4-1.1
- (no CPE)range: < 8.0+git.1660773729.3789a6d-3.85.1
- (no CPE)range: < 8.0+git.1660773729.3789a6d-3.85.1
- (no CPE)range: < 9.0+git.1660748476.c118d23-3.32.1
- (no CPE)range: < 8.0+git.1660773402.d845a45-3.47.1
- (no CPE)range: < 8.0+git.1660773402.d845a45-3.47.1
- (no CPE)range: < 9.0+git.1660747489.119efcd-3.19.1
- (no CPE)range: < 9.0+git.1651855288.a2341ad-3.22.1
- (no CPE)range: < 6.7.4-4.23.1
- (no CPE)range: < 6.7.4-4.23.1
- (no CPE)range: < 6.7.4-3.29.1
- (no CPE)range: < 6.7.4-4.23.1
- (no CPE)range: < 6.7.4-3.29.1
- (no CPE)range: < 0.0.0+git.1654529662.75fa04a-3.27.1
- (no CPE)range: < 0.0.0+git.1654529662.75fa04a-3.27.1
- (no CPE)range: < 0.0.0+git.1654529662.75fa04a7-3.15.1
- (no CPE)range: < 0.0.0+git.1654529662.75fa04a-3.27.1
- (no CPE)range: < 0.0.0+git.1654529662.75fa04a7-3.15.1
- (no CPE)range: < 14.0.1~dev4-3.12.1
- (no CPE)range: < 14.0.1~dev4-3.12.1
- (no CPE)range: < 4.0.2~dev3-3.12.1
- (no CPE)range: < 4.0.2~dev3-3.12.1
- (no CPE)range: < 4.0.2~dev3-3.12.1
- (no CPE)range: < 4.0.2~dev3-3.12.1
- (no CPE)range: < 4.0.2~dev3-3.12.1
- (no CPE)range: < 4.0.2~dev3-3.12.1
- (no CPE)range: < 14.0.1~dev46-3.34.1
- (no CPE)range: < 14.0.1~dev46-3.34.1
- (no CPE)range: < 18.3.1~dev92-3.43.1
- (no CPE)range: < 18.3.1~dev92-3.43.1
- (no CPE)range: < 1.11.29-3.40.1
- (no CPE)range: < 1.11.29-3.40.1
- (no CPE)range: < 1.11.29-3.42.1
- (no CPE)range: < 1.11.29-3.42.1
- (no CPE)range: < 1.11.29-3.42.1
- (no CPE)range: < 2.2.28-bp153.2.3.1
- (no CPE)range: < 3.6.16-3.13.1
- (no CPE)range: < 3.6.16-3.13.1
- (no CPE)range: < 3.6.16-4.3.1
- (no CPE)range: < 3.6.16-3.13.1
- (no CPE)range: < 3.6.16-4.3.1
- (no CPE)range: < 2.16.0-3.18.1
- (no CPE)range: < 2.16.0-4.18.1
- (no CPE)range: < 9.0.8~dev22-12.45.1
- (no CPE)range: < 9.0.8~dev22-12.45.1
- (no CPE)range: < 11.0.4~dev4-3.37.1
- (no CPE)range: < 12.0.5~dev6-14.48.1
- (no CPE)range: < 14.1.1~dev11-4.41.1
- (no CPE)range: < 12.0.5~dev6-14.48.1
- (no CPE)range: < 4.0.2~dev3-12.38.1
- (no CPE)range: < 4.0.2~dev3-12.38.1
- (no CPE)range: < 13.0.8~dev206-6.41.1
- (no CPE)range: < 18.3.1~dev92-3.41.1
Patches
42044dac5c696[3.2.x] Fixed CVE-2022-28346 -- Protected QuerySet.annotate(), aggregate(), and extra() against SQL injection in column aliases.
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.
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.
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"))
93cae5cb2f9aFixed CVE-2022-28346 -- Protected QuerySet.annotate(), aggregate(), and extra() against SQL injection in column aliases.
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- github.com/advisories/GHSA-2gwj-7jmv-h26rghsaADVISORY
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/HWY6DQWRVBALV73BPUVBXC3QIYUM24IK/mitrevendor-advisory
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/LTZVAKU5ALQWOKFTPISE257VCVIYGFQI/mitrevendor-advisory
- nvd.nist.gov/vuln/detail/CVE-2022-28346ghsaADVISORY
- www.debian.org/security/2022/dsa-5254ghsavendor-advisoryWEB
- www.openwall.com/lists/oss-security/2022/04/11/1ghsaWEB
- docs.djangoproject.com/en/4.0/releases/securityghsaWEB
- github.com/django/django/commit/2044dac5c6968441be6f534c4139bcf48c5c7e48ghsaWEB
- github.com/django/django/commit/2c09e68ec911919360d5f8502cefc312f9e03c5dghsaWEB
- github.com/django/django/commit/800828887a0509ad1162d6d407e94d8de7eafc60ghsaWEB
- github.com/django/django/commit/93cae5cb2f9a4ef1514cf1a41f714fef08005200ghsaWEB
- github.com/pypa/advisory-database/tree/main/vulns/django/PYSEC-2022-190.yamlghsaWEB
- groups.google.com/forum/ghsaWEB
- lists.debian.org/debian-lts-announce/2022/04/msg00013.htmlghsamailing-listWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/HWY6DQWRVBALV73BPUVBXC3QIYUM24IKghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/LTZVAKU5ALQWOKFTPISE257VCVIYGFQIghsaWEB
- security.netapp.com/advisory/ntap-20220609-0002ghsaWEB
- www.djangoproject.com/weblog/2022/apr/11/security-releasesghsaWEB
- docs.djangoproject.com/en/4.0/releases/security/mitre
- groups.google.com/forum/mitre
- security.netapp.com/advisory/ntap-20220609-0002/mitre
- www.djangoproject.com/weblog/2022/apr/11/security-releases/mitre
News mentions
0No linked articles in our index yet.