diff --git a/README.md b/README.md index bf6cdb6..0db0736 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`. | @@ -636,10 +637,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 875ba19..973d185 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. @@ -583,6 +585,24 @@ 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 puts its own file of + exclusions that run before the rules in the same place. + - 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. + - `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 @@ -612,7 +632,7 @@ sends it. scanner from an ordinary client. In front of gitea, 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: @@ -644,9 +664,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 @@ -660,15 +681,47 @@ 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, such as a search for `env`, + OpenJDK's `Runtime.java`, whose path holds both `runtime` and `java.`, or + a file named `java.lang.String.md`. All of those requests would be + refused. `Referer` keeps the other rules, except after a gitea search (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 own paths, the fields where gitea sends a search, a file name or a + branch name skip the rules that refused ordinary gitea requests there. + `internal/waf/gitea.conf` lists each rule and the request it refused. The + fields are: + - `q` on gitea's search pages, for code, issues, pull requests, commits, + repositories, branches, tags, packages and users. A code search holds + code, such as `