check / check (push) Waiting to run
The package passes nothing to an app since rproxy took that over, so it is named after the command it serves. The rproxy tests call their stand-in for servewaf standIn, since a local servewaf would hide the package. No change of behaviour. Model: opus-5-5
65 lines
2.0 KiB
Go
65 lines
2.0 KiB
Go
package servewaf
|
|
|
|
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, without the query,
|
|
// as sent or once percent-decoded, 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. The ban's notes give the path as sent.
|
|
func (rq *request) trapPath(now time.Time) bool {
|
|
path := rules.Path(rq.in)
|
|
if !slices.Contains(rq.h.config.TrapPaths, path) &&
|
|
!slices.Contains(rq.h.config.TrapPaths, rules.DecodeOnce(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 ""
|
|
}
|
|
}
|