GHSA-3wr7-993q-jrff: Coraza WAF: filename* charset bypass evades upload rules

GHSA-3wr7-993q-jrffMEDIUM
Published October 8, 2026
CISO Take

Coraza, a Go web application firewall, mis-parsed multipart upload filenames declared via the RFC 5987 `filename*` parameter with any charset other than utf-8 or us-ascii (such as iso-8859-1), so file-extension rules like a `.php` blocklist never saw the real name. The practical severity is Medium (CVSS 4.0, Attack Complexity High) because the bypass only helps against backends that decode a non-UTF-8 `filename*` or treat a `filename*`-only part as a file, such as Werkzeug/Flask and busboy-based Node.js stacks (multer, Fastify), while Go's standard library and PHP resolve the same name Coraza did. There is no KEV listing, no public exploit, no Nuclei template and no EPSS score, so near-term exploitation pressure is low. The blast radius is nonetheless wide: 6,147 downstream dependents, and Coraza often fronts AI/ML upload endpoints such as model, dataset and document-ingestion APIs. Upgrade to 3.8.1 (3.8.0 is also affected and its fix was incomplete), then review which of your upload backends are Werkzeug or busboy-based and add rules on MULTIPART_STRICT_ERROR and MULTIPART_FILENAME_CHARSET.

Sources:GitHub AdvisoryNVDATLAS

What is the risk?

Medium. The bypass is deterministic, needs no authentication or user interaction, and is network reachable. Attack Complexity is High because it depends on backend-specific filename handling outside the attacker's control. The effect is limited to filename-derived inspection (FILES, FILES_NAMES, FILES_COMBINED_SIZE); body, ARGS and header inspection still apply. Exploitation likelihood is low to moderate with no known exploit or KEV status. The real risk is that it removes a compensating control: a vulnerable upload handler behind the WAF becomes exposed to payloads the WAF was meant to block. The impact is Scope Changed and Integrity Low, because the protected application must itself be vulnerable.

How does the attack unfold?

Initial Access
The attacker sends a multipart upload to an AI service behind Coraza, with a decoy `filename` and the real name in a non-UTF-8 `filename*`.
AML.T0049
Defense Evasion
Coraza's parser drops the `filename*` value, so FILES-based extension and size rules never see the real filename.
AML.T0107
Exploitation
A backend that honors `filename*` (e.g. Werkzeug or busboy) stores the file under the attacker-chosen name.
Impact
A malicious script or artifact lands in the AI pipeline's storage, which may lead to code execution or pipeline compromise if the backend is also vulnerable.

What systems are affected?

PackageEcosystemVulnerable RangePatched
Anthropic Pythongo< 3.8.13.8.1
4.0K5.4K dependentsPushed 8d ago93% patched~9d to patchFull package profile →

Do you use Anthropic Python? You're affected.

How severe is it?

CVSS 3.1
4.0 / 10
EPSS
N/A
Exploitation Status
No known exploitation
Sophistication
Moderate

What is the attack surface?

AVACPRUISCIA
AVNetwork
ACHigh
PRNone
UINone
SChanged
CNone
ILow
ANone

What should I do?

1 step
  1. 1) Upgrade github.com/corazawaf/coraza/v3 to 3.8.1 or later; 3.8.0 is affected because its fix was incomplete (numbered continuations next to a bare filename*). 2) Add an allowlist rule such as: SecRule MULTIPART_FILENAME_CHARSET:upfile "!@rx ^(?:utf-8|iso-8859-1|us-ascii)$" "id:199,phase:2,t:none,t:lowercase,deny". Use an anchored regex, not @within, because @within lets an empty charset slip through. 3) Block on MULTIPART_STRICT_ERROR (CRS rule 200003) and MULTIPART_DUPLICATE_PART_HEADER. 4) Enforce extension and content-type validation in the backend itself, and do not rely on the WAF alone. 5) Detect: search access and audit logs for multipart parts containing filename*= with a charset other than UTF-8, and for uploads whose stored filename differs from the logged one. 6) Check whether your upload backend is Werkzeug/Flask or busboy-based, since those are the ones that can disagree with the WAF.

