
Avviso: CVE-2026-38361 molteplici vulnerabilità DoS (CWE-400/CWE-670) in dash-uploader (Python/PyPI)
Molteplici problemi Denial of Service (DoS) non autenticati in fohrloop/dash-uploader (Python, PyPI), inclusi (ma non limitati a) crash del processo per esaurimento della memoria (OOM), troncamento dei file a zero byte, esaurimento permanente del disco e bypass completo del limite max_file_size documentato. Ulteriori percorsi di abuso delle risorse esistono attraverso lo stesso insieme di parametri non sanificato.
Il repository è stato archiviato il 2025-07-19 senza un manutentore attivo. Ogni versione pubblicata (da 0.1.0 a 0.7.0a2) è affetta e lo rimarrà. Il pacchetto registra ancora circa 28.000 download mensili.
Chiunque esegua dash-uploader in produzione deve applicare una mitigazione da sé. La soluzione consigliata è migrare al componente dcc.Upload integrato di Plotly Dash. Vedi Mitigazione per tutte le opzioni.
Il gestore HTTP di dash-uploader accetta richieste POST non autenticate con parametri controllati dall'attaccante che confluiscono nell'allocazione di memoria, nelle operazioni sui file e nella creazione di directory senza controlli sui limiti, senza limiti di frequenza e senza meccanismo di pulizia. Quattro problemi indipendenti nello stesso percorso di codice:
Verificato su un sistema con 7,7 GB: 5 richieste POST concorrenti con resumableTotalChunks=30000000 hanno attivato l'OOM killer di Linux entro 2 secondi. Il log del kernel conferma:
Out of memory: Killed process 24203 (python3) total-vm:8302276kB, anon-rss:7068012kB
Ogni richiesta alloca circa 2,9 GB tramite una list comprehension su range(1, resumableTotalChunks + 1). Il processo del server viene terminato e l'applicazione diventa completamente non disponibile fino a un riavvio manuale.
Un file contenente 42 byte di dati è stato ridotto a 0 byte tramite una singola richiesta POST con resumableTotalChunks=0. La causa principale è che all() di Python restituisce True per gli iterabili vuoti, il che inganna il gestore di upload facendogli trattare zero chunk come un upload completato. Il file esistente viene eliminato tramite os.unlink() e sostituito con un file vuoto.
10 directory temporanee orfane con file chunk sono state create e persistono indefinitamente sul disco. Una ricerca a livello di codebase in tutti i file sorgente per cleanup, ttl, expire, garbage, purge, cron, schedule e periodic non ha restituito alcun risultato. L'unica chiamata di pulizia (shutil.rmtree) viene eseguita esclusivamente su upload completati. Non esiste alcun meccanismo per recuperare spazio su disco dalle sessioni incomplete.
max_file_size (verificato)Il server ha accettato un chunk da 5 MB per un file che dichiarava resumableTotalSize=999999999999 (~999 GB) con HTTP 200. Il parametro max_file_size viene passato solo al componente React JavaScript. Il server non controlla mai la dimensione del file, la dimensione dei chunk, Content-Length o MAX_CONTENT_LENGTH di Flask. Uno sviluppatore che imposta max_file_size=10 non ha alcuna protezione lato server.
# 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
...
Lo stesso percorso di codice produce sia la primitiva OOM (resumableTotalChunks grande) sia la primitiva di troncamento dei file (resumableTotalChunks=0).
Un attaccante invia richieste POST non autenticate all'endpoint /API/resumable.
resumableTotalChunks=30000000 allocano ~2,9 GB ciascuna, attivando l'OOM killer.resumableTotalChunks=0; Python all([])=True inganna il server facendogli sovrascrivere il file target con contenuto vuoto.Non è richiesta autenticazione né privilegi.
resumableTotalChunks)os.makedirs() con resumableIdentifier non sanificatoall() di Python che restituisce True per gli iterabili vuoti quando resumableTotalChunks=0max_file_size viene applicato solo nel JavaScript lato client, mentre il gestore lato server non effettua alcuna validazione della dimensione e non imposta mai MAX_CONTENT_LENGTH di Flaskdash_uploader/httprequesthandler.py (metodo BaseHttpRequestHandler._post)dash_uploader/upload.py (funzione Upload, parametro max_file_size)dash_uploader/configure_upload.py (MAX_CONTENT_LENGTH mancante)Opzioni per gli utenti con deployment attivi, in ordine di preferenza:
dcc.Upload, il componente di upload ufficiale distribuito con Plotly Dash. Non ha un parametro per il numero di chunk, nessuno stato temporaneo su disco e rispetta MAX_CONTENT_LENGTH di Flask. Nessuno dei quattro problemi qui descritti si applica. Ideale per file piccoli e medi. Per upload molto grandi, vedere il punto 2.MAX_CONTENT_LENGTH), limiti su qualsiasi numero di chunk fornito dal client e una allowlist per i nomi di file accettati.MAX_CONTENT_LENGTH di Flask a livello di applicazione (la libreria non lo fa) e rifiutare gli input a livello di applicazione o di reverse proxy quando si verifica una delle seguenti condizioni:
resumableTotalChunks <= 0resumableTotalChunks supera un limite ragionevole (es. 10.000)resumableTotalSize supera il max_file_size configurato dallo sviluppatore0.6.1 (linea stabile). Le pre-release arrivano fino a 0.7.0a2.dash. Dipendenza opzionale: pyyaml. Licenza: MIT.Muhammad Fitri Bin Mohd Sultan
| CVE ID | CVE-2026-38361 (NVD) |
| Vulnerabilità | Consumo incontrollato di risorse (CWE-400), Implementazione del flusso di controllo sempre errata (CWE-670) |
| CVSS 3.1 | 7.5 / Alto (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H) |
| Prodotto | dash-uploader |
| Versioni interessate | 0.1.0 fino a 0.7.0a2 (tutte le 18 release) |
| Versione corretta | nessuna (progetto archiviato il 2025-07-19) |
| Vettore d'attacco | Remoto, non autenticato |
| Scopritore | Muhammad Fitri Bin Mohd Sultan |
| Assegnato da | MITRE, 2026-05-07 |
| Correlato | CVE-2026-38360 (path traversal nella stessa libreria) |
| Data | Evento |
|---|
| 2026-03-19 | Vulnerabilità scoperte durante una ricerca sulla sicurezza su un deployment in produzione. |
| 2026-03-22 | Richiesta CVE inviata a MITRE. |
| 2026-05-07 | CVE-2026-38361 assegnato da MITRE. |
| 2026-05-07 | Pubblicato l'advisory pubblico. |
| 2026-05-09 | Record CVE pubblicato sul database CVE di MITRE e sul NVD. |