fix(edge): CVE-2026-109618/-109619 multipart mitigations (2026.10.6) #13

Merged
jknapp merged 1 commits from fix/cve-2026-109618-multipart-edge into main 2026-10-21 14:38:28 +00:00
Owner

Interim edge mitigation for PHP CVE-2026-109618 (GHSA-w8hw-w9fx-j79w, "RCE via OOM corruption") and CVE-2026-109619 (GHSA-6wmf-4rv5-p3rp), until patched PHP is deployed. VERSION 2026.10.6. Ships through this repo's own CI images; no WHP release needed.

HAProxy (templates/hap_listener.tpl)

acl is_multipart_form req.fhdr(content-type) -m sub -i multipart/form-data
http-request deny deny_status 400 hdr x-whp-edge-block "multipart-without-content-length" if is_multipart_form !{ req.hdr_cnt(content-length) gt 0 }

It sits outside the coraza_spoe_backend block, so it applies with the WAF on or off, and trusted IPs are not exempt.

Coraza (coraza-spoa/overrides.conf, 209610-209616)

  • 209610: an ARGS_NAMES or FILES_NAMES entry with more than 16 [ is denied.
  • 209611-209613: part-header budget (+4 per ARGS_POST/FILES entry, -1 per MULTIPART_PART_HEADERS value). Denied below -32, so a single part needs 37 or more headers.
  • 209614-209615: more than 64 parts with an empty filename="" or filename= are denied.
  • 209616: a multipart PHP_SESSION_UPLOAD_PROGRESS field is denied (-619).

The ids are inside the engine range 200000-209999. WHP's break-glass rule 991000001 already removes that whole range, so break-glass lifts these rules on current WHP.

Measured

  • Lab (HAProxy 3.0.11 from this image, curl 8.11 with quiche):
    • Blocked with 400: h1 chunked, h1 CL+TE, h1 -T -, h2 with no CL, h3 with no CL.
    • Passed with 200: h1/h2/h3 -F uploads (CL present), and chunked urlencoded (not multipart).
  • Real browsers against 192.168.1.148:
    • Chrome 155 over h2 and over h3 (forced QUIC): form submit with a file input, fetch(FormData) and XHR(FormData) all sent content-length and passed.
    • Only fetch with a ReadableStream body (duplex:'half') arrived without CL, and it got 400.
    • Firefox 153: form submit and XHR sent CL and passed.
  • Coraza: the WHP harness (scripts/tests/coraza-body-parse-harness/cve109618.go, Coraza v3.7.0 + CRS 4.25.0, real overrides.conf) passed with 0 failures in the enforce, detect_only and whp01 profiles.
    • Each attack also has a control run with the CVE block stripped, and every control passed (not blocked).
    • Break-glass check: with the ids moved out of the engine range, the emergency host is blocked again.
  • The coraza-spoa image (v0.7.1) starts with this file.
  • scripts/test-multipart-cve-mitigation.py is green, and turns red when either rule is mutated.

Limitation

Coraza sees only the first 16 KB of a body (tune.bufsize). An attacker who pads ahead of the abusive parts, or sends one part whose header block is over 16 KB, gets past the body rules. Both cases are recorded as LIMITATION in the harness. Only the no-Content-Length rule is enforced on the full request.

🤖 Generated with Claude Code

Interim edge mitigation for PHP CVE-2026-109618 (GHSA-w8hw-w9fx-j79w, "RCE via OOM corruption") and CVE-2026-109619 (GHSA-6wmf-4rv5-p3rp), until patched PHP is deployed. VERSION 2026.10.6. Ships through this repo's own CI images; no WHP release needed. ## HAProxy (`templates/hap_listener.tpl`) ``` acl is_multipart_form req.fhdr(content-type) -m sub -i multipart/form-data http-request deny deny_status 400 hdr x-whp-edge-block "multipart-without-content-length" if is_multipart_form !{ req.hdr_cnt(content-length) gt 0 } ``` It sits outside the `coraza_spoe_backend` block, so it applies with the WAF on or off, and trusted IPs are not exempt. ## Coraza (`coraza-spoa/overrides.conf`, 209610-209616) - 209610: an ARGS_NAMES or FILES_NAMES entry with more than 16 `[` is denied. - 209611-209613: part-header budget (+4 per ARGS_POST/FILES entry, -1 per MULTIPART_PART_HEADERS value). Denied below -32, so a single part needs 37 or more headers. - 209614-209615: more than 64 parts with an empty `filename=""` or `filename=` are denied. - 209616: a multipart `PHP_SESSION_UPLOAD_PROGRESS` field is denied (-619). The ids are inside the engine range 200000-209999. WHP's break-glass rule 991000001 already removes that whole range, so break-glass lifts these rules on current WHP. ## Measured - **Lab** (HAProxy 3.0.11 from this image, curl 8.11 with quiche): - Blocked with 400: h1 chunked, h1 CL+TE, h1 `-T -`, h2 with no CL, h3 with no CL. - Passed with 200: h1/h2/h3 `-F` uploads (CL present), and chunked urlencoded (not multipart). - **Real browsers against 192.168.1.148:** - Chrome 155 over h2 and over h3 (forced QUIC): form submit with a file input, `fetch(FormData)` and XHR(FormData) all sent content-length and passed. - Only `fetch` with a ReadableStream body (`duplex:'half'`) arrived without CL, and it got 400. - Firefox 153: form submit and XHR sent CL and passed. - **Coraza:** the WHP harness (`scripts/tests/coraza-body-parse-harness/cve109618.go`, Coraza v3.7.0 + CRS 4.25.0, real overrides.conf) passed with 0 failures in the enforce, detect_only and whp01 profiles. - Each attack also has a control run with the CVE block stripped, and every control passed (not blocked). - Break-glass check: with the ids moved out of the engine range, the emergency host is blocked again. - The `coraza-spoa` image (v0.7.1) starts with this file. - `scripts/test-multipart-cve-mitigation.py` is green, and turns red when either rule is mutated. ## Limitation Coraza sees only the first 16 KB of a body (`tune.bufsize`). An attacker who pads ahead of the abusive parts, or sends one part whose header block is over 16 KB, gets past the body rules. Both cases are recorded as LIMITATION in the harness. Only the no-Content-Length rule is enforced on the full request. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jknapp added 1 commit 2026-10-21 14:27:59 +00:00
PHP "RCE via OOM corruption" (GHSA-w8hw-w9fx-j79w) and the session
upload-progress variant (GHSA-6wmf-4rv5-p3rp), interim until patched PHP is
on every customer image.

HAProxy (hap_listener.tpl, every frontend render, WAF on or off):
  deny 400 multipart/form-data with no Content-Length (HTTP/1.1 chunked,
  HTTP/2-3 streamed bodies) -- vector (a), the post_max_size bypass.
  Measured: Chrome 155 (h2 and h3) and Firefox 153 send content-length on
  form submits, FormData fetch and XHR; only a ReadableStream fetch body
  (duplex:'half') has none.

Coraza (coraza-spoa/overrides.conf, 209610-209616):
  209610       ARGS_NAMES/FILES_NAMES with more than 16 '['
  209611-613   part-header budget (4/part, deny 32 below zero)
  209614-615   more than 64 empty-filename file parts
  209616       multipart PHP_SESSION_UPLOAD_PROGRESS field
Ids sit in the engine range 200000-209999 that WHP's break-glass rule
(991000001) already removes, so no WHP release is needed for break-glass
to lift them. Each flips the engine On for itself, like the day-one
promotions, so they deny in every mode the sidecar runs in.

Limitation: Coraza sees only the first 16 KB of a body.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jknapp merged commit 4345732dad into main 2026-10-21 14:38:28 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: cloud-hosting-platform/haproxy-manager-base#13