How is it classified?

Which compliance frameworks are affected?

This CVE is relevant to:

ISO 42001
A.6.2.6-AI system operation and monitoring
NIST AI RMF
MANAGE 2.3-Responses to previously unknown risks are managed
OWASP LLM Top 10
LLM03-Supply Chain

Frequently Asked Questions

What is GHSA-3wr7-993q-jrff?

Coraza, a Go web application firewall, mis-parsed multipart upload filenames declared via the RFC 5987 `filename*` parameter with any charset other than utf-8 or us-ascii (such as iso-8859-1), so file-extension rules like a `.php` blocklist never saw the real name. The practical severity is Medium (CVSS 4.0, Attack Complexity High) because the bypass only helps against backends that decode a non-UTF-8 `filename*` or treat a `filename*`-only part as a file, such as Werkzeug/Flask and busboy-based Node.js stacks (multer, Fastify), while Go's standard library and PHP resolve the same name Coraza did. There is no KEV listing, no public exploit, no Nuclei template and no EPSS score, so near-term exploitation pressure is low. The blast radius is nonetheless wide: 6,147 downstream dependents, and Coraza often fronts AI/ML upload endpoints such as model, dataset and document-ingestion APIs. Upgrade to 3.8.1 (3.8.0 is also affected and its fix was incomplete), then review which of your upload backends are Werkzeug or busboy-based and add rules on MULTIPART_STRICT_ERROR and MULTIPART_FILENAME_CHARSET.

Is GHSA-3wr7-993q-jrff actively exploited?

No confirmed active exploitation of GHSA-3wr7-993q-jrff has been reported, but organizations should still patch proactively.

How to fix GHSA-3wr7-993q-jrff?

1) Upgrade github.com/corazawaf/coraza/v3 to 3.8.1 or later; 3.8.0 is affected because its fix was incomplete (numbered continuations next to a bare `filename*`). 2) Add an allowlist rule such as: SecRule MULTIPART_FILENAME_CHARSET:upfile "!@rx ^(?:utf-8|iso-8859-1|us-ascii)$" "id:199,phase:2,t:none,t:lowercase,deny". Use an anchored regex, not @within, because @within lets an empty charset slip through. 3) Block on MULTIPART_STRICT_ERROR (CRS rule 200003) and MULTIPART_DUPLICATE_PART_HEADER. 4) Enforce extension and content-type validation in the backend itself, and do not rely on the WAF alone. 5) Detect: search access and audit logs for multipart parts containing `filename*=` with a charset other than UTF-8, and for uploads whose stored filename differs from the logged one. 6) Check whether your upload backend is Werkzeug/Flask or busboy-based, since those are the ones that can disagree with the WAF.

What systems are affected by GHSA-3wr7-993q-jrff?

This vulnerability affects the following AI/ML architecture patterns: WAF-protected model serving APIs, RAG document ingestion pipelines, ML model and dataset upload endpoints, API gateways and ingress controllers using Coraza, Notebook and ML platform web frontends.

What is the CVSS score for GHSA-3wr7-993q-jrff?

GHSA-3wr7-993q-jrff has a CVSS v3.1 base score of 4.0 (MEDIUM).

What is the AI security impact?

Affected AI Architectures

WAF-protected model serving APIsRAG document ingestion pipelinesML model and dataset upload endpointsAPI gateways and ingress controllers using CorazaNotebook and ML platform web frontends

MITRE ATLAS Techniques

AML.T0049Exploit Public-Facing Application
AML.T0107Exploitation for Defense Evasion

Compliance Controls Affected

ISO 42001: A.6.2.6
NIST AI RMF: MANAGE 2.3
OWASP LLM Top 10: LLM03

