Every CVE Search/ Get API key

CVE-2026-107833

OWASP Coraza WAF: Unbounded recursion in JSON response body processor causes CPU exhaustion

5.9 MediumFix available

OWASP Coraza WAF is a golang modsecurity compatible web application firewall library. From 3.0.0 until 3.8.0, ProcessResponse in internal/bodyprocessors/json.go passes the ignoreJSONRecursionLimit value of -1 to readJSON, while the recursive guard only stops at zero. A network attacker who can cause an application protected by Coraza to return deeply nested JSON can make response-body processing perform quadratic work, consuming one CPU core for seconds per response within the default ResponseBodyLimit. Request JSON processing is not affected by this specific path because it uses the configured request recursion limit, and exploitation requires response-body inspection to be enabled. This issue is fixed in version 3.8.0.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
High
Privileges needed
None
User interaction
None

What an attacker can affect

Confidentiality
None
Integrity
None
Availability
High

Exploit likelihood

0.39%Unchanged since Oct 10, 2026
90%50%10%1%

What each source reports

CVE List Score used 5.9

OWASP Coraza WAF: Unbounded recursion in JSON response body processor causes CPU exhaustion

OWASP Coraza WAF is a golang modsecurity compatible web application firewall library. From 3.0.0 until 3.8.0, ProcessResponse in internal/bodyprocessors/json.go passes the ignoreJSONRecursionLimit value of -1 to readJSON, while the recursive guard only stops at zero. A network attacker who can cause an application protected by Coraza to return deeply nested JSON can make response-body processing perform quadratic work, consuming one CPU core for seconds per response within the default ResponseBodyLimit. Request JSON processing is not affected by this specific path because it uses the configured request recursion limit, and exploitation requires response-body inspection to be enabled. This issue is fixed in version 3.8.0.

Record
CVE-2026-107833
CVSS 3.1
5.9 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H
Weakness
CWE-674
Updated
2026-11-02
EUVDEUVD-2026-95926 5.9

OWASP Coraza WAF is a golang modsecurity compatible web application firewall library. From 3.0.0 until 3.8.0, ProcessResponse in internal/bodyprocessors/json.go passes the ignoreJSONRecursionLimit value of -1 to readJSON, while the recursive guard only stops at zero. A network attacker who can cause an application protected by Coraza to return deeply nested JSON can make response-body processing perform quadratic work, consuming one CPU core for seconds per response within the default ResponseBodyLimit. Request JSON processing is not affected by this specific path because it uses the configured request recursion limit, and exploitation requires response-body inspection to be enabled. This issue is fixed in version 3.8.0.

Record
EUVD-2026-95926
CVSS 3.1
5.9 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H
Updated
2026-11-02
GitHub AdvisoriesGHSA-3c6w-j9xm-8h2h 5.9

Coraza: Unbounded recursion in JSON response body processor causes CPU exhaustion

Summary

The JSON response body processor parses response bodies with no recursion limit. ProcessResponse calls readJSON(ss, ignoreJSONRecursionLimit), and that constant is -1. The guard in readItems only fires on == 0, so counting down from -1 (-2, -3, ...) never reaches it. The guard is effectively dead on the response path. The request path is fine: ProcessRequest passes the configured limit (default 1024). There is no equivalent directive or default for responses.

Parsing a deeply nested JSON response is CPU-bound and its cost grows quadratically with nesting depth. A 512 KiB response (the default ResponseBodyLimit) holds about 87,000 nesting levels and takes ~12 s to process, keeping one core busy the whole time.

Root cause

internal/bodyprocessors/json.go

const ignoreJSONRecursionLimit = -1                     // line 51

func (js *jsonBodyProcessor) ProcessResponse(reader io.Reader, v ..., _ plugintypes.BodyProcessorOptions) error {
    ...
    data, err := readJSON(ss, ignoreJSONRecursionLimit) // line 62, passes -1
}

func (js *jsonBodyProcessor) ProcessRequest(...) error {
    ...
    data, err := readJSON(ss, bpo.RequestBodyRecursionLimit) // line 32, default 1024
}

The guard and the decrement:

