11 puntos por nuremberg 15 일 전 | 4 comentarios | Compartir por WhatsApp

Resumen general en una línea

Una vulnerabilidad de agotamiento de CPU preautenticación en MultiPartParser de Django ocurre cuando el cuerpo de una parte con Content-Transfer-Encoding: base64 contiene principalmente espacios en blanco, y una sola solicitud de unos 2.5 MB provoca un tiempo de procesamiento más de 2,100 veces mayor de lo normal (CVE-2026-33033)

Resumen

  • Puede activarse sin autenticación, incluso en servidores con configuración predeterminada
    • Como el middleware CSRF accede a request.POST antes de entrar a la view, MultiPartParser se ejecuta automáticamente, por lo que incluso en endpoints autenticados ya se consumen varios segundos durante la etapa de verificación CSRF
  • Una sola solicitud de 20 MB ocupa un worker individual durante alrededor de 1 minuto
    • En una configuración típica de gunicorn con 4 a 16 workers, apenas unas decenas de solicitudes simultáneas pueden dejar el servidor prácticamente paralizado
  • Django procesa las solicitudes multipart/form-data con MultiPartParser, y como el middleware CSRF accede a request.POST antes de entrar a la view, este parser siempre se ejecuta incluso sin autenticación
  • El núcleo de la vulnerabilidad está en una estructura donde se multiplican tres capas
    • (Layer 1) while-loop de alineación base64: si al eliminar espacios en blanco del chunk el estado remaining != 0 se mantiene, field_stream.read(1) se sigue llamando repetidamente sobre el resto completo del stream
    • (Layer 2) costo oculto O(C) de LazyStream.read(1): cada llamada a read(1) internamente extrae un buffer completo de ~64 KB y luego vuelve a insertar 65,535 bytes con unget(), repitiendo ese patrón
    • (Layer 3) concatenación O(C) de bytes en unget(): cada vez se crea un objeto nuevo con bytes + self._leftover
  • Una sola solicitud de 2.5 MB provoca internamente alrededor de 86 GB de copias de memoria, y en un M2 ocupa por completo un worker durante unos 5.3 segundos. Con 20 MB, tarda cerca de 1 minuto
  • Dentro de unget() ya existía código de sanity check (_update_unget_history), pero este ataque usa un patrón monótonamente decreciente donde el tamaño de unget() baja en 1 en cada llamada, por lo que nunca cumple la condición de detección (number_equal > 40)
  • El núcleo del parche del equipo de Django cambia read(4 - remaining) por read(self._chunk_size), para leer 64 KB de una vez en lugar de 1 a 3 bytes. Con eso, las llamadas a read bajan de 2.5 millones a unas 40
  • El valor predeterminado de client_max_body_size en Nginx es 1 MB, pero suele relajarse en endpoints de carga de archivos, y el valor predeterminado de LimitRequestBody en Apache httpd es 1 GB, así que solo con el proxy no se garantiza la defensa
  • La vulnerabilidad fue descubierta usando Claude Code + Codex, y resulta llamativo que un framework refinado durante casi 20 años aún conservara un DoS preautenticación

4 comentarios

 
kalista22 14 일 전

Vamosss

 
tangokorea 14 일 전

¿Alguien ya probó hacerlo directamente?

 
nuremberg 14 일 전

Hay un PoC hecho para demostración en GitHub.

https://github.com/ch4n3-yoon/CVE-2026-33033-PoC

 
tangokorea 14 일 전

Está buenísimo.