Cloudanix VulnDB

CVE-2025-66221

None

CVSS

—

EPSS

0.5%

Published

Nov 29, 2025

CISA due date

—

Cloudanix analysis

Next patch cycle SSVC: Track

A flaw in a common Python web component can let specially named Windows files disrupt application file features unless the component is updated.

Chainable
Exploitation
Remote Unauthenticated
Attacker needs
None
Complexity
Moderate
Impact
Denial Of Service
Component
Library
Class
Path Traversal
Remediation
Upgrade
Effort
Medium
Patch risk
Low

What it actually does

Werkzeug's safe_join() path validation could allow Windows reserved device names, such as CON, PRN, AUX, NUL, COM1-COM9, and LPT1-LPT9, to be treated as safe path components. On Windows, these names are interpreted by the operating system as device objects rather than normal files, even when they appear inside another directory or include extensions. Applications that use safe_join() to constrain user-supplied filenames for download, upload, static file access, or file management may therefore interact with special devices instead of intended files. The primary technical risks are denial of service through blocking or error-prone file operations, unexpected reads or writes to device handles, bypass of filename assumptions, and application-specific logic errors. This is not a typical arbitrary file read path traversal on its own, but it undermines safe path handling on Windows deployments.

Why it matters to the business

Organizations running Python web applications that rely on Werkzeug safe_join() on Windows may experience service instability, request hangs, failed file operations, or unexpected behavior in file-handling features exposed to users. The risk is lower than widely exploited remote code execution vulnerabilities: EPSS is low at 0.00518, it is not listed in CISA KEV, and no public exploit maturity is indicated. However, Werkzeug is a common dependency in Flask-based and other Python web applications, so exposure may exist indirectly through application frameworks or custom file-serving code. Because the issue has been public for 253 days, affected applications should be remediated as part of normal dependency hygiene, with higher urgency for internet-facing Windows-hosted services that accept user-controlled file paths.

How an attack unfolds

An attacker first identifies a Windows-hosted application endpoint that accepts a filename or path and uses Werkzeug safe_join() or an equivalent wrapper to keep access within an intended base directory. The attacker supplies a Windows reserved device name, such as NUL, CON, AUX, PRN, COM1, or LPT1, possibly with an extension or URL encoding to fit application validation rules. Vulnerable safe_join() logic may approve the value because it does not escape the base directory syntactically. When the application subsequently opens, reads, writes, sends, deletes, or otherwise processes the joined path, Windows resolves the component as a device name rather than a normal file. Depending on the endpoint and operation, this may cause blocking behavior, discarded writes, unexpected exceptions, misleading success states, or application-level denial of service. There is currently no indication that this vulnerability is in KEV, used in ransomware campaigns, or associated with named threat actors.

How to fix it

Upgrade Werkzeug to a version that includes the fix for this issue, or if consuming directly from the Git repository, update to commit 1c7beb671d1af67a07684ffa1fffdb0fc76c7fa4 or later. Rebuild and redeploy all applications and containers that vendor or bundle Werkzeug. If immediate upgrade is not possible, add application-level validation that rejects Windows reserved device names case-insensitively, including names with extensions: CON, PRN, AUX, NUL, COM1 through COM9, LPT1 through LPT9, CONIN$, and CONOUT$. Apply this validation before calling safe_join() or performing filesystem operations. Normalize and decode user input before validation, reject alternate separators, and avoid accepting arbitrary user-controlled paths where a server-generated identifier can be used instead. For public file-serving or upload endpoints, combine the fix with allowlisted filenames, strict extension checks, size limits, and request timeouts.

How to confirm the fix worked

Inventory Python applications and dependency manifests for Werkzeug usage, including transitive dependencies from Flask or other Pallets projects. Confirm the deployed Werkzeug source includes commit 1c7beb671d1af67a07684ffa1fffdb0fc76c7fa4 or a release containing that fix. On Windows test systems, exercise file-handling endpoints with reserved names such as NUL, CON, AUX, PRN, COM1, and LPT1, including variants like CON.txt, nul.log, and URL-encoded forms. A remediated application should reject these names before attempting filesystem access and should return a controlled client error rather than hanging, opening a device, or producing an unhandled exception. For packaged applications, validate both build-time lockfiles and runtime installed packages, because container images, virtual environments, and vendored dependencies may differ from source repositories.

