Compare commits
1
Commits
0fb0675588
..
next
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fcbda6ece |
@@ -714,15 +714,19 @@ effective settings are logged at start, unless `SWWAF_LOG_LEVEL` is `warn` or
|
|||||||
request body. A size, such as `128K`, at most `1G`, has it read a body of form
|
request body. A size, such as `128K`, at most `1G`, has it read a body of form
|
||||||
data or multipart up to that size, the rest of a longer one passing on to the
|
data or multipart up to that size, the rest of a longer one passing on to the
|
||||||
app as it arrives, without being held, and a JSON or XML body no larger than
|
app as it arrives, without being held, and a JSON or XML body no larger than
|
||||||
that size, since those cannot be read in part. A body whose type ends in
|
that size, since those cannot be read in part. A body is JSON when its type is
|
||||||
`+json`, or is `text/json`, is JSON, and one whose type ends in `+xml` is XML.
|
`application/json` or `text/json`, or an `application/` or `text/` type ending
|
||||||
Any other body reaches the app uninspected, and so does a larger JSON or XML
|
in `+json`, and XML when its type is `application/xml` or `text/xml`, or an
|
||||||
body: the Core Rule Set would read any other body as form data, where binary
|
`application/` or `text/` type ending in `+xml`. Any other body reaches the
|
||||||
content such as a git push trips rules written for text. A body it reads that
|
app uninspected, and so does a larger JSON or XML body: the Core Rule Set
|
||||||
Coraza cannot parse (rule 900440), and a multipart body that fails Coraza's
|
would read any other body as form data, where binary content such as a git
|
||||||
strict checks (rule 900450), add 5 to the score, as a rule rated critical
|
push trips rules written for text. A body it reads that Coraza cannot parse
|
||||||
does, since no rule reads what comes after the fault; a multipart body that
|
(rule 900440), and a multipart body that fails Coraza's strict checks (rule
|
||||||
reaches the limit does not, since the limit can cut it inside a part's header.
|
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
|
||||||
|
before the colon of a part's header line, or between the carriage return and
|
||||||
|
the line feed that end a part's header line or the empty line after its
|
||||||
|
headers, since Coraza takes the line the limit cuts for a malformed header.
|
||||||
The client has until `SWWAF_CLIENT_REQUEST_TIMEOUT` runs out to send the part
|
The client has until `SWWAF_CLIENT_REQUEST_TIMEOUT` runs out to send the part
|
||||||
that is read, and a request whose body passes `SWWAF_REQUEST_MAX_BYTES` within
|
that is read, and a request whose body passes `SWWAF_REQUEST_MAX_BYTES` within
|
||||||
it is refused before anything reaches the app. Of a file in a multipart body,
|
it is refused before anything reaches the app. Of a file in a multipart body,
|
||||||
|
|||||||
+9
-10
@@ -105,9 +105,10 @@ SecRuleUpdateTargetById 944110 "!REQUEST_HEADERS:Referer"
|
|||||||
// bodyDirectives have the Core Rule Set read the part of a request body
|
// bodyDirectives have the Core Rule Set read the part of a request body
|
||||||
// Inspect gives it, which is at most one byte longer than the limit, up to
|
// Inspect gives it, which is at most one byte longer than the limit, up to
|
||||||
// the limit, %d bytes, and read JSON and XML as Coraza's recommended
|
// the limit, %d bytes, and read JSON and XML as Coraza's recommended
|
||||||
// configuration has it in its rules 200000, 200001 and 200006, with any
|
// configuration has it in its rules 200000, 200001 and 200006, with
|
||||||
// type ending in +xml, and text/json, besides; form data and multipart
|
// text/json, and any application or text type ending in +xml or +json,
|
||||||
// Coraza knows by itself. %% stands for a % Coraza reads.
|
// besides; form data and multipart Coraza knows by itself. %% stands for
|
||||||
|
// a % Coraza reads.
|
||||||
const bodyDirectives = `
|
const bodyDirectives = `
|
||||||
SecRequestBodyAccess On
|
SecRequestBodyAccess On
|
||||||
SecRequestBodyLimit %d
|
SecRequestBodyLimit %d
|
||||||
@@ -132,13 +133,11 @@ SecRule REQBODY_PROCESSOR "@rx ^(?:URLENCODED|MULTIPART|JSON|XML)$" \
|
|||||||
# its strict checks (900450), each add 5 to the score, as a rule the Core
|
# its strict checks (900450), each add 5 to the score, as a rule the Core
|
||||||
# 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 that reaches the
|
# them in its rules 200002 and 200003. A multipart body the limit cuts
|
||||||
# limit is let off both (900430), since the limit can cut it inside a
|
# before the colon of a part's header line, or between the carriage return
|
||||||
# part's header, which Coraza takes for such a fault. Coraza parses any
|
# and the line feed that end a part's header line or the empty line after
|
||||||
# form data body.
|
# its headers, adds 5 too, since Coraza takes the line the limit cuts for a
|
||||||
SecRule INBOUND_DATA_ERROR "@eq 1" "id:900430,phase:2,pass,nolog,chain"
|
# malformed header. Coraza parses any form data body.
|
||||||
SecRule REQBODY_PROCESSOR "@streq MULTIPART" \
|
|
||||||
"ctl:ruleRemoveById=900440,ctl:ruleRemoveById=900450"
|
|
||||||
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,\
|
||||||
|
|||||||
+38
-10
@@ -613,29 +613,57 @@ func TestBodyCorazaCannotParseIsAMatch(t *testing.T) {
|
|||||||
|
|
||||||
// The multipart bodies Coraza cannot parse fail its strict checks too:
|
// The multipart bodies Coraza cannot parse fail its strict checks too:
|
||||||
// one whose type names its boundary twice, and one with a part header
|
// one whose type names its boundary twice, and one with a part header
|
||||||
// that has no colon, before the attack.
|
// that has no colon, before the attack. They do so padded past the
|
||||||
body = field("q", injection) + end
|
// limit too, which cuts them in the padding.
|
||||||
wantBody(t, crs, post(multipart+"; boundary=c"), body, matched(900440, 900450),
|
noColon := "--b\r\nContent-Disposition form-data; name=\"a\"\r\n\r\n1\r\n"
|
||||||
body)
|
pad := field("pad", strings.Repeat("a", bodyLimit))
|
||||||
|
|
||||||
body = "--b\r\nContent-Disposition form-data; name=\"a\"\r\n\r\n1\r\n" +
|
for _, tc := range []struct{ header, head string }{
|
||||||
field("q", injection) + end
|
{multipart + "; boundary=c", ""},
|
||||||
wantBody(t, crs, post(multipart), body, matched(900440, 900450), body)
|
{multipart, noColon},
|
||||||
|
} {
|
||||||
|
body = tc.head + field("q", injection) + end
|
||||||
|
wantBody(t, crs, post(tc.header), body, matched(900440, 900450), body)
|
||||||
|
|
||||||
|
body = tc.head + field("q", injection) + pad + end
|
||||||
|
wantBody(t, crs, post(tc.header), body, matched(900440, 900450),
|
||||||
|
body[:bodyLimit+1])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMultipartBodyCutInsideAPartHeaderIsNotAMatch(t *testing.T) {
|
func TestMultipartBodyCutBeforeAPartHeadersColonIsAMatch(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// The limit falls in the middle of the name of the second part's
|
// The limit falls in the middle of the name of the second part's
|
||||||
// header, which Coraza, reading up to the limit, finds without a colon.
|
// header, which Coraza, reading up to the limit, cannot tell from a
|
||||||
|
// header without a colon.
|
||||||
cut := "--b\r\nContent-Di"
|
cut := "--b\r\nContent-Di"
|
||||||
first := field("pad", strings.Repeat("a", bodyLimit-len(field("pad", ""))-len(cut)))
|
first := field("pad", strings.Repeat("a", bodyLimit-len(field("pad", ""))-len(cut)))
|
||||||
body := first + cut + "sposition: form-data; name=\"q\"\r\n\r\n1\r\n" + end
|
body := first + cut + "sposition: form-data; name=\"q\"\r\n\r\n1\r\n" + end
|
||||||
|
|
||||||
wantBody(t, readingBodies(t), post(multipart), body, waf.Result{},
|
wantBody(t, readingBodies(t), post(multipart), body, matched(900440, 900450),
|
||||||
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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user