GHSA-w253-m66g-rx24: Coraza WAF: Content-Type parsing flaw bypasses body inspection

GHSA-w253-m66g-rx24MEDIUM
Published October 8, 2026
CISO Take

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.

Sources:GitHub AdvisoryNVDATLAS

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?

Initial Access
Attacker sends a POST to an internet-facing AI service behind Coraza, with a form Content-Type carrying 'charset=UTF-8' or a duplicate or whitespace-prefixed Content-Type header.
AML.T0049
Defense Evasion
Coraza picks no body processor, or the wrong one, so ARGS_POST, FILES and REQUEST_BODY stay empty and its custom rules never fire.
AML.T0107
Payload Delivery
The backend reads the first Content-Type header, parses the full body and receives the hidden payload, such as injection strings or a malicious upload.
AML.T0093
Impact
The vulnerable AI application processes the attacker's input without WAF filtering, which can mean integrity compromise, tool misuse or malicious file placement.

What systems are affected?

PackageEcosystemVulnerable RangePatched
Anthropic Pythongo>= 3.0.4, < 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
5.8 / 10
EPSS
N/A
Exploitation Status
No known exploitation
Sophistication
Trivial

What is the attack surface?

AVACPRUISCIA
AVNetwork
ACLow
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 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.

How is it classified?

Which compliance frameworks are affected?

This CVE is relevant to:

EU AI Act
Article 15-Accuracy, robustness and cybersecurity
ISO 42001
A.6.2.6-AI system operation and monitoring
NIST AI RMF
MEASURE 2.7-AI system security and resilience are evaluated and documented
OWASP LLM Top 10
LLM01-Prompt Injection

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

Model serving behind WAFLLM API gatewaysRAG ingestion pipelinesAgent frameworks with public endpointsML web UIs and notebooksKubernetes ingress / Caddy / Envoy WAF deployments

MITRE ATLAS Techniques

AML.T0049Exploit Public-Facing Application
AML.T0093Prompt Infiltration via Public-Facing Application
AML.T0107Exploitation for Defense Evasion

Compliance Controls Affected

EU AI Act: Article 15
ISO 42001: A.6.2.6
NIST AI RMF: MEASURE 2.7
OWASP LLM Top 10: LLM01

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:N

Timeline

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

Related Vulnerabilities