GHSA-g4qm-m288-5cp9: Github.com/corazawaf/coraza/v3 Coraza Has Cookie Parser Confusion Vulnerability

MODERATE
CVSS 4.0
CWE-436
Published
Advisory Summary

GHSA-g4qm-m288-5cp9 is a MODERATE (CVSS 4.0) coraza has cookie parser confusion vulnerability (CWE-436: Interpretation Conflict) affecting Github.com/corazawaf/coraza/v3 libraries in the Go ecosystem. The issue involves coraza has cookie parser confusion. Remediation is available by upgrading affected packages to version 3.8.1.

GHSA-g4qm-m288-5cp9 at a Glance

CVE Identifier N/A
GHSA Identifier GHSA-g4qm-m288-5cp9
Severity MODERATE
CVSS v3.1 Base Score 4.0
CWE Classification CWE-436: Interpretation Conflict
Affected Product Github.com/corazawaf/coraza/v3
Package Ecosystem Go
Published Date
Last Updated

Security Impact

CVSS: 4.0

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:L/A:N).

Attack Vector Network (AV:N)
Attack Complexity High (AC:H)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Changed (S:C)
Confidentiality None (C:N)
Integrity Low (I:L)
Availability None (A:N)

How to Fix GHSA-g4qm-m288-5cp9

To remediate this vulnerability, upgrade all affected Github.com/corazawaf/coraza/v3 dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

github.com/corazawaf/coraza/v3 fixed: 3.8.1
Upgrade github.com/corazawaf/coraza/v3 to 3.8.1
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for github.com/corazawaf/coraza/v3

How to Check if You Are Affected

To determine whether your application is affected by GHSA-g4qm-m288-5cp9, inspect your project dependencies for Github.com/corazawaf/coraza/v3:

inspect dependencies for github.com/corazawaf/coraza/v3

