# Django SQL Injection via FilteredRelation: CVE-2025-13372

> Critical Django SQL injection vulnerability through FilteredRelation dictionary expansion on PostgreSQL databases.

**URL:** https://www.ciptadusa.com/blog/django-sql-injection-filteredrelation-cve-2025-13372  
**Type:** blog  
**Author:** PT Cipta Dua Saudara  
**Category:** Application Security  
**Published:** 2026-05-30  
**Cover:** https://www.ciptadusa.com/media/defaults/blog-cover.svg  

## Article

# Django SQL Injection via FilteredRelation: CVE-2025-13372

## Overview

A critical SQL injection vulnerability was discovered in Django's ORM system, specifically in the `FilteredRelation` feature. Tracked as CVE-2025-13372, this vulnerability affects Django versions 5.2 before 5.2.9, 5.1 before 5.1.15, and 4.2 before 4.2.27.

## Technical Details

### Vulnerability Description

The vulnerability exists in how Django's `FilteredRelation` handles column aliases when using dictionary expansion (`**kwargs`) with `QuerySet.annotate()` or `QuerySet.alias()` methods on PostgreSQL databases.

### Root Cause

When developers use dictionary expansion with `QuerySet.annotate()` or `QuerySet.alias()`, Django fails to properly sanitize column alias names. This allows attackers to inject arbitrary SQL code through specially crafted dictionary keys.

### Affected Code Patterns

```python
# Vulnerable pattern
from django.db.models import FilteredRelation, Q

# Using dictionary expansion with user-controlled data
user_input = request.GET.get('filter_name', 'default')
kwargs = {user_input: FilteredRelation('relation', condition=Q(...))}

# This is vulnerable
queryset = queryset.annotate(**kwargs)
```

### Exploitation Example

An attacker could inject SQL through the dictionary key:

```python
# Malicious input
user_input = "malicious_alias'; DROP TABLE users; --"

# Results in SQL injection
kwargs = {user_input: FilteredRelation('relation')}
queryset = queryset.annotate(**kwargs)
```

## Impact Assessment

### Severity: Critical

- **Confidentiality**: High - Access to all database data
- **Integrity**: High - Modification or deletion of data
- **Availability**: High - Potential database destruction

### Affected Databases

- PostgreSQL (primary target)
- MySQL and MariaDB (CVE-2025-59681 addresses similar issues)

## Mitigation Strategies

### Immediate Actions

1. **Update Django** to patched versions:
   - Django 5.2.9 or later
   - Django 5.1.15 or later
   - Django 4.2.27 or later

2. **Audit Codebase**: Search for vulnerable patterns:
   ```bash
   grep -r "annotate(\*\*" --include="*.py"
   grep -r "alias(\*\*" --include="*.py"
   ```

### Code Remediation

```python
# Before (vulnerable)
kwargs = {user_input: FilteredRelation('relation')}
queryset = queryset.annotate(**kwargs)

# After (safe)
# Option 1: Use explicit keyword arguments
queryset = queryset.annotate(
    safe_name=FilteredRelation('relation')
)

# Option 2: Validate input against allowlist
ALLOWED_FILTERS = ['category', 'status', 'type']
if user_input in ALLOWED_FILTERS:
    queryset = queryset.annotate(**{user_input: FilteredRelation('relation')})
```

### Defense in Depth

```python
# Use Django's built-in protection mechanisms
from django.db.models import FilteredRelation, Q

def safe_annotate_with_filtered_relation(queryset, filter_name, relation, condition):
    """Safely apply FilteredRelation with input validation."""
    # Validate filter name
    if not filter_name.isidentifier():
        raise ValueError("Invalid filter name")
    
    # Use explicit keyword argument
    return queryset.annotate(
        **{filter_name: FilteredRelation(relation, condition=condition)}
    )
```

## Related Vulnerabilities

### CVE-2025-59681

Similar SQL injection vulnerability affecting MySQL and MariaDB through `QuerySet.annotate()`, `QuerySet.alias()`, `QuerySet.aggregate()`, and `QuerySet.extra()` methods.

### CVE-2024-39329

User enumeration vulnerability through timing attacks on authentication backends.

## Best Practices for Django ORM Security

### Input Validation

1. **Never trust user input**: Always validate and sanitize
2. **Use allowlists**: Define acceptable values for dynamic parameters
3. **Avoid string interpolation**: Use parameterized queries

### ORM Usage

```python
# Safe patterns
# 1. Use explicit keyword arguments
queryset = queryset.annotate(count=Count('items'))

# 2. Use Q objects for dynamic conditions
from django.db.models import Q
condition = Q(status='active') & Q(created_at__year=2024)
queryset = queryset.filter(condition)

# 3. Use Django's built-in methods
queryset = queryset.values('category').annotate(total=Count('id'))
```

### Security Testing

```python
# Test for SQL injection
import pytest
from django.test import RequestFactory

def test_filtered_relation_injection():
    """Test that SQL injection attempts are blocked."""
    malicious_input = "test'; DROP TABLE users; --"
    
    with pytest.raises(ValueError):
        # Should raise error for invalid input
        safe_annotate_with_filtered_relation(
            MyModel.objects.all(),
            malicious_input,
            'relation',
            Q(status='active')
        )
```

## Django Security Updates

Django's security team has a strong track record of addressing vulnerabilities promptly. Regular security updates are released through:

- Django security mailing list
- GitHub security advisories
- Django documentation security pages

## Conclusion

CVE-2025-13372 underscores the importance of input validation even when using ORM systems. While ORMs provide abstraction layers that can prevent many SQL injection attacks, developers must still be cautious with dynamic query construction.

## References

- [NVD - CVE-2025-13372](https://nvd.nist.gov/vuln/detail/CVE-2025-13372)
- [Django Security Releases](https://docs.djangoproject.com/en/dev/releases/security/)
- [Django Documentation - FilteredRelation](https://docs.djangoproject.com/en/stable/ref/models/expressions/#filteredrelation)

---

*Markdown version of https://www.ciptadusa.com/blog/django-sql-injection-filteredrelation-cve-2025-13372 — generated for AI agents and LLM crawlers.*
