GHSA-x26q-wvhg-fh4m: Input Validation

Published Oct 8, 2026
·
Updated

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.VARIABLEURIPARSEERROR.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 ARGSGET 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 QUERYSTRING and an empty ARGSGET 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 VARIABLEURIPARSEERROR variable that was never wired up.

Consequences on the error branch:

- ARGSGET / ARGSGETNAMES / ARGS (union) are empty — ExtractGetArguments is never called. - QUERYSTRING is empty (initial query := "" at line 835 persists through to queryString.Set(query) at line 866). - REQUESTFILENAME / REQUESTBASENAME 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). - URLENCODEDERROR 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. - REQUESTURIRAW (set unconditionally at line 822, before the parse) is populated correctly.

Any rule targeting ARGSGET, ARGS, ARGSNAMES, ARGSGETNAMES, or QUERYSTRING — 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 ARGSGET "@contains ATTACKHEREXYZ" "id:9001,phase:1,deny,status:403" SecRule QUERYSTRING "@contains ATTACKHEREXYZ" "id:9002,phase:1,deny,status:403" ))

for , uri := range []string{ "/search?q=ATTACKHEREXYZ", // baseline "/search?q=ATTACKHEREXYZ\x00&y=1", // NUL byte "/search?q=ATTACKHEREXYZ\ninjected: header", // bare LF "/search?q=ATTACKHEREXYZ\rhdr: x", // bare CR "/search?q=ATTACKHEREXYZ\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 | QUERYSTRING | ARGSGET | interrupted? | |---|---|---|---| | /search?q=ATTACKHEREXYZ | q=ATTACKHEREXYZ | 1 entry | yes (403) | | /search?q=ATTACKHEREXYZ\x00&y=1 | "" | 0 entries | no — BYPASS | | /search?q=ATTACKHEREXYZ\ninjected: header | "" | 0 entries | no — BYPASS | | /search?q=ATTACKHEREXYZ\rhdr: x | "" | 0 entries | no — BYPASS | | /search?q=ATTACKHEREXYZ\tx=1 | "" | 0 entries | no — BYPASS |

REQUESTURIRAW is populated correctly in every case (line 822 sets it before the parse), so a rule written against REQUESTURIRAW still catches the attack. CRS and most operator-written rules target ARGSGET / ARGS / QUERYSTRING — those do not fire.

HTTP-layer reachability check (stock net/http):

$ printf 'GET /?q=ATTACKHEREXYZ\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 URIPARSEERROR

The code to fix this is already present as a commented-out block at transaction.go:840–851. Re-enable it, promote the referenced VARIABLEURIPARSEERROR to a real transaction variable, and populate ARGSGET / QUERYSTRING 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 ARGSGET } else { path = uri } } else { ... }

2. Ship a companion rule in coraza.conf-recommended

conf SecRule URIPARSEERROR "@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 URLENCODEDERROR

The current code uses URLENCODEDERROR 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 URIPARSEERROR 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 (REQUESTURIRAW is populated before the parse, which is why REQUESTURIRAW-targeted rules still catch the attack) - Commented-out fallback at lines 840–851 referencing VARIABLEURIPARSEERROR - 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 inspection bypasses.

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.

Affected Software

1 affected componentFixes available
go/github.com/corazawaf/coraza/v3>=3.0.0<3.8.0
3.8.0

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade go/github.com/corazawaf/coraza/v3 to a version that resolves this vulnerability.

    Fixed in 3.8.0
  2. Configuration

    Re-enable the existing fallback in internal/corazawaf/transaction.go lines 840–851: when URI parsing fails, split the raw URI on '?', populate ARGS_GET from the raw query tail, set QUERY_STRING from that tail, and set a dedicated URI_PARSE_ERROR transaction variable.

    Coraza transaction URI processing URI parse error fallback = Populate ARGS_GET and QUERY_STRING from the raw query tail
  3. Configuration

    Ship and enable a companion rule in coraza.conf-recommended that denies requests when URI_PARSE_ERROR equals 1, providing a fail-closed default for malformed URIs.

    Coraza WAF URI_PARSE_ERROR = @eq 1; deny; status:403

Event History

Oct 8, 2026
Advisory Published
via GitHub·05:46 PM
Data Sourced
via GitHub·05:46 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

What request characteristics are required to trigger the inspection gap?

The request URI must contain a raw control byte, such as NUL, newline, carriage return, tab, another byte from 0x00–0x1F, or 0x7F. The URI must also include query parameters that would otherwise need to be inspected by GET-argument rules.

2

What is the practical impact on Coraza rules?

When URI parsing fails, QUERY_STRING is empty and ARGS_GET is not populated. Rules that rely on those variables may not inspect query-string values in the malformed request, allowing integrity-impacting payloads to evade those checks.

3

How can I determine whether my rules are affected?

Review rules for reliance on QUERY_STRING or ARGS_GET, then test them with a URI containing a raw control byte and a query string. If parsing takes the error path, the query string and GET argument collection will be empty for that transaction.

4

What remediation information is available?

A related Coraza release is v3.8.0, and the advisory references commit 0321af96cef18fbafb40980cf075d7cc449a66fa. Review that release and commit when planning an update or backport.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203