
Avis : CVE-2026-38361 multiples vulnérabilités de déni de service (CWE-400/CWE-670) dans dash-uploader (Python/PyPI)
De multiples problèmes de déni de service (DoS) non authentifiés dans fohrloop/dash-uploader (Python, PyPI), incluant (sans s'y limiter) un crash du processus par manque de mémoire (OOM), une troncature de fichier à zéro octet, un épuisement permanent du disque et un contournement complet de la limite max_file_size documentée. D'autres voies d'abus de ressources existent via le même ensemble de paramètres non assainis.
Le dépôt a été archivé le 2025-07-19 sans mainteneur actif. Chaque version publiée (0.1.0 à 0.7.0a2) est concernée et le restera. Le paquet attire toujours environ 28 000 téléchargements mensuels.
Toute personne exécutant dash-uploader en production doit appliquer elle-même une mesure d'atténuation. La correction recommandée consiste à migrer vers le composant dcc.Upload intégré de Plotly Dash. Voir Atténuation pour toutes les options.
Le gestionnaire HTTP de dash-uploader accepte des requêtes POST non authentifiées avec des paramètres contrôlés par l'attaquant qui alimentent l'allocation mémoire, les opérations sur fichiers et la création de répertoires, sans vérification de bornes, sans limite de débit et sans mécanisme de nettoyage. Quatre problèmes indépendants dans le même chemin de code :
Vérifié sur un système de 7,7 Go : 5 requêtes POST concurrentes avec resumableTotalChunks=30000000 ont déclenché le tueur OOM de Linux en moins de 2 secondes. Le journal du noyau le confirme :
Out of memory: Killed process 24203 (python3) total-vm:8302276kB, anon-rss:7068012kB
Chaque requête alloue environ 2,9 Go via une compréhension de liste sur range(1, resumableTotalChunks + 1). Le processus serveur est tué et l'application devient totalement indisponible jusqu'à un redémarrage manuel.
Un fichier contenant 42 octets de données a été réduit à 0 octet via une seule requête POST avec resumableTotalChunks=0. La cause racine est que all() de Python renvoie True pour les itérables vides, ce qui trompe le gestionnaire de téléversement en lui faisant considérer zéro fragment comme un téléversement terminé. Le fichier existant est supprimé via os.unlink() et remplacé par un fichier vide.
10 répertoires temporaires orphelins avec des fichiers de fragments ont été créés et ont persisté indéfiniment sur le disque. Une recherche dans l'ensemble de la base de code, dans tous les fichiers source, pour cleanup, ttl, expire, garbage, purge, cron, schedule et periodic n'a renvoyé aucun résultat. Le seul appel de nettoyage (shutil.rmtree) s'exécute exclusivement lors des téléversements terminés. Il n'existe aucun mécanisme pour récupérer l'espace disque des sessions incomplètes.
max_file_size (vérifié)Le serveur a accepté un fragment de 5 Mo pour un fichier déclarant resumableTotalSize=999999999999 (~999 Go) avec un code HTTP 200. Le paramètre max_file_size n'est transmis qu'au composant JavaScript React. Le serveur ne vérifie jamais la taille du fichier, la taille des fragments, Content-Length ou MAX_CONTENT_LENGTH de Flask. Un développeur qui définit max_file_size=10 ne bénéficie d'aucune protection côté serveur.
# 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
...
Le même chemin de code produit à la fois l'OOM (resumableTotalChunks élevé) et la primitive de troncature de fichier (resumableTotalChunks=0).
Un attaquant envoie des requêtes POST non authentifiées au point de terminaison /API/resumable.
resumableTotalChunks=30000000 allouent ~2,9 Go chacune, déclenchant le tueur OOM.resumableTotalChunks=0 ; all([])=True de Python trompe le serveur en lui faisant écraser le fichier cible avec un contenu vide.Aucune authentification ni privilège requis.
resumableTotalChunks)os.makedirs() avec un resumableIdentifier non assainiall() de Python qui renvoie True pour les itérables vides lorsque resumableTotalChunks=0max_file_size n'est appliqué que dans le JavaScript côté client, tandis que le gestionnaire côté serveur n'effectue aucune validation de taille et ne définit jamais MAX_CONTENT_LENGTH de Flaskdash_uploader/httprequesthandler.py (méthode BaseHttpRequestHandler._post)dash_uploader/upload.py (fonction Upload, paramètre max_file_size)dash_uploader/configure_upload.py (MAX_CONTENT_LENGTH manquant)Options pour les utilisateurs actuellement en production, par ordre de préférence :
dcc.Upload, le composant de téléversement officiel fourni avec Plotly Dash. Il n'a pas de paramètre de nombre de fragments, pas d'état temporaire sur disque, et respecte MAX_CONTENT_LENGTH de Flask. Aucun des quatre problèmes décrits ici ne s'y applique. Idéal pour les fichiers petits et moyens. Pour les très gros téléversements, voir le point 2.MAX_CONTENT_LENGTH), des bornes sur tout nombre de fragments fourni par le client, et une liste blanche pour les noms de fichiers acceptés.MAX_CONTENT_LENGTH de Flask au niveau de l'application (la bibliothèque ne le fait pas), et rejetez les entrées au niveau de l'application ou du proxy inverse lorsque l'une des conditions suivantes est vraie :
resumableTotalChunks <= 0resumableTotalChunks dépasse une borne raisonnable (par exemple 10 000)resumableTotalSize dépasse le max_file_size configuré par le développeur0.6.1 (ligne stable). Les préversions vont jusqu'à 0.7.0a2.dash. Dépendance optionnelle : pyyaml. Licence : MIT.Muhammad Fitri Bin Mohd Sultan
| Identifiant CVE | CVE-2026-38361 (NVD) |
| Vulnérabilité | Consommation incontrôlée de ressources (CWE-400), Implémentation toujours incorrecte du flux de contrôle (CWE-670) |
| CVSS 3.1 | 7.5 / Élevée (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H) |
| Produit | dash-uploader |
| Versions concernées | de 0.1.0 à 0.7.0a2 (les 18 versions) |
| Version corrigée | aucune (projet archivé le 2025-07-19) |
| Vecteur d'attaque | À distance, sans authentification |
| Découvreur | Muhammad Fitri Bin Mohd Sultan |
| Attribué par | MITRE, 2026-05-07 |
| Lié | CVE-2026-38360 (traversée de chemin dans la même bibliothèque) |
| Date | Événement |
|---|
| 2026-03-19 | Vulnérabilités découvertes lors d'une recherche en sécurité sur un déploiement en production. |
| 2026-03-22 | Demande de CVE soumise à MITRE. |
| 2026-05-07 | CVE-2026-38361 attribué par MITRE. |
| 2026-05-07 | Avis public publié. |
| 2026-05-09 | Enregistrement CVE publié sur la base de données CVE de MITRE et le NVD. |