GitHub Advisoryadvisory · Go
GHSA-g4qm-m288-5cp9 Coraza has Cookie Parser Confusion
MEDIUM
Official page ↗
- Ecosystem
- Go
- Packages
- github.com/corazawaf/coraza/v3
- Severity
- Moderate
- CVSS vector
- CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:L/A:N
- Weaknesses
- CWE-436
- Reviewed
- 2026-10-08
Affected packages
• Go github.com/corazawaf/coraza/v3: < 3.8.1 → fixed in 3.8.1
Description
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 ( 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…
Related
Weakness · CWE (1)
- CWE-436 Interpretation Conflict weakness
References
- https://github.com/corazawaf/coraza/security/advisories/GHSA-g4qm-m288-5cp9 web
- https://github.com/corazawaf/coraza/commit/0b940e197ad9983fb3aa36e84f1f81ff985461af web
- https://github.com/corazawaf/coraza/commit/9f8521398d1ff023b958fad0b944cac265763866 web
- https://github.com/corazawaf/coraza package
- https://github.com/corazawaf/coraza/releases/tag/v3.8.1 web