
Preuve de concept éducative démontrant une injection SQL via des alias dynamiques dans les méthodes annotate() et alias() de Django (CVE-2025-57833). Comprend du code vulnérable, un exemple d'exploitation et une analyse de correctif.
Un alias est un nom donné à une colonne ou à un résultat d'agrégation dans une requête SQL.
Dans Django, les alias sont utilisés avec les méthodes annotate() ou alias() pour nommer des champs calculés.
Exemple :
from django.db.models import Count
books = Book.objects.annotate(book_count=Count('id'))
Ici, book_count est un alias pour le résultat de Count('id').
Cela permet de récupérer un nom lisible pour une colonne calculée.
Pour aller vite, on peut insérer les données via le shell :
python manage.py shell
Commandes à exécuter :
from myapp.models import Author, Book
a = Author.objects.create(name=" houchi pierre")
Book.objects.create(title="Les belles filles", author=a)
Book.objects.create(title="Les baux arcons", author=a)
exit()
Avant Django 4.2.23, il était possible d’injecter du SQL via des alias dynamiques fournis par l’utilisateur.
Cette vulnérabilité est connue sous CVE-2025-57833.
Elle affectait les fonctions annotate() et alias() lorsqu’un dictionnaire avec expansion (**kwargs) était utilisé avec des clés non filtrées provenant de l’utilisateur.
myapp/views.py)import json
from django.db.models import Count
from django.http import JsonResponse
from .models import Author
def vulnerable_view(request):
# On récupère l'alias depuis l'URL
alias_param = request.GET.get("alias", "{}")
try:
# ⚠️ Vulnérable : évaluation directe des données utilisateurs
alias_dict = json.loads(alias_param)
for key, value in alias_dict.items():
alias_dict[key] = eval(value) # dangereux ! permet l'exécution de code arbitraire
# Création de la queryset avec annotation dynamique
qs = Author.objects.annotate(**alias_dict).values("name", *alias_dict.keys())
# Retour des résultats
return JsonResponse(list(qs), safe=False)
except Exception as e:
return JsonResponse({"error": str(e)})
alias_param = request.GET.get("alias", "{}")
Récupère la valeur du paramètre alias passé par l’utilisateur.
alias_dict = json.loads(alias_param)
Transforme la chaîne JSON en dictionnaire Python.
alias_dict[key] = eval(value)
⚠️ Très dangereux : eval() exécute la chaîne comme du code Python.
L’utilisateur pourrait injecter du SQL destructeur.
Author.objects.annotate(**alias_dict)
Applique dynamiquement les annotations avec les alias fournis.
myapp/models.py)from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
def __str__(self):
return self.title
Explication :
Author et Book sont reliés par une relation ForeignKey.Author pour calculer des informations sur ses livres.http://127.0.0.1:8000/vuln/?alias={"evil); DROP TABLE myapp_book;--":"Count('id')"}
Django interdit désormais certains caractères dans les alias :
--)Si l’URL contient ces caractères, Django lève une erreur :
Column aliases cannot contain whitespace characters, quotation marks, semicolons, or SQL comments.
http://127.0.0.1:8000/vuln/?alias={"books_count":"Count('book')"}
eval() sur des valeurs provenant de l’utilisateur.eval() sur des données utilisateurs.Auteur : Loïc
Date : 06/09/2025