Skip to main content
CVE Vulnerability Database
Vulnerability Database/CVE-2025-13372

CVE-2025-13372: Django FilteredRelation SQLi Vulnerability

CVE-2025-13372 is a SQL injection vulnerability in Django's FilteredRelation affecting PostgreSQL implementations. Attackers can exploit column aliases via crafted dictionaries. This article covers technical details, affected versions, impact, and mitigation strategies.

Published:

CVE-2025-13372 Overview

CVE-2025-13372 is a SQL injection vulnerability in the Django web framework affecting the FilteredRelation query construct. Attackers can inject SQL through column aliases when a suitably crafted dictionary is expanded as **kwargs into QuerySet.annotate() or QuerySet.alias() on PostgreSQL backends. The flaw is classified under CWE-89 (Improper Neutralization of Special Elements used in an SQL Command). Django maintainers credit Stackered for reporting the issue and released fixes on December 2, 2025.

Critical Impact

Applications that pass user-controlled dictionary keys into QuerySet.annotate() or QuerySet.alias() with FilteredRelation on PostgreSQL can leak database contents through injected SQL fragments in column aliases.

Affected Products

  • Django 5.2 before 5.2.9
  • Django 5.1 before 5.1.15
  • Django 4.2 before 4.2.27
  • Unsupported series (5.0.x, 4.1.x, 3.2.x) may also be affected but were not evaluated

Discovery Timeline

  • 2025-12-02 - Django releases security fixes and publishes advisory, crediting Stackered for reporting the issue
  • 2025-12-02 - CVE-2025-13372 published to NVD
  • 2026-06-17 - Last updated in NVD database

Technical Details for CVE-2025-13372

Vulnerability Analysis

The vulnerability resides in Django's ORM handling of FilteredRelation, an API used to create conditional joins in querysets. When a developer expands a dictionary as keyword arguments into QuerySet.annotate(**kwargs) or QuerySet.alias(**kwargs), the dictionary keys become column aliases in the generated SQL. Django did not adequately neutralize these alias identifiers before embedding them into the PostgreSQL query text.

An attacker who controls the keys of that dictionary can inject arbitrary SQL fragments into the alias position of a SELECT clause. Because the injection point sits inside the query rendered against PostgreSQL, the resulting payload executes with the privileges of the application database user. Impact is limited to confidentiality of database data based on the CVSS scoring, and exploitation requires user interaction, typically a request that triggers the vulnerable code path.

Root Cause

The root cause is missing identifier quoting and validation on FilteredRelation column aliases prior to SQL generation. Django's ORM treats developer-supplied kwargs as trusted identifiers, but applications frequently forward client-supplied names (for example, sort keys, filter names, or dynamic field selectors) directly into annotate() or alias() without sanitization.

Attack Vector

Exploitation requires an application that dynamically builds FilteredRelation annotations from external input on a PostgreSQL database. A typical pattern is a request handler that accepts a JSON body or query parameters and expands them into QuerySet.annotate(**request_data). The attacker supplies a key containing SQL metacharacters, which Django then emits verbatim into the alias portion of the generated statement. See the Django Weblog security release notes for the vendor description of the attack path.

Detection Methods for CVE-2025-13372

Indicators of Compromise

  • PostgreSQL query logs containing unusual SQL tokens (quotes, comments, UNION, ;) inside column alias positions of SELECT statements originating from Django worker processes
  • Application error logs showing django.db.utils.ProgrammingError or PostgreSQL syntax errors near FilteredRelation usage
  • HTTP request payloads with JSON keys or query parameter names containing SQL metacharacters targeting endpoints backed by annotate() or alias()

Detection Strategies

  • Audit source code for calls to QuerySet.annotate(**data) or QuerySet.alias(**data) where data originates from request input, and flag any that combine with FilteredRelation
  • Enable PostgreSQL statement logging (log_statement = 'all') in staging environments and grep for anomalous alias syntax
  • Deploy web application firewall rules that block request parameter names containing quotes, semicolons, or SQL keywords

Monitoring Recommendations

  • Monitor application logs for spikes in ORM-generated query failures on endpoints that expose dynamic annotations
  • Track Django version inventory across production services and alert when instances fall below 5.2.9, 5.1.15, or 4.2.27
  • Correlate WAF alerts with database error rates to identify probing attempts against FilteredRelation code paths

How to Mitigate CVE-2025-13372

Immediate Actions Required

  • Upgrade Django to 5.2.9, 5.1.15, or 4.2.27 depending on the deployed series
  • Audit all uses of QuerySet.annotate() and QuerySet.alias() and confirm that dictionary keys are not derived from untrusted input
  • Rotate database credentials if PostgreSQL logs show evidence of injected alias payloads

Patch Information

The Django project released patched versions 5.2.9, 5.1.15, and 4.2.27 on December 2, 2025. Details are available in the Django Security Release Documentation and the Django Weblog security announcement. Users on unsupported series (5.0.x, 4.1.x, 3.2.x) should migrate to a supported release, as those branches were not evaluated but may share the defect.

Workarounds

  • Validate and allowlist dictionary keys before passing them to annotate() or alias(), restricting them to a fixed set of identifier characters (^[A-Za-z_][A-Za-z0-9_]*$)
  • Avoid dictionary unpacking of untrusted data into ORM methods; construct keyword arguments explicitly from vetted values
  • Apply least-privilege database roles so that the Django application user cannot read tables outside its functional scope
bash
# Upgrade Django to a patched release matching your series
pip install --upgrade 'Django>=5.2.9,<5.3'   # 5.2.x series
pip install --upgrade 'Django>=5.1.15,<5.2'  # 5.1.x series
pip install --upgrade 'Django>=4.2.27,<4.3'  # 4.2.x LTS series

# Verify installed version
python -c "import django; print(django.get_version())"

Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

Default Legacy - Prefooter | Experience the World’s Most Advanced Cybersecurity Platform

Experience the Most Advanced Cybersecurity Platform

See how the world’s most intelligent, autonomous cybersecurity platform can protect your organization today and into the future.