func readItems(json gjson.Result, objKey []byte, maxRecursion int, res map[string]string) error {
    if maxRecursion == 0 {                              // line 106
        return errors.New("max recursion reached while reading json object")
    }
    ...
    iterationError = readItems(value, objKey, maxRecursion-1, res) // line 126

Note that ProcessResponse discards BodyProcessorOptions (the parameter is _), so even a caller that wanted to set a limit on responses has no way to.

Why the cost is quadratic

Every nesting level re-parses the remaining nested document through gjson.ForEach, so total work is O(n²) in the depth. Numbers below were measured on an Intel Core Ultra 7 255H, Go 1.22.2, gjson v1.18.0, at commit db9850b2 (v3.7.0-55):

depth   bytes    ProcessResponse time
5000    30004    30 ms
10000   60004    119 ms
20000   120004   456 ms
40000   240004   2.18 s
87381   524290   12.09 s

Log-log slope between adjacent rows lands between 1.93 and 2.26 (2.09 across the full range), which matches quadratic. Roughly 87,000 levels is the most that fits inside the default 512 KiB ResponseBodyLimit.

PoC

Save as internal/bodyprocessors/poc_json_test.go, then:

go test -v -timeout 120s -run TestPoCJSONResponse ./internal/bodyprocessors/...
package bodyprocessors_test

import (
      "strings"
      "testing"
      "time"

      "github.com/corazawaf/coraza/v3/experimental/plugins/plugintypes"
      "github.com/corazawaf/coraza/v3/internal/bodyprocessors"
      "github.com/corazawaf/coraza/v3/internal/corazawaf"
)

func nestedJSON(depth int) string {
      var sb strings.Builder
      sb.Grow(depth*6 + 4)
      for i := 0; i < depth; i++ {
              sb.WriteString(`{"a":`)
      }
      sb.WriteString("null")
      for i := 0; i < depth; i++ {
              sb.WriteByte('}')
      }
      return sb.String()
}

func TestPoCJSONResponse(t *testing.T) {
      proc, _ := bodyprocessors.GetBodyProcessor("json")

      // Request path is bounded, response path is not.
      body := nestedJSON(5000)
      v := corazawaf.NewTransactionVariables()
      errReq := proc.ProcessRequest(strings.NewReader(body), v,
              plugintypes.BodyProcessorOptions{RequestBodyRecursionLimit: 1024})
      errRes := proc.ProcessResponse(strings.NewReader(body), v,
              plugintypes.BodyProcessorOptions{})
      t.Logf("depth=5000 ProcessRequest  err=%v", errReq)
      t.Logf("depth=5000 ProcessResponse err=%v", errRes)

      // Quadratic scaling on the response path.
      for _, depth := range []int{5000, 10000, 20000, 40000, 87381} {
              b := nestedJSON(depth)
              vv := corazawaf.NewTransactionVariables()
        …
Record
GHSA-3c6w-j9xm-8h2h
CVSS 3.1
5.9 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H
Weakness
CWE-674
Updated
2026-11-02
NVD 5.9

OWASP Coraza WAF is a golang modsecurity compatible web application firewall library. From 3.0.0 until 3.8.0, ProcessResponse in internal/bodyprocessors/json.go passes the ignoreJSONRecursionLimit value of -1 to readJSON, while the recursive guard only stops at zero. A network attacker who can cause an application protected by Coraza to return deeply nested JSON can make response-body processing perform quadratic work, consuming one CPU core for seconds per response within the default ResponseBodyLimit. Request JSON processing is not affected by this specific path because it uses the configured request recursion limit, and exploitation requires response-body inspection to be enabled. This issue is fixed in version 3.8.0.

Record
CVE-2026-107833
CVSS 3.1
5.9 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H
Weakness
CWE-674
Updated
2026-11-02
OSV 5.9

OWASP Coraza WAF: Unbounded recursion in JSON response body processor causes CPU exhaustion

OWASP Coraza WAF is a golang modsecurity compatible web application firewall library. From 3.0.0 until 3.8.0, ProcessResponse in internal/bodyprocessors/json.go passes the ignoreJSONRecursionLimit value of -1 to readJSON, while the recursive guard only stops at zero. A network attacker who can cause an application protected by Coraza to return deeply nested JSON can make response-body processing perform quadratic work, consuming one CPU core for seconds per response within the default ResponseBodyLimit. Request JSON processing is not affected by this specific path because it uses the configured request recursion limit, and exploitation requires response-body inspection to be enabled. This issue is fixed in version 3.8.0.

Record
CVE-2026-107833
CVSS 3.1
5.9 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H
Weakness
CWE-674
Updated
2026-11-02
OSVGHSA-3c6w-j9xm-8h2h 5.9

Coraza: Unbounded recursion in JSON response body processor causes CPU exhaustion

Summary

The JSON response body processor parses response bodies with no recursion limit. ProcessResponse calls readJSON(ss, ignoreJSONRecursionLimit), and that constant is -1. The guard in readItems only fires on == 0, so counting down from -1 (-2, -3, ...) never reaches it. The guard is effectively dead on the response path. The request path is fine: ProcessRequest passes the configured limit (default 1024). There is no equivalent directive or default for responses.

Parsing a deeply nested JSON response is CPU-bound and its cost grows quadratically with nesting depth. A 512 KiB response (the default ResponseBodyLimit) holds about 87,000 nesting levels and takes ~12 s to process, keeping one core busy the whole time.

Root cause

internal/bodyprocessors/json.go

const ignoreJSONRecursionLimit = -1                     // line 51

func (js *jsonBodyProcessor) ProcessResponse(reader io.Reader, v ..., _ plugintypes.BodyProcessorOptions) error {
    ...
    data, err := readJSON(ss, ignoreJSONRecursionLimit) // line 62, passes -1
}

func (js *jsonBodyProcessor) ProcessRequest(...) error {
    ...
    data, err := readJSON(ss, bpo.RequestBodyRecursionLimit) // line 32, default 1024
}

The guard and the decrement:

func readItems(json gjson.Result, objKey []byte, maxRecursion int, res map[string]string) error {
    if maxRecursion == 0 {                              // line 106
        return errors.New("max recursion reached while reading json object")
    }
    ...
    iterationError = readItems(value, objKey, maxRecursion-1, res) // line 126

Note that ProcessResponse discards BodyProcessorOptions (the parameter is _), so even a caller that wanted to set a limit on responses has no way to.

Why the cost is quadratic

Every nesting level re-parses the remaining nested document through gjson.ForEach, so total work is O(n²) in the depth. Numbers below were measured on an Intel Core Ultra 7 255H, Go 1.22.2, gjson v1.18.0, at commit db9850b2 (v3.7.0-55):

depth   bytes    ProcessResponse time
5000    30004    30 ms
10000   60004    119 ms
20000   120004   456 ms
40000   240004   2.18 s
87381   524290   12.09 s

Log-log slope between adjacent rows lands between 1.93 and 2.26 (2.09 across the full range), which matches quadratic. Roughly 87,000 levels is the most that fits inside the default 512 KiB ResponseBodyLimit.

PoC

Save as internal/bodyprocessors/poc_json_test.go, then:

go test -v -timeout 120s -run TestPoCJSONResponse ./internal/bodyprocessors/...
package bodyprocessors_test

import (
      "strings"
      "testing"
      "time"

      "github.com/corazawaf/coraza/v3/experimental/plugins/plugintypes"
      "github.com/corazawaf/coraza/v3/internal/bodyprocessors"
      "github.com/corazawaf/coraza/v3/internal/corazawaf"
)

func nestedJSON(depth int) string {
      var sb strings.Builder
      sb.Grow(depth*6 + 4)
      for i := 0; i < depth; i++ {
              sb.WriteString(`{"a":`)
      }
      sb.WriteString("null")
      for i := 0; i < depth; i++ {
              sb.WriteByte('}')
      }
      return sb.String()
}

func TestPoCJSONResponse(t *testing.T) {
      proc, _ := bodyprocessors.GetBodyProcessor("json")

      // Request path is bounded, response path is not.
      body := nestedJSON(5000)
      v := corazawaf.NewTransactionVariables()
      errReq := proc.ProcessRequest(strings.NewReader(body), v,
              plugintypes.BodyProcessorOptions{RequestBodyRecursionLimit: 1024})
      errRes := proc.ProcessResponse(strings.NewReader(body), v,
              plugintypes.BodyProcessorOptions{})
      t.Logf("depth=5000 ProcessRequest  err=%v", errReq)
      t.Logf("depth=5000 ProcessResponse err=%v", errRes)

      // Quadratic scaling on the response path.
      for _, depth := range []int{5000, 10000, 20000, 40000, 87381} {
              b := nestedJSON(depth)
              vv := corazawaf.NewTransactionVariables()
        …
Record
GHSA-3c6w-j9xm-8h2h
CVSS 3.1
5.9 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H
Weakness
CWE-674
Updated
2026-11-02
OSVGO-2026-6690 No score

Coraza: Unbounded recursion in JSON response body processor causes CPU exhaustion in github.com/corazawaf/coraza

Coraza: Unbounded recursion in JSON response body processor causes CPU exhaustion in github.com/corazawaf/coraza

Record
GO-2026-6690
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
github.com/corazawaf/coraza/v3
Go
3.0.0 up to 3.8.0
3.8.0
github.com/corazawaf/coraza
Go
All versions None known
github.com/corazawaf/coraza/v2
Go
All versions None known

History

History
RecordedChangedSources
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-107833 is a medium vulnerability (CVSS 5.9) in Corazawaf Coraza, published October 8, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.39%, the 33rd percentile. Fixed in github.com/corazawaf/coraza/v3 3.8.0.

References

This page doesn't list everything

Left out here: the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open