diff --git a/README.md b/README.md index 940aebf..9aca07b 100644 --- a/README.md +++ b/README.md @@ -192,9 +192,9 @@ account, a key, a token or an alert destination stays off until you give one. - Any `SWWAF_` setting can be given as a file instead, for secrets and long lists: `SWWAF_ADMIN_TOKEN_FILE` names the file that holds `SWWAF_ADMIN_TOKEN`. The file's contents are the value, less one newline at the end. Setting both - forms stops the start. `SWWAF_LOG_REMOTE_TLS_CA_FILE` already names a file, so - it has no `_FILE` form, and no `SWRP_` setting has one, since `rproxy` never - touches disk. + forms stops the start. `SWWAF_LOG_REMOTE_TLS_CA_FILE` and + `SWWAF_CORE_RULE_SET_EXCLUSIONS_FILE` already name a file, so they have no + `_FILE` form. No `SWRP_` setting has one, since `rproxy` never touches disk. - A duration is Go's syntax, with `d` for days: `90s`, `15m`, `24h`, `7d`. - A size is a number of bytes with an optional `K`, `M` or `G`, in powers of 1024: `512K`, `100M`, `5G`. @@ -272,6 +272,7 @@ Every setting the code reads, with its default: | `SWWAF_WAF_DISABLED_RULES` | `920340,920420,920440,920640,930130,930140` | Core Rule Set rule ids switched off; a list you give replaces the default, so repeat these six to keep them off. | | `SWWAF_WAF_EXEMPT_PATHS` | empty | Path prefixes the Core Rule Set does not inspect. | | `SWWAF_WAF_BODY_LIMIT` | `off` | How much of a request body the Core Rule Set reads, a size up to `64K`; `off` reads none. | +| `SWWAF_CORE_RULE_SET_EXCLUSIONS_FILE` | unset | A file of Core Rule Set exclusions, as Coraza directives, that switch rules off for some fields or paths only. | | `SWWAF_BLOCKLIST_URLS` | empty | Blocklists to fetch, text files of addresses and netblocks one to a line, such as `https://www.spamhaus.org/drop/drop.txt`. | | `SWWAF_BLOCKLIST_REFRESH` | `24h` | How long after a list was last tried it is fetched again, `1h` or more. | | `SWWAF_BLOCKLIST_ACTION` | `deny` | What a client on a blocklist gets: `deny`, `limit:` of every limit, or `log`. | @@ -553,10 +554,13 @@ and the status for a size or time limit, such as `413` for a body too large. headers of every request, and no body unless `SWWAF_WAF_BODY_LIMIT` is set. In front of a code forge, bodies carry what people write and publish, which the rules take for attacks. -- `smallwebwaf` changes the Core Rule Set in six ways so that it lets ordinary +- `smallwebwaf` changes the Core Rule Set in eight ways so that it lets ordinary requests to a code forge through, such as git over HTTP and sign-in through - Git Credential Manager. No setting undoes them; "Attack detection" in - [`SPEC.md`](SPEC.md) lists them. + Git Credential Manager. On gitea's own paths, its searches, file names and + branch names get through too. No setting undoes the changes; "Attack + detection" in [`SPEC.md`](SPEC.md) lists them. +- `SWWAF_CORE_RULE_SET_EXCLUSIONS_FILE` switches single rules off for an app's + own fields or paths. - Rule files are plain text, one rule to a line, edited by hand and read again while `servewaf` runs. The image ships one file of probes no real visitor sends (see [`docs/rules.md`](docs/rules.md)). diff --git a/SPEC.md b/SPEC.md index 524962a..9003ed4 100644 --- a/SPEC.md +++ b/SPEC.md @@ -36,7 +36,8 @@ to them. - the state files; - a lookup database; - a `SWWAF_` setting given as a file; - - the file `SWWAF_LOG_REMOTE_TLS_CA_FILE` names. + - the file `SWWAF_LOG_REMOTE_TLS_CA_FILE` names; + - the file `SWWAF_CORE_RULE_SET_EXCLUSIONS_FILE` names. ## Non-goals @@ -81,7 +82,7 @@ to them. of bans is full. - `internal/rules`: reads the rule files at start and again when they change, and tells which rules a request matches. - - `internal/waf`: the Core Rule Set, with the six changes listed under + - `internal/waf`: the Core Rule Set, with the eight changes listed under "Attack detection", as Coraza directives. - `internal/lookup`: looks clients up through GeoJS or in the lookup database. @@ -325,9 +326,10 @@ Conventions: lists: `SWWAF_ADMIN_TOKEN_FILE` names the file that holds `SWWAF_ADMIN_TOKEN`. The file's contents are the value, less one newline at the end, and are checked as the value itself would be. Setting both forms stops the start, and - so does naming a file that cannot be read. `SWWAF_LOG_REMOTE_TLS_CA_FILE` - already holds a file name, so it has no `_FILE` form. No `SWRP_` setting has - one, since `rproxy` never touches disk. + so does naming a file that cannot be read. `SWWAF_LOG_REMOTE_TLS_CA_FILE` and + `SWWAF_CORE_RULE_SET_EXCLUSIONS_FILE` already hold a file name, so they have + no `_FILE` form. No `SWRP_` setting has one, since `rproxy` never touches + disk. - Settings, files included, are read once at start. Changing one means restarting the container. The files `servewaf` watches while it runs are its state files, its rule files and the lookup database. @@ -582,6 +584,27 @@ files, the limits and the bans still apply to the client that sends it. probes these rules caught (see "Rule files"). A list you give replaces the default, so name these six ids in it to keep them off. +- `SWWAF_CORE_RULE_SET_EXCLUSIONS_FILE` names a file of exclusions, written as + Coraza directives. An exclusion switches one rule off for some fields or paths + only, where `SWWAF_WAF_DISABLED_RULES` switches it off everywhere. The file + holds an app's own exclusions; the eighth change below holds gitea's. + - `servewaf` loads the file after `smallwebwaf`'s own setup rules and before + the Core Rule Set's rules. The Core Rule Set loads its own + `REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf` at the same point. + - So the file holds only `SecAction` and `SecRule` lines that exclude with + `ctl:ruleRemoveTargetById` or `ctl:ruleRemoveById`. These run with each + request, before the rules they exclude, those of phase 1 included. + - A rule that `SWWAF_WAF_DISABLED_RULES` switches off and the file names is + simply off. + - The start stops on a file that cannot be read or does not parse, whatever + `SWWAF_WAF_MODE` is. + - It stops too on a file that names a number from 900000 to 900999, the ids + of `smallwebwaf`'s own rules, alone or in a range. Comment lines are not + checked. + - It stops too on a directive that removes or changes rules as the file + loads, such as `SecRuleRemoveById`, since none of the Core Rule Set's + rules is loaded yet. + - `SWWAF_WAF_BODY_LIMIT` switches body inspection on, up to that size. It takes at most `64K`, since `servewaf` gets no more of a body than that (see "The answer"). Form data and multipart bodies are read up to the size. JSON and XML @@ -610,7 +633,7 @@ files, the limits and the bans still apply to the client that sends it. scanner from an ordinary client. In front of a code forge, registry and git clients get `404` and `401` by design, hundreds of times a minute. -`smallwebwaf` changes the Core Rule Set 4.25.0 in six ways, since in front of +`smallwebwaf` changes the Core Rule Set 4.25.0 in eight ways, since in front of gitea it would otherwise refuse ordinary requests. The changes hold in front of every app, and no setting undoes them: @@ -642,9 +665,10 @@ every app, and no setting undoes them: or `docker-build`. In these parameters, what only these three rules would catch reaches the app, such as the command `|cat /etc/passwd`. Path traversal, SQL and script injection, and PHP, Java and Node.js code are - still refused there. Every other parameter keeps all three rules. An app - that uses one of these parameters as a file on the server, or passes it to a - shell, must check the value itself. A rule file can refuse it instead. + still refused there. Other parameters keep all three rules, except on the + gitea paths the eighth change names. An app that uses one of these + parameters as a file on the server, or passes it to a shell, must check the + value itself. A rule file can refuse it instead. 5. Two cookies are left out, and `Referer` keeps fewer rules: - The Core Rule Set reads the request without the `gitea_flash` and @@ -658,15 +682,83 @@ every app, and no setting undoes them: only to its own pages. Every other cookie is read in full. An app that reads a cookie of either name must check it itself. - `Referer` is not checked for a Unix command given without arguments - (932340) or for Java starting a process (944110). `Referer` names the page - a request comes from, on everything that page loads and on every link - followed from it. The Core Rule Set takes some page addresses for attacks, - such as a search for `env`, or OpenJDK's `Runtime.java`, whose path holds - both `runtime` and `java.`. All of those requests would be refused. - `Referer` keeps the other rules. An app that passes `Referer` to a shell - must check it itself. + (932340), for Java starting a process (944110) or for a Java class name + (944130). `Referer` names the page a request comes from, on everything + that page loads and on every link followed from it. The Core Rule Set + takes some page addresses for attacks: + - a search for `env`; + - OpenJDK's `Runtime.java`, whose path holds both `runtime` and `java.`; + - a file named `java.lang.String.md`. + + Every request from such a page would be refused. `Referer` keeps the + other rules, except after some gitea searches (the eighth change). An + app that passes `Referer` to a shell must check it itself. + 6. Responses are not inspected. A raw file from a repository, such as a shell script, looks to the response rules like source code leaking from the server. +7. The `from` parameter of a container registry's upload path, + `/v2//blobs/uploads/`, is not checked for command names (932260). When + a push reuses a layer, `from` names the image the layer comes from. An image + such as `owner/python3-app` would have that push refused. +8. On gitea's paths, some fields skip the rules that refused ordinary gitea + requests there. They are the fields where gitea sends a search, a file name + or a branch name. `internal/waf/gitea.conf` lists each rule and the request + it refused. The fields are: + - `q` on gitea's searches of code, of issues and pull requests, and of + commits. A code search holds code, such as `