
Aviso: CVE-2026-38361 múltiples vulnerabilidades DoS (CWE-400/CWE-670) en dash-uploader (Python/PyPI)
Múltiples problemas de denegación de servicio (DoS) no autenticados en fohrloop/dash-uploader (Python, PyPI), incluidos, entre otros, la caída del proceso por agotamiento de memoria (OOM), la truncación de archivos a cero bytes, el agotamiento permanente del disco y la omisión completa del límite documentado de max_file_size. Existen vías adicionales de abuso de recursos a través del mismo conjunto de parámetros sin sanitizar.
El repositorio fue archivado el 2025-07-19 sin un mantenedor activo. Todas las versiones publicadas (de 0.1.0 a 0.7.0a2) están afectadas y lo seguirán estando. El paquete sigue obteniendo aproximadamente 28 000 descargas mensuales.
Cualquiera que ejecute dash-uploader en producción debe aplicar una mitigación por su cuenta. La solución recomendada es migrar al componente integrado dcc.Upload de Plotly Dash. Consulte Mitigación para conocer todas las opciones.
El manejador HTTP de dash-uploader acepta solicitudes POST no autenticadas con parámetros controlados por el atacante que se utilizan en la asignación de memoria, operaciones de archivo y creación de directorios sin comprobaciones de límites, sin límites de tasa y sin mecanismo de limpieza. Cuatro problemas independientes en la misma ruta de código:
Verificado en un sistema de 7,7 GB: 5 solicitudes POST concurrentes con resumableTotalChunks=30000000 activaron el OOM killer de Linux en 2 segundos. El registro del kernel lo confirma:
Out of memory: Killed process 24203 (python3) total-vm:8302276kB, anon-rss:7068012kB
Cada solicitud asigna aproximadamente 2,9 GB mediante una comprensión de lista sobre range(1, resumableTotalChunks + 1). El proceso del servidor se termina y la aplicación queda completamente indisponible hasta un reinicio manual.
Un archivo que contenía 42 bytes de datos se redujo a 0 bytes mediante una única solicitud POST con resumableTotalChunks=0. La causa raíz es que all() de Python devuelve True para iterables vacíos, lo que engaña al manejador de subida para que trate cero fragmentos como una subida completada. El archivo existente se elimina mediante os.unlink() y se reemplaza por un archivo vacío.
Se crearon 10 directorios temporales huérfanos con archivos de fragmentos que persistieron indefinidamente en el disco. Una búsqueda en toda la base de código en todos los archivos fuente de cleanup, ttl, expire, garbage, purge, cron, schedule y periodic arrojó cero resultados. La única llamada de limpieza (shutil.rmtree) se ejecuta exclusivamente en subidas completadas. No existe ningún mecanismo para recuperar espacio en disco de sesiones incompletas.
max_file_size (verificada)El servidor aceptó un fragmento de 5 MB para un archivo que declaraba resumableTotalSize=999999999999 (~999 GB) con HTTP 200. El parámetro max_file_size se pasa únicamente al componente JavaScript de React. El servidor nunca comprueba el tamaño del archivo, el tamaño del fragmento, Content-Length ni MAX_CONTENT_LENGTH de Flask. Un desarrollador que establezca max_file_size=10 no tiene protección en el lado del servidor.
# dash_uploader/httprequesthandler.py
def _post(self):
resumableTotalChunks = request.form.get("resumableTotalChunks", type=int) # attacker-controlled, no bounds
...
chunk_paths = [
os.path.join(temp_dir, get_chunk_name(resumableFilename, x))
for x in range(1, resumableTotalChunks + 1) # unbounded; e.g. 30M -> ~2.9 GB -> OOM
]
upload_complete = all([os.path.exists(p) for p in chunk_paths]) # all([]) is True -> truncation when chunks=0
if upload_complete:
target_file_name = os.path.join(temp_root, resumableFilename)
if os.path.exists(target_file_name):
os.unlink(target_file_name) # existing file deleted
with open(target_file_name, "ab") as target_file:
for p in chunk_paths: # empty list -> empty file written
...
La misma ruta de código produce tanto la caída por OOM (resumableTotalChunks grande) como la primitiva de truncación de archivos (resumableTotalChunks=0).
Un atacante envía solicitudes POST no autenticadas al endpoint /API/resumable.
resumableTotalChunks=30000000 asignan ~2,9 GB cada una, activando el OOM killer.resumableTotalChunks=0; Python all([])=True engaña al servidor para que sobrescriba el archivo objetivo con contenido vacío.No se requiere autenticación ni privilegios.
resumableTotalChunks)os.makedirs() con resumableIdentifier sin sanitizarall() de Python que devuelve True para iterables vacíos cuando resumableTotalChunks=0max_file_size se aplica solo en el JavaScript del lado del cliente, mientras que el manejador del lado del servidor no realiza ninguna validación de tamaño y nunca establece MAX_CONTENT_LENGTH de Flaskdash_uploader/httprequesthandler.py (método BaseHttpRequestHandler._post)dash_uploader/upload.py (función Upload, parámetro max_file_size)dash_uploader/configure_upload.py (falta MAX_CONTENT_LENGTH)Opciones para los usuarios con implementaciones actuales, en orden de preferencia:
dcc.Upload, el componente de subida oficial incluido con Plotly Dash. No tiene parámetro de número de fragmentos, no tiene estado temporal en disco y respeta MAX_CONTENT_LENGTH de Flask. Ninguno de los cuatro problemas aquí aplica. Es el más adecuado para archivos pequeños y medianos. Para subidas muy grandes, consulte el punto 2.MAX_CONTENT_LENGTH), límites en cualquier recuento de fragmentos proporcionado por el cliente y una lista blanca de nombres de archivo aceptados.MAX_CONTENT_LENGTH de Flask a nivel de aplicación (la biblioteca no lo hace) y rechace las entradas en la capa de aplicación o proxy inverso cuando se cumpla cualquiera de las siguientes condiciones:
resumableTotalChunks <= 0resumableTotalChunks supere un límite razonable (p. ej., 10 000)resumableTotalSize supere el max_file_size configurado por el desarrollador0.6.1 (línea estable). Las versiones preliminares llegan hasta 0.7.0a2.dash. Dependencia opcional: pyyaml. Licencia: MIT.Muhammad Fitri Bin Mohd Sultan
| ID de CVE | CVE-2026-38361 (NVD) |
| Vulnerabilidad | Consumo de recursos no controlado (CWE-400), Implementación de flujo de control siempre incorrecta (CWE-670) |
| CVSS 3.1 | 7.5 / Alta (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H) |
| Producto | dash-uploader |
| Versiones afectadas | de 0.1.0 a 0.7.0a2 (las 18 versiones) |
| Versión corregida | ninguna (proyecto archivado el 2025-07-19) |
| Vector de ataque | Remoto, no autenticado |
| Descubridor | Muhammad Fitri Bin Mohd Sultan |
| Asignado por | MITRE, 2026-05-07 |
| Relacionado | CVE-2026-38360 (recorrido de rutas en la misma biblioteca) |
| Fecha | Evento |
|---|
| 2026-03-19 | Vulnerabilidades descubiertas durante una investigación de seguridad en un despliegue de producción. |
| 2026-03-22 | Solicitud de CVE enviada a MITRE. |
| 2026-05-07 | CVE-2026-38361 asignado por MITRE. |
| 2026-05-07 | Aviso público publicado. |
| 2026-05-09 | Registro CVE publicado en la base de datos CVE de MITRE y en el NVD. |