Analyze the terminal output to locate any version of Github.com/corazawaf/coraza/v3 in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 3.8.1), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-436: Interpretation Conflict, where product a handles inputs or steps differently than product b, which causes a to perform incorrect actions based on its perception of b's state. In Github.com/corazawaf/coraza/v3, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Summary Coraza's cookie parser (`internal/cookies.ParseCookies`) does not strip ASCII control characters (CTLs) from the edges of a cookie name/value before they're matched against `REQUEST_COOKIES` / `REQUEST_COOKIES_NAMES`. When a CTL sits directly next to the `=` separator, Coraza absorbs it into the adjacent name or value, while several backend cookie parsers trim it away — so the WAF and the application disagree about the cookie it just received. ## Root cause - `ParseCookies` (`internal/cookies/cookies.go:17,26,31`) trims via `net/textproto.TrimString`, which strips only space (`0x20`) and tab (`0x09`). - RFC 6265 §4.1.1 defines cookie `name` as an HTTP `token` and `value` as `cookie-octet`, both excluding the full C0 control range (`0x00–0x1F`, `0x7F`) — not just space/tab. - Input `a\v=\t'` (vertical tab `\v` next to `=`) keeps `\v` in the name (`a\v`), yielding name=`a\v`, value=`\t'`. ## Confirmed divergence from real backends | Implementation | Name | Value | |---|---|---| | Coraza (< 3.8.0) | `a\v` | `\t'` | | Python `http.cookies`, and the Werkzeug/Flask version in the report below | `a` | `'` | | PHP `$_COOKIE` | `a` | `\t'` | | Node.js `cookie` package | `a\v` | `'` | RFC 6265 itself calls this exact cookie-pair invalid, so there's no single spec-correct reference — but Coraza's boundary handling diverges from 2 of these 3 widely-used backends. Correction (2026-10-02): current Werkzeug (3.1.9, checked during review of the 3.8.1 follow-up) keeps `a\v` as the name, like Node's `cookie` package. The name divergence therefore applies to PHP and to Python's `http.cookies`, not to every Werkzeug version. ## Impact An attacker can pad a `Cookie` header with a CTL adjacent to `=` so Coraza indexes a different name/value than the backend application does. A `SecRule` scoped to a specific cookie name or value can then miss the cookie the application actually processes — a WAF bypass for cookie-carried attacks. ## Affected component `internal/cookies.ParseCookies`, consumed via `REQUEST_COOKIES` / `REQUEST_COOKIES_NAMES`. ## Fix Trim the full CTL range (not just space/tab) from both ends of the extracted name and value, treating a boundary-adjacent CTL as a delimiter rather than token content — aligning with RFC 6265's `token`/`cookie-octet` grammar. The fix does not attempt to resolve what happens when a CTL lands in the *interior* of an otherwise-plausible name (e.g. `ab\vcd`). That case is disputed among the backends themselves — Python's `http.cookies` rejects the whole pair, PHP strips the CTL from the middle, Node's `cookie` package keeps it — so there is no consensus to converge on. It is left as a separate follow-up rather than guessed at here. ### Implementation note The trim is deliberately hand-rolled (a byte-wise scan on `b <= ' ' || b == 0x7f`, which covers octets `0x00–0x20` plus `0x7F`) rather than delegated to the standard library. This is a conscious choice on a security hot path and should not be "simplified" away later: - **`strings.TrimFunc` was measured and rejected.** It invokes its predicate through a func value once per byte scanned, which Go cannot devirtualize through `strings.indexFunc`. On an Apple M2, parsing a 64 KiB CTL-saturated `Cookie` header costs **167.6 µs** via `TrimFunc` versus **33.9 µs** byte-wise — a ~5× CPU amplification handed to an attacker, on input that is attacker-controlled and parsed on every request. Allocation counts are identical either way; the cost is purely the per-byte indirect call. - **`strings.TrimSpace` is not a substitute.** It misses most of the CTL range (`0x00–0x08`, `0x0E–0x1F`, `0x7F`) and additionally trims `U+0085` and `U+00A0`, whose multi-byte UTF-8 encodings a backend would not strip — reintroducing the very parser-disagreement class this advisory closes. The byte-wise implementation was verified equivalent to a `TrimFunc`-based one across all 16,843,009 byte strings of length 0–3, including invalid UTF-8, with zero mismatches. `BenchmarkParseCookies/CTLFlood` guards the hot path against a future regression to a per-byte indirect call. ## Proof of Concept (original report) > Hi, @fzipi, i hope you doing well, i'm RelunSec from InsiteTech.jp > > we discovered a parser confusion in cookie parser, i used a simple flask app that print the cookies > > ```py > from flask import Flask, request > > app = Flask(__name__) > > @app.route('/') > def index(): > # 1. Print all cookies as a dictionary to your terminal console > print("All cookies:", request.cookies) > > return "Cookies logged in terminal!" > > if __name__ == '__main__': > app.run(debug=True) > ``` > > and a go setup > > ```go > package cookies > > import ( > "fmt" > "testing" > ) > > func TestParseCookie(t *testing.T) { > inputs := []string{ > "a\v=\t'", > } > > fmt.Println("\n==========================================") > fmt.Println(" COOKIE PARSE DIRECT LOCAL RUN ") > fmt.Println("==========================================") > > for _, input := range inputs { > // Calling the exact lowercase function name from the repo > cookies := ParseCookies(input) > > fmt.Printf("-> Input: %q\n", input) > fmt.Printf(" Output: %q\n", cookies) > fmt.Println("------------------------------------------") > } > fmt.Println("==========================================") > } > ``` > > i runned the go program as you can see > > ```go > relunsec@relunsec:~/software/coraza/internal/cookies$ go test > > ========================================== > COOKIE PARSE DIRECT LOCAL RUN > ========================================== > -> Input: "a\v=\t'" > Output: map["a\v":["\t'"]] > ------------------------------------------ > ========================================== > PASS > ok github.com/corazawaf/coraza/v3/internal/cookies 0.003s > ``` > > and then i sended a curl request to the python flask web app > > ```bash > relunsec@relunsec:~/software/coraza/internal/cookies$ curl 127.0.0.1:5000 -H $'Cookie: a\v=\t' > Cookies logged in terminal! > ``` > > and then i saw in the running flask app terminal > > ```python > All cookies: ImmutableMultiDict([('a', "'")]) > ``` > > as you can see python see that as the a cookie and the value of it is `'`, while coraza see it in a different name and a value > > an attacker can craft a crafted payload that evade cookie inspection and then perfom their attack ### Patched in 3.8.1 The 3.8.0 fix was incomplete. 3.8.1 completes it: trimming control characters in 3.8.0 turned a cookie whose name is only control characters (`\x01=payload`) into a cookie with an empty name, and empty names have always been skipped, so its value was no longer inspected. Node's `cookie` package (`{"\x01": "payload"}`, `{"": "payload"}`) and Werkzeug still pass such pairs to the application. 3.8.1 keeps them in `REQUEST_COOKIES` under the name `""`. This is an intentional deviation from ModSecurity v2 and v3, which skip empty names. 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). Unchanged vector; precondition stated per the project's triage guidance. Attack Complexity is High because the bypass depends on a specific backend cookie parser: the original trim discrepancy affects backends that split `a\v` as `a` (PHP's `$_COOKIE`, Python's `http.cookies`) and only rules keyed on a cookie name, and the 3.8.0 regression affects backends that pass empty or control-character-only cookie names to the application (Node's `cookie` package, Werkzeug for `\x01`). 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._

