Gitea setup: Core Rule Set blocks attacks, not ordinary Gitea traffic (closes #198)
check / check (push) Successful in 3m20s
check / check (push) Successful in 3m20s
The Gitea setup runs the Core Rule Set in block mode. Rules that refused ordinary Gitea requests are switched off only where they misfired: - deploy/gitea/crs-exclusions.conf, loaded through the new setting SWWAF_CORE_RULE_SET_EXCLUSIONS_FILE, covers Gitea's search, file-name and branch-name fields, Referer, and file paths; - smallwebwaf's defaults gain a seventh change: the registry upload path's from parameter skips 932260. A file that cannot be read, does not parse or names an id from 900000 to 900999 stops servewaf at start. internal/waf's tests and script/gitea-setup send the recorded requests in internal/waf/testdata/gitea-requests.txt and three attacks. Model: opus-5-5
This commit is contained in:
+51
-12
@@ -1,8 +1,9 @@
|
||||
// Package waf runs the OWASP Core Rule Set 4.25.0, through Coraza, on the
|
||||
// method, the URL with its query and the headers of a request, and on its
|
||||
// body while SWWAF_WAF_BODY_LIMIT is set, with the six changes smallwebwaf
|
||||
// makes to it, as "Attack detection" under "Settings" in SPEC.md describes
|
||||
// them. It reads no response.
|
||||
// body while SWWAF_WAF_BODY_LIMIT is set, with the seven changes smallwebwaf
|
||||
// makes to it and the exclusions in SWWAF_CORE_RULE_SET_EXCLUSIONS_FILE, as
|
||||
// "Attack detection" under "Settings" in SPEC.md describes them. It reads no
|
||||
// response.
|
||||
//
|
||||
// smallwebwaf writes only to its state directory, so Coraza is built with
|
||||
// its no_fs_access tag, as the Dockerfile and script/build build it: of a
|
||||
@@ -11,10 +12,13 @@
|
||||
package waf
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -29,9 +33,9 @@ import (
|
||||
// paranoia level for %d, and bodyDirectives for %s while
|
||||
// SWWAF_WAF_BODY_LIMIT is set. Each rule smallwebwaf adds has an id from
|
||||
// 900000 to 900999, the ids the Core Rule Set keeps for the rules that set
|
||||
// it up, which SWWAF_WAF_DISABLED_RULES refuses, so that no setting
|
||||
// switches one off. Coraza joins a line ending in \ to the next, without
|
||||
// the spaces at the start of the next.
|
||||
// it up, which SWWAF_WAF_DISABLED_RULES and the exclusions file refuse, so
|
||||
// that no setting switches one off. Coraza joins a line ending in \ to the
|
||||
// next, without the spaces at the start of the next.
|
||||
const directives = `
|
||||
# The engine only detects. smallwebwaf compares the request's anomaly
|
||||
# score with SWWAF_WAF_ANOMALY_THRESHOLD itself, in block and detect mode
|
||||
@@ -100,6 +104,13 @@ SecRuleUpdateTargetById 932260 "!ARGS:path|!ARGS:files|!ARGS:skip-to|\
|
||||
# Inspect.
|
||||
SecRuleUpdateTargetById 932340 "!REQUEST_HEADERS:Referer"
|
||||
SecRuleUpdateTargetById 944110 "!REQUEST_HEADERS:Referer"
|
||||
|
||||
# The seventh: a container image push that reuses a layer names the image
|
||||
# it takes the layer from in the query parameter from, which is not
|
||||
# checked for command names. 932260 refused the push of a layer from
|
||||
# owner/python3-app.
|
||||
SecRule REQUEST_FILENAME "@rx ^/v2/.+/blobs/uploads/$" \
|
||||
"id:900500,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932260;ARGS:from"
|
||||
`
|
||||
|
||||
// bodyDirectives have the Core Rule Set read the part of a request body
|
||||
@@ -160,8 +171,20 @@ type Params struct {
|
||||
// BodyLimit is the most of a request body the Core Rule Set reads
|
||||
// (SWWAF_WAF_BODY_LIMIT), 0 while it is off and it reads none.
|
||||
BodyLimit int64
|
||||
// ExclusionsFile is the path of a file of exclusions loaded after the
|
||||
// rest (SWWAF_CORE_RULE_SET_EXCLUSIONS_FILE), "" while there is none.
|
||||
ExclusionsFile string
|
||||
}
|
||||
|
||||
// smallwebwafRuleID matches an id from 900000 to 900999, which an
|
||||
// exclusions file may not name: those are the ids of smallwebwaf's own
|
||||
// rules.
|
||||
var smallwebwafRuleID = regexp.MustCompile(`\b900\d{3}\b`)
|
||||
|
||||
// errSmallwebwafRuleID is the error of an exclusions file that names an
|
||||
// id of smallwebwaf's own rules.
|
||||
var errSmallwebwafRuleID = errors.New("an id of smallwebwaf's own rules")
|
||||
|
||||
// CoreRuleSet is the Core Rule Set, ready to inspect requests. It is safe
|
||||
// for concurrent use.
|
||||
type CoreRuleSet struct {
|
||||
@@ -169,9 +192,11 @@ type CoreRuleSet struct {
|
||||
bodyLimit int64
|
||||
}
|
||||
|
||||
// New returns the Core Rule Set with the six changes, at params'
|
||||
// paranoia level, without the rules it switches off, and reading request
|
||||
// bodies up to params' limit.
|
||||
// New returns the Core Rule Set with the seven changes, at params'
|
||||
// paranoia level, without the rules it switches off, reading request
|
||||
// bodies up to params' limit, and with params' exclusions file loaded
|
||||
// last. An exclusions file that cannot be read, does not parse or names
|
||||
// an id of smallwebwaf's own rules is an error.
|
||||
func New(params Params) (*CoreRuleSet, error) {
|
||||
body := ""
|
||||
if params.BodyLimit > 0 {
|
||||
@@ -189,9 +214,23 @@ func New(params Params) (*CoreRuleSet, error) {
|
||||
text += "SecRuleRemoveById " + strings.Join(ids, " ") + "\n"
|
||||
}
|
||||
|
||||
waf, err := coraza.NewWAF(coraza.NewWAFConfig().
|
||||
WithRootFS(coreruleset.FS).
|
||||
WithDirectives(text))
|
||||
config := coraza.NewWAFConfig().WithRootFS(coreruleset.FS).WithDirectives(text)
|
||||
|
||||
if params.ExclusionsFile != "" {
|
||||
exclusions, err := os.ReadFile(params.ExclusionsFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read the exclusions file: %w", err)
|
||||
}
|
||||
|
||||
if id := smallwebwafRuleID.Find(exclusions); id != nil {
|
||||
return nil, fmt.Errorf("the exclusions file names %s: %w", id,
|
||||
errSmallwebwafRuleID)
|
||||
}
|
||||
|
||||
config = config.WithDirectives(string(exclusions))
|
||||
}
|
||||
|
||||
waf, err := coraza.NewWAF(config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load the Core Rule Set: %w", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user