Principales conclusiones
- Werkzeug incluye herramientas de seguridad para el hash de contraseñas, la depuración de nombres de archivo y la unión segura de rutas, además de un depurador protegido con PIN.
- Su historial de CVE se centra en el depurador, el análisis de formularios multiparte y la función función auxiliar safe_join() y el análisis de cookies.
- La rama 3.1.x es la única para la que se garantizan las correcciones de seguridad del desarrollador original, y la versión 3.1.6 o posteriores resuelven todos los CVE mencionados aquí.
- ELS de TuxCare incorpora correcciones de seguridad a Werkzeug 1.0.1, 2.2.3 y 2.3.8, distribuidas a través de pip.
Un escáner de dependencias señala werkzeug como vulnerable, y nadie del equipo recuerda haberlo instalado, porque Flask lo incorporó por su cuenta. Werkzeug es el conjunto de herramientas subyacente a Flask que gestiona la infraestructura de solicitudes y respuestas detrás de cada ruta. Cuenta con su propio historial de CVE y sus propios valores predeterminados, independientes de lo que Flask añada por encima.
En este artículo se explica contra qué protege Werkzeug, dónde se encuentran sus CVE verificadas, cómo comprobar tu propia vulnerabilidad y cómo reforzar la seguridad de una aplicación de Werkzeug en producción.
Qué es Werkzeug y cómo se integra en Flask
Werkzeug es una biblioteca de utilidades WSGI mantenida por el proyecto Pallets, el mismo grupo responsable de Flask, Jinja y Click. Se encarga de los aspectos técnicos de bajo nivel de las solicitudes y respuestas HTTP: análisis de encabezados, cookies y cuerpos de formularios multiparte, coincidencia de rutas URL y provisión de un depurador para el desarrollo local.
Flask se basa en Werkzeug, en lugar de sustituirlo. Flask proporciona decoradores de enrutamiento, plantillas y estructura de aplicación, pero sus objetos de solicitud y respuesta son subclases de los de Werkzeug, y gran parte del mecanismo WSGI subyacente también procede de Werkzeug. Al instalar Flask se instala Werkzeug como dependencia, por lo que la mayoría de los equipos lo ejecutan sin añadirlo nunca a un archivo de requisitos de forma deliberada.
En producción, un servidor WSGI como Gunicorn analiza el código HTTP sin procesar y proporciona a Flask un entorno WSGI. A partir de ahí, cualquier persona que pueda enviar una solicitud a tu aplicación tiene acceso a un fallo en la gestión de solicitudes de Werkzeug. El enrutamiento se produce antes de que se ejecute la función de vista, y la mayor parte del resto de datos de la solicitud se analizan cuando algo los lee. El análisis de formularios, por ejemplo, se activa en el momento en que el código accede a request.form o request.files.
Las protecciones de seguridad nativas de Werkzeug
Werkzeug incluye varios complementos de seguridad y medidas de protección que una aplicación de Flask puede utilizar sin necesidad de añadir otra dependencia. La mayoría de ellos solo son útiles si la aplicación los invoca realmente.
werkzeug.security proporciona generate_password_hash() y check_password_hash(), que añaden una sal y realizan el hash de las contraseñas utilizando un algoritmo configurable, en lugar de dejar esa tarea al código de la aplicación. secure_filename() elimina los separadores de ruta y otros caracteres peligrosos de un nombre de archivo proporcionado por el usuario, de modo que el nombre que devuelve es mucho más seguro para guardarlo en un directorio controlado por la aplicación. Sin embargo, solo tiene en cuenta el nombre. No comprueba lo que ya existe en el disco y puede devolver una cadena vacía, por lo que el código de subida sigue teniendo que gestionar ambas situaciones.
safe_join() existe por la misma razón a nivel de directorio. Su finalidad es combinar un directorio base de confianza con una ruta proporcionada por el usuario y rechazar cualquier resultado que se salga de ese directorio base, que es precisamente lo que hace send_from_directory() utiliza para servir archivos de forma segura. El depurador interactivo añade un requisito de PIN antes de mostrar una consola de Python activa en un navegador, con el fin de impedir que un desconocido que acceda por casualidad a un punto final de depuración pueda ejecutar código.
Ninguna de estas medidas de protección es completa por sí sola. Cada una de ellas reduce un riesgo específico, y dos de ellas, safe_join() y el PIN del depurador, han presentado fallos documentados precisamente en el riesgo que se pretendía mitigar. De ahí proviene una buena parte del historial de CVE que se muestra a continuación.
Werkzeug: Riesgos de seguridad y CVE
A fecha de septiembre de 2026, el registro público de CVE de Werkzeug suma nueve divulgaciones desde 2023. Varias de las CVE más recientes cuentan tanto con una puntuación CVSS v3.1 como con una CVSS v4.0, y los rastreadores no siempre muestran ambas. La puntuación más alta depende de la CVE en cuestión. La función safe_join() obtiene una puntuación más alta según la v4.0, mientras que el CVE-2024-49767 obtiene una puntuación más alta según la v3.1. Ver solo una de ellas puede hacer que un hallazgo parezca más o menos urgente, pero el fallo subyacente es idéntico.
| CVE | Gravedad (CVSS v3.1) | Versiones afectadas | Corregido en |
| 34069 | 7,5 Alto | < 3.0.3 | 3.0.3 |
| 46136 | 7,5 Alto (NVD) / 5,7 Moderado (GitHub) | 2.0.0rc1–2.3.7, 3.0.0 | 2.3.8, 3.0.1 |
| 25577 | 7,5 Alto | < 2.2.3 | 2.2.3 |
| 49767 | 7,5 Alto | 2.0.0rc1–3.0.5 | 3.0.6 |
| 49766 | 5.3 Medio | <= 3.0.5 (Windows, Python < 3.11) | 3.0.6 |
| 66221 | 5.3 Medio | < 3.1.4 | 3.1.4 |
| 21860 | 5.3 Medio | < 3.1.5 | 3.1.5 |
| 27199 | 5.3 Medio | <= 3.1.5 | 3.1.6 |
| 23934 | 3,5 Bajo (NVD) / 2,6 Bajo (GitHub) | < 2.2.3 | 2.2.3 |
Vulnerabilidad de ejecución remota de código en el depurador
El problema del depurador, CVE-2024-34069, y sus mecanismos de explotación CSRF se tratan en detalle en el análisis de TuxCare sobre lo que supone el fin de la vida útil de Werkzeug para las aplicaciones de Python. En resumen, un depurador supone un riesgo de ejecución de código incluso cuando está limitado a localhost y protegido por un PIN. El ataque requiere un dominio y un subdominio controlados por el atacante, un desarrollador que interactúe con ellos e introduzca el PIN del depurador, y una URL adivinada que active el depurador. Si se consigue esto, el atacante accede a un depurador restringido a localhost y ejecuta código en el equipo del desarrollador. Se corrigió en la versión 3.0.3 del proyecto original, y ninguna versión 2.x del proyecto original incluye la corrección, ya que el soporte para la serie 2.x del proyecto original finalizó en la versión 2.3.8. Para los equipos que siguen utilizando la serie 2.x, el servicio ELS de TuxCare lo cubre con una corrección adaptada para Werkzeug 2.3.8, y el CVE Tracker detalla la cobertura actual para cada versión compatible.
Denegación de servicio por análisis sintáctico multiparte
De la misma ruta de código se derivan tres CVE distintos. El CVE-2023-25577 permitía que una solicitud contuviera un número ilimitado de partes multipart, por lo que el coste del análisis aumentaba proporcionalmente al número de partes controladas por el atacante hasta que se agotaba la CPU o la memoria. Se corrigió en la versión 2.2.3, que es también la versión en la que se introdujo el límite de max_form_parts .
La vulnerabilidad CVE-2023-46136 afectaba al mismo analizador desde un ángulo diferente, mediante un único elemento manipulado. Un archivo que comienza con un carácter CR o LF y va seguido de megabytes de datos que no contienen más caracteres CR ni LF obliga al analizador a seguir escaneando un búfer cada vez mayor en busca del límite. La NVD le asigna una puntuación de 7,5 (Alta) y el aviso de GitHub, una de 5,7 (Moderada). Algunos rastreadores muestran una tercera puntuación, 8,0 (Alta), que procede de la puntuación CNA del registro oficial de CVE y no del aviso actual de GitHub. El desglose completo de esa diferencia se encuentra en el artículo sobre el fin de vida útil de Werkzeug.
CVE-2024-49767 solucionó otra vulnerabilidad en el mismo analizador sintáctico. El cuerpo de un formulario manipulado podría eludir max_form_memory_size por completo, por lo que el límite de memoria establecido por una aplicación nunca se aplicaba realmente. El NVD le otorga una puntuación de 7,5 (Alta, CVSS v3.1), mientras que el aviso de GitHub le asigna una puntuación de 6,9 (Moderada, CVSS v4.0).
Un límite del analizador sintáctico solo resulta útil si se aplica con la suficiente antelación como para impedir el uso de memoria controlada por el atacante, y el límite adecuado depende del tipo de ataque. max_form_parts limita las inundaciones de partes minúsculas como las que se encuentran detrás de CVE-2023-25577. max_content_length y un límite de tamaño del cuerpo en el proxy inverso restringen el tamaño total de la solicitud, y ninguno de ellos depende de que el analizador multiparte detecte el problema internamente.
Fallos en la ruta de Windows en safe_join
safe_join() ha recibido cuatro parches en unos dieciséis meses; todas las correcciones son específicas de Windows y todas se refieren a la forma en que la función determina si una ruta es segura.
CVE-2024-49766 es la más antigua de las cuatro. En las versiones de Python anteriores a la 3.11 que se ejecutan en Windows, os.path.isabs() no reconoce rutas UNC como //servidor/recurso compartido como absolutas, y safe_join() se basa en esa comprobación, por lo que una ruta UNC podría pasar desapercibida. Esto se ha corregido en la versión 3.0.6.
Los otros tres afectan a nombres de dispositivos reservados por Windows, como CON, AUX y NUL, que Windows considera que están presentes y son legibles en todos los directorios, independientemente de lo que contenga realmente cada uno de ellos. La vulnerabilidad CVE-2025-66221 reveló que safe_join() aceptaba esos nombres sin más, y la versión 3.1.4 lo corrigió. CVE-2026-21860 reveló que la primera corrección no tenía en cuenta las extensiones compuestas, como CON.txt.html, nombres con espacios al final y algunos nombres especiales adicionales, lo cual se solucionó en la versión 3.1.5. Posteriormente, CVE-2026-27199 demostró que una ruta de varios segmentos, como example/NUL seguía pasando, y la versión 3.1.6 solucionó ese problema.
En cada uno de esos tres casos de nombres de dispositivos, send_from_directory() abre el dispositivo correctamente y, a continuación, se queda bloqueado indefinidamente en la lectura, ocupando un trabajador por cada solicitud hasta que el proceso se queda sin trabajadores para atender el tráfico real.
Vulnerabilidad en el análisis de cookies
CVE-2023-23934 es la entrada más discreta de esta lista. El NVD le otorga una puntuación de 3,5 (baja), y GitHub, la CNA que asignó el CVE, le otorga una puntuación de 2,6 (baja). Algunos navegadores aceptan una cookie con el formato =valor sin nombre, y las versiones de Werkzeug anteriores a la 2.2.3 analizaban una cookie como =__Host-test=bad como una cookie __Host-test legítima.
Así pues, un subdominio malicioso o comprometido podría insertar esa cookie malformada y hacer que Werkzeug leyera un valor controlado por el atacante bajo el __Host-test , lo que anula por completo el propósito del __Host- . Se ha corregido en la versión 2.2.3.
Ambas puntuaciones son bajas porque el ataque requiere tanto un navegador vulnerable como un subdominio adyacente malicioso o comprometido. La consecuencia es otra historia si la cookie falsificada contiene un token de sesión o un token CSRF. El CVSS puntúa el mecanismo, pero no tiene ni idea de lo que una aplicación concreta almacena en esa cookie.
Cómo comprobar si tu versión de Werkzeug tiene vulnerabilidades
Comprobar la exposición lleva unos dos minutos por servicio. El objetivo es responder a una pregunta: ¿se está ejecutando realmente la versión de Werkzeug en producción en una rama que aún recibe correcciones?
Empieza por lo que está instalado, no por lo que indique un archivo de requisitos.
pip show herramienta
Ejecuta esto dentro del contenedor o del entorno virtual que realmente está gestionando el tráfico. Cualquier versión anterior a la 3.1 queda fuera de la única rama para la que se garantizan las correcciones de seguridad del código fuente. Pallets puede retroportar una corrección a la rama de características anterior si se solicita, pero no se garantiza. Una versión 3.1.x anterior a la 3.1.6 se encuentra en la rama correcta, pero aún le falta al menos una corrección, y la versión actual en el momento de redactar este artículo es la 3.1.8. En cualquier caso, las columnas «Versiones afectadas» y «Corregido en» de la tabla anterior indican si cada CVE es aplicable y qué versión incluye su corrección de origen, y la función exclusiva para Windows safe_join() solo son relevantes en equipos con Windows.
Los números de versión por sí solos no determinan qué CVE se aplican a una versión concreta de Python y a un sistema operativo determinado, por lo que conviene ejecutar una herramienta de auditoría en ese mismo entorno.
pip install pip-audit
pip-audit
Sin argumentos, pip-audit comprueba los paquetes instalados en el entorno activo. Si, por el contrario, lo que hay que auditar es un archivo de requisitos, pip-audit -r requirements.txt se encarga de ello. pip-audit se mantiene bajo la Python Packaging Authority y, por defecto, obtiene los datos de vulnerabilidades de PyPI, aunque OSV está disponible como fuente alternativa mediante -s osv. Algunos equipos también utilizan un segundo escáner, como Safety, para obtener otra perspectiva de los datos de los avisos de seguridad.
La actualización del paquete en el disco no sustituye el código de Werkzeug que un worker en ejecución ya haya importado. Los workers de Gunicorn o uWSGI que permanecen activos durante mucho tiempo mantienen cargado el código antiguo hasta que se reinician; por lo tanto, reinicia los workers tras cualquier cambio en Werkzeug y comprueba que la nueva versión se haya cargado realmente y no solo se haya instalado.
En cuanto a las noticias sobre seguridad de Werkzeug en concreto, el CVE Tracker de TuxCare enumera las vulnerabilidades corregidas por ELS a medida que se confirman en versiones antiguas que ya no reciben correcciones del desarrollador original.
Buenas prácticas para la seguridad de las aplicaciones de Werkzeug
Parte del riesgo de Werkzeug proviene de la configuración, no de la versión, y estos hábitos se aplican en cada lanzamiento.
Empieza con el depurador. Establece debug=False y nunca actives el depurador interactivo en el entorno de producción, ni siquiera vinculado a 127.0.0.1, ya que el CVE-2024-34069 demostró que localhost no es el límite seguro que parece. Ejecútalo detrás de un servidor WSGI de producción como Gunicorn, uWSGI o Waitress, porque run_simple() y flask run son herramientas de desarrollo que nunca se diseñaron para gestionar tráfico de producción.
A continuación, establece límites de solicitudes de forma deliberada. max_content_length, max_form_memory_sizey max_form_parts deben coincidir con lo que la aplicación acepta realmente, y el tamaño del cuerpo también debe limitarse en el proxy inverso, de modo que las peticiones multiparte demasiado grandes o excesivamente complejas sean rechazadas antes de que puedan consumir una cantidad considerable de memoria o CPU. Los valores explícitos son más importantes en las versiones anteriores a la 3.1, donde max_form_memory_size tenía por defecto un valor ilimitado. Werkzeug 3.1 cambió ese valor por defecto a 500 kB.
Siempre que sea posible, sirve los archivos estáticos desde un servidor web dedicado, como nginx, lo que mantiene send_from_directory() y safe_join() queden completamente fuera de esa ruta de solicitud. Además, ejecuta el proceso de la aplicación como un usuario que no sea root, de modo que, si se cuela un error de ruta, tenga menos acceso.
La configuración subsana las vulnerabilidades que una actualización de versión no resuelve por sí sola, y lo contrario también es cierto. Ninguno de estos ajustes sustituye al uso de una versión actual de la serie 3.1.x, y todas las vulnerabilidades CVE de la tabla anterior se han corregido en la versión 3.1.6 y posteriores.
Cómo TuxCare ELS garantiza la seguridad de Werkzeug tras el fin de su vida útil
No siempre es posible realizar una actualización dentro de un plazo determinado. Una versión proporcionada por un proveedor, una implementación certificada o un entorno de ejecución de Python cuya migración no se haya presupuestado pueden hacer que una aplicación siga utilizando una versión antigua de Werkzeug mucho después de que las vulnerabilidades (CVE) de esa tabla empiecen a afectarla.
Soporte de ciclo de vida ilimitado de TuxCare para Werkzeug sigue proporcionando correcciones de seguridad para las versiones 1.0.1, 2.2.3 y 2.3.8, que se distribuyen como paquetes firmados a través de pip. Los equipos pueden seguir utilizando la versión que ya tienen instalada mientras siguen recibiendo correcciones de seguridad, de modo que la aparición de un nuevo CVE no se convierte automáticamente en una migración de emergencia, y la migración real puede llevarse a cabo según su propio calendario.
Preguntas frecuentes sobre la seguridad de Werkzeug
¿Cómo puedo comprobar si mi versión de Werkzeug tiene vulnerabilidades conocidas?
Ejecuta pip show werkzeug en el entorno implementado para confirmar la versión instalada y, a continuación, ejecuta pip-audit en ese mismo entorno para comprobar que sus paquetes cumplen con los avisos de seguridad conocidos. Para los CVE que se tratan aquí, la versión 3.1.6 es la primera versión del desarrollador original que incluye todas las correcciones, aunque la 3.1.8 es la versión actual en el momento de redactar este artículo y la más recomendable.
¿La actualización de Flask también soluciona los CVE de Werkzeug?
No necesariamente. Flask establece una versión mínima de Werkzeug, en lugar de un límite máximo, y pip install –upgrade flask no afecta a la versión de Werkzeug ya instalada, siempre y cuando siga cumpliendo ese mínimo. Merece la pena actualizar Flask, pero comprobar directamente la versión de Werkzeug instalada es la única forma de saberlo con certeza.
¿Cuál es la forma más segura de ejecutar Werkzeug en un entorno de producción?
Ejecútalo detrás de un servidor WSGI dedicado, como Gunicorn, uWSGI o Waitress, con debug=Falsey un valor explícito para max_content_length, max_form_memory_sizey max_form_parts , y los archivos estáticos servidos por un servidor web independiente en lugar de mediante send_from_directory().
¿Por qué algunas vulnerabilidades CVE de Werkzeug muestran puntuaciones de gravedad diferentes según el lugar?
Varias vulnerabilidades CVE recientes de Werkzeug tienen una puntuación tanto según CVSS v3.1 como según CVSS v4.0, y ambos marcos de referencia ponderan los mismos detalles de forma diferente. La CVE-2026-27199, una de las safe_join() , tiene una puntuación de 5,3 según CVSS v3.1 en el NVD y de 6,3 según CVSS v4.0 en el aviso de GitHub, por lo que la cifra que muestra un gestor de vulnerabilidades depende de la fuente y el estándar que utilice por defecto. La vulnerabilidad se comporta de la misma manera en ambos casos.

