Mediumghsa:GHSA-x26q-wvhg-fh4m

    github.com/corazawaf/coraza/v3: Improper Input Validation (ghsa:GHSA-x26q-wvhg-fh4m)

    Published Sources: Hersteller

    Share

    What is affected, what to do

    Update github.com/corazawaf/coraza/v3 to version 3.8.0.

    Not sure whether and how you are affected? Ask a question about this vulnerability

    Metrics

    —

    Priority

    No value is available for this vulnerability.

    —

    CVSS v3

    No value is available for this vulnerability.

    —

    EPSS score

    No value is available for this vulnerability.

    Show all metrics
    Severity
    Medium
    Priority (0–325)

    The priority score is a consolidated metric that condenses several individual signals into a single, action-guiding ranking: technical severity (CVSS), exploitation likelihood (EPSS), known-exploited status (KEV) and the context of the affected asset. The aim is to answer the only truly important question in vulnerability management: what first?

    Read in glossary
    30.00 Methodology
    Exploit maturity

    An exploit is code or a technique that specifically uses a concrete vulnerability to force unintended behaviour — such as executing code or escalating privileges. It is the tool that turns a theoretical flaw into a practical attack.

    Read in glossary
    no public PoC known
    KEV status

    The KEV catalogue is a list maintained by the US authority CISA of vulnerabilities for which active exploitation in the wild has been confirmed. It answers not the question „how severe?“ but „is it already being attacked?“ — thereby sharply prioritising patching.

    Read in glossary
    —
    Published
    CWE

    CWE is a catalogue of typical weakness types in software and hardware — such as flawed input validation, unsafe memory handling or poor authentication. Where a CVE names a concrete vulnerability in a concrete product, a CWE describes the underlying class of defect.

    Read in glossary
    CWE-20, CWE-436

    Classification under NIS2 and CRA

    Rule-based classification from exploitation status and severity. It does not replace a case-by-case assessment.

    NIS2

    NIS2 is EU Directive (EU) 2022/2555 to raise the level of cybersecurity across the Union. It substantially broadens obligations compared with the first NIS directive: more sectors, mandatory risk management, staged reporting duties and personal accountability of senior management.

    Read in glossary
    Handle the vulnerability in regular vulnerability handling under Art. 21(2)(e) NIS2.
    CRA

    The CRA is EU Regulation (EU) 2024/2847 setting horizontal cybersecurity requirements for „products with digital elements“ — from industrial controllers and software to connected consumer devices. For the first time manufacturers must ensure security across the entire product lifecycle, handle vulnerabilities and provide security updates.

    Read in glossary
    From 11.12.2027, Annex I Part II CRA requires manufacturers of affected products with digital elements to handle vulnerabilities without delay and provide security updates.
    ISO/IEC 27001

    ISO/IEC 27001 is the internationally leading standard for an information security management system (ISMS). It describes a risk-based, process-oriented framework to systematically plan, implement, review and improve information security.

    Read in glossary
    Control A.8.8, management of technical vulnerabilities: assess relevance to your systems, plan remediation and keep evidence.

    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 score

    Source: GitHub Security Advisories

    Affected products

    The following versions are affected. Older releases of the same product line are also vulnerable unless stated otherwise.

    Affected products
    Affected versionsFixed in
    gogithub.com/corazawaf/coraza/v3
    • >= 3.0.0, < 3.8.0
    • 3.8.0

    Vendors and products

    Weakness classes (CWE)

    • CWE-20Improper Input Validation

      Class

      The product receives input or data, but it does not validate or incorrectly validates that the input has the properties that are required to process the data safely and correctly.

      Entry at MITRE CWE-20
    • CWE-436Interpretation Conflict

      Class

      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.

      Entry at MITRE CWE-436