GHSA-w253-m66g-rx24: Coraza WAF: Content-Type parsing flaw bypasses body inspection
GHSA-w253-m66g-rx24MEDIUMCoraza, a widely used open-source Go web application firewall, skips inspection of form bodies in two cases. The first is a Content-Type that carries a parameter such as 'charset=UTF-8'. The second is a request with duplicate Content-Type headers, or one with leading Unicode whitespace in the media type. In those cases custom rules on ARGS_POST, FILES and REQUEST_BODY see nothing, while the backend still parses the payload normally. This matters for AI teams because Coraza is often placed in front of LLM gateways, model-serving APIs and agent endpoints, and the bypass lets injection strings or malicious uploads through unseen. The CVSS score is a moderate 5.8, with no known exploitation: it is not in CISA KEV, no public exploit or Nuclei template exists, and no EPSS data is available. The blast radius is large, though, with 6,147 downstream dependents, and the issue affects versions 3.0.4 up to but excluding 3.8.1. Upgrade to 3.8.1 (3.8.0 is also affected because its first fix was incomplete). Deployments that run the OWASP CRS with rules 901340 and 920620 are partly mitigated. Custom-rule-only deployments should add a rule that denies requests with more than one Content-Type header.
What is the risk?
Medium overall (CVSS 3.1: 5.8, AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:N). The flaw is deterministic, needs no authentication and is trivial to trigger, because the attacker only changes a header. It does not compromise Coraza itself. Instead it silently disables body-based detection, so the real impact depends on whether the protected AI application is vulnerable to the hidden payload. There are no signs of active exploitation: it is not in KEV, no public exploit or scanner template exists, and EPSS is unavailable. Exposure is highest for internet-facing AI endpoints that rely on custom Coraza rules rather than the OWASP CRS. The CRS already includes rules 901340 and 920620, which cover the two variants. The tracked package risk score is 31/100, and the package has 56 other CVEs.
How does the attack unfold?
What systems are affected?
| Package | Ecosystem | Vulnerable Range | Patched |
|---|---|---|---|
| Anthropic Python | go | >= 3.0.4, < 3.8.1 | 3.8.1 |
Do you use Anthropic Python? You're affected.
How severe is it?
What is the attack surface?
What should I do?
1 step1) Upgrade github.com/corazawaf/coraza/v3 to 3.8.1 or later and rebuild any binaries or plugins that embed it, such as Caddy builds or ingress images. Run
go list -m allor an SBOM scan to find transitive uses. 2) Enable OWASP CRS, which includes rule 901340 to force body inspection and rule 920620 to check Content-Type sanity. 3) For custom-rule-only deployments, addSecRule &REQUEST_HEADERS:Content-Type "@gt 1" "deny,status:400,id:..."and consider forcing the processor withctl:requestBodyProcessor=URLENCODEDfor known form endpoints. 4) Keep backend input validation in place, so the WAF is not the only control for AI endpoints. 5) Detection: look in access logs for POST requests with duplicate Content-Type headers, parameterizedapplication/x-www-form-urlencoded; charset=...values, or unusual leading whitespace in Content-Type, especially where phase-2 rules did not fire. Review historical logs from before patching for payloads that should have been blocked.
How is it classified?
Which compliance frameworks are affected?
This CVE is relevant to:
Frequently Asked Questions
What is GHSA-w253-m66g-rx24?
Coraza, a widely used open-source Go web application firewall, skips inspection of form bodies in two cases. The first is a Content-Type that carries a parameter such as 'charset=UTF-8'. The second is a request with duplicate Content-Type headers, or one with leading Unicode whitespace in the media type. In those cases custom rules on ARGS_POST, FILES and REQUEST_BODY see nothing, while the backend still parses the payload normally. This matters for AI teams because Coraza is often placed in front of LLM gateways, model-serving APIs and agent endpoints, and the bypass lets injection strings or malicious uploads through unseen. The CVSS score is a moderate 5.8, with no known exploitation: it is not in CISA KEV, no public exploit or Nuclei template exists, and no EPSS data is available. The blast radius is large, though, with 6,147 downstream dependents, and the issue affects versions 3.0.4 up to but excluding 3.8.1. Upgrade to 3.8.1 (3.8.0 is also affected because its first fix was incomplete). Deployments that run the OWASP CRS with rules 901340 and 920620 are partly mitigated. Custom-rule-only deployments should add a rule that denies requests with more than one Content-Type header.
Is GHSA-w253-m66g-rx24 actively exploited?
No confirmed active exploitation of GHSA-w253-m66g-rx24 has been reported, but organizations should still patch proactively.
How to fix GHSA-w253-m66g-rx24?
1) Upgrade github.com/corazawaf/coraza/v3 to 3.8.1 or later and rebuild any binaries or plugins that embed it, such as Caddy builds or ingress images. Run `go list -m all` or an SBOM scan to find transitive uses. 2) Enable OWASP CRS, which includes rule 901340 to force body inspection and rule 920620 to check Content-Type sanity. 3) For custom-rule-only deployments, add `SecRule &REQUEST_HEADERS:Content-Type "@gt 1" "deny,status:400,id:..."` and consider forcing the processor with `ctl:requestBodyProcessor=URLENCODED` for known form endpoints. 4) Keep backend input validation in place, so the WAF is not the only control for AI endpoints. 5) Detection: look in access logs for POST requests with duplicate Content-Type headers, parameterized `application/x-www-form-urlencoded; charset=...` values, or unusual leading whitespace in Content-Type, especially where phase-2 rules did not fire. Review historical logs from before patching for payloads that should have been blocked.
What systems are affected by GHSA-w253-m66g-rx24?
This vulnerability affects the following AI/ML architecture patterns: Model serving behind WAF, LLM API gateways, RAG ingestion pipelines, Agent frameworks with public endpoints, ML web UIs and notebooks, Kubernetes ingress / Caddy / Envoy WAF deployments.
What is the CVSS score for GHSA-w253-m66g-rx24?
GHSA-w253-m66g-rx24 has a CVSS v3.1 base score of 5.8 (MEDIUM).
What is the AI security impact?
Affected AI Architectures
MITRE ATLAS Techniques
AML.T0049Exploit Public-Facing ApplicationAML.T0093Prompt Infiltration via Public-Facing ApplicationAML.T0107Exploitation for Defense EvasionCompliance Controls Affected
What are the technical details?
Original Advisory
### Summary Coraza fails to inspect URL-encoded form bodies when their valid `Content-Type` contains a media-type parameter, for example: ```http Content-Type: application/x-www-form-urlencoded; charset=UTF-8 ``` An unauthenticated attacker can use this header to hide the complete form body from custom Coraza rules that inspect `ARGS_POST` or `REQUEST_BODY`, while the bundled Go HTTP middleware forwards the body and the backend parses it normally. This is a deterministic request-body inspection bypass. Suggested severity: **Medium**. Current OWASP CRS includes rule `901340`, which forces fallback inspection and mitigates this path; this report does not claim a bypass of an unmodified current CRS ruleset ### Details The affected component is request-body processor selection in [`internal/corazawaf/transaction.go`](https://github.com/corazawaf/coraza/blob/db9850b2dd8992f97a8cefe08d0cb4edd966a04c/internal/corazawaf/transaction.go#L371-L390). `Transaction.AddRequestHeader` compares the complete lowercased header value using exact equality: ```go case "content-type": val := strings.ToLower(value) if val == "application/x-www-form-urlencoded" { tx.variables.reqbodyProcessor.Set("URLENCODED") } else if strings.HasPrefix(val, "multipart/form-data") { tx.variables.reqbodyProcessor.Set("MULTIPART") } ``` Source: [`internal/corazawaf/transaction.go`, lines 383-390](https://github.com/corazawaf/coraza/blob/db9850b2dd8992f97a8cefe08d0cb4edd966a04c/internal/corazawaf/transaction.go#L383-L390). The media type of `application/x-www-form-urlencoded; charset=UTF-8` remains `application/x-www-form-urlencoded`; `charset` is a parameter. Because Coraza compares the entire header, the comparison fails and `REQBODY_PROCESSOR` remains empty. `ProcessRequestBody` treats the empty processor as success: ```go rbp = strings.ToLower(rbp) if rbp == "" { tx.WAF.Rules.Eval(types.PhaseRequestBody, tx) return tx.interruption, nil } ``` Source: [`internal/corazawaf/transaction.go`, lines 1115-1120](https://github.com/corazawaf/coraza/blob/db9850b2dd8992f97a8cefe08d0cb4edd966a04c/internal/corazawaf/transaction.go#L1115-L1120). Coraza therefore evaluates phase 2 without parsing the body and without setting `REQBODY_ERROR`. The [URL-encoded processor that normally populates `ARGS_POST`, `REQUEST_BODY`, and `REQUEST_BODY_LENGTH`](https://github.com/corazawaf/coraza/blob/db9850b2dd8992f97a8cefe08d0cb4edd966a04c/internal/bodyprocessors/urlencoded.go#L19-L33) is never invoked. The [bundled middleware buffers and reconstructs the full request body before invoking the backend](https://github.com/corazawaf/coraza/blob/db9850b2dd8992f97a8cefe08d0cb4edd966a04c/http/middleware.go#L69-L97). The backend consequently receives data that was absent from Coraza's body variables. The issue exists in the standard compiled implementation; no special build tag is required. Request-body access defaults to Off in an empty configuration, but Coraza's recommended configuration enables it with `SecRequestBodyAccess On`. ### PoC Save this self-contained test as `parameterized_form_bypass_test.go` in the Coraza repository root: ```go package coraza_test import ( "net/http" "net/http/httptest" "strings" "testing" coraza "github.com/corazawaf/coraza/v3" corazahttp "github.com/corazawaf/coraza/v3/http" ) func TestParameterizedFormInspectionBypass(t *testing.T) { cfg := coraza.NewWAFConfig().WithDirectives(` SecRuleEngine On SecRequestBodyAccess On SecRule ARGS_POST:cmd "@streq evil" "id:910001,phase:2,deny,status:403,t:none" `) waf, err := coraza.NewWAF(cfg) if err != nil { t.Fatal(err) } backendSaw := "" handler := corazahttp.WrapHandler(waf, http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { t.Fatal(err) } backendSaw = r.PostForm.Get("cmd") w.WriteHeader(http.StatusNoContent) }, )) req := httptest.NewRequest( http.MethodPost, "http://example.test/", strings.NewReader("cmd=evil"), ) req.Header.Set( "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8", ) rec := httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusNoContent { t.Fatalf("expected request to reach backend, status=%d", rec.Code) } if backendSaw != "evil" { t.Fatalf("backend did not receive malicious value: %q", backendSaw) } } ``` Run: ```bash go test . -run TestParameterizedFormInspectionBypass -v ``` Observed: ```text === RUN TestParameterizedFormInspectionBypass --- PASS: TestParameterizedFormInspectionBypass (0.00s) PASS ``` The test passes only when Coraza fails to return its configured 403 response and the backend parses `cmd=evil`. As a control, change the header to: ```http Content-Type: application/x-www-form-urlencoded ``` Coraza then selects the URL-encoded processor, exposes `cmd=evil` as `ARGS_POST:cmd`, and returns 403 before the backend runs. ### Impact This is a parser differential and request-body inspection bypass affecting applications protected by custom Coraza rules. An unauthenticated attacker can add `charset=UTF-8` to an ordinary form request. Coraza then runs phase-2 rules without the submitted parameters and reports no body-processing failure, while the application receives and processes those parameters. Rules relying on these variables are affected on this path: - `ARGS_POST` - `ARGS` for POST-derived values - `ARGS_POST_NAMES` - `ARGS_NAMES` - `REQUEST_BODY` - `REQUEST_BODY_LENGTH` This defeats custom rules intended to reject injection strings, dangerous commands, or forbidden business values in form fields. The final application impact depends on the backend behavior that the bypassed rule was intended to protect. Affected operators are those who enable request-body access and rely on automatic URL-encoded parsing without current CRS rule `901340`, `ctl:forceRequestBodyVariable=On`, an explicitly forced processor, or equivalent backend validation. The fix is to parse Content-Type according to MIME syntax and compare the normalized media type without parameters, for example with Go's `mime.ParseMediaType`. If an expected body has no processor, Coraza should expose an explicit processing error instead of silently evaluating phase 2 with empty variables. ## Follow-up (2026-09-30): duplicate Content-Type headers desynchronize processor selection from the parsed body The original fix made body-processor selection tolerant of media-type parameters (`charset=UTF-8` etc.) by switching from exact equality to `strings.HasPrefix`. A second, distinct gap was found while reviewing that same selection code: it never accounted for a request carrying *more than one* `Content-Type` header. ### Root cause `Transaction.AddRequestHeader` is called once per header by the integrator (documented contract). Its `content-type` case runs on every call: ```go case "content-type": val := strings.ToLower(value) if strings.HasPrefix(val, "application/x-www-form-urlencoded") { tx.variables.reqbodyProcessor.Set("URLENCODED") } else if strings.HasPrefix(val, "multipart/form-data") { tx.variables.reqbodyProcessor.Set("MULTIPART") } ``` A request with two `Content-Type` headers therefore has its body-processor selection silently overwritten by the *last* one, since each call unconditionally calls `Set`. Meanwhile, `ProcessRequestBody` extracts the `mimeType` it passes to whichever processor gets selected from `requestHeaders.Get("content-type")[0]` -- the *first* value (`internal/corazawaf/transaction.go`, a few hundred lines below `AddRequestHeader`) -- and Go's `net/http` `Header.Get` (what a typical backend uses) also returns only the first value. So selection follows the last header while everything else follows the first, and the two can disagree entirely. ### PoC ```http Content-Type: multipart/form-data; boundary=XyZ Content-Type: application/x-www-form-urlencoded ``` with a genuine multipart body containing `cmd=evil` and a `shell.php` file upload. Against commit `19b86824`: - `reqbodyProcessor` resolves to `"URLENCODED"` (the second, spoofed header wins). - The URL-encoded processor runs against the raw multipart body text, parses without error, and produces nothing. - `ARGS_POST:cmd`, `FILES`, and `FILES_NAMES` are all empty -- the entire payload is invisible to any rule inspecting those variables. - The backend (or any integrator using `Header.Get`, which reads the first value) still sees `Content-Type: multipart/form-data` and parses `cmd=evil` and the uploaded `shell.php` normally. Current OWASP CRS includes rule `920620` (`Content-Type` header sanity check via count/format), which catches this shape; the recommended `coraza.conf-recommended` has no equivalent, so a Coraza deployment with only custom rules (no CRS) is exposed. ### Fix Only the first `Content-Type` header may set `reqbodyProcessor`: guard the existing logic with `if tx.variables.reqbodyProcessor.Get() == ""`. This makes header-driven processor selection consistent with `ProcessRequestBody`'s own `mimeType` extraction and with standard `Header.Get` semantics, without a new field (nothing else can set `reqbodyProcessor` before headers finish processing; `ctl:requestBodyProcessor` and `ForceRequestBodyVariable` both run afterward, in phase 1 rule evaluation and body processing respectively, and are unaffected). As defense in depth, a rule author can also detect the anomaly directly today, with no code change: `SecRule &REQUEST_HEADERS:Content-Type "@gt 1" "deny,..."` -- left as a suggested addition to `coraza.conf-recommended` rather than bundled into this fix, since it is a policy choice (deny vs. flag) rather than a correctness requirement. ### AI involvement disclosure - **AI tools/models used:** Claude Sonnet 5 (Anthropic), via Claude Code. - **What was generated/assisted:** the vulnerability hypothesis and repro shape were supplied by the reporter as an existing written finding; Claude Sonnet 5 independently re-derived the root cause by reading the current source, traced the `mimeType`/`Header.Get` first-value asymmetry, wrote and ran a fresh PoC against commit `19b86824` confirming the full bypass (`ARGS_POST`/`FILES`/`FILES_NAMES` all empty), verified the fix closes it, and drafted this addendum. - **Review performed:** reproduced by hand against a clean checkout of commit `19b86824` before and after the fix using the real `Transaction` API (`AddRequestHeader` x2, `WriteRequestBody`, `ProcessRequestBody`); confirmed the regression test fails deterministically against the pre-fix code and passes after it; ran the full test suite, the build-tag matrix (`coraza.no_memoize`, `coraza.rule.multiphase_evaluation`, `coraza.rule.no_regex_multiline`), and the `testing/coreruleset` CRS regression suite, all green; reviewed by a human maintainer (fzipi) before this addendum was submitted. Fix: https://github.com/corazawaf/coraza-ghsa-w253-m66g-rx24/pull/2 ### Patched in 3.8.1 The 3.8.0 fix was incomplete. 3.8.1 completes it: only the first `Content-Type` header now selects the body processor (the one a typical backend reads with `Header.Get`), and leading whitespace in the media type, including Unicode whitespace that `net/http` accepts, is trimmed the way `mime.ParseMediaType` trims it. Upgrade to 3.8.1; 3.8.0 is listed as affected. ### Severity (revised 2026-10-02) `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:N` (5.8, Medium). Attack Complexity is Low: every form and multipart parser accepts these Content-Type values. The previous vector used Scope Unchanged (5.3). 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 finds an internet-facing LLM gateway or ML platform upload API that sits behind Coraza with custom rules blocking command-injection and prompt-injection patterns in form fields. They send a normal POST containing the malicious payload, adding 'charset=UTF-8' to the form Content-Type, or sending a multipart/form-data header followed by a second spoofed urlencoded header. Coraza selects no processor or the wrong one, so ARGS_POST, FILES and FILES_NAMES stay empty and its rules do not fire. The backend reads the first Content-Type header and parses the form or multipart body. The payload then reaches the application, for example a `cmd=` value passed to a tool-calling agent, a poisoned document injected into a RAG ingestion pipeline, or a `shell.php` upload.
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:L/PR:N/UI:N/S:C/C:N/I:L/A:NReferences
- github.com/advisories/GHSA-w253-m66g-rx24
- github.com/corazawaf/coraza/commit/3b6241ef895b089c66a59e51068a81cf40986e4d
- github.com/corazawaf/coraza/commit/a55950ffa161f33a29e14f87d926d7df3b85cc74
- github.com/corazawaf/coraza/commit/dd100261cf1d2e2b053803a50c9db28dcaaa4b7c
- github.com/corazawaf/coraza/releases/tag/v3.8.1
- github.com/corazawaf/coraza/security/advisories/GHSA-w253-m66g-rx24
Timeline
Related Vulnerabilities
CVE-2026-9460310.0Podman: checkpoint images override sandbox on run
Same package: anthropicCVE-2026-544498.8LangBot: RCE via arbitrary STDIO MCP command
Same package: anthropicCVE-2026-277758.8Gitea: cached permission check allows repo takeover
Same package: anthropicCVE-2026-554298.7Coder: cross-workspace agent hijack via app ID reuse
Same package: anthropicCVE-2026-75748.7Claude Desktop: VM integrity bypass enables RCE
Same package: anthropic