1
0
mirror of https://github.com/django/django.git synced 2025-10-10 07:19:11 +00:00

Fixed CVE-2025-59681 -- Protected QuerySet.annotate(), alias(), aggregate(), and extra() against SQL injection in column aliases on MySQL/MariaDB.

Thanks sw0rd1ight for the report.

Follow up to 93cae5cb2f9a4ef1514cf1a41f714fef08005200.
This commit is contained in:
Mariusz Felisiak 2025-09-10 09:53:52 +02:00 committed by Jacob Walls
parent 6c82b0bc91
commit 41b43c74bd
8 changed files with 64 additions and 39 deletions

View File

@ -51,12 +51,12 @@ from django.utils.tree import Node
__all__ = ["Query", "RawQuery"] __all__ = ["Query", "RawQuery"]
# RemovedInDjango70Warning: When the deprecation ends, replace with: # RemovedInDjango70Warning: When the deprecation ends, replace with:
# Quotation marks ('"`[]), whitespace characters, semicolons, percent signs # Quotation marks ('"`[]), whitespace characters, semicolons, percent signs,
# or inline SQL comments are forbidden in column aliases. # hashes, or inline SQL comments are forbidden in column aliases.
# FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile(r"['`\"\]\[;\s]|%|--|/\*|\*/") # FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile(r"['`\"\]\[;\s]|%|#|--|/\*|\*/")
# Quotation marks ('"`[]), whitespace characters, semicolons, or inline # Quotation marks ('"`[]), whitespace characters, semicolons, hashes, or inline
# SQL comments are forbidden in column aliases. # SQL comments are forbidden in column aliases.
FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile(r"['`\"\]\[;\s]|--|/\*|\*/") FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile(r"['`\"\]\[;\s]|#|--|/\*|\*/")
# Inspired from # Inspired from
# https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS # https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
@ -1222,11 +1222,12 @@ class Query(BaseExpression):
) )
if FORBIDDEN_ALIAS_PATTERN.search(alias): if FORBIDDEN_ALIAS_PATTERN.search(alias):
raise ValueError( raise ValueError(
"Column aliases cannot contain whitespace characters, quotation marks, " "Column aliases cannot contain whitespace characters, hashes, "
# RemovedInDjango70Warning: When the deprecation ends, replace # RemovedInDjango70Warning: When the deprecation ends, replace
# with: # with:
# "semicolons, percent signs, or SQL comments." # "quotation marks, semicolons, percent signs, or SQL "
"semicolons, or SQL comments." # "comments."
"quotation marks, semicolons, or SQL comments."
) )
def add_annotation(self, annotation, alias, select=True): def add_annotation(self, annotation, alias, select=True):

View File

@ -7,4 +7,11 @@ Django 4.2.25 release notes
Django 4.2.25 fixes one security issue with severity "high" and one security Django 4.2.25 fixes one security issue with severity "high" and one security
issue with severity "low" in 4.2.24. issue with severity "low" in 4.2.24.
... CVE-2025-59681: Potential SQL injection in ``QuerySet.annotate()``, ``alias()``, ``aggregate()``, and ``extra()`` on MySQL and MariaDB
======================================================================================================================================
:meth:`.QuerySet.annotate`, :meth:`~.QuerySet.alias`,
: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 (follow up to
:cve:`2022-28346`).

View File

@ -7,4 +7,11 @@ Django 5.1.13 release notes
Django 5.1.13 fixes one security issue with severity "high" and one security Django 5.1.13 fixes one security issue with severity "high" and one security
issue with severity "low" in 5.1.12. issue with severity "low" in 5.1.12.
... CVE-2025-59681: Potential SQL injection in ``QuerySet.annotate()``, ``alias()``, ``aggregate()``, and ``extra()`` on MySQL and MariaDB
======================================================================================================================================
:meth:`.QuerySet.annotate`, :meth:`~.QuerySet.alias`,
: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 (follow up to
:cve:`2022-28346`).

View File

@ -8,6 +8,15 @@ Django 5.2.7 fixes one security issue with severity "high", one security issue
with severity "low", and one bug in 5.2.6. Also, the latest string translations with severity "low", and one bug in 5.2.6. Also, the latest string translations
from Transifex are incorporated. from Transifex are incorporated.
CVE-2025-59681: Potential SQL injection in ``QuerySet.annotate()``, ``alias()``, ``aggregate()``, and ``extra()`` on MySQL and MariaDB
======================================================================================================================================
:meth:`.QuerySet.annotate`, :meth:`~.QuerySet.alias`,
: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 (follow up to
:cve:`2022-28346`).
Bugfixes Bugfixes
======== ========

View File

@ -2244,8 +2244,8 @@ class AggregateTestCase(TestCase):
def test_alias_sql_injection(self): def test_alias_sql_injection(self):
crafted_alias = """injected_name" from "aggregation_author"; --""" crafted_alias = """injected_name" from "aggregation_author"; --"""
msg = ( msg = (
"Column aliases cannot contain whitespace characters, quotation marks, " "Column aliases cannot contain whitespace characters, hashes, quotation "
"semicolons, or SQL comments." "marks, semicolons, or SQL comments."
) )
with self.assertRaisesMessage(ValueError, msg): with self.assertRaisesMessage(ValueError, msg):
Author.objects.aggregate(**{crafted_alias: Avg("age")}) Author.objects.aggregate(**{crafted_alias: Avg("age")})

View File

@ -1161,12 +1161,12 @@ class NonAggregateAnnotationTestCase(TestCase):
crafted_alias = """injected_name" from "annotations_book"; --""" crafted_alias = """injected_name" from "annotations_book"; --"""
# RemovedInDjango70Warning: When the deprecation ends, replace with: # RemovedInDjango70Warning: When the deprecation ends, replace with:
# msg = ( # msg = (
# "Column aliases cannot contain whitespace characters, quotation " # "Column aliases cannot contain whitespace characters, hashes, "
# "marks, semicolons, percent signs, or SQL comments." # "quotation marks, semicolons, percent signs, or SQL comments."
# ) # )
msg = ( msg = (
"Column aliases cannot contain whitespace characters, quotation marks, " "Column aliases cannot contain whitespace characters, hashes, quotation "
"semicolons, or SQL comments." "marks, semicolons, or SQL comments."
) )
with self.assertRaisesMessage(ValueError, msg): with self.assertRaisesMessage(ValueError, msg):
Book.objects.annotate(**{crafted_alias: Value(1)}) Book.objects.annotate(**{crafted_alias: Value(1)})
@ -1175,12 +1175,12 @@ class NonAggregateAnnotationTestCase(TestCase):
crafted_alias = """injected_name" from "annotations_book"; --""" crafted_alias = """injected_name" from "annotations_book"; --"""
# RemovedInDjango70Warning: When the deprecation ends, replace with: # RemovedInDjango70Warning: When the deprecation ends, replace with:
# msg = ( # msg = (
# "Column aliases cannot contain whitespace characters, quotation " # "Column aliases cannot contain whitespace characters, hashes, "
# "marks, semicolons, percent signs, or SQL comments." # "quotation marks, semicolons, percent signs, or SQL comments."
# ) # )
msg = ( msg = (
"Column aliases cannot contain whitespace characters, quotation marks, " "Column aliases cannot contain whitespace characters, hashes, quotation "
"semicolons, or SQL comments." "marks, semicolons, or SQL comments."
) )
with self.assertRaisesMessage(ValueError, msg): with self.assertRaisesMessage(ValueError, msg):
Book.objects.annotate(**{crafted_alias: FilteredRelation("author")}) Book.objects.annotate(**{crafted_alias: FilteredRelation("author")})
@ -1199,18 +1199,19 @@ class NonAggregateAnnotationTestCase(TestCase):
"alias;", "alias;",
# RemovedInDjango70Warning: When the deprecation ends, add this: # RemovedInDjango70Warning: When the deprecation ends, add this:
# "alias%", # "alias%",
# [] are used by MSSQL. # [] and # are used by MSSQL.
"alias[", "alias[",
"alias]", "alias]",
"ali#as",
] ]
# RemovedInDjango70Warning: When the deprecation ends, replace with: # RemovedInDjango70Warning: When the deprecation ends, replace with:
# msg = ( # msg = (
# "Column aliases cannot contain whitespace characters, quotation " # "Column aliases cannot contain whitespace characters, hashes, "
# "marks, semicolons, percent signs, or SQL comments." # "quotation marks, semicolons, percent signs, or SQL comments."
# ) # )
msg = ( msg = (
"Column aliases cannot contain whitespace characters, quotation marks, " "Column aliases cannot contain whitespace characters, hashes, quotation "
"semicolons, or SQL comments." "marks, semicolons, or SQL comments."
) )
for crafted_alias in tests: for crafted_alias in tests:
with self.subTest(crafted_alias): with self.subTest(crafted_alias):
@ -1516,12 +1517,12 @@ class AliasTests(TestCase):
crafted_alias = """injected_name" from "annotations_book"; --""" crafted_alias = """injected_name" from "annotations_book"; --"""
# RemovedInDjango70Warning: When the deprecation ends, replace with: # RemovedInDjango70Warning: When the deprecation ends, replace with:
# msg = ( # msg = (
# "Column aliases cannot contain whitespace characters, quotation " # "Column aliases cannot contain whitespace characters, hashes, "
# "marks, semicolons, percent signs, or SQL comments." # "quotation marks, semicolons, percent signs, or SQL comments."
# ) # )
msg = ( msg = (
"Column aliases cannot contain whitespace characters, quotation marks, " "Column aliases cannot contain whitespace characters, hashes, quotation "
"semicolons, or SQL comments." "marks, semicolons, or SQL comments."
) )
with self.assertRaisesMessage(ValueError, msg): with self.assertRaisesMessage(ValueError, msg):
Book.objects.alias(**{crafted_alias: Value(1)}) Book.objects.alias(**{crafted_alias: Value(1)})
@ -1530,12 +1531,12 @@ class AliasTests(TestCase):
crafted_alias = """injected_name" from "annotations_book"; --""" crafted_alias = """injected_name" from "annotations_book"; --"""
# RemovedInDjango70Warning: When the deprecation ends, replace with: # RemovedInDjango70Warning: When the deprecation ends, replace with:
# msg = ( # msg = (
# "Column aliases cannot contain whitespace characters, quotation " # "Column aliases cannot contain whitespace characters, hashes, "
# "marks, semicolons, percent signs, or SQL comments." # "quotation marks, semicolons, percent signs, or SQL comments."
# ) # )
msg = ( msg = (
"Column aliases cannot contain whitespace characters, quotation marks, " "Column aliases cannot contain whitespace characters, hashes, quotation "
"semicolons, or SQL comments." "marks, semicolons, or SQL comments."
) )
with self.assertRaisesMessage(ValueError, msg): with self.assertRaisesMessage(ValueError, msg):
Book.objects.alias(**{crafted_alias: FilteredRelation("authors")}) Book.objects.alias(**{crafted_alias: FilteredRelation("authors")})

View File

@ -44,8 +44,8 @@ class ValuesExpressionsTests(TestCase):
def test_values_expression_alias_sql_injection(self): def test_values_expression_alias_sql_injection(self):
crafted_alias = """injected_name" from "expressions_company"; --""" crafted_alias = """injected_name" from "expressions_company"; --"""
msg = ( msg = (
"Column aliases cannot contain whitespace characters, quotation marks, " "Column aliases cannot contain whitespace characters, hashes, quotation "
"semicolons, or SQL comments." "marks, semicolons, or SQL comments."
) )
with self.assertRaisesMessage(ValueError, msg): with self.assertRaisesMessage(ValueError, msg):
Company.objects.values(**{crafted_alias: F("ceo__salary")}) Company.objects.values(**{crafted_alias: F("ceo__salary")})
@ -54,8 +54,8 @@ class ValuesExpressionsTests(TestCase):
def test_values_expression_alias_sql_injection_json_field(self): def test_values_expression_alias_sql_injection_json_field(self):
crafted_alias = """injected_name" from "expressions_company"; --""" crafted_alias = """injected_name" from "expressions_company"; --"""
msg = ( msg = (
"Column aliases cannot contain whitespace characters, quotation marks, " "Column aliases cannot contain whitespace characters, hashes, quotation "
"semicolons, or SQL comments." "marks, semicolons, or SQL comments."
) )
with self.assertRaisesMessage(ValueError, msg): with self.assertRaisesMessage(ValueError, msg):
JSONFieldModel.objects.values(f"data__{crafted_alias}") JSONFieldModel.objects.values(f"data__{crafted_alias}")

View File

@ -1967,8 +1967,8 @@ class Queries5Tests(TestCase):
def test_extra_select_alias_sql_injection(self): def test_extra_select_alias_sql_injection(self):
crafted_alias = """injected_name" from "queries_note"; --""" crafted_alias = """injected_name" from "queries_note"; --"""
msg = ( msg = (
"Column aliases cannot contain whitespace characters, quotation marks, " "Column aliases cannot contain whitespace characters, hashes, quotation "
"semicolons, or SQL comments." "marks, semicolons, or SQL comments."
) )
with self.assertRaisesMessage(ValueError, msg): with self.assertRaisesMessage(ValueError, msg):
Note.objects.extra(select={crafted_alias: "1"}) Note.objects.extra(select={crafted_alias: "1"})