Compare commits

..
1 Commits
Author SHA1 Message Date
clawbot fbe87a7c5e Core Rule Set reads request bodies up to SWWAF_WAF_BODY_LIMIT (closes #116)
check / check (push) Canceled after 0s
SWWAF_WAF_BODY_LIMIT (default off, at most 1G) has the Core Rule Set read
form data and multipart up to the limit, the rest streaming on, and JSON
and XML no larger than it, with text/json and the application and text
types ending in +json or +xml. The part read is held for the app. A size
or time limit met while reading ends the request. Content-Encoding is
refused again on these kinds. A body Coraza cannot parse, or a multipart
body failing its strict checks, adds 5, as does a multipart body the limit
cuts before the colon of a part's header. Coraza is built with
no_fs_access, so writes no file. Rule 900300 moves to phase 2.

Judgement call: Content-Encoding is refused on a JSON or XML body too
large to read, as SPEC.md allows.

Model: opus-5-5
2026-10-08 10:04:58 +00:00
3 changed files with 13 additions and 36 deletions
+11 -13
View File
@@ -724,19 +724,17 @@ effective settings are logged at start, unless `SWWAF_LOG_LEVEL` is `warn` or
(rule 900440), and a multipart body that fails Coraza's strict checks (rule (rule 900440), and a multipart body that fails Coraza's strict checks (rule
900450), add 5 to the score, as a rule rated critical does, since no rule 900450), add 5 to the score, as a rule rated critical does, since no rule
reads what comes after the fault. So does a multipart body the limit cuts reads what comes after the fault. So does a multipart body the limit cuts
before the colon of a part's header line, or between the carriage return and before the colon of a part's header line, which Coraza cannot tell from a
the line feed that end a part's header line or the empty line after its header without one. The client has until `SWWAF_CLIENT_REQUEST_TIMEOUT` runs
headers, since Coraza takes the line the limit cuts for a malformed header. out to send the part that is read, and a request whose body passes
The client has until `SWWAF_CLIENT_REQUEST_TIMEOUT` runs out to send the part `SWWAF_REQUEST_MAX_BYTES` within it is refused before anything reaches the
that is read, and a request whose body passes `SWWAF_REQUEST_MAX_BYTES` within app. Of a file in a multipart body, Coraza counts the bytes and writes
it is refused before anything reaches the app. Of a file in a multipart body, nothing. Body inspection suits apps whose forms carry no code. In front of
Coraza counts the bytes and writes nothing. Body inspection suits apps whose gitea it refuses issue and comment text, wiki pages and files saved in the web
forms carry no code. In front of gitea it refuses issue and comment text, wiki editor that hold shell commands or code (932125, 932235, 932250 and others),
pages and files saved in the web editor that hold shell commands or code package descriptions that show code, PyPI uploads (922130), and attachments
(932125, 932235, 932250 and others), package descriptions that show code, PyPI named like `debug.log` or `config.yml` (932180), until the rule ids the
uploads (922130), and attachments named like `debug.log` or `config.yml` request log names are added to `SWWAF_WAF_DISABLED_RULES`.
(932180), until the rule ids the request log names are added to
`SWWAF_WAF_DISABLED_RULES`.
- `SWWAF_TRAP_PATHS` (default empty): paths the app never serves and only - `SWWAF_TRAP_PATHS` (default empty): paths the app never serves and only
scanners ask for, such as `/wp-login.php,/xmlrpc.php` in front of gitea; a scanners ask for, such as `/wp-login.php,/xmlrpc.php` in front of gitea; a
request for one bans its client for a clear sign of attack, as a `ban` rule request for one bans its client for a clear sign of attack, as a `ban` rule
+2 -4
View File
@@ -134,10 +134,8 @@ SecRule REQBODY_PROCESSOR "@rx ^(?:URLENCODED|MULTIPART|JSON|XML)$" \
# Rule Set rates critical does: no rule reads what comes after the fault, # Rule Set rates critical does: no rule reads what comes after the fault,
# which the app may still read. Coraza's recommended configuration refuses # which the app may still read. Coraza's recommended configuration refuses
# them in its rules 200002 and 200003. A multipart body the limit cuts # them in its rules 200002 and 200003. A multipart body the limit cuts
# before the colon of a part's header line, or between the carriage return # before the colon of a part's header line adds 5 too, since Coraza cannot
# and the line feed that end a part's header line or the empty line after # tell it from a header without one. Coraza parses any form data body.
# its headers, adds 5 too, since Coraza takes the line the limit cuts for a
# malformed header. Coraza parses any form data body.
SecRule REQBODY_ERROR "!@eq 0" "id:900440,phase:2,pass,severity:'CRITICAL',\ SecRule REQBODY_ERROR "!@eq 0" "id:900440,phase:2,pass,severity:'CRITICAL',\
setvar:'tx.inbound_anomaly_score_pl1=+5'" setvar:'tx.inbound_anomaly_score_pl1=+5'"
SecRule MULTIPART_STRICT_ERROR "!@eq 0" "id:900450,phase:2,pass,\ SecRule MULTIPART_STRICT_ERROR "!@eq 0" "id:900450,phase:2,pass,\
-19
View File
@@ -645,25 +645,6 @@ func TestMultipartBodyCutBeforeAPartHeadersColonIsAMatch(t *testing.T) {
body[:bodyLimit+1]) body[:bodyLimit+1])
} }
func TestMultipartBodyCutBeforeALineFeedInAPartsHeadersIsAMatch(t *testing.T) {
t.Parallel()
crs := readingBodies(t)
headerLine := "--b\r\nContent-Disposition: form-data; name=\"q\"\r"
// The limit falls between the carriage return and the line feed that
// end the second part's header line, and then between those that end
// the empty line after it. Coraza, reading up to the limit, takes the
// line ending in a lone carriage return for a malformed header.
for _, cut := range []int{len(headerLine), len(headerLine + "\n\r")} {
first := field("pad", strings.Repeat("a", bodyLimit-len(field("pad", ""))-cut))
body := first + field("q", "1") + end
wantBody(t, crs, post(multipart), body, matched(900440, 900450),
body[:bodyLimit+1])
}
}
// TestCorazaWritesNoFile is not parallel, since it sets TMPDIR, the // TestCorazaWritesNoFile is not parallel, since it sets TMPDIR, the
// system's temporary directory, for the whole test process. // system's temporary directory, for the whole test process.
func TestCorazaWritesNoFile(t *testing.T) { func TestCorazaWritesNoFile(t *testing.T) {