CVE-2023-25577 is a denial-of-service vulnerability in Werkzeug, the WSGI utility library that underpins Flask and a large share of the Python web ecosystem. The flaw lives in Werkzeug's multipart form-data parser: when processing an incoming multipart/form-data request, the parser re-scanned previously buffered data for every new part it encountered instead of skipping past data it had already processed. That made parsing cost scale roughly quadratically (O(n²)) with the number of parts in a request. An attacker who sent a request containing an unusually large number of form parts — including file parts — could force a Werkzeug-based application to burn excessive CPU and memory just handling the upload, well before any application logic ran. Depending on deployment, this could stall or crash the worker process handling the request, effectively a low-effort denial-of-service vector against any endpoint that accepts multipart uploads.
This is a memory-safety-adjacent supply chain issue in the classic sense: the vulnerable code isn't in application logic teams write themselves, it's in a transitive dependency that almost every Flask app, and many other WSGI frameworks that reuse Werkzeug's form parsing, pull in without a second thought. That's exactly the kind of exposure that's easy to miss in manual reviews and only shows up reliably through dependency-aware scanning.
Affected Versions and Components
The vulnerability is in Werkzeug's multipart parser, specifically the code path in werkzeug.formparser responsible for splitting an incoming request body into individual form parts. Versions of Werkzeug prior to 2.2.3 are affected. Any application built on Flask, or directly on Werkzeug, that accepts multipart/form-data input — file uploads, forms with many fields, API endpoints that ingest multipart payloads — is potentially exposed if it's running on a vulnerable Werkzeug release.
Because Werkzeug sits underneath Flask as a core dependency, the practical blast radius is large: it isn't limited to projects that call Werkzeug's parsing APIs directly. Any Flask application inherits whatever version of Werkzeug is pinned (or left unpinned) in its dependency tree, which means the exposure often traces back to a requirements.txt, Pipfile.lock, or pyproject.toml entry that was never revisited after initial setup. The fix landed in Werkzeug 2.2.3, which limits the number of parts a request is allowed to contain and reworks the parsing logic so it no longer re-scans already-consumed buffer data.
Severity: CVSS, EPSS, and KEV Context
This is a denial-of-service issue, not a code execution or data disclosure bug — the impact is availability, not confidentiality or integrity. NVD's scoring for CVE-2023-25577 reflects that: a network-exploitable, low-complexity, no-privileges, no-user-interaction vulnerability whose consequence is resource exhaustion rather than compromise. Concretely, that maps to a High-severity availability impact with no confidentiality or integrity impact, consistent with a straightforward algorithmic-complexity DoS: an unauthenticated attacker can send a single crafted request and degrade or knock over the affected worker.
As of this writing, CVE-2023-25577 does not appear in CISA's Known Exploited Vulnerabilities (KEV) catalog, and there's no widely reported evidence of active in-the-wild exploitation campaigns targeting it specifically. That's consistent with the nature of the bug: it's a nuisance-and-availability issue rather than something that yields a foothold, credentials, or lateral movement, so it tends to sit lower on attacker priority lists than RCE or auth-bypass classes of bugs. That said, "no known exploitation" is not the same as "safe to ignore" — algorithmic-complexity DoS bugs are cheap to weaponize once a target is known to be vulnerable, since the "exploit" is just a crafted HTTP request, not a memory-corruption payload requiring careful engineering. Internet-facing upload endpoints, form submission APIs, and any service that accepts arbitrary multipart bodies from untrusted clients are the realistic targets.
Disclosure Timeline
Werkzeug is maintained by the Pallets project, and this vulnerability followed a fairly typical responsible-disclosure-to-patch-release path for a Python ecosystem library:
- The multipart parsing inefficiency was identified as a security-relevant issue affecting Werkzeug versions prior to 2.2.3.
- The Pallets maintainers shipped Werkzeug 2.2.3, which fixed the parser's quadratic behavior and added an explicit cap on the number of multipart parts a request is allowed to contain.
- The issue was assigned CVE-2023-25577 and published via GitHub Security Advisories and the National Vulnerability Database, with the advisory crediting the fix to the 2.2.3 release and describing the root cause as unbounded, re-scanning multipart parsing.
- Downstream distributions and package managers (PyPI metadata, Linux distro security trackers, and dependency-scanning tools) subsequently flagged installations pinned below 2.2.3 as vulnerable, prompting a wave of dependency bumps across Flask-based codebases.
Because Werkzeug is such a foundational dependency, the practical timeline for organizations wasn't just "patch released" — it was however long it took each downstream project to notice the advisory, bump the pin, and redeploy. For projects with loose or unpinned version constraints, the fix may have arrived automatically on the next pip install. For projects with strict lockfiles — which is most production Python services, for good reason — the fix required an explicit, deliberate update.
Remediation Steps
The remediation for CVE-2023-25577 is straightforward, but the operational challenge is finding every place the vulnerable version is actually running:
- Upgrade Werkzeug to 2.2.3 or later. This is the definitive fix — it caps the number of multipart parts per request and removes the quadratic re-scan behavior. If you're on a newer Flask release, check whether it already pins a fixed Werkzeug version; if you manage Werkzeug independently, update it explicitly in your lockfile.
- Audit your full dependency graph, not just direct requirements. Werkzeug is frequently a transitive dependency pulled in by Flask, and Flask itself may be pulled in by other frameworks or internal libraries. A vulnerable Werkzeug pin can hide several layers deep in a
poetry.lockorPipfile.lockthat no one has looked at recently. - Regenerate and re-pin lockfiles after upgrading, then verify the resolved version in the actual deployed artifact (container image, wheel, or virtualenv) rather than trusting the source manifest alone — it's common for a
requirements.txtto sayWerkzeug>=2.0while a stale lockfile or cached image still ships 2.2.2 or earlier. - Add request-size and part-count limits at the edge as defense in depth. Even with the patched parser, a reverse proxy or WAF rule that caps request body size and multipart part count reduces the attack surface for this entire class of bug, not just this one CVE.
- Re-scan and re-verify after remediation. Confirm via SBOM or dependency scan that no build artifact — including cached CI images, container base layers, and previously built wheels — still bundles a pre-2.2.3 Werkzeug.
- Monitor for anomalous multipart traffic as a compensating control while patches roll out across a fleet, particularly on internet-facing upload endpoints, since spikes in CPU or memory tied to form-parsing activity are a useful early indicator.
How Safeguard Helps
CVE-2023-25577 is a textbook example of why supply chain visibility has to go deeper than a top-level requirements.txt review. The vulnerable code was buried in a transitive dependency of one of the most widely used Python web libraries, and the only way to know with confidence whether a given service was exposed was to know exactly which version of Werkzeug was actually resolved and shipped in each build artifact — not just what a manifest declared.
Safeguard builds continuous SBOMs across your codebases and build pipelines, so instead of manually auditing lockfiles project by project, you get a live, queryable inventory of every dependency version actually deployed — including transitive ones like Werkzeug pulled in under Flask. When a CVE like this one is published, Safeguard correlates it against that inventory automatically, surfacing exactly which services, repos, and container images are running an affected version rather than leaving teams to grep through lockfiles under time pressure.
Beyond detection, Safeguard prioritizes findings using real-world context — CVSS impact, EPSS exploitation likelihood, and KEV status — so a low-exploitation-probability availability bug like CVE-2023-25577 doesn't get triaged with the same urgency as an actively exploited RCE, while still ensuring it doesn't fall off the remediation backlog entirely. Policy gates can also flag new builds that reintroduce a vulnerable Werkzeug pin, closing the gap where a fixed dependency quietly regresses through a stale cache, an unpinned range, or a reintroduced lockfile. For a dependency this pervasive, that continuous, artifact-level visibility is the difference between confirming remediation and hoping it happened.