Typical attack path

  1. Find an application endpoint that accepts user-controlled filenames or relative paths, such as download, upload, attachment, static asset, import, export, or file-preview functionality.
  2. Confirm or infer that the application is hosted on Windows and uses Werkzeug or a Werkzeug-dependent Python framework for safe path joining.
  3. Submit a reserved Windows device name such as CON, AUX, NUL, PRN, COM1, or LPT1, optionally with an extension such as CON.txt or URL encoding.
  4. Cause the application to pass the value through safe_join(), which may incorrectly consider the path safe because it remains under the configured base directory.
  5. Trigger the application file operation so Windows resolves the component as a special device instead of a regular file.
  6. Leverage the resulting behavior for denial of service, request hangs, application errors, discarded writes, or bypass of expected filename handling.

How to detect it

To assess prior exploitation, review HTTP access logs, reverse proxy logs, WAF events, application logs, Python exception traces, and endpoint telemetry from Windows-hosted services that perform file operations using user-supplied names. Focus on requests to download, upload, attachment, media, static, export, import, preview, or file-management routes where the path, filename, or query parameter contains Windows reserved device names. Look for case-insensitive use of CON, PRN, AUX, NUL, COM1-COM9, LPT1-LPT9, CONIN$, or CONOUT$, including names with extensions, mixed slashes, dot suffixes, and URL encoding. Correlate suspicious requests with application errors, long-running requests, elevated response times, worker restarts, 500 responses, timeout messages, or Windows file I/O errors. In application telemetry, look for exceptions raised during open(), send_file(), send_from_directory(), save(), unlink(), rename(), or archive extraction operations involving those names. Exploitation may not leave a persistent file artifact because Windows resolves the names as devices, so network and application logs are the primary evidence sources.

  • (?i)(^|[\\/])(con|prn|aux|nul)(\.[^\\/?]*)?($|[\\/?#])
  • (?i)(^|[\\/])(com[1-9]|lpt[1-9])(\.[^\\/?]*)?($|[\\/?#])
  • (?i)(^|[\\/])(conin\$|conout\$)(\.[^\\/?]*)?($|[\\/?#])
  • %43%4f%4e
  • %63%6f%6e
  • %4e%55%4c
  • %6e%75%6c
  • %41%55%58
  • %61%75%78
  • %50%52%4e
  • %70%72%6e
  • CON.txt
  • con.txt
  • NUL.txt
  • nul.txt
  • AUX.txt
  • aux.txt
  • PRN.txt
  • prn.txt
  • COM1.txt
  • com1.txt
  • LPT1.txt
  • lpt1.txt
  • OSError: [Errno 22] Invalid argument
  • PermissionError: [Errno 13] Permission denied
  • werkzeug.utils.safe_join

What your security team should do

  • Prioritize remediation for internet-facing Windows-hosted Python applications that expose file download, upload, preview, attachment, static-file, archive extraction, or path-based routing features.
  • Upgrade Werkzeug to a fixed version or Git commit 1c7beb671d1af67a07684ffa1fffdb0fc76c7fa4 or later, then rebuild and redeploy affected services.
  • Add defense-in-depth filename validation that blocks Windows reserved device names even if the application is not currently deployed on Windows, to prevent future platform-dependent exposure.
  • Search access logs, application logs, WAF logs, and exception telemetry for requests containing Windows device names, especially against file-handling endpoints.
  • Add regression tests for reserved device names with mixed case, extensions, path separators, and URL-encoded input.
  • Use dependency scanning and SBOM tooling to identify transitive Werkzeug usage across Flask-based applications.
  • Because there is no KEV listing, no ransomware association, low EPSS, and no known public exploit maturity, handle this as a targeted hardening and patch-management item unless local exposure is confirmed.

The record

Werkzeug safe_join() allows Windows special device names

Affected packages

Ecosystem Package Introduced Fixed
PyPI werkzeug 0 3.1.4
Chainguard airflow-2 0 0
Chainguard airflow-2-bitnami-compat 0 0
Chainguard airflow-2-compat 0 0
Chainguard airflow-2-iamguarded-compat 0 0
Chainguard airflow-3 0 0
Chainguard airflow-3-bitnami-compat 0 0
Chainguard airflow-3-compat 0 3.1.3-r0
Chainguard airflow-3-iamguarded-compat 0 3.1.3-r0
Chainguard airflow-core-2 0 0
Chainguard airflow-core-2-compat 0 0
Chainguard airflow-core-2-oci-entrypoint 0 0
Chainguard azure-functions-python-worker 0 4.41.1-r0
Chainguard emissary 0 0
Chainguard emissary-apiext 0 0
Chainguard emissary-oci-entrypoint 0 0
Chainguard kubeflow-jupyter-web-app 0 1.10.0-r7
Chainguard kubeflow-pipelines-visualization-server 0 0
Chainguard kubeflow-volumes-web-app 0 0
Chainguard localstack 0 4.11.1-r1
Chainguard localstack-compat 0 4.11.1-r1
Chainguard mlflow 0 3.6.0-r1
Chainguard mlflow-bitnami 0 3.6.0-r1
Chainguard mlflow-iamguarded-compat 0 3.6.0-r1
Chainguard py3-supported-azure-functions-worker 0 4.41.1-r0
Chainguard py3.10-ambassador 0 0
Chainguard py3.11-ambassador 0 0
Chainguard py3.11-azure-functions-worker 0 4.41.1-r0
Chainguard py3.12-ambassador 0 0
Chainguard py3.12-azure-functions-worker 0 4.41.1-r0
Chainguard py3.13-ambassador 0 0
Chainguard py3.13-azure-functions-worker 0 4.41.1-r0
Chainguard superset-4.1 0 4.1.4-r3
Chainguard superset-4.1-entrypoint 0 4.1.4-r3
Chainguard superset-4.1-iamguarded-compat 0 4.1.4-r3
Chainguard superset-5.0 0 5.0.0-r8
Chainguard superset-5.0-entrypoint 0 5.0.0-r8
Chainguard superset-5.0-iamguarded-compat 0 5.0.0-r8
Chainguard tensorflow-cpu-jupyter 0 2.20.0-r5
Chainguard tensorflow-gpu-jupyter 0 2.20.0-r4
Wolfi airflow-3 0 0
Wolfi airflow-3-bitnami-compat 0 0
Wolfi airflow-3-compat 0 3.1.3-r0
Wolfi airflow-3-iamguarded-compat 0 3.1.3-r0
Wolfi emissary 0 0
Wolfi emissary-apiext 0 0
Wolfi emissary-oci-entrypoint 0 0
Wolfi kubeflow-jupyter-web-app 0 1.10.0-r7
Wolfi kubeflow-pipelines-visualization-server 0 0
Wolfi kubeflow-volumes-web-app 0 0
Wolfi mlflow 0 3.6.0-r1
Wolfi mlflow-bitnami 0 3.6.0-r1
Wolfi mlflow-iamguarded-compat 0 3.6.0-r1
Wolfi py3.10-ambassador 0 0
Wolfi py3.11-ambassador 0 0
Wolfi py3.12-ambassador 0 0
Wolfi py3.13-ambassador 0 0
Wolfi superset-4.1 0 4.1.4-r3
Wolfi superset-4.1-entrypoint 0 4.1.4-r3
Wolfi superset-4.1-iamguarded-compat 0 4.1.4-r3
Wolfi superset-5.0 0 5.0.0-r8
Wolfi superset-5.0-entrypoint 0 5.0.0-r8
Wolfi superset-5.0-iamguarded-compat 0 5.0.0-r8
Wolfi tensorflow-cpu-jupyter 0 2.20.0-r5
openSUSE:Tumbleweed python-openapi-core 0 0.22.0-1.1

Related vulnerabilities