fix(waf): 400 duplicate Content-Type; JSON depth limit 64 (round 1)
- hap_listener.tpl (Coraza block): `http-request deny deny_status 400 if
{ req.fhdr_cnt(content-type) gt 1 }`. With two Content-Type lines Coraza
evaluates every value (text/xml on either line selects its XML processor,
which accepts a form body without error) while Apache merges the lines
and PHP parses the body as a form. Measured with Coraza v3.7.0 + PHP 8.3:
`application/x-www-form-urlencoded` + `text/xml` put the payload in
$_POST uninspected. Live: two lines -> 400 over HTTP/1.1 and HTTP/2; one
comma-joined line (browser XHR double-set) is unaffected.
- coraza-spoa/config.yaml: SecRequestBodyJsonDepthLimit 64 (was 1024 from
the recommended file). Coraza's JSON flattening is quadratic in depth:
depth 300 (601 B) = 297 ms, depth 1000 (2 KB) = 3.1 s, past the 100 ms
fail-open timeout. Over the limit it is a 0.1 ms parse error, which WHP's
enforce-mode rule denies on a complete body (live: depth 65/300/1000 ->
403, depth 64 and a depth-9 global-styles body -> 200). Legitimate depth
measured on WordPress core, Gutenberg, WooCommerce and Elementor JSON:
max 8.
- test-waf-body-inspection.py: 17 tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4f21bb2f76
commit
cc0652e0c0
3 files changed
+39
-2
No files matched your search
@@ -635,6 +635,19 @@ frontend web
|
||||
# 50 MB upload is not stored at the edge.
|
||||
option http-buffer-request
|
||||
|
||||
# Exactly one Content-Type, or 400. With two Content-Type lines Coraza and
|
||||
# the backend disagree about what the body IS: Coraza evaluates every value
|
||||
# (`text/xml` on either line selects its XML processor, which accepts a
|
||||
# form body without error), while Apache merges the lines with ", " and
|
||||
# PHP cuts the media type at the first ',' / ';'. Measured (Coraza v3.7.0
|
||||
# + PHP 8.3): `application/x-www-form-urlencoded` + `text/xml` puts the
|
||||
# payload in $_POST with nothing inspected. req.fhdr_cnt counts header
|
||||
# LINES, so a single comma-joined value (which browsers produce when a
|
||||
# script calls setRequestHeader twice) is not affected here; that form is
|
||||
# parsed consistently enough by both sides (see the report's matrix).
|
||||
# Browsers, curl, PHP-curl and the common HTTP libraries send one line.
|
||||
http-request deny deny_status 400 if { req.fhdr_cnt(content-type) gt 1 }
|
||||
|
||||
# Tell Coraza whether it got the whole body. Coraza cannot tell on its
|
||||
# own (REQUEST_BODY_LENGTH is only filled by the urlencoded/raw
|
||||
# processors), and the difference matters: a TRUNCATED JSON body, or a
|
||||
|
||||
Reference in new issue
Block a user