Principaux enseignements
- Werkzeug propose des outils de sécurité pour le hachage des mots de passe, la validation des noms de fichiers et la concaténation sécurisée des chemins d'accès, ainsi qu'un débogueur protégé par code PIN.
- Son historique CVE concerne principalement le débogueur, l'analyse des formulaires en plusieurs parties et la fonction fonction d'aide safe_join() et l'analyse des cookies.
- La branche 3.1.x est la seule à bénéficier de correctifs de sécurité garantis par l'amont, et la version 3.1.6 ou une version ultérieure corrige tous les CVE mentionnés ici.
- ELS de TuxCare intègre des correctifs de sécurité dans les versions 1.0.1, 2.2.3 et 2.3.8 de Werkzeug, distribuées via pip.
Un analyseur de dépendances signale werkzeug comme vulnérable, et personne dans l'équipe ne se souvient l'avoir installé, car Flask l'a intégré de lui-même. Werkzeug est la boîte à outils sous-jacente à Flask qui gère l'infrastructure des requêtes et des réponses derrière chaque route. Il dispose de son propre historique de vulnérabilités (CVE) et de ses propres paramètres par défaut, distincts de ceux que Flask ajoute par-dessus.
Cet article explique contre quoi Werkzeug assure une protection, où se situent les CVE vérifiées, comment évaluer votre propre vulnérabilité et comment renforcer la sécurité d'une application Werkzeug en production.
Qu'est-ce que Werkzeug et comment s'intègre-t-il à Flask ?
Werkzeug est une bibliothèque d'utilitaires WSGI gérée par le projet Pallets, le même groupe à l'origine de Flask, Jinja et Click. Elle gère les mécanismes de bas niveau des requêtes et réponses HTTP : analyse des en-têtes, des cookies et des corps de formulaire multipart, correspondance des routes URL, et mise à disposition d'un débogueur pour le développement local.
Flask s'appuie sur Werkzeug plutôt que de le remplacer. Flask fournit des décorateurs de routage, des modèles et une structure d'application, mais ses objets de requête et de réponse sont des sous-classes de ceux de Werkzeug, et une grande partie du mécanisme WSGI sous-jacent provient également de Werkzeug. L'installation de Flask entraîne l'installation de Werkzeug en tant que dépendance, ce qui explique pourquoi la plupart des équipes l'utilisent sans jamais l'ajouter intentionnellement à un fichier de dépendances.
En production, un serveur WSGI tel que Gunicorn analyse les données HTTP brutes et fournit à Flask un environnement WSGI. À partir de là, une faille dans la gestion des requêtes par Werkzeug est accessible à toute personne capable d’envoyer une requête à votre application. Le routage s’effectue avant l’exécution de votre fonction de vue, et la plupart des autres données de requête sont analysées au moment où elles sont lues. L’analyse des formulaires, par exemple, se déclenche dès que le code accède à request.form ou request.files.
Protections de sécurité natives de Werkzeug
Werkzeug fournit plusieurs outils d'aide à la sécurité et dispositifs de protection qu'une application Flask peut utiliser sans avoir à ajouter de dépendance supplémentaire. La plupart d'entre eux ne sont utiles que si l'application les appelle effectivement.
werkzeug.security fournit generate_password_hash() et check_password_hash(), qui ajoutent un sel et hachent les mots de passe à l’aide d’un algorithme configurable, au lieu de laisser cette tâche au code de l’application. secure_filename() supprime les séparateurs de chemin et autres caractères dangereux d’un nom de fichier fourni par l’utilisateur, de sorte que le nom qu’elle renvoie est beaucoup plus sûr à enregistrer dans un répertoire contrôlé par l’application. Elle ne prend toutefois en compte que le nom. Elle ne vérifie pas ce qui existe déjà sur le disque et peut renvoyer une chaîne vide ; le code de téléchargement doit donc toujours gérer ces deux cas de figure.
safe_join() existe pour la même raison au niveau du répertoire. Elle sert à combiner un répertoire de base fiable avec un chemin fourni par l'utilisateur et à refuser de renvoyer tout élément qui s'échappe de ce répertoire de base, ce qui est précisément le rôle de send_from_directory() s'appuie pour servir des fichiers en toute sécurité. Le débogueur interactif impose la saisie d’un code PIN avant d’afficher une console Python active dans un navigateur, afin d’empêcher un inconnu tombant par hasard sur un point de terminaison de débogage d’exécuter du code.
Aucune de ces mesures de protection n'est suffisante à elle seule. Chacune d'entre elles réduit un risque spécifique, et deux d'entre elles, safe_join() et le code PIN du débogueur, ont présenté des failles avérées concernant précisément le risque qu’elles étaient censées éliminer. C’est de là que provient une grande partie de l’historique des CVE présenté ci-dessous.
Risques de sécurité liés à Werkzeug et CVE
En septembre 2026, le registre public des CVE de Werkzeug comptait neuf vulnérabilités divulguées depuis 2023. Plusieurs des CVE les plus récentes sont associées à la fois à un score CVSS v3.1 et à un score CVSS v4.0, mais les outils de suivi ne les affichent pas toujours tous les deux. Le score le plus élevé varie selon la CVE. La fonction cluster safe_join() obtient un score plus élevé selon la version v4.0, tandis que le CVE-2024-49767 obtient un score plus élevé selon la version v3.1. Le fait de ne voir qu’un seul de ces scores peut donner l’impression que la vulnérabilité est plus ou moins urgente, mais le bug sous-jacent est identique.
| CVE | Gravité (CVSS v3.1) | Versions concernées | Corrigé dans |
| 34069 | 7,5 Élevé | < 3.0.3 | 3.0.3 |
| 46136 | 7,5 Élevé (NVD) / 5,7 Modéré (GitHub) | 2.0.0rc1–2.3.7, 3.0.0 | 2.3.8, 3.0.1 |
| 25577 | 7,5 Élevé | < 2.2.3 | 2.2.3 |
| 49767 | 7,5 Élevé | 2.0.0rc1–3.0.5 | 3.0.6 |
| 49766 | 5.3 Moyen | <= 3.0.5 (Windows, Python < 3.11) | 3.0.6 |
| 66221 | 5.3 Moyen | < 3.1.4 | 3.1.4 |
| 21860 | 5.3 Moyen | < 3.1.5 | 3.1.5 |
| 27199 | 5.3 Moyen | <= 3.1.5 | 3.1.6 |
| 23934 | 3,5 Faible (NVD) / 2,6 Faible (GitHub) | < 2.2.3 | 2.2.3 |
Vulnérabilité permettant l'exécution de code à distance via le débogueur
Le problème lié au débogueur, CVE-2024-34069, ainsi que les mécanismes d'exploitation CSRF associés, sont abordés en détail dans l'analyse de TuxCare consacrée à ce que la fin de vie de Werkzeug implique pour les applications Python. En résumé, un débogueur représente un risque d’exécution de code même lorsqu’il est limité à localhost et protégé par un code PIN. L’attaque nécessite un domaine et un sous-domaine contrôlés par l’attaquant, un développeur qui interagit avec eux et saisit le code PIN du débogueur, ainsi qu’une URL devinée qui déclenche le débogueur. Si l’attaquant parvient à mener cette attaque à bien, il accède à un débogueur limité à localhost et exécute du code sur la machine du développeur. Ce problème a été corrigé en amont dans la version 3.0.3, et aucune version 2.x en amont ne comporte ce correctif, puisque le support de la branche 2.x en amont s’est arrêté à la version 2.3.8. Pour les équipes qui restent sur la branche 2.x, l’ELS de TuxCare couvre ce problème grâce à un correctif rétroporté pour Werkzeug 2.3.8, et le CVE Tracker répertorie la couverture actuelle pour chaque version prise en charge.
Déni de service par analyse syntaxique en plusieurs parties
Trois CVE distincts proviennent du même chemin de code. La faille CVE-2023-25577 permettait à une requête de contenir un nombre illimité de parties multipart, de sorte que le coût d’analyse augmentait proportionnellement au nombre de parties contrôlées par l’attaquant, jusqu’à épuisement des ressources du processeur ou de la mémoire. Elle a été corrigée dans la version 2.2.3, qui est également celle qui a introduit le paramètre limite max_form_parts .
La vulnérabilité CVE-2023-46136 exploitait le même analyseur syntaxique sous un angle différent, à l’aide d’un seul élément spécialement conçu. Un fichier commençant par un caractère CR ou LF et suivi de plusieurs mégaoctets de données ne contenant aucun autre caractère CR ou LF force l’analyseur syntaxique à continuer de parcourir un tampon de plus en plus volumineux à la recherche de la limite. Le NVD lui attribue un niveau de gravité de 7,5 (Élevé) et l’avis de GitHub un niveau de 5,7 (Modéré). Certains outils de suivi affichent un troisième chiffre, 8,0 (« Élevé »), qui provient du score CNA figurant dans la fiche CVE officielle plutôt que de l’avis actuel de GitHub. La répartition complète de cette divergence figure dans l’article consacré à la fin de vie de Werkzeug.
La vulnérabilité CVE-2024-49767 a permis de corriger une autre faille dans le même analyseur syntaxique. Un corps de formulaire spécialement conçu pouvait contourner max_form_memory_size , de sorte que la limite de mémoire définie par l’application ne s’appliquait jamais réellement. Le NVD attribue à cette vulnérabilité un score de 7,5 (Élevé, CVSS v3.1), tandis que l’avis publié sur GitHub lui attribue un score de 6,9 (Modéré, CVSS v4.0).
Une limite de parseur n'est utile que si elle est appliquée suffisamment tôt pour empêcher l'utilisation de la mémoire contrôlée par l'attaquant, et la limite appropriée dépend de l'attaque. max_form_parts limite les flux massifs de minuscules éléments, comme ceux à l'origine de la vulnérabilité CVE-2023-25577. max_content_length et une limite de taille du corps au niveau du proxy inverse limitent la taille totale de la requête, et aucune de ces deux mesures ne dépend de la capacité du parseur multipart à détecter le problème en interne.
Failles liées au chemin d'accès Windows dans safe_join
safe_join() a fait l'objet de quatre correctifs en environ seize mois ; chaque correction concernait spécifiquement Windows et portait sur la manière dont la fonction détermine si un chemin d'accès est sûr.
La vulnérabilité CVE-2024-49766 est la plus ancienne des quatre. Sur les versions de Python antérieures à la version 3.11 fonctionnant sous Windows, os.path.isabs() ne reconnaît pas les chemins UNC tels que //serveur/partage comme des chemins absolus, et safe_join() s'appuie sur cette vérification ; un chemin UNC pourrait donc passer entre les mailles du filet. Ce problème est corrigé dans la version 3.0.6.
Les trois autres concernent des noms de périphériques réservés par Windows, tels que CON, AUX et NUL, que Windows considère comme présents et accessibles dans chaque répertoire, quel que soit le contenu réel de ce dernier. La vulnérabilité CVE-2025-66221 a révélé que safe_join() acceptait ces noms sans autre, et la version 3.1.4 a corrigé ce problème. Le CVE-2026-21860 a révélé que le premier correctif ne prenait pas en compte les extensions composées telles que CON.txt.html, les noms se terminant par un espace, ainsi que quelques noms particuliers supplémentaires, ce que la version 3.1.5 a corrigé. Ensuite, le CVE-2026-27199 a montré qu’un chemin d’accès à plusieurs segments, tel que example/NUL passait toujours, et la version 3.1.6 a corrigé ce problème.
Dans chacun de ces trois cas de nom de périphérique, send_from_directory() ouvre le périphérique avec succès, puis se bloque indéfiniment lors de la lecture, mobilisant ainsi un worker par requête jusqu'à ce que le processus n'ait plus de workers disponibles pour traiter le trafic réel.
Vulnérabilité liée à l'analyse des cookies
La vulnérabilité CVE-2023-23934 est la moins médiatisée de cette liste. Le NVD lui attribue une note de 3,5 (faible), tandis que GitHub, l’organisme CNA qui lui a attribué le numéro CVE, lui attribue une note de 2,6 (faible). Certains navigateurs acceptent un cookie dont la forme ressemble à =valeur sans nom, et les versions de Werkzeug antérieures à la 2.2.3 analysaient un cookie tel que =__Host-test=bad comme un cookie __Host-test légitime.
Ainsi, un sous-domaine malveillant ou compromis pourrait implanter ce cookie mal formé et amener Werkzeug à lire une valeur contrôlée par l'attaquant sous le paramètre protégé __Host-test , ce qui va à l’encontre de l’objectif même du __Host- . Ce problème a été corrigé dans la version 2.2.3.
Ces deux scores sont faibles car l'attaque nécessite à la fois un navigateur vulnérable et un sous-domaine adjacent malveillant ou compromis. Les conséquences sont tout autres si le cookie falsifié contient un jeton de session ou un jeton CSRF. Le CVSS évalue le mécanisme, mais il n'a aucune idée de ce qu'une application donnée stocke dans ce cookie.
Comment vérifier si votre version de Werkzeug présente des failles de sécurité
La vérification de l'exposition prend environ deux minutes par service. L'objectif est de répondre à une question : la version de Werkzeug est-elle réellement en production sur une branche qui bénéficie encore de correctifs ?
Partez de ce qui est installé, et non de ce qui est indiqué dans un cahier des charges.
pip show werkzeug
Exécutez cette commande à l’intérieur du conteneur ou de l’environnement virtuel qui gère effectivement le trafic. Toute version antérieure à la 3.1 se trouve en dehors de la seule branche pour laquelle les correctifs de sécurité en amont sont garantis. Pallets peut, sur demande, rétroporter un correctif vers la branche de fonctionnalités précédente, mais cela n’est pas garanti. Une version 3.1.x antérieure à la 3.1.6 se trouve sur la bonne branche mais il lui manque encore au moins un correctif ; la version actuelle au moment de la rédaction de ce document est la 3.1.8. Quoi qu’il en soit, les colonnes « Versions concernées » et « Corrigé dans » du tableau ci-dessus indiquent si chaque CVE s’applique et quelle version intègre son correctif en amont, ainsi que la fonction safe_join() ne s’appliquent qu’aux hôtes Windows.
Les numéros de version ne suffisent pas à eux seuls à déterminer quelles vulnérabilités CVE s'appliquent à une version donnée de Python et à un système d'exploitation donné ; il convient donc d'utiliser un outil d'audit dans ce même environnement.
pip install pip-audit
pip-audit
Sans argument, pip-audit vérifie les paquets installés dans l'environnement actif. Si c'est un fichier de dépendances qui doit être vérifié, pip-audit -r requirements.txt s'en charge. pip-audit est géré par la Python Packaging Authority et récupère par défaut les données de vulnérabilité depuis PyPI, OSV étant disponible comme source alternative via l'option -s osv. Certaines équipes utilisent également un deuxième scanner, tel que Safety, pour obtenir un autre point de vue sur les données des avis de sécurité.
La mise à jour du paquet sur le disque ne remplace pas le code Werkzeug déjà importé par un worker en cours d'exécution. Les workers Gunicorn ou uWSGI qui s'exécutent sur une longue durée conservent l'ancien code chargé jusqu'à leur redémarrage ; il faut donc redémarrer les workers après toute modification de Werkzeug et vérifier que la nouvelle version est bien chargée, et pas seulement installée.
En ce qui concerne plus particulièrement les actualités relatives à la sécurité de Werkzeug, le CVE Tracker de TuxCare répertorie les vulnérabilités corrigées par ELS dès qu'elles sont confirmées dans des versions plus anciennes qui ne bénéficient plus de correctifs en amont.
Bonnes pratiques pour sécuriser les applications Werkzeug
Une partie des risques liés à Werkzeug provient de la configuration, et non de la version, et ces bonnes pratiques s'appliquent à chaque nouvelle version.
Commencez par le débogueur. Définissez debug=False et n'activez jamais le débogueur interactif en production, même s'il est lié à 127.0.0.1, car la vulnérabilité CVE-2024-34069 a démontré que « localhost » n’est pas la barrière de sécurité qu’il semble être. Exécutez le code derrière un serveur WSGI de production tel que Gunicorn, uWSGI ou Waitress, car run_simple() et flask run sont des outils de développement qui n’ont jamais été conçus pour gérer le trafic de production.
Ensuite, définissez délibérément des limites de requêtes. max_content_length, max_form_memory_sizeet max_form_parts doivent correspondre à ce que l’application accepte réellement, et la taille du corps doit également être plafonnée au niveau du proxy inverse, afin que les requêtes multipart trop volumineuses ou trop complexes soient rejetées avant qu’elles ne puissent consommer trop de mémoire ou de ressources CPU. Les valeurs explicites sont particulièrement importantes dans les versions antérieures à la 3.1, où max_form_memory_size était par défaut illimité. Werkzeug 3.1 a modifié cette valeur par défaut pour la fixer à 500 ko.
Dans la mesure du possible, servez les fichiers statiques à partir d'un serveur web dédié tel que nginx, ce qui permet de send_from_directory() et safe_join() soient totalement exclus de ce chemin de requête. Exécutez également le processus de l’application en tant qu’utilisateur non root, afin qu’un bug de chemin qui parviendrait à passer ait moins de possibilités d’action.
La configuration permet de combler les failles qu’une mise à jour de version ne résout pas à elle seule, et l’inverse est également vrai. Aucun de ces paramètres ne remplace l’utilisation d’une version 3.1.x à jour, et toutes les vulnérabilités CVE répertoriées dans le tableau ci-dessus sont corrigées dans la version 3.1.6 et les versions ultérieures.
Comment la solution ELS de TuxCare assure la sécurité de Werkzeug après sa fin de vie
La mise à niveau n'est pas toujours possible dans les délais impartis. Une version fournie par un fournisseur, un déploiement certifié ou un moteur d'exécution Python dont la migration n'a pas été prévue dans le budget peuvent tous maintenir une application sur une ancienne version de Werkzeug bien après que les vulnérabilités (CVE) répertoriées dans ce tableau ont commencé à la concerner.
Assistance à vie de TuxCare pour Werkzeug continue de fournir des correctifs de sécurité pour les versions 1.0.1, 2.2.3 et 2.3.8, distribués sous forme de paquets signés via pip. Les équipes peuvent conserver la version qu’elles utilisent déjà tout en continuant à recevoir des correctifs de sécurité ; ainsi, l’apparition d’un nouveau CVE n’entraîne pas automatiquement une migration d’urgence, et la migration effective peut avoir lieu selon leur propre calendrier.
Foire aux questions sur la sécurité de Werkzeug
Comment puis-je vérifier si ma version de Werkzeug présente des failles de sécurité connues ?
Exécutez pip show werkzeug dans l'environnement déployé pour vérifier la version installée, puis exécutez pip-audit dans ce même environnement pour vérifier que ses paquets ne sont pas concernés par des avis de sécurité connus. Pour les CVE abordées ici, la version 3.1.6 est la première version en amont contenant tous les correctifs, bien que la version 3.1.8 soit la version actuelle au moment de la rédaction de cet article et constitue donc la meilleure option.
La mise à jour de Flask permet-elle également de corriger les vulnérabilités CVE de Werkzeug ?
Pas forcément. Flask définit une version minimale de Werkzeug plutôt qu'une limite maximale, et la commande « pip install –upgrade flask » ne modifie pas la version de Werkzeug déjà installée tant qu'elle respecte cette exigence minimale. Il est recommandé de mettre à jour Flask, mais la seule façon d'en avoir la certitude est de vérifier directement la version de Werkzeug associée.
Quelle est la manière la plus sûre d'utiliser Werkzeug en environnement de production ?
Exécutez-le derrière un serveur WSGI dédié tel que Gunicorn, uWSGI ou Waitress, avec debug=Falseet une valeur explicite pour max_content_length, max_form_memory_sizeet max_form_parts , ainsi que les fichiers statiques servis par un serveur web distinct plutôt que par la fonction send_from_directory().
Pourquoi certaines vulnérabilités CVE de Werkzeug affichent-elles des niveaux de gravité différents selon les sources ?
Plusieurs CVE récentes concernant Werkzeug présentent à la fois un score CVSS v3.1 et un score CVSS v4.0, et ces deux référentiels pondèrent différemment les mêmes éléments. La CVE-2026-27199, l’une des correction de la fonction , affiche un score de 5,3 selon le CVSS v3.1 dans le NVD et de 6,3 selon le CVSS v4.0 dans l’avis publié sur GitHub ; le chiffre affiché par un outil de suivi dépend donc de la source et de la norme qu’il utilise par défaut. Le comportement de la vulnérabilité reste toutefois identique dans les deux cas.

