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 "" } }