
Advisory : CVE-2026-38360 traversée de chemin (CWE-22) dans dash-uploader (Python/PyPI)
Traversée de chemin non authentifiée dans fohrloop/dash-uploader (Python, PyPI) permettant l'écriture arbitraire de fichiers, conduisant à (sans s'y limiter) l'exécution de code à distance (RCE), l'écrasement du code source de l'application, le XSS stocké et l'installation de portes dérobées persistantes.
Le dépôt a été archivé le 19 juillet 2025 sans mainteneur actif. Toutes les versions publiées (0.1.0 à 0.7.0a2) sont affectées et le resteront. Le package totalise encore environ 28 000 téléchargements mensuels.
Quiconque exécute dash-uploader en production doit appliquer lui-même une mesure d'atténuation. La correction recommandée est de migrer vers le composant dcc.Upload intégré de Plotly Dash. Voir Atténuation pour toutes les options.
| CVE ID | CVE-2026-38360 (NVD) |
| Vulnérabilité | Path Traversal (CWE-22) |
| CVSS 3.1 | 9,8 / Critique (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) |
| Produit | dash-uploader |
| Versions affectées | 0.1.0 à 0.7.0a2 (toutes les 18 versions) |
| Version corrigée | aucune (projet archivé le 19 juillet 2025) |
| Vecteur d'attaque | distant, non authentifié |
| Découvreur | Muhammad Fitri Bin Mohd Sultan |
| Assigné par | MITRE, 7 mai 2026 |
| Lié | CVE-2026-38361 (DoS dans la même bibliothèque) |
Trois paramètres contrôlés par l'utilisateur provenant de request.form.get() dans dash_uploader/httprequesthandler.py sont passés directement à os.path.join() et os.makedirs() sans aucune désinfection ni validation :
upload_id (ligne 57 → ligne 161, BaseHttpRequestHandler.get_temp_root) : contrôle le répertoire de destination. Un attaquant peut envoyer upload_id=../../../../usr/local/lib/python3.10/site-packages et les fichiers sont écrits dans le répertoire des packages Python.
resumableFilename (ligne 51 → ligne 108, BaseHttpRequestHandler._post) : contrôle le nom de fichier final. Un attaquant peut sortir du répertoire de téléchargement via le nom de fichier même avec un upload_id légitime.
resumableIdentifier (ligne 54 → ligne 64, BaseHttpRequestHandler._post) : utilisé avec os.makedirs() pour créer le répertoire temporaire. Un attaquant peut créer des répertoires arbitraires n'importe où sur le système de fichiers.
Le point de terminaison de téléchargement (/API/dash-uploader par défaut) ne nécessite aucune authentification. Le hook http_request_handler ajouté dans v0.5.0 permet des vérifications pré-requête via post_before(), mais la méthode vulnérable _post() lit tous les paramètres directement depuis request.form après le retour du hook. Le hook ne peut pas nettoyer les paramètres avant que la bibliothèque ne les traite. Un développeur qui ajoute l'authentification via le hook reste vulnérable au path traversal de la part d'un utilisateur authentifié.
# 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
Trois puits d'attaque par traversée indépendants partagent la même cause racine : les valeurs du formulaire atteignent os.path.join et os.makedirs sans validation.
Un attaquant distant non authentifié envoie une requête HTTP POST multipart au point de terminaison de téléchargement. En injectant des séquences de traversée de chemin (../) dans le paramètre de formulaire upload_id, l'attaquant contrôle le répertoire de destination du fichier téléchargé. Par exemple, upload_id=../../../../usr/local/lib/python3.10/site-packages écrit des fichiers dans le répertoire de packages de Python, permettant une RCE au prochain démarrage de l'interpréteur via l'auto-exécution de .pth.
Aucun jeton d'authentification, de session ou CSRF n'est requis. Deux paramètres supplémentaires (resumableFilename et resumableIdentifier) fournissent des vecteurs de traversée indépendants via le même point de terminaison. La configuration par défaut de la bibliothèque, telle que présentée dans la documentation officielle de démarrage rapide, est exploitable avec une seule commande curl.
Écriture arbitraire de fichiers dans tout répertoire accessible en écriture par le processus serveur. Cela se traduit par l'exécution de code à distance (RCE) via plusieurs primitives bien connues :
Primitives RCE
.pth Python déposé dans site-packages. Exécute le code fourni par l'attaquant au prochain démarrage de l'interpréteur.sitecustomize.py ou usercustomize.py. S'exécute à chaque démarrage de Python.app.wsgi, wsgi.py). S'exécute au prochain rechargement du worker./etc/cron.d/, /etc/cron.hourly/, spool crontab utilisateur) lorsque le processus dispose des privilèges nécessaires. Exécution planifiée./etc/systemd/system/, ~/.config/systemd/user/). S'exécute au prochain démarrage du service ou au redémarrage./etc/ld.so.preload lorsque le processus s'exécute en tant que root. Précharge le code de l'attaquant dans chaque exécution binaire ultérieure.~/.bashrc, ~/.profile, ~/.bash_profile). S'exécute à la prochaine connexion interactive de l'utilisateur de l'application.~/.ssh/authorized_keys. Accorde un accès SSH persistant à l'hôte en tant qu'utilisateur de l'application.Impact sur la couche web
site-packages), affectant chaque utilisateur à chaque chargement de page jusqu'au redémarrage de l'application.Impact sur le système de fichiers
os.makedirs() avec le paramètre non nettoyé resumableIdentifier (utilisable pour l'épuisement des inodes ou pour préparer des écritures dans des arborescences de répertoires inexistantes).dash_uploader/httprequesthandler.pyBaseHttpRequestHandler.get_temp_root()BaseHttpRequestHandler._post()Options pour les utilisateurs actuellement déployés, par ordre de préférence :
dcc.Upload, le composant de téléchargement officiel fourni avec Plotly Dash. Les fichiers arrivent au callback sous forme de chaîne base64 ; aucun gestionnaire d'écriture sur le système de fichiers n'est exposé et aucun chemin de destination contrôlé par le client n'existe, donc la classe de bug ici ne s'applique pas. Idéal pour les fichiers petits et moyens. Pour les très gros téléchargements, voir point 2.werkzeug.utils.secure_filename() et un répertoire de destination côté serveur codé en dur. N'acceptez jamais les valeurs upload_id, nom de fichier ou identifiant fournies par le client comme composants de chemin.upload_id, resumableFilename et resumableIdentifier par rapport à une liste blanche stricte (par exemple, UUID uniquement) à une couche qui réécrit ou rejette la requête avant que le gestionnaire de la bibliothèque ne la voie. Le hook http_request_handler de la bibliothèque n'EMPÊCHE PAS la traversée car les paramètres sont lus depuis request.form après le retour du hook ; la désinfection doit avoir lieu au-dessus de la bibliothèque..., des variantes encodées (%2e%2e, ..%2f, %2e%2e%2f), ou des chemins absolus.| Date | Événement |
|---|---|
| 2026-03-17 | Vulnérabilité découverte lors de recherches en sécurité sur un déploiement de production. |
| 2026-03-19 | Demande de CVE soumise à MITRE. |
| 2026-05-07 | CVE-2026-38360 assigné 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. |
0.6.1 (ligne stable). Les pré-versions vont jusqu'à 0.7.0a2.dash. Dépendance optionnelle : pyyaml. Licence : MIT.Muhammad Fitri Bin Mohd Sultan