package servewaf // The heuristic refusal_limit. It reads SWWAF_ERROR_BURST_THRESHOLD and // SWWAF_BAN_RESPONSE. import ( "context" "net/http" "time" "sneak.berlin/go/smallwebwaf/internal/requestlog" ) // refusalLimit counts the request once it has been answered, if servewaf // refused it after a rule file match, a trap path or a Core Rule Set match, and // in observe mode if enforce mode would have: more than // SWWAF_ERROR_BURST_THRESHOLD such refusals of the client within a minute break // a limit, which bans the client. A client in SWWAF_ALLOW_NETS, which the // heuristics skip, is not counted, and nothing is while the threshold is off. // Its figures are the window, the count and the limit. type refusalLimit struct{} func (refusalLimit) name() string { return "refusal_limit" } func (refusalLimit) check(_ context.Context, rq *request, now time.Time) *verdict { cfg := rq.h.config if cfg.ErrorBurstThreshold == 0 { return nil } if !rq.attack && !rq.ruleBlocked && !rq.wafBlocked { return nil } hit, over := rq.h.limiter.CountRefusal(rq.h.clientGroup(rq.client), now, cfg.ErrorBurstThreshold) if !over { return nil } // What the client was sent, or in observe mode would have been. status := rq.out.status switch rq.line.WouldAction { case requestlog.ActionRuleBlocked, requestlog.ActionWAFBlocked: status = http.StatusForbidden case requestlog.ActionBanned: status = cfg.BanResponse } rq.banForLimit(now, hit, status) return &verdict{action: requestlog.ActionBanned, figures: limitFigures(hit)} }