Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐹
🐹 Go
Not in CISA KEV
MEDIUM severity

CVE-2026-107825 — coraza

MEDIUMFix: corazawaf/coraza@0321af9

CVE-2026-107825 is a medium-severity (CVSS 4) Improper Input Validation vulnerability in github.com/corazawaf/coraza/v3. A fix is available for github.com/corazawaf/coraza/v3 — see the affected versions and patch details below.

OWASP Coraza WAF: ProcessURI silently drops QUERY_STRING and ARGS_GET on URI parse failure — defense-in-depth bypass for non-net/http integrations

Also known asGHSA-x26q-wvhg-fh4mGO-2026-6699
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Oct 23, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-107825.

EPSS Exploitation Probability

via FIRST.org ↗
0.3%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs16th percentile — riskier than 16% of all scored CVEsHighest risk

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

How urgent is this, really

CVE-2026-107825 by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.

Where this sits among everything scored

Of 395,249 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.

Real-World Exposure

1 pkg affected
🐹github.com/corazawaf/coraza/v3

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.

Description

Root Cause

File: internal/corazawaf/transaction.go, lines 834–866.

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

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:

URIQUERY_STRINGARGS_GETinterrupted?
/search?q=ATTACK_HERE_XYZq=ATTACK_HERE_XYZ1 entryyes (403)
/search?q=ATTACK_HERE_XYZ\x00&y=1""0 entriesno — BYPASS
/search?q=ATTACK_HERE_XYZ\ninjected: header""0 entriesno — BYPASS
/search?q=ATTACK_HERE_XYZ\rhdr: x""0 entriesno — BYPASS
/search?q=ATTACK_HERE_XYZ\tx=1""0 entriesno — 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:

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

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 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 Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐹Gogithub.com/corazawaf/coraza/v3≥ 3.0.0&&< 3.8.03.8.0go get github.com/corazawaf/coraza/v3@v3.8.0

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/corazawaf/coraza/v3, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update github.com/corazawaf/coraza/v3 to 3.8.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-107825 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

Frequently Asked Questions

## 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
O3 Security · Impact-Aware SCA

Is CVE-2026-107825 in your dependencies?

Find it across Go, including transitive dependencies.

CVE-2026-107825: coraza — Fixed in 3.8.0