CVE-2025-13372
Medium · 4.3Django is vulnerable to SQL injection in column aliases
CVSS
4.3
EPSS
0.9%
Published
Dec 2, 2025
CISA due date
—
Cloudanix analysis
A bug in a popular web framework can let someone tamper with database queries on PostgreSQL when an app unsafely names certain query shortcuts, which may expose or change stored data.
- Exploitation
- Remote Authenticated
- Attacker needs
- Low Privilege
- Complexity
- Moderate
- Impact
- Data Exposure
- Component
- Web Framework
- Class
- SQL Injection
- Remediation
- Upgrade
- Effort
- Low
- Patch risk
- None
What it actually does
On PostgreSQL, Django FilteredRelation mishandles column aliases when a crafted dictionary is expanded as **kwargs into QuerySet.annotate() or QuerySet.alias(). Alias names can be interpolated into generated SQL, so attacker-controlled identifiers become part of the query executed as the application database role. Impact is SQL injection in that ORM path only (not RCE by itself) and requires the unsafe annotate/alias pattern on a vulnerable Django release.
Why it matters to the business
If an app feeds untrusted input into FilteredRelation annotate/alias kwargs, attackers may read or change data the database user can access, harming confidentiality and integrity. Django is widely used, so any remaining 5.2 < 5.2.9, 5.1 < 5.1.15, or 4.2 < 4.2.27 (and unevaluated 5.0.x, 4.1.x, 3.2.x) is in scope. Likelihood is low: CVSS 4.3, EPSS ~0.009 and flat over 30 days, not in CISA KEV, no ransomware use, exploit maturity none. Public for ~300 days, so unpatched production is lingering patch debt rather than a new crisis.
How an attack unfolds
An attacker finds a Django+PostgreSQL app that builds FilteredRelation and calls annotate() or alias() with a dictionary expanded as keyword arguments, where keys (aliases) can be influenced by the client. They submit alias names that break out of the intended identifier context. Django interpolates those aliases into SQL sent to PostgreSQL without adequate handling for this construction. PostgreSQL parses the injected fragments as part of the query, changing semantics (extra clauses, broader results, or other SQL) under the app's DB role. This needs a reachable unsafe coding pattern; no named threat actors or KEV campaigns are associated.
How to fix it
Upgrade Django to 5.2.9, 5.1.15, or 4.2.27 (or newer supported). Upstream fix: git 6ef1f6fbe33e9882f82dae026b527800ecb78c36. Move unsupported 5.0.x, 4.1.x, and 3.2.x onto a patched supported line. Never pass request/API/untrusted dicts as **kwargs to annotate() or alias(), especially with FilteredRelation; allowlist dynamic annotation names. Redeploy all workers and rebuild images so old Django packages are gone.
How to confirm the fix worked
In each runtime, confirm django.get_version() is at least 5.2.9, 5.1.15, or 4.2.27. Search code for FilteredRelation used with annotate( or alias( and ** expansion. Check requirements.txt, poetry.lock, Pipfile.lock, and container images for django below those versions. In staging, exercise FilteredRelation annotation paths and confirm aliases are treated as identifiers (unexpected alias characters rejected, not interpolated). Do not use production data or live exploit tests.
Typical attack path
- Locate a Django app on PostgreSQL that uses FilteredRelation with QuerySet.annotate() or QuerySet.alias()
- Confirm alias names come from a dictionary expanded as **kwargs and can be influenced by the client
- Submit crafted alias keys intended to break out of the column-alias context
- Cause the ORM to include those aliases in SQL sent to PostgreSQL
- Influence query results or other SQL under the application's database role
How to detect it
No in-the-wild exploitation or named actors are reported. Hunt whether it was already abused by combining code exposure with data-store logs: find FilteredRelation used with annotate()/alias() and user-influenced kwargs; pull PostgreSQL logs (csvlog/stderr/pgaudit) around those endpoints for syntax errors near SELECT aliases or unexpected JOIN/ON SQL; review Django/app logs for ProgrammingError/DataError on annotate/alias; check DB audit for anomalous SELECTs from the app role. Correlate web request IDs with DB timestamps. Lack of those errors does not prove absence of successful (non-erroring) injection.
- django.db.utils.ProgrammingError
- django.db.utils.DataError
- psycopg2.errors.SyntaxError
- psycopg.errors.SyntaxError
- FilteredRelation
- QuerySet.annotate
- QuerySet.alias
- ERROR: syntax error at or near
What your security team should do
- Inventory Django+PostgreSQL apps and lockfiles; treat 5.2<5.2.9, 5.1<5.1.15, 4.2<4.2.27 and unsupported 5.0/4.1/3.2 as exposed
- Patch to 5.2.9, 5.1.15, or 4.2.27 (or later) and redeploy so no process still loads old Django
- Code-review FilteredRelation + annotate()/alias(**kwargs); stop passing untrusted dict keys as aliases
- After patching, review PostgreSQL and app error logs for historical alias-related SQL syntax failures
- Keep standard SQL-injection detections; this CVE is not KEV-listed and has no public exploit maturity, so prioritize by exposure of the unsafe ORM pattern
The record
An issue was discovered in 5.2 before 5.2.9, 5.1 before 5.1.15, and 4.2 before 4.2.27. `FilteredRelation` is subject to SQL injection in column aliases, using a suitably crafted dictionary, with dictionary expansion, as the `**kwargs` passed to `QuerySet.annotate()` or `QuerySet.alias()` on PostgreSQL. Earlier, unsupported Django series (such as 5.0.x, 4.1.x, and 3.2.x) were not evaluated and may also be affected. Django would like to thank Stackered for reporting this issue.
Affected packages
| Ecosystem | Package | Introduced | Fixed |
|---|---|---|---|
| PyPI | django | 4.2a1 | 4.2.27 |
| PyPI | django | 5.1a1 | 5.1.15 |
| PyPI | django | 5.2a1 | 5.2.9 |
| Bitnami | django | 4.2.0 | 4.2.27 |
| Chainguard | authentik | 0 | 2025.10.2-r2 |
| Chainguard | authentik-go-server | 0 | 2025.10.2-r2 |
| Chainguard | awx | 0 | 24.6.1-r20 |
| Mageia:9 | python-django | 0 | 4.1.13-1.9.mga9 |
| SUSE:Linux Enterprise Module for Package Hub 15 SP6 | python-Django | 0 | 4.2.11-150600.3.44.1 |
| SUSE:Linux Enterprise Module for Package Hub 15 SP7 | python-Django | 0 | 4.2.11-150600.3.44.1 |
| openSUSE:Leap 15.6 | python-Django | 0 | 4.2.11-150600.3.44.1 |
| openSUSE:Leap 16.0 | python-Django | 0 | 5.2.4-bp160.4.1 |
| openSUSE:Tumbleweed | python-Django | 0 | 5.2.9-1.1 |
| openSUSE:Tumbleweed | python-Django4 | 0 | 4.2.27-1.1 |
| openSUSE:Tumbleweed | python-Django6 | 0 | 6.0-1.1 |
| pypi | Django | 4.2a1 | No fix |
| pypi | Django | 5.1a1 | No fix |
| pypi | Django | 5.2a1 | No fix |
Related vulnerabilities
- GHSA-rqw2-ghq9-44m7 Django is vulnerable to SQL injection in column aliases Same vulnerability
- GHSA-8p8v-wh79-9r56 Django vulnerable to Uncontrolled Resource Consumption Also affects django
- GHSA-mjgh-79qc-68w3 Django has a Race Condition vulnerability Also affects django
- CVE-2026-25673 An issue was discovered in 6.0 before 6.0.3, 5.2 before 5.2.12, and 4.2 before 4.2.29. Also affects django
- CVE-2026-25674 An issue was discovered in 6.0 before 6.0.3, 5.2 before 5.2.12, and 4.2 before 4.2.29. Also affects django
- GHSA-6426-9fv3-65x8 Django has an SQL Injection issue Also affects django
- GHSA-gvg8-93h5-g6qq Django has an SQL Injection issue Also affects django
- GHSA-2mcm-79hx-8fxw Django has Observable Timing Discrepancy Also affects django