What are the technical details?

Original Advisory

## Summary Coraza extracts a multipart file upload's filename using Go's standard-library `mime.ParseMediaType`, which only decodes the RFC 5987/6266 extended `filename*` `Content-Disposition` parameter when its declared charset is exactly `us-ascii` or `utf-8`. Any other charset — including `iso-8859-1`, which RFC 5987 explicitly permits — is silently dropped by the stdlib, with no error and no fallback signal. This lets an attacker present one filename to Coraza and a different one to the backend application in the same request. ## Affected variables `FILES` (documented as *"the original filenames as submitted by the client in the multipart upload"*), `FILES_SIZES`, and the file-vs-field routing decision that determines whether a part is tracked as a file at all. `MULTIPART_FILENAME` is not affected. It is declared in Coraza but is not populated by any code path in the affected versions. ## Details `internal/bodyprocessors/multipart.go`'s `originFileName` calls `mime.ParseMediaType` on the raw `Content-Disposition` header and reads `dispositionParams["filename"]`, which then populates `FILES`/`FILES_SIZES` for parts recognized as file uploads. Per RFC 7578 §4.2 and RFC 6266, when both `filename` and `filename*` are present, `filename*` is authoritative. Go's `mime.ParseMediaType` implements this precedence internally (`decode2231Enc` in `mediatype.go`), but only for `filename*` values whose charset token is `us-ascii` or `utf-8`: ```go // mime/mediatype.go if charset != "us-ascii" && charset != "utf-8" { // TODO: unsupported encoding return "", false } ``` When `decode2231Enc` returns `false`, the stdlib silently leaves whatever was parsed for the plain `filename` parameter untouched (or leaves `filename` absent entirely if only `filename*` was supplied) — there is no error, no partial-parse indicator, nothing a caller can detect. The stdlib also discards the RFC 5987 `language` component of `filename*` entirely; it is not recoverable from the parsed params map at all. ### Verified behavior (prior to the fix) | `Content-Disposition` fields | Effective filename (`FILES`) | Recognized as file upload? | |---|---|---| | `filename="safe.jpg"; filename*=UTF-8''shell.php` | `shell.php` (correct) | yes | | `filename="safe.jpg"; filename*=iso-8859-1''shell.php` | `safe.jpg` (decoy wins) | yes, but with the wrong name | | `filename*=iso-8859-1''shell.php` (no plain `filename`) | *(none)* | **no** — processed as an ordinary form field into `ARGS_POST` instead | In the second row, any rule inspecting `FILES` for a dangerous extension only ever sees `safe.jpg`, while a backend that follows RFC 7578 precedence and accepts `iso-8859-1` (an RFC-5987-legal, non-exotic charset) uses `shell.php` as the filename. In the third row, the upload skips file-specific rule scope and size-limit tracking (`FILES_COMBINED_SIZE`) entirely. ## Impact A rule set that blocks file uploads by extension or name via `FILES` (or scopes rules to `FILES`/`FILES_NAMES`) can be bypassed by supplying the real filename via `filename*` under any charset other than `utf-8`/`us-ascii` — `iso-8859-1` is sufficient and is a standards-compliant choice, not an obscure or malformed one — optionally alongside an innocuous decoy `filename`. A part carrying only `filename*` additionally escapes file-vs-field routing, so `FILES`, `FILES_NAMES` and `FILES_COMBINED_SIZE` never see it. The bypass is deterministic against Coraza and requires no authentication or user interaction. Its effect is bounded to filename-derived inspection: request body content, `ARGS` and headers are still inspected, and a part carrying only `filename*` is routed into `ARGS_POST`. Coraza itself neither stores nor serves the uploaded file, so whether an evaded rule was preventing a change of state depends on the backend's own `filename*` precedence handling and upload handling. This is scored as a scope-changing bypass with low integrity impact (`S:C`, `I:L`) rather than a direct compromise. ## Proof of Concept ``` Content-Disposition: form-data; name="upload"; filename="safe.jpg"; filename*=iso-8859-1''shell.php ``` Processed through `internal/bodyprocessors/multipart.go` prior to the fix, `FILES` for this part contained `safe.jpg`. A `SecRule FILES "\.php$"` (or similar extension-blocklist rule) did not fire, while a backend honoring RFC 5987/7578 `filename*` precedence uses `shell.php` as the filename. ## Resolution Fixed by locating and decoding `filename*` independently of `mime.ParseMediaType`'s charset restriction (a quoted-string-aware scanner over the raw header). The fix also populates `MULTIPART_FILENAME`/`MULTIPART_NAME`, which were previously declared but unpopulated. **Both readings are exposed, not just `filename*`.** An earlier version of this fix made `filename*` unconditionally authoritative over the plain `filename`, per RFC 7578 §4.2 — this closed the PoC above but relocated the same bypass: swap which field carries the real name (`filename="shell.php"; filename*=iso-8859-1''safe.jpg`) and a backend that resolves the plain `filename` instead is missed, since the discarded reading never reached `FILES` at all. Fixed in `89399e67`: when a well-formed `filename*` picks a different value than the plain `filename`, both are added to `FILES`/`FILES_SIZES`/`MULTIPART_FILENAME` for the same part (one temp file, one byte count, two names). A `SecRule FILES "\.php$"` now catches the payload regardless of which field carries the real name. Coraza does not select a "safe" charset allowlist on the operator's behalf. Instead, `filename*`'s declared charset and language are exposed as their own rule-matchable collections, `MULTIPART_FILENAME_CHARSET` and `MULTIPART_FILENAME_LANGUAGE`, alongside `MULTIPART_FILENAME`, so operators can enforce their own allowlist. Design credit: **@airween**. For `Content-Disposition: form-data; name="upload"; filename="safe.jpg"; filename*=UTF-8''shell.php`: ``` MULTIPART_FILENAME:upload = [shell.php, safe.jpg] (filename* first, plain filename kept alongside) MULTIPART_FILENAME_CHARSET:upload = UTF-8 MULTIPART_FILENAME_LANGUAGE:upload = "" (empty string; language is optional) ``` Rule writers can enforce a charset allowlist directly. Use an anchored regex rather than `@within`: ``` SecRule MULTIPART_FILENAME_CHARSET:upfile "!@rx ^(?:utf-8|iso-8859-1|us-ascii)$" \ "id:199,phase:2,t:none,t:lowercase,deny" ``` `!@within utf-8,iso-8859-1,us-ascii` looks equivalent and is not. `@within` treats its parameter as the haystack and the variable as the needle, so an empty value is trivially contained in it and the negation never fires. A `filename*` that declares no charset at all — `filename*=''shell.php`, which RFC 5987's grammar does not permit but which Coraza accepts and exposes rather than rejecting — therefore passes an `@within` allowlist untouched. Measured: | `MULTIPART_FILENAME_CHARSET` | `!@within utf-8,...` | `!@rx ^(?:utf-8\|...)$` | |---|---|---| | `UTF-8` | quiet | quiet | | `shift_jis` | fires | fires | | *(empty, from* `filename*=''...`*)* | **quiet** | fires | Both spellings stay quiet on a part with no `filename*` at all, because the collections are then absent rather than empty and the rule never evaluates. That is what makes an allowlist rule safe to apply to all traffic rather than only to known upload fields. ### Discrepancies are no longer dropped silently The same silent-fallback pattern this advisory describes existed on two neighbouring paths: a part whose `Content-Disposition` was present but unparseable, and a part carrying a `filename*` that did not match the `charset'[language]'value` shape at all. Both were reduced to a part with no filename — routed into `ARGS_POST`, outside `FILES`-scoped rule coverage, with nothing to signal that a filename had been discarded. Both now raise `MULTIPART_STRICT_ERROR` (rule 200003 in CRS). A part with *no* `Content-Disposition` at all is not flagged: it simply carries no filename. `mime.ParseMediaType` accepts every real-world filename shape tested — raw UTF-8 and emoji, spaces, Windows backslashes, a bare `%`, unquoted values — so this signal does not reach legitimate uploads. ### New variable: `MULTIPART_DUPLICATE_PART_HEADER` Set to `1` when a part repeats a header (two `Content-Disposition` headers) or repeats a parameter inside its `Content-Disposition` (two `filename` parameters). A duplicate is precisely where backends disagree about which value wins, so the duplicate itself is the signal; it also contributes to `MULTIPART_STRICT_ERROR`. ``` SecRule MULTIPART_STRICT_ERROR "@eq 1" "id:201,phase:2,deny,t:none,chain" SecRule MULTIPART_DUPLICATE_PART_HEADER "@eq 1" ``` ModSecurity adds the same variable in its fix for [GHSA-5pww-8rfg-9crf](https://github.com/owasp-modsecurity/ModSecurity/security/advisories/GHSA-5pww-8rfg-9crf) and lists it in the recommended audit-log format as `DH`. Coraza implements it under the same name so that a rule set referencing it parses on both engines. ### Note on percent-decoding Coraza percent-decodes the `filename*` value, so `MULTIPART_FILENAME` and `FILES` hold the name the application will actually resolve. This matters for rules that match on file extension: CRS 933110 and 944140 target `FILES` with `t:none,t:lowercase,t:removeWhitespace` and perform no URL decoding, so an encoded separator such as `filename*=UTF-8''shell%2Ephp` would evade them if the raw value were stored instead. ModSecurity's fix stored the raw, still-encoded value when this was first written; after the difference was raised on its advisory it now percent-decodes as well, so the two engines agree on this point. ### Three further discrepancies found in post-fix review Reviewing the fix (`23446b5e`) against test coverage turned up three more cases where Coraza's parsed value could disagree with what a backend resolves, closed in `f30df398`: - An unresolvable `%` escape in `filename*` (`filename*=UTF-8''safe.jpg%ZZ`) is still kept as-is rather than substituting a decoy plain `filename`, but now also raises `MULTIPART_STRICT_ERROR`. Without this, the discrepancy was silent whenever a backend decoded the same escape differently or rejected it outright. - A quoted `filename*` value (`filename*="UTF-8''shell.php"`) is unwrapped before parsing. RFC 5987 does not permit `filename*` to be a quoted-string, but a general Content-Disposition parser such as Go's `mime.ParseMediaType` accepts a quoted value for any parameter. Without unwrapping, the literal quote characters ended up in `MULTIPART_FILENAME`/`MULTIPART_FILENAME_CHARSET`, so an anchored rule (e.g. `\.php$`) did not match a filename the backend resolved cleanly. - `MULTIPART_FILENAME`, `MULTIPART_FILENAME_CHARSET`, `MULTIPART_FILENAME_LANGUAGE` and `MULTIPART_NAME` are multi-valued collections: two parts sharing one `name` (e.g. a multi-file upload field) each keep their own filename instead of the later part overwriting the earlier one. ModSecurity's fix for [GHSA-5pww-8rfg-9crf](https://github.com/owasp-modsecurity/ModSecurity/security/advisories/GHSA-5pww-8rfg-9crf) independently identifies and fixes the same class of bug, describing it as "a second, distinct bypass," by keeping the equivalent variables multi-valued. ### The precedence decision itself was found to relocate the bypass Reviewing `filename*`-as-authoritative, @jptosso demonstrated (with a repro against Go's own `mime/multipart` as the reference backend) that this precedence choice doesn't close the two-sided decoy: it only decides which of the two payload shapes above is caught, not both. Checked against ModSecurity v3's actual fix for GHSA-5pww-8rfg-9crf: it makes the identical unconditional-precedence choice (single value, `filename*` wins, plain `filename` discarded), with no record of the two-sided case being considered there either — this is not a case of Coraza deviating from a more careful upstream fix. Fixed in `89399e67` by keeping both readings rather than picking one (see Resolution above). Test coverage for the original PoC direction only ever exercised the `filename="safe.jpg"; filename*=...''shell.php` order; the swapped order (`filename="shell.php"; filename*=...''safe.jpg`) is now a dedicated regression case, since that gap in coverage is exactly why the one-sided version shipped in the first place. ### Patched in 3.8.1 The 3.8.0 fix was incomplete. 3.8.1 completes it: an RFC 2231 numbered continuation (`filename*0=`, `filename*0*=`, ...) next to a bare `filename*` parameter now sets `MULTIPART_STRICT_ERROR`, and the Content-Disposition duplicate-parameter check added in 3.8.0 is now linear instead of quadratic in the number of parameters. Upgrade to 3.8.1; 3.8.0 is listed as affected. ### Severity (revised 2026-10-02) `CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:L/A:N` (4.0, Medium). Attack Complexity is High because the bypass depends on a specific backend behaviour outside the attacker's control. Go's `mime.ParseMediaType` and PHP resolve the same filename Coraza did, so they are not affected. The discrepancy exists only for backends that decode a non-UTF-8 `filename*` or treat a `filename*`-only part as a file, such as Werkzeug/Flask and busboy-based Node.js backends (Express with multer, Fastify). The continuation case fixed in 3.8.1 is resolved only by Werkzeug. The previous vector scored Attack Complexity Low (5.8). Impact metrics follow the convention used across Coraza's WAF-bypass advisories: the vulnerable component is Coraza, but the impact lands on the protected application, so Scope is Changed. The bypass hides a payload from inspection; the application still has to be vulnerable to it, so Integrity is Low and Confidentiality is not scored separately. _AI involvement in this section: Claude Opus 5.5 (Anthropic), via Claude Code, re-derived the CVSS vector from the project's triage guidance (AGENTS.md, "CVSS preconditions get verified, not copied from the report") and drafted this text. A human maintainer (fzipi) chose the `S:C/I:L` impact convention and directed this update._