GHSA-g4qm-m288-5cp9 Affected Github.com/corazawaf/coraza/v3 Versions

1 component(s)
Fixed Versions:
3.8.1
Product / Package Ecosystem Affected Versions Fixed Version
github.com/corazawaf/coraza/v3
Go
< 3.8.1
3.8.1

References

Frequently Asked Questions

What is GHSA-g4qm-m288-5cp9?
GHSA-g4qm-m288-5cp9 is a security vulnerability in Github.com/corazawaf/coraza/v3 involving Coraza has Cookie Parser Confusion under CWE-436: Interpretation Conflict.
Which Github.com/corazawaf/coraza/v3 versions are affected?
Affected versions include releases prior to 3.8.1 across affected packages: github.com/corazawaf/coraza/v3.
What version fixes GHSA-g4qm-m288-5cp9?
Upgrading to 3.8.1 (or the latest patched release for your respective branch) resolves the vulnerability.
Is github.com/corazawaf/coraza/v3 affected?
Yes, github.com/corazawaf/coraza/v3 is affected in releases prior to 3.8.1. Upgrade your dependency to 3.8.1 or later.
What is the CVSS score of GHSA-g4qm-m288-5cp9?
GHSA-g4qm-m288-5cp9 has a CVSS v3.1 base score of 4.0 (MODERATE) with vector string CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:L/A:N.
What CWE is associated with GHSA-g4qm-m288-5cp9?
GHSA-g4qm-m288-5cp9 is classified under CWE-436: Interpretation Conflict (Product A handles inputs or steps differently than Product B, which causes A to perform incorrect actions based on its perception of B's state.).
How can I check Go dependencies for GHSA-g4qm-m288-5cp9?
Run the dependency tree command: 'inspect dependencies for github.com/corazawaf/coraza/v3'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-g4qm-m288-5cp9",
  "modified" : "2026-10-08T17:51:31Z",
  "published" : "2026-10-08T17:51:30Z",
  "aliases" : [ ],
  "summary" : "Coraza has Cookie Parser Confusion",
  "details" : "## Summary\nCoraza's cookie parser (`internal/cookies.ParseCookies`) does not strip ASCII control characters (CTLs) from the edges of a cookie name/value before they're matched against `REQUEST_COOKIES` / `REQUEST_COOKIES_NAMES`. When a CTL sits directly next to the `=` separator, Coraza absorbs it into the adjacent name or value, while several backend cookie parsers trim it away — so the WAF and the application disagree about the cookie it just received.\n\n## Root cause\n- `ParseCookies` (`internal/cookies/cookies.go:17,26,31`) trims via `net/textproto.TrimString`, which strips only space (`0x20`) and tab (`0x09`).\n- RFC 6265 §4.1.1 defines cookie `name` as an HTTP `token` and `value` as `cookie-octet`, both excluding the full C0 control range (`0x00–0x1F`, `0x7F`) — not just space/tab.\n- Input `a\\v=\\t'` (vertical tab `\\v` next to `=`) keeps `\\v` in the name (`a\\v`), yielding name=`a\\v`, value=`\\t'`.\n\n## Confirmed divergence from real backends\n| Implementation | Name | Value |\n|---|---|---|\n| Coraza (< 3.8.0) | `a\\v` | `\\t'` |\n| Python `http.cookies`, and the Werkzeug/Flask version in the report below | `a` | `'` |\n| PHP `$_COOKIE` | `a` | `\\t'` |\n| Node.js `cookie` package | `a\\v` | `'` |\n\nRFC 6265 itself calls this exact cookie-pair invalid, so there's no single spec-correct reference — but Coraza's boundary handling diverges from 2 of these 3 widely-used backends.\n\nCorrection (2026-10-02): current Werkzeug (3.1.9, checked during review of the 3.8.1 follow-up) keeps `a\\v` as the name, like Node's `cookie` package. The name divergence therefore applies to PHP and to Python's `http.cookies`, not to every Werkzeug version.\n\n## Impact\nAn attacker can pad a `Cookie` header with a CTL adjacent to `=` so Coraza indexes a different name/value than the backend application does. A `SecRule` scoped to a specific cookie name or value can then miss the cookie the application actually processes — a WAF bypass for cookie-carried attacks.\n\n## Affected component\n`internal/cookies.ParseCookies`, consumed via `REQUEST_COOKIES` / `REQUEST_COOKIES_NAMES`.\n\n## Fix\nTrim the full CTL range (not just space/tab) from both ends of the extracted name and value, treating a boundary-adjacent CTL as a delimiter rather than token content — aligning with RFC 6265's `token`/`cookie-octet` grammar.\n\nThe fix does not attempt to resolve what happens when a CTL lands in the *interior* of an otherwise-plausible name (e.g. `ab\\vcd`). That case is disputed among the backends themselves — Python's `http.cookies` rejects the whole pair, PHP strips the CTL from the middle, Node's `cookie` package keeps it — so there is no consensus to converge on. It is left as a separate follow-up rather than guessed at here.\n\n### Implementation note\nThe trim is deliberately hand-rolled (a byte-wise scan on `b <= ' ' || b == 0x7f`, which covers octets `0x00–0x20` plus `0x7F`) rather than delegated to the standard library. This is a conscious choice on a security hot path and should not be \"simplified\" away later:\n\n- **`strings.TrimFunc` was measured and rejected.** It invokes its predicate through a func value once per byte scanned, which Go cannot devirtualize through `strings.indexFunc`. On an Apple M2, parsing a 64 KiB CTL-saturated `Cookie` header costs **167.6 µs** via `TrimFunc` versus **33.9 µs** byte-wise — a ~5× CPU amplification handed to an attacker, on input that is attacker-controlled and parsed on every request. Allocation counts are identical either way; the cost is purely the per-byte indirect call.\n- **`strings.TrimSpace` is not a substitute.** It misses most of the CTL range (`0x00–0x08`, `0x0E–0x1F`, `0x7F`) and additionally trims `U+0085` and `U+00A0`, whose multi-byte UTF-8 encodings a backend would not strip — reintroducing the very parser-disagreement class this advisory closes.\n\nThe byte-wise implementation was verified equivalent to a `TrimFunc`-based one across all 16,843,009 byte strings of length 0–3, including invalid UTF-8, with zero mismatches. `BenchmarkParseCookies/CTLFlood` guards the hot path against a future regression to a per-byte indirect call.\n\n## Proof of Concept (original report)\n\n> Hi, @fzipi, i hope you doing well, i'm RelunSec from InsiteTech.jp\n>\n> we discovered a parser confusion in cookie parser, i used a simple flask app that print the cookies\n>\n> ```py\n> from flask import Flask, request\n>\n> app = Flask(__name__)\n>\n> @app.route('/')\n> def index():\n>     # 1. Print all cookies as a dictionary to your terminal console\n>     print(\"All cookies:\", request.cookies)\n>\n>     return \"Cookies logged in terminal!\"\n>\n> if __name__ == '__main__':\n>     app.run(debug=True)\n> ```\n>\n> and a go setup\n>\n> ```go\n> package cookies\n>\n> import (\n> \t\"fmt\"\n> \t\"testing\"\n> )\n>\n> func TestParseCookie(t *testing.T) {\n> \tinputs := []string{\n> \"a\\v=\\t'\",\n> \t}\n>\n> \tfmt.Println(\"\\n==========================================\")\n> \tfmt.Println(\"     COOKIE PARSE DIRECT LOCAL RUN       \")\n> \tfmt.Println(\"==========================================\")\n>\n> \tfor _, input := range inputs {\n> \t\t// Calling the exact lowercase function name from the repo\n> \t\tcookies := ParseCookies(input)\n>\n> \t\tfmt.Printf(\"-> Input:   %q\\n\", input)\n> \t\tfmt.Printf(\"   Output:  %q\\n\", cookies)\n> \t\tfmt.Println(\"------------------------------------------\")\n> \t}\n> \tfmt.Println(\"==========================================\")\n> }\n> ```\n>\n> i runned the go program as you can see\n>\n> ```go\n> relunsec@relunsec:~/software/coraza/internal/cookies$ go test\n>\n> ==========================================\n>      COOKIE PARSE DIRECT LOCAL RUN\n> ==========================================\n> -> Input:   \"a\\v=\\t'\"\n>    Output:  map[\"a\\v\":[\"\\t'\"]]\n> ------------------------------------------\n> ==========================================\n> PASS\n> ok  \tgithub.com/corazawaf/coraza/v3/internal/cookies\t0.003s\n> ```\n>\n> and then i sended a curl request to the python flask web app\n>\n> ```bash\n> relunsec@relunsec:~/software/coraza/internal/cookies$ curl 127.0.0.1:5000 -H $'Cookie: a\\v=\\t'\n> Cookies logged in terminal!\n> ```\n>\n> and then i saw in the running flask app terminal\n>\n> ```python\n> All cookies: ImmutableMultiDict([('a', \"'\")])\n> ```\n>\n> as you can see python see that as the a cookie and the value of it is `'`, while coraza see it in a different name and a value\n>\n> an attacker can craft a crafted payload that evade cookie inspection and then perfom their attack\n\n### Patched in 3.8.1\n\nThe 3.8.0 fix was incomplete. 3.8.1 completes it: trimming control characters in 3.8.0 turned a cookie whose name is only control characters (`\\x01=payload`) into a cookie with an empty name, and empty names have always been skipped, so its value was no longer inspected. Node's `cookie` package (`{\"\\x01\": \"payload\"}`, `{\"\": \"payload\"}`) and Werkzeug still pass such pairs to the application. 3.8.1 keeps them in `REQUEST_COOKIES` under the name `\"\"`. This is an intentional deviation from ModSecurity v2 and v3, which skip empty names. Upgrade to 3.8.1; 3.8.0 is listed as affected.\n\n\n### Severity (revised 2026-10-02)\n\n`CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:L/A:N` (4.0, Medium).\n\nUnchanged vector; precondition stated per the project's triage guidance. Attack Complexity is High because the bypass depends on a specific backend cookie parser: the original trim discrepancy affects backends that split `a\\v` as `a` (PHP's `$_COOKIE`, Python's `http.cookies`) and only rules keyed on a cookie name, and the 3.8.0 regression affects backends that pass empty or control-character-only cookie names to the application (Node's `cookie` package, Werkzeug for `\\x01`).\n\nImpact 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.\n\n_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._",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:L/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "github.com/corazawaf/coraza/v3"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "3.8.1"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/corazawaf/coraza/security/advisories/GHSA-g4qm-m288-5cp9"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/corazawaf/coraza/commit/0b940e197ad9983fb3aa36e84f1f81ff985461af"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/corazawaf/coraza/commit/9f8521398d1ff023b958fad0b944cac265763866"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/corazawaf/coraza"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/corazawaf/coraza/releases/tag/v3.8.1"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-436" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-10-08T17:51:30Z",
    "nvd_published_at" : null
  }
}