Core Rule Set reads request bodies up to SWWAF_WAF_BODY_LIMIT (closes #116)
check / check (push) Waiting to run
check / check (push) Waiting to run
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 (with +json, text/json and +xml) no larger than it. 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, but not a multipart body reaching the limit. 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
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"sneak.berlin/go/smallwebwaf/internal/alerts"
|
||||
@@ -16,7 +19,8 @@ import (
|
||||
// SWWAF_WAF_ANOMALY_THRESHOLD is a match: it raises the waf_block alert,
|
||||
// and in block mode refuses the request, which is an offence its client's
|
||||
// history counts, and so returns ActionWAFBlocked. It returns "" for a
|
||||
// request it does not refuse.
|
||||
// request it does not refuse, and for one whose body meets a size or time
|
||||
// limit while the Core Rule Set reads it, which it notes nothing of.
|
||||
func (rq *request) checkCoreRuleSet() string {
|
||||
cfg := rq.h.config
|
||||
if cfg.WAFMode == config.WAFModeOff || pathExempt(rq.in.URL, cfg.WAFExemptPaths) {
|
||||
@@ -24,7 +28,12 @@ func (rq *request) checkCoreRuleSet() string {
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
result := rq.h.coreRuleSet.Inspect(rq.in, rq.client)
|
||||
|
||||
result := rq.inspect()
|
||||
if rq.refused.Load() != nil {
|
||||
return "" // the refusal for that limit, which check returns
|
||||
}
|
||||
|
||||
rq.line.DurationWAF = new(requestlog.Milliseconds(time.Since(start)))
|
||||
rq.line.WAFRuleIDs = result.RuleIDs
|
||||
rq.line.WAFScore = &result.Score
|
||||
@@ -49,6 +58,39 @@ func (rq *request) checkCoreRuleSet() string {
|
||||
return requestlog.ActionWAFBlocked
|
||||
}
|
||||
|
||||
// inspect runs the Core Rule Set on the request, which reads the part of
|
||||
// its body it inspects within SWWAF_CLIENT_REQUEST_TIMEOUT, and keeps that
|
||||
// part for the app. A client that runs out of time is refused with 408
|
||||
// here, and a body over SWWAF_REQUEST_MAX_BYTES with 413 as it is read;
|
||||
// check returns the refusal. A body that breaks off for any other reason
|
||||
// is passed on as far as it came, and the request to the app fails there,
|
||||
// as it would have without the Core Rule Set.
|
||||
func (rq *request) inspect() waf.Result {
|
||||
if rq.body == nil {
|
||||
// Nothing is read of no body, so nothing can go wrong reading it.
|
||||
result, _, _ := rq.h.coreRuleSet.Inspect(rq.in, rq.client, http.NoBody)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
_ = rq.rc.SetReadDeadline(rq.clientRequestDeadline())
|
||||
result, read, err := rq.h.coreRuleSet.Inspect(rq.in, rq.client, rq.body)
|
||||
// The timeouts that run while the request goes to the app take over.
|
||||
_ = rq.rc.SetReadDeadline(time.Time{})
|
||||
|
||||
rq.body.readByCoreRuleSet = read
|
||||
|
||||
if errors.Is(err, os.ErrDeadlineExceeded) {
|
||||
rq.refuse(refusal{
|
||||
status: http.StatusRequestTimeout,
|
||||
action: requestlog.ActionTimedOut,
|
||||
limit: "SWWAF_CLIENT_REQUEST_TIMEOUT",
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// alertWAFBlock raises the waf_block alert for the request, which the Core
|
||||
// Rule Set scored at result, at or over SWWAF_WAF_ANOMALY_THRESHOLD. Its
|
||||
// detail gives the rule ids, the score, the method and the path with the
|
||||
|
||||
Reference in New Issue
Block a user