Exploitation Scenario

An attacker targets a model-hosting or document-ingestion service behind a Coraza WAF that blocks .php, .py and .jsp uploads through a FILES rule. They send a multipart upload with `Content-Disposition: form-data; name="upload"; filename="safe.jpg"; filename*=iso-8859-1''shell.php`. Coraza records only `safe.jpg` and lets the request through. A Flask/Werkzeug or Node busboy backend resolves `filename*` and saves the file as shell.php. If the upload directory is executable or served, the attacker gains code execution, or plants a malicious script or artifact in the AI pipeline's storage. Exploitation depends on the backend's own weaknesses.

Weaknesses (CWE)

CWE-20 — Improper Input Validation: The product receives input or data, but it does not validate or incorrectly validates that the input has the properties that are required to process the data safely and correctly.

  • [Architecture and Design] Consider using language-theoretic security (LangSec) techniques that characterize inputs using a formal language and build "recognizers" for that language. This effectively requires parsing to be a distinct layer that effectively enforces a boundary between raw input and internal data representations, instead of allowing parser code to be scattered throughout the program, where it could be subject to errors or inconsistencies that create weaknesses. [REF-1109] [REF-1110] [REF-1111]
  • [Architecture and Design] Use an input validation framework such as Struts or the OWASP ESAPI Validation API. Note that using a framework does not automatically address all input validation problems; be mindful of weaknesses that could arise from misusing the framework itself (CWE-1173).

Source: MITRE CWE corpus.

CVSS Vector

CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:L/A:N

Timeline

Published
October 8, 2026
Last Modified
October 8, 2026
First Seen
October 8, 2026

Related Vulnerabilities