check / check (push) Waiting to run
SWWAF_TRAP_PATHS: a request whose path, as a path rule sees it, is one of them is a clear sign of attack, banned as a ban rule's match is; the ban's notes give its trap_path. Checked after the rate limits, before the rule files. SWWAF_ERROR_BURST_THRESHOLD (default 30, or off): more refusals in a minute after a block or ban rule or a trap path, or for a missing or wrong token, ban the client as a broken limit does. Counted in clients.json's minute_refusals; limit_hit error_burst, notes kind refusals. A token refusal is now the offence token_refused, and smallwebwaf_offences_total counts every kind the history does. Judgement call: the threshold is not lowered by a client's limit percentage. Model: opus-5-5
63 lines
1.9 KiB
Go
63 lines
1.9 KiB
Go
package proxy
|
|
|
|
import (
|
|
"slices"
|
|
"time"
|
|
|
|
"sneak.berlin/go/smallwebwaf/internal/bans"
|
|
"sneak.berlin/go/smallwebwaf/internal/requestlog"
|
|
"sneak.berlin/go/smallwebwaf/internal/rules"
|
|
)
|
|
|
|
// trapPath reports whether the request asks for a path in
|
|
// SWWAF_TRAP_PATHS: its path as a path rule sees it, before any decoding
|
|
// and without the query, is one of them. Such a request is a clear sign of
|
|
// attack, as a ban rule's match is: it bans the client's netblock, or in
|
|
// observe mode raises the alert for the ban it would have made.
|
|
func (rq *request) trapPath(now time.Time) bool {
|
|
path := rules.Path(rq.in)
|
|
if !slices.Contains(rq.h.config.TrapPaths, path) {
|
|
return false
|
|
}
|
|
|
|
rq.attack = true
|
|
rq.banForAttack(now, bans.Notes{TrapPath: path})
|
|
|
|
return true
|
|
}
|
|
|
|
// checkRules checks the request against the rules of the rule files at
|
|
// now, notes the ids of those it matches in the log line, and returns the
|
|
// action of the rule that refuses it, ActionRuleBlocked for a block rule
|
|
// and ActionBanned for a ban rule, or "" when none does. A ban rule bans
|
|
// the client's netblock for a clear sign of attack, or in observe mode
|
|
// raises the alert for the ban it would have made. Either rule's match
|
|
// is noted as an offence, for the client's history.
|
|
func (rq *request) checkRules(now time.Time) string {
|
|
matched := rq.h.rules.Match(rq.in)
|
|
|
|
for _, rule := range matched {
|
|
rq.line.RuleIDs = append(rq.line.RuleIDs, rule.ID)
|
|
rq.h.metrics.RuleMatched(rule.ID, rule.Action)
|
|
}
|
|
|
|
if len(matched) == 0 {
|
|
return ""
|
|
}
|
|
|
|
// Only the last rule matched can refuse the request.
|
|
switch last := matched[len(matched)-1]; last.Action {
|
|
case rules.ActionBlock:
|
|
rq.ruleBlocked = true
|
|
|
|
return requestlog.ActionRuleBlocked
|
|
case rules.ActionBan:
|
|
rq.attack = true
|
|
rq.banForAttack(now, bans.Notes{RuleID: last.ID, Target: last.Target})
|
|
|
|
return requestlog.ActionBanned
|
|
default:
|
|
return ""
|
|
}
|
|
}
|