Cloudanix VulnDB

CVE-2026-27199

None

CVSS

—

EPSS

0.6%

Published

Feb 21, 2026

CISA due date

—

Cloudanix analysis

Schedule a fix SSVC: Track

A flaw in a common Python web library can let someone make certain Windows-hosted websites hang by requesting specially named files.

Exploitation
Remote Unauthenticated
Attacker needs
None
Complexity
Moderate
Impact
Denial Of Service
Component
Library
Class
Improper Input Validation
Remediation
Upgrade
Effort
Low
Patch risk
Low

What it actually does

Werkzeug versions 3.1.5 and earlier do not fully reject Windows reserved device names when safe_join receives multiple path segments, such as example/NUL. Applications that use safe_join directly, or indirectly through send_from_directory, to serve user-selected files on Windows can be made to open a reserved device name as if it were a normal file. The open succeeds, but the subsequent read can hang indefinitely, tying up the request handler or worker. The impact is availability degradation or denial of service rather than data disclosure or code execution.

Why it matters to the business

Public or internal Python web applications running on Windows and serving files through Werkzeug may become slow or unavailable if an attacker sends repeated requests for crafted filenames. The current exploitation signal is low: EPSS is 0.00556, exploit maturity is listed as none, and it is not in the CISA Known Exploited Vulnerabilities catalog or known ransomware use. However, because exploitation is unauthenticated and network-reachable when an affected file-serving route exists, internet-facing Windows deployments should still be remediated during normal patch cycles or sooner for high-availability services.

How an attack unfolds

An attacker first identifies a Python web application running on Windows that exposes a route backed by Werkzeug safe_join or send_from_directory, such as a static-file, upload-download, attachment, or user-content endpoint. The attacker then sends a request where the user-controlled path contains at least one ordinary directory segment followed by a Windows device filename, for example a path ending in /NUL, /CON, /PRN, /AUX, /COM1, or /LPT1. In vulnerable versions, the path validation permits the multi-segment value. Windows resolves the final reserved device name specially; Werkzeug opens it successfully and then attempts to read from it. The read does not complete, leaving the application thread, process, or worker occupied. Repeating the request can exhaust the available worker pool, connection capacity, or reverse-proxy timeouts, causing legitimate users to experience stalled downloads, elevated latency, or service unavailability.

How to fix it

Upgrade Werkzeug to version 3.1.6 or later. If consuming directly from the pallets/werkzeug Git repository, use a revision that includes commit 04da1b5221b7a7b57e82246e4b5741d37a6b2e56 or a later fixed commit. Rebuild and redeploy all applications and container images that bundle vulnerable Werkzeug versions, including transitive dependencies from Flask-based applications. If immediate upgrade is not possible, add application- or reverse-proxy-level filtering for Windows reserved device names in every user-controlled path segment, not only the final full path. Reject case-insensitive basenames matching CON, PRN, AUX, NUL, COM1 through COM9, or LPT1 through LPT9, including names with extensions such as NUL.txt. Also enforce conservative request timeouts, worker limits, and download timeouts to reduce the blast radius of hanging reads. Avoid exposing arbitrary user-supplied paths to send_from_directory without strict allowlisting.

How to confirm the fix worked

Confirm that deployed applications no longer use Werkzeug 3.1.5 or earlier by checking package metadata, lockfiles, SBOMs, and runtime environments, for example with pip show Werkzeug, pip freeze, poetry.lock, requirements.txt, Pipfile.lock, or importlib.metadata.version('werkzeug'). For Git-pinned builds, confirm the pinned commit includes 04da1b5221b7a7b57e82246e4b5741d37a6b2e56 or a later fix. On a non-production Windows test instance, request a file-serving route with paths such as example/NUL, example/CON, example/AUX.txt, example/COM1, and example/LPT1. A fixed deployment should promptly reject the request, usually with 400 or 404 behavior depending on the application, and must not hang the worker or leave the connection open indefinitely. Monitor worker counts, request duration, and error logs during the test to verify that no request handler remains stuck.

Typical attack path

  1. Find a Flask/Werkzeug-based web application or API endpoint that serves files from a user-supplied path.
  2. Confirm the target is likely running on Windows or probe for Windows-specific behavior using reserved device-name filenames.
  3. Send a crafted file request with multiple path segments where the final segment is a Windows reserved device name, such as example/NUL.
  4. Observe that the request hangs or consumes server-side processing time instead of returning a normal not-found or bad-request response.
  5. Repeat the crafted requests concurrently to consume application workers, threads, or request slots.
  6. Maintain pressure until the service becomes degraded or unavailable for legitimate users.

How to detect it

Look for evidence of requests to file-serving routes where the URL path ends with or contains Windows reserved device names after another path segment. Primary log sources are web server access logs, reverse-proxy logs, WAF logs, application request logs, APM traces, and load balancer logs. Pay special attention to requests with long durations, upstream timeout responses, gateway timeout responses, client disconnects, incomplete responses, or a sudden increase in concurrent requests from the same source. In application telemetry, look for Flask/Werkzeug endpoints that serve files and show long-running requests without corresponding bytes sent. On Windows hosts, correlate these requests with Python worker processes that remain busy, thread dumps showing file read/send operations, elevated worker utilization, or repeated worker recycling due to timeouts. Because exploitation does not require successful authentication and may not create files or persistent artifacts, detection is primarily log- and telemetry-based rather than filesystem-based.

  • /NUL
  • /CON
  • /PRN
  • /AUX
  • /COM1
  • /COM2
  • /COM3
  • /COM4
  • /COM5
  • /COM6
  • /COM7
  • /COM8
  • /COM9
  • /LPT1
  • /LPT2
  • /LPT3
  • /LPT4
  • /LPT5
  • /LPT6
  • /LPT7
  • /LPT8
  • /LPT9
  • %2fNUL
  • %2fCON
  • %2fPRN
  • %2fAUX
  • %2fCOM1
  • %2fLPT1
  • \NUL
  • \CON
  • \PRN
  • \AUX
  • \COM1
  • \LPT1
  • NUL.txt
  • CON.txt
  • PRN.txt
  • AUX.txt
  • COM1.txt
  • LPT1.txt
  • upstream timed out
  • gateway timeout
  • client timed out
  • request timed out

What your security team should do

  • Inventory Python web applications that run on Windows and use Werkzeug, Flask static-file serving, send_from_directory, or direct safe_join calls.
  • Prioritize upgrade to Werkzeug 3.1.6 or later for internet-facing Windows deployments and high-availability services.
  • Add temporary WAF, reverse-proxy, or application rules blocking path segments that are Windows reserved device names: CON, PRN, AUX, NUL, COM1-COM9, and LPT1-LPT9, including variants with extensions.
  • Review access logs for historical requests ending in reserved device names, especially requests with unusually long duration, client disconnects, gateway timeouts, or upstream timeouts.
  • Tune request, upstream, and worker timeouts so a single hanging file read cannot indefinitely consume an application worker.
  • Add regression tests for file-serving endpoints to ensure Windows device names are rejected in every path segment.
  • Ensure dependency scanners and SBOM tooling flag Werkzeug versions 3.1.5 and earlier in application repositories, containers, and build artifacts.

The record

Werkzeug is a comprehensive WSGI web application library. Versions 3.1.5 and below, the safe_join function allows Windows device names as filenames if preceded by other path segments. This was previously reported as GHSA-hgf8-39gv-g3f2, but the added filtering failed to account for the fact that safe_join accepts paths with multiple segments, such as example/NUL. The function send_from_directory uses safe_join to safely serve files at user-specified paths under a directory. If the application is running on Windows, and the requested path ends with a special device name, the file will be opened successfully, but reading will hang indefinitely. This issue has been fixed in version 3.1.6.

Affected packages

Ecosystem Package Introduced Fixed
PyPI werkzeug 0 3.1.6
Chainguard airflow-2 0 2.11.1-r0
Chainguard airflow-core-2 0 2.11.1-r0
Chainguard kubeflow-pipelines-visualization-server 0 2.16.0-r1
Chainguard kubeflow-volumes-web-app 0 1.10.0-r11
Chainguard litellm 0 1.81.12.1-r0
Chainguard localstack 0 4.13.1-r2
Chainguard mlflow 0 3.9.0-r1
Chainguard mlflow-iamguarded-compat 0 3.9.0-r1
Chainguard open-webui 0 0.8.3-r1
Chainguard py3.10-ambassador 0 3.10.0-r20
Chainguard py3.11-ambassador 0 3.10.0-r20
Chainguard py3.11-azure-functions-worker 0 4.43.0-r0
Chainguard py3.12-ambassador 0 3.10.0-r20
Chainguard py3.12-azure-functions-worker 0 4.43.0-r0
Chainguard py3.13-ambassador 0 3.10.0-r20
Chainguard py3.13-azure-functions-worker 0 4.43.0-r0
Chainguard superset-5.0 0 5.0.0-r19
Chainguard superset-6.0 0 6.0.0-r3
Chainguard tensorflow-cpu-jupyter 0 2.20.0-r12
Chainguard tensorflow-gpu-jupyter 0 2.20.0-r11
Wolfi kubeflow-pipelines-visualization-server 0 2.16.0-r1
Wolfi kubeflow-volumes-web-app 0 1.10.0-r11
Wolfi mlflow 0 3.9.0-r1
Wolfi mlflow-iamguarded-compat 0 3.9.0-r1
Wolfi open-webui 0 0.8.3-r1
Wolfi py3.10-ambassador 0 3.10.0-r20
Wolfi py3.11-ambassador 0 3.10.0-r20
Wolfi py3.12-ambassador 0 3.10.0-r20
Wolfi py3.13-ambassador 0 3.10.0-r20
Wolfi superset-5.0 0 5.0.0-r19
Wolfi superset-6.0 0 6.0.0-r3
Wolfi tensorflow-cpu-jupyter 0 2.20.0-r12

Related vulnerabilities