Security advisory GHSA-frmv-pr5f-9mcr: Django vulnerable to SQL injection via _connector keyword argument in QuerySet and Q objects.
Package: django (pypi), vulnerable version: 5.2.7, fixed in: 5.2.8
CWE: CWE-89
Files changed by the fix:
django/db/models/query_utils.py

Fix diff:
diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py
index c383b8064088..c1baf5c43632 100644
--- a/django/db/models/query_utils.py
+++ b/django/db/models/query_utils.py
@@ -48,8 +48,12 @@ class Q(tree.Node):
     XOR = "XOR"
     default = AND
     conditional = True
+    connectors = (None, AND, OR, XOR)
 
     def __init__(self, *args, _connector=None, _negated=False, **kwargs):
+        if _connector not in self.connectors:
+            connector_reprs = ", ".join(f"{conn!r}" for conn in self.connectors[1:])
+            raise ValueError(f"_connector must be one of {connector_reprs}, or None.")
         super().__init__(
             children=[*args, *sorted(kwargs.items())],
             connector=_connector,
diff --git a/docs/releases/4.2.26.txt b/docs/releases/4.2.26.txt
index ae274c3361c0..20cf48f05c11 100644
--- a/docs/releases/4.2.26.txt
+++ b/docs/releases/4.2.26.txt
@@ -16,3 +16,10 @@ Windows. As a consequence, :class:`~django.http.HttpResponseRedirect`,
 :func:`redirect() <django.shortcuts.redirect>` were subject to a potential
 denial-of-service attack via certain inputs with a very large number of Unicode
 characters (follow up to :cve:`2025-27556`).
+
+CVE-2025-64459: Potential SQL injection via ``_connector`` keyword argument
+===========================================================================
+
+:meth:`.QuerySet.filter`, :meth:`~.QuerySet.exclude`, :meth:`~.QuerySet.get`,
+and :class:`~.Q` were subject to SQL injection using a suitably crafted
+dictionary, with dictionary expansion, as the ``_connector`` argument.
diff --git a/docs/releases/5.1.14.txt b/docs/releases/5.1.14.txt
index 8dba96e48726..d762dbf3cd27 100644
--- a/docs/releases/5.1.14.txt
+++ b/docs/releases/5.1.14.txt
@@ -16,3 +16,10 @@ Windows. As a consequence, :class:`~django.http.HttpResponseRedirect`,
 :func:`redirect() <django.shortcuts.redirect>` were subject to a potential
 denial-of-service attack via certain inputs with a very large number of Unicode
 characters (follow up to :cve:`2025-27556`).
+
+CVE-2025-64459: Potential SQL injection via ``_connector`` keyword argument
+===========================================================================
+
+:meth:`.QuerySet.filter`, :meth:`~.QuerySet.exclude`, :meth:`~.QuerySet.get`,
+and :class:`~.Q` were subject to SQL injection using a suitably crafted
+dictionary, with dictionary expansion, as the ``_connector`` argument.
diff --git a/docs/releases/5.2.8.txt b/docs/releases/5.2.8.txt
index 947fce8d84de..0a0038ba2096 100644
--- a/docs/releases/5.2.8.txt
+++ b/docs/releases/5.2.8.txt
@@ -18,6 +18,13 @@ Windows. As a consequence, :class:`~django.http.HttpResponseRedirect`,
 denial-of-service attack via certain inputs with a very large number of Unicode
 characters (follow up to :cve:`2025-27556`).
 
+CVE-2025-64459: Potential SQL injection via ``_connector`` keyword argument
+===========================================================================
+
+:meth:`.QuerySet.filter`, :meth:`~.QuerySet.exclude`, :meth:`~.QuerySet.get`,
+and :class:`~.Q` were subject to SQL injection using a suitably crafted
+dictionary, with dictionary expansion, as the ``_connector`` argument.
+
 Bugfixes
 ========
 
diff --git a/tests/queries/test_q.py b/tests/queries/test_q.py
index 1a62aca06114..52200b2ecff1 100644
--- a/tests/queries/test_q.py
+++ b/tests/queries/test_q.py
@@ -272,6 +272,11 @@ def test_create_helper(self):
                     Q(*items, _connector=connector),
                 )
 
+    def test_connector_validation(self):
+        msg = f"_connector must be one of {Q.AND!r}, {Q.OR!r}, {Q.XOR!r}, or None."
+        with self.assertRaisesMessage(ValueError, msg):
+            Q(_connector="evil")
+
     def test_referenced_base_fields(self):
         # Make sure Q.referenced_base_fields retrieves all base fields from
         # both filters and F expressions.