GitHub Advisoryadvisory · Go
GHSA-x26q-wvhg-fh4m Coraza: ProcessURI silently drops QUERY_STRING and ARGS_GET on URI parse failure — defense-in-depth bypass for non-net/http integrations
MEDIUM
Official page ↗
- Aliases
- CVE-2026-107825
- 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-20, CWE-436
- Reviewed
- 2026-10-08
Affected packages
• Go github.com/corazawaf/coraza/v3: >= 3.0.0, < 3.8.0 → fixed in 3.8.0
Description
Root Cause
File: `internal/corazawaf/transaction.go`, lines 834–866.
```go
parsedURL, err := url.ParseRequestURI(uri)
query := ""
if err != nil {
tx.variables.urlencodedError.Set(err.Error())
path = uri
tx.variables.requestURI.Set(uri)
/*
tx.Variables.VARIABLE_URI_PARSE_ERROR.Set("1")
posRawQuery := strings.Index(uri, "?")
if posRawQuery != -1 {
tx.ExtractArguments("GET", uri[posRawQuery+1:])
path = uri[:posRawQuery]
query = uri[posRawQuery+1:]
} else {
path = uri
}
tx.Variables.RequestUri.Set(uri)
*/
} else {
tx.ExtractGetArguments(parsedURL.RawQuery) // only path that populates ARGS_GET
tx.variables.requestURI.Set(parsedURL.String())
path = parsedURL.Path
query = parsedURL.RawQuery
}
...
tx.variables.queryString.Set(query)
```
When `url.ParseRequestURI(uri)` returns an error — which Go's stdlib does for any URI containing raw control bytes (`\x00`, `\n`, `\r`, `\t`, other `0x00–0x1F`, `0x7F`) — the error branch silently produces an empty `QUERY_STRING` and an empty `ARGS_GET` collection. The fallback logic that should split on `?` and populate the GET arguments from the raw tail is already present in the source as a commented-out block, referencing a `VARIABLE_URI_PARSE_ERROR` variable that was never wired up.
Consequences on the error branch:
• `ARGS_GET` / `ARGS_GET_NAMES` / `ARGS` (union) are empty — `ExtractGetArguments` is never called.
• `QUERY_STRING` is empty (initial `query := ""` at line 835 persists through to `queryString.Set(query)` at line 866).
• `REQUEST_FILENAME` / `REQUEST_BASENAME` contain the entire URI including any `?…` query suffix (because `path = uri` at line 838 bypasses the parse, and the subsequent `strings.LastIndexAny(path, "/\\")` runs over the raw URI).
• `URLENCODED_ERROR` is set to the Go error message. That variable is *also* set by the urlencoded body processor on body-decode failures, so an operator cannot distinguish "malformed URI" from "malformed request body" without string-matching the error text.
• `REQUEST_URI_RAW` (set unconditionally at line 822, before the parse) is populated correctly.
Any rule targeting `ARGS_GET`, `ARGS`, `ARGS_NAMES`, `ARGS_GET_NAMES`, or `QUERY_STRING` — which is the default target set for the vast majority of OWASP CRS GET-side signature rules — does not fire against attacker content that reaches Coraza via a URI Go's `net/url` rejects.
Reachability
This issue does not affect the standard `coraza/v3/http` + `net/http` integration. Go's `http.ReadRequest` calls `url.ParseRequestURI` first and rejects malformed URIs with `400 Bad Request` before `ProcessURI` is invoked. Verified experimentally against a Coraza-wrapped `net/http` server — a raw request with a control-byte-laced URI produced `HTTP 400`, and the handler was never reached.
The bug is reachable when an integration forwards raw URI bytes to `tx.ProcessURI` directly, bypassing Go's HTTP parser:
• `coraza-spoa` — HAProxy SPOP agent. Receives URI from HAProxy, which permits bytes `net/http` rejects.
• `coraza-proxy-wasm` — Envoy WASM filter. Passes the `:path` pseudo-header from Envoy.
• Custom FFI/WASM hosts and any embedder calling `tx.ProcessURI(rawURI, method, httpVersion)` with bytes not pre-validated by Go's URL parser.
This gates the attack to Attack Complexity:High — a standard Go HTTP deployment is not exposed.
Proof of Concept
Direct-API reproduction (simulating the non-net/http integration path):
```go
waf, _ := coraza.NewWAF(coraza.NewWAFConfig().WithDirectives(`
SecRuleEngine On
SecRule ARGS_GET "@contains ATTACK_HERE_XYZ" "id:9001,phase:1,deny,status:403"
SecRule QUERY_STRING "@contains ATTACK_HERE_XYZ" "id:9002,phase:1,deny,status:403"
`))
for _, uri := range []string{
"/search?q=ATTACK_HERE_XYZ", // baseline
"/search?q=ATTACK_HERE_XYZ\x00&y=1", // NUL byte
"/search?q=ATTACK_HERE_XYZ\ninjected: header", // bare LF
"/search?q=ATTACK_HERE_XYZ\rhdr: x", // bare CR
"/search?q=ATTACK_HERE_XYZ\tx=1", // tab
} {
tx := waf.NewTransaction()
tx.ProcessURI(uri, "GET", "HTTP/1.1")
it := tx.ProcessRequestHeaders()
// inspect tx.Variables().QueryString().Get() and tx.Variables().ArgsGet().FindAll()
tx.Close()
}
```
Observed:
| URI | `QUERY_STRING` | `ARGS_GET` | interrupted? |
|---|---|---|---|
| `/search?q=ATTACK_HERE_XYZ` | `q=ATTACK_HERE_XYZ` | 1 entry | yes (403) |
| `/search?q=ATTACK_HERE_XYZ\x00&y=1` | `""` | 0 entries | no — BYPASS |
| `/search?q=ATTACK_HERE_XYZ\ninjected: header` | `""` | 0 entries | no — BYPASS |
| `/search?q=ATTACK_HERE_XYZ\rhdr: x` | `""` | 0 entries | no — BYPASS |
| `/search?q=ATTACK_HERE_XYZ\tx=1` | `""` | 0 entries | no — BYPASS |
`REQUEST_URI_RAW` is populated correctly in every case (line 822 sets it before the parse), so a rule written against `REQUEST_URI_RAW` still catches the attack. CRS and most operator-written rules target `ARGS_GET` / `ARGS` / `QUERY_STRING` — those do not fire.
HTTP-layer reachability check (stock `net/http`):
```
$ printf 'GET /?q=ATTACK_HERE_XYZ\x00&y=1 HTTP/1.1\r\nHost: x\r\n\r\n' | nc 127.0.0.1 8092
HTTP/1.1 400 Bad Request
```
Confirms the exposure is limited to non-net/http integrations.
Mitigation
Recommended fixes, in order:
1. Re-enable the existing fallback and wire up `URI_PARSE_ERROR`
The code to fix this is already present as a commented-out block at `transaction.go:840–851`. Re-enable it, promote the referenced `VARIABLE_URI_PARSE_ERROR` to a real transaction variable, and populate `ARGS_GET` / `QUERY_STRING` from the raw `?…` tail:
```go
if err != nil {
tx.variables.urlencodedError.Set(err.Error())
tx.variables.uriParseError.Set("1") // new variable
tx.variables.requestURI.Set(uri)
if i := strings.Index(uri, "?"); i != -1 {
path = uri[:i]
query = uri[i+1:]
tx.ExtractGetArguments(query) // populate ARGS_GET
} else {
path = uri
}
} else {
...
}
```
2. Ship a companion rule in `coraza.conf-recommended`
```conf
SecRule URI_PARSE_ERROR "@eq 1" \
"id:'200010',phase:1,t:none,log,deny,status:400,msg:'URI failed to parse'"
```
This gives operators a fail-closed default (analogous to rule 200003 for multipart strict error and rule 200002 for body-parse error), so non-net/http integrations at least stop the request regardless of downstream rule coverage.
3. Do not overload `URLENCODED_ERROR`
The current code uses `URLENCODED_ERROR` for URI parse failures. That variable is also set by the urlencoded body processor on body-decode errors; operators cannot distinguish the two causes without string-matching the error text, and any rule they add will fire on both classes of failure. A dedicated `URI_PARSE_ERROR` variable (per the commented-out TODO) is the right shape.
Affected versions
All releases on the v3 branch (`>= 3.0.0, <= 3.7.0`); the silent-drop behavior has been present since the first v3 release. Only deployments using non-net/http integrations (coraza-spoa, coraza-proxy-wasm, custom FFI) are exposed in practice.
References
• `internal/corazawaf/transaction.go` lines 834–866 (ProcessURI error branch)
• `internal/corazawaf/transaction.go` line 822 (`REQUEST_URI_RAW` is populated before the parse, which is why `REQUEST_URI_RAW`-targeted rules still catch the attack)
• Commented-out fallback at lines 840–851 referencing `VARIABLE_URI_PARSE_ERROR`
• CWE-20 — Improper Input Validation
• CWE-436 — Interpretation Conflict
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 stays High: the bypass only applies to integrations that pass Coraza a raw URI that Go's URL parser rejects, which `net/http` does not. The previous vector scored Integrity High (6.8); it is scored here like Coraza's other inspect…
Related
Alias · CVE (1)
- CVE-2026-107825 OWASP Coraza WAF is a golang modsecurity compatible web application firewall library. vulnerability