
Aviso: CVE-2026-38360 recorrido de rutas (CWE-22) en dash-uploader (Python/PyPI)
Path traversal no autenticado en fohrloop/dash-uploader (Python, PyPI) que permite la escritura arbitraria de archivos, lo que conduce a (entre otros) ejecución remota de código (RCE), sobrescritura del código fuente de la aplicación, XSS almacenado e instalación de puertas traseras persistentes.
El repositorio fue archivado el 2025-07-19 sin un mantenedor activo. Todas las versiones publicadas (desde 0.1.0 hasta 0.7.0a2) están afectadas y seguirán estándolo. El paquete sigue registrando aproximadamente 28 000 descargas mensuales.
Cualquier persona que ejecute dash-uploader en producción debe aplicar una mitigación por su cuenta. La solución recomendada es migrar al componente dcc.Upload integrado de Plotly Dash. Consulta Mitigación para conocer todas las opciones.
| ID de CVE | CVE-2026-38360 (NVD) |
| Vulnerabilidad | Path Traversal (CWE-22) |
| CVSS 3.1 | 9.8 / Crítico (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) |
| Producto | dash-uploader |
| Versiones afectadas | 0.1.0 hasta 0.7.0a2 (las 18 versiones) |
| Versión corregida | ninguna (proyecto archivado el 2025-07-19) |
| Vector de ataque | Remoto, sin autenticación |
| Descubridor | Muhammad Fitri Bin Mohd Sultan |
| Asignado por | MITRE, 2026-05-07 |
| Relacionado | CVE-2026-38361 (DoS en la misma librería) |
Tres parámetros controlados por el usuario, obtenidos de request.form.get() en dash_uploader/httprequesthandler.py, se pasan directamente a os.path.join() y os.makedirs() sin ninguna sanitización ni validación:
upload_id (línea 57 → línea 161, BaseHttpRequestHandler.get_temp_root): controla el directorio de destino. Un atacante puede enviar upload_id=../../../../usr/local/lib/python3.10/site-packages y los archivos se escribirán en el directorio de paquetes de Python.
resumableFilename (línea 51 → línea 108, BaseHttpRequestHandler._post): controla el nombre de archivo final. Un atacante puede salir del directorio de carga mediante el nombre de archivo incluso con un upload_id legítimo.
resumableIdentifier (línea 54 → línea 64, BaseHttpRequestHandler._post): se usa con os.makedirs() para crear el directorio temporal. Un atacante puede crear directorios arbitrarios en cualquier parte del sistema de archivos.
El endpoint de carga (/API/dash-uploader por defecto) no requiere autenticación. El hook http_request_handler añadido en v0.5.0 permite comprobaciones previas a la petición mediante post_before(), pero el método vulnerable _post() lee todos los parámetros directamente de request.form después de que el hook retorne. El hook no puede sanitizar los parámetros antes de que la librería los procese. Un desarrollador que añada autenticación mediante el hook seguirá siendo vulnerable al path traversal por parte de un usuario autenticado.
# dash_uploader/httprequesthandler.py
def _post(self):
resumableFilename = request.form.get("resumableFilename", default="error", type=str)
resumableIdentifier = request.form.get("resumableIdentifier", default="error", type=str)
upload_id = request.form.get("upload_id", default="", type=str)
...
temp_root = self.get_temp_root(upload_id) # upload_id flows in here
temp_dir = os.path.join(temp_root, resumableIdentifier) # raw user input -> os.path.join
if not os.path.isdir(temp_dir):
os.makedirs(temp_dir) # raw user input -> os.makedirs
def get_temp_root(self, upload_id):
return os.path.join(self.upload_folder, upload_id) # no sanitization: ../../ escapes upload_folder
Tres sumideros de traversal independientes comparten la misma causa raíz: los valores del formulario llegan a os.path.join y os.makedirs sin validación.
Un atacante remoto sin autenticar envía una petición HTTP POST multipart al endpoint de carga. Al inyectar secuencias de path traversal (../) en el parámetro de formulario upload_id, el atacante controla el directorio de destino del archivo subido. Por ejemplo, upload_id=../../../../usr/local/lib/python3.10/site-packages escribe archivos en el directorio de paquetes de Python, lo que permite RCE en el siguiente arranque del intérprete mediante la ejecución automática de .pth.
No se requiere autenticación, token de sesión ni token CSRF. Dos parámetros adicionales (resumableFilename y resumableIdentifier) proporcionan vectores de traversal independientes a través del mismo endpoint. La configuración predeterminada de la librería, tal como se muestra en la documentación oficial de inicio rápido, es explotable con un único comando curl.
Escritura arbitraria de archivos en cualquier directorio escribible por el proceso del servidor. Esto se traduce en ejecución remota de código (RCE) mediante varias primitivas bien conocidas:
Primitivas de RCE
.pth de Python en site-packages. Ejecuta código suministrado por el atacante en el siguiente arranque del intérprete.sitecustomize.py o usercustomize.py. Se ejecuta en cada inicio de Python.app.wsgi, wsgi.py). Se ejecuta en la siguiente recarga del worker./etc/cron.d/, /etc/cron.hourly/, cola de crontab del usuario) cuando el proceso tiene los privilegios necesarios. Ejecución programada./etc/systemd/system/, ~/.config/systemd/user/). Se ejecuta en el siguiente inicio del servicio o reinicio./etc/ld.so.preload cuando el proceso se ejecuta como root. Precarga código del atacante en cada ejecución binaria posterior.~/.bashrc, ~/.profile, ~/.bash_profile). Se ejecuta en el siguiente inicio de sesión interactivo del usuario de la aplicación.~/.ssh/authorized_keys. Otorga acceso SSH persistente al host como usuario de la aplicación.Impacto a nivel web
site-packages), que afecta a todos los usuarios en cada carga de página hasta que la aplicación se reinicie.Impacto en el sistema de archivos
os.makedirs() con el parámetro resumableIdentifier sin sanitizar (utilizable para el agotamiento de inodos o para preparar escrituras en árboles de directorios inexistentes).dash_uploader/httprequesthandler.pyBaseHttpRequestHandler.get_temp_root()BaseHttpRequestHandler._post()Opciones para los usuarios que la tienen implementada actualmente, en orden de preferencia:
dcc.Upload, el componente de carga oficial incluido con Plotly Dash. Los archivos llegan al callback como una cadena base64; no se expone ningún handler que escriba en el sistema de archivos y no existe ninguna ruta de destino controlada por el cliente, por lo que esta clase de bug no aplica. Es la mejor opción para archivos pequeños y medianos. Para cargas muy grandes, consulta el punto 2.werkzeug.utils.secure_filename() y un directorio de destino fijo en el lado del servidor. Nunca aceptes valores de upload_id, nombre de archivo o identificador suministrados por el cliente como componentes de una ruta.upload_id, resumableFilename y resumableIdentifier contra una lista de permitidos estricta (p. ej., solo UUID) en una capa que reescriba o rechace la petición antes de que el handler de la librería la vea. El hook http_request_handler de la librería NO previene el traversal porque los parámetros se leen de request.form después de que el hook retorne; la sanitización debe realizarse por encima de la librería..., variantes codificadas (%2e%2e, ..%2f, %2e%2e%2f) o rutas absolutas.| Fecha | Evento |
|---|---|
| 2026-03-17 | Vulnerabilidad descubierta durante una investigación de seguridad en una implementación de producción. |
| 2026-03-19 | Solicitud de CVE enviada a MITRE. |
| 2026-05-07 | CVE-2026-38360 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. |
0.6.1 (línea estable). Las versiones preliminares llegan hasta 0.7.0a2.dash. Dependencia opcional: pyyaml. Licencia: MIT.Muhammad Fitri Bin Mohd Sultan