fix(edge): CVE-2026-109618/-109619 multipart mitigations (2026.10.6)
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>
This commit is contained in:
1 parent
b6cc3adfaa
commit
729098a8f9
4 files changed
+279
-1
No files matched your search
@@ -616,6 +616,34 @@ frontend web
|
||||
http-request set-path /suspended if is_suspended_domain
|
||||
use_backend default-backend if is_suspended_domain
|
||||
{%- endif %}
|
||||
|
||||
# CVE-2026-109618 (GHSA-w8hw-w9fx-j79w, PHP "RCE via OOM corruption"),
|
||||
# vector (a): a multipart/form-data body with no declared length is not
|
||||
# bounded by post_max_size -- PHP's multipart parser keeps reading it. A
|
||||
# body WITH a Content-Length is checked against post_max_size before it is
|
||||
# parsed. So a multipart request must carry exactly what every browser and
|
||||
# HTTP library already sends for a form: a Content-Length.
|
||||
#
|
||||
# req.hdr_cnt(content-length) is the header as the client sent it, on
|
||||
# every protocol: HTTP/1.1 chunked has none (and the H1 mux DROPS a
|
||||
# Content-Length sent alongside Transfer-Encoding, so smuggling one in
|
||||
# beside chunked does not pass either); HTTP/2 and HTTP/3 have none when
|
||||
# the client streams the body. H1/H2/H3 all check a declared
|
||||
# content-length against the DATA received, so it cannot lie about the
|
||||
# length (same reasoning as the WAF body stamp below).
|
||||
# Browsers send content-length for form posts and FormData uploads on
|
||||
# h1/h2/h3; the only body a browser sends without one is a fetch() with a
|
||||
# ReadableStream body (Chrome, h2/h3 only, `duplex: 'half'`), which is not
|
||||
# what form uploads use.
|
||||
#
|
||||
# Not WAF-gated and not exempt for trusted IPs: this is a protocol
|
||||
# guarantee the vulnerable parser needs, independent of Coraza and its
|
||||
# mode. req.fhdr (not req.hdr) so a comma inside the value cannot split it.
|
||||
# The rest of the advisory (part-header floods, empty file controls, deep
|
||||
# field names) needs the body and lives in coraza-spoa/overrides.conf
|
||||
# (209610-209616). Remove once patched PHP is on every customer image.
|
||||
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 }
|
||||
{%- if coraza_spoe_backend %}
|
||||
|
||||
# Coraza WAF inspection via SPOE. Runs AFTER rate-limit and IP-block
|
||||
|
||||
Reference in new issue
Block a user