Security advisory GHSA-rqw2-ghq9-44m7: Django is vulnerable to SQL injection in column aliases
Package: django (pypi), vulnerable version: 5.2.8, fixed in: 5.2.9
CWE: CWE-89
Files changed by the fix:
django/db/backends/postgresql/compiler.py

Fix diff:
diff --git a/django/db/backends/postgresql/compiler.py b/django/db/backends/postgresql/compiler.py
index dc2db148aed5..38b61c489838 100644
--- a/django/db/backends/postgresql/compiler.py
+++ b/django/db/backends/postgresql/compiler.py
@@ -1,6 +1,6 @@
 from django.db.models.sql.compiler import (
     SQLAggregateCompiler,
-    SQLCompiler,
+    SQLCompiler as BaseSQLCompiler,
     SQLDeleteCompiler,
 )
 from django.db.models.sql.compiler import SQLInsertCompiler as BaseSQLInsertCompiler
@@ -25,6 +25,15 @@ def __str__(self):
         return "UNNEST(%s)" % ", ".join(self)
 
 
+class SQLCompiler(BaseSQLCompiler):
+    def quote_name_unless_alias(self, name):
+        if "$" in name:
+            raise ValueError(
+                "Dollar signs are not permitted in column aliases on PostgreSQL."
+            )
+        return super().quote_name_unless_alias(name)
+
+
 class SQLInsertCompiler(BaseSQLInsertCompiler):
     def assemble_as_sql(self, fields, value_rows):
         # Specialize bulk-insertion of literal values through UNNEST to
diff --git a/docs/releases/4.2.27.txt b/docs/releases/4.2.27.txt
index 7ffa5fa45858..e95dc63f74ef 100644
--- a/docs/releases/4.2.27.txt
+++ b/docs/releases/4.2.27.txt
@@ -7,6 +7,14 @@ Django 4.2.27 release notes
 Django 4.2.27 fixes one security issue with severity "high", one security issue
 with severity "moderate", and one bug in 4.2.26.
 
+CVE-2025-13372: Potential SQL injection in ``FilteredRelation`` column aliases on PostgreSQL
+============================================================================================
+
+:class:`.FilteredRelation` was subject to SQL injection in column aliases,
+using a suitably crafted dictionary, with dictionary expansion, as the
+``**kwargs`` passed to :meth:`.QuerySet.annotate` or :meth:`.QuerySet.alias` on
+PostgreSQL.
+
 Bugfixes
 ========
 
diff --git a/docs/releases/5.1.15.txt b/docs/releases/5.1.15.txt
index 2c4e02959031..f55623ea9684 100644
--- a/docs/releases/5.1.15.txt
+++ b/docs/releases/5.1.15.txt
@@ -7,6 +7,14 @@ Django 5.1.15 release notes
 Django 5.1.15 fixes one security issue with severity "high", one security issue
 with severity "moderate", and one bug in 5.1.14.
 
+CVE-2025-13372: Potential SQL injection in ``FilteredRelation`` column aliases on PostgreSQL
+============================================================================================
+
+:class:`.FilteredRelation` was subject to SQL injection in column aliases,
+using a suitably crafted dictionary, with dictionary expansion, as the
+``**kwargs`` passed to :meth:`.QuerySet.annotate` or :meth:`.QuerySet.alias` on
+PostgreSQL.
+
 Bugfixes
 ========
 
diff --git a/docs/releases/5.2.9.txt b/docs/releases/5.2.9.txt
index 9dfcc392a036..08c298999a56 100644
--- a/docs/releases/5.2.9.txt
+++ b/docs/releases/5.2.9.txt
@@ -7,6 +7,14 @@ Django 5.2.9 release notes
 Django 5.2.9 fixes one security issue with severity "high", one security issue
 with severity "moderate", and several bugs in 5.2.8.
 
+CVE-2025-13372: Potential SQL injection in ``FilteredRelation`` column aliases on PostgreSQL
+============================================================================================
+
+:class:`.FilteredRelation` was subject to SQL injection in column aliases,
+using a suitably crafted dictionary, with dictionary expansion, as the
+``**kwargs`` passed to :meth:`.QuerySet.annotate` or :meth:`.QuerySet.alias` on
+PostgreSQL.
+
 Bugfixes
 ========
 
diff --git a/tests/annotations/tests.py b/tests/annotations/tests.py
index 7a1212122427..78e5408d0fe7 100644
--- a/tests/annotations/tests.py
+++ b/tests/annotations/tests.py
@@ -1507,3 +1507,14 @@ def test_alias_filtered_relation_sql_injection(self):
         )
         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(
+            **{"crafted_alia$": FilteredRelation("authors")}
+        ).values("name", "crafted_alia$")
+        if connection.vendor == "postgresql":
+            msg = "Dollar signs are not permitted in column aliases on PostgreSQL."
+            with self.assertRaisesMessage(ValueError, msg):
+                list(qs)
+        else:
+            self.assertEqual(qs.first()["name"], self.b1.name)