Remove everything specific to a Gitea deployment (closes #209)
check / check (push) Successful in 3m20s
check / check (push) Successful in 3m20s
Delete the Gitea compose setup, its document, its two scripts and their make targets. Drop the Core Rule Set exclusions written for Gitea: the ten query parameters skipped by rules 930120, 932160 and 932260, and the gitea_flash and redirect_to cookies. The remaining changes are renumbered, five in all. Documents and tests use neutral examples. Model: opus-5-5
This commit is contained in:
1 parent
f8676debda
commit
1c70104677
22 files changed
+142
-1419
No files matched your search
@@ -81,7 +81,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 five changes listed under
|
||||
"Attack detection", as Coraza directives.
|
||||
- `internal/lookup`: looks clients up through GeoJS or in the lookup
|
||||
database.
|
||||
@@ -402,10 +402,10 @@ scrapers that send many requests slowly over hours or days. The hour limit is
|
||||
the main one.
|
||||
|
||||
- Per minute: 120, 2 requests a second. It leaves room for a person opening two
|
||||
pages at once with an empty cache. A gitea page opened with an empty cache
|
||||
pages at once with an empty cache. A web app's page opened with an empty cache
|
||||
loads 20 to 60 files, and a handful once cached.
|
||||
- Per hour: 1,500. A person reading code in gitea for a whole hour, a page every
|
||||
20 seconds, makes about 900 requests. A scraper making one request every 2
|
||||
- Per hour: 1,500. A person reading a site for a whole hour, a page every 20
|
||||
seconds, makes about 900 requests. A scraper making one request every 2
|
||||
seconds makes 1,800 an hour, more than the limit.
|
||||
- Per day: 6,000, four hours at the hour limit. A scraper making one request
|
||||
every 10 seconds makes 8,640 a day, more than the limit.
|
||||
@@ -417,7 +417,7 @@ the blocklists. An abuser that trips none of them is not caught.
|
||||
|
||||
Some legitimate clients pass the limits:
|
||||
|
||||
- An Actions runner asks gitea for work all day and can pass the limits.
|
||||
- A CI runner asks its code forge for work all day and can pass the limits.
|
||||
- Everyone behind one shared address, such as an office, shares one client's
|
||||
limits.
|
||||
|
||||
@@ -550,12 +550,11 @@ service's abuse comes from.
|
||||
By default the Core Rule Set reads the method, the URL with its query string,
|
||||
and the headers of every request. That is where automated attacks show:
|
||||
injection in query strings, path traversal, attacks carried in headers,
|
||||
scanners' user agents. It reads no request body. In front of a code forge such
|
||||
as gitea, bodies carry what people write and publish, such as issue text and
|
||||
files saved in the web editor. The Core Rule Set takes the shell commands and
|
||||
code in them for attacks. The default gives up refusing an attack carried in a
|
||||
body. The rule files, the limits and the bans still apply to the client that
|
||||
sends it.
|
||||
scanners' user agents. It reads no request body. In front of a code forge,
|
||||
bodies carry what people write and publish, such as issue text and files saved
|
||||
in the web editor. The Core Rule Set takes the shell commands and code in them
|
||||
for attacks. The default gives up refusing an attack carried in a body. The rule
|
||||
files, the limits and the bans still apply to the client that sends it.
|
||||
|
||||
- The Core Rule Set skips a path under `SWWAF_WAF_EXEMPT_PATHS`.
|
||||
- `SWWAF_WAF_PARANOIA_LEVEL` and `SWWAF_WAF_ANOMALY_THRESHOLD` are the Core Rule
|
||||
@@ -591,15 +590,14 @@ sends it.
|
||||
data, where binary content such as a git push trips rules written for text. A
|
||||
body the Core Rule Set cannot parse adds 5 to the score, and so does a
|
||||
multipart body cut by the limit in the middle of a part's header. Body
|
||||
inspection suits apps whose forms carry no code; `docs/gitea.md` records what
|
||||
it refuses in front of gitea.
|
||||
inspection suits apps whose forms carry no code.
|
||||
- `SWWAF_TRAP_PATHS` names paths the app never serves and only scanners ask for,
|
||||
such as `/wp-login.php` in front of gitea. This is the short form of a `path`
|
||||
rule with the `ban` action, for deployments that mount no rule files. A trap
|
||||
path is matched whole, without the query, against the path as sent and once
|
||||
percent-decoded: `/wp-login.php` matches `/wp-login.php?x=1` and
|
||||
`/%77p-login.php`, and does not match `/wp-login.php/` or
|
||||
`/blog/wp-login.php`.
|
||||
such as `/wp-login.php` in front of an app that is not WordPress. This is the
|
||||
short form of a `path` rule with the `ban` action, for deployments that mount
|
||||
no rule files. A trap path is matched whole, without the query, against the
|
||||
path as sent and once percent-decoded: `/wp-login.php` matches
|
||||
`/wp-login.php?x=1` and `/%77p-login.php`, and does not match `/wp-login.php/`
|
||||
or `/blog/wp-login.php`.
|
||||
- `SWWAF_ERROR_BURST_THRESHOLD` counts a client's refusals per minute of these
|
||||
kinds, and of no other:
|
||||
- a rule file match;
|
||||
@@ -609,12 +607,12 @@ sends it.
|
||||
More than this breaks a limit (see "Bans"). A client trying one attack after
|
||||
another is refused many times a minute; a person rarely more than a few
|
||||
times. Answers the app gives are not counted, since they do not tell a
|
||||
scanner from an ordinary client. In front of gitea, registry and git clients
|
||||
get `404` and `401` by design, hundreds of times a minute.
|
||||
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
|
||||
gitea it would otherwise refuse ordinary requests. The changes hold in front of
|
||||
every app, and no setting undoes them:
|
||||
`smallwebwaf` changes the Core Rule Set 4.25.0 in five ways, since in front of a
|
||||
code forge it would otherwise refuse ordinary requests. The changes hold in
|
||||
front of every app, and no setting undoes them:
|
||||
|
||||
1. `PUT`, `PATCH` and `DELETE` are allowed methods besides `GET`, `HEAD`, `POST`
|
||||
and `OPTIONS`. APIs, container image pushes and package uploads use them.
|
||||
@@ -627,52 +625,20 @@ every app, and no setting undoes them:
|
||||
body cannot be inspected. The other headers the Core Rule Set refuses, such
|
||||
as `Proxy`, stay refused.
|
||||
3. The `redirect_uri` parameter is not checked for a URL naming an IP address or
|
||||
`localhost` (931100, 934110). Git Credential Manager, git-credential-oauth
|
||||
and tea, which gitea registers for OAuth sign-in, ask to be sent back to
|
||||
`http://127.0.0.1` on the user's own machine. The server never fetches that
|
||||
address.
|
||||
4. Ten query parameters are not checked against the Core Rule Set's lists of
|
||||
system files (930120), shell paths (932160) and command names (932260):
|
||||
- `path`, `files`, `skip-to` and `sub_path`;
|
||||
- `ref`, `sha`, `branch` and `workflow`;
|
||||
- `artifactName` and `redirect_to`.
|
||||
|
||||
gitea puts into them file paths, branch and workflow names, and the page to
|
||||
return to after signing in. It reads these values as names in a repository
|
||||
or in its own records, or as a page of its own site, never as a command. Any
|
||||
name on those lists can be an ordinary file or branch, such as `.gitignore`
|
||||
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.
|
||||
|
||||
5. Two cookies are left out, and `Referer` keeps fewer rules:
|
||||
- The Core Rule Set reads the request without the `gitea_flash` and
|
||||
`redirect_to` cookies. gitea puts a message naming what was just done into
|
||||
`gitea_flash`. It puts into `redirect_to` the page to return to after
|
||||
signing in. The Core Rule Set takes file names in them, such as
|
||||
`package.json`, for attacks. A browser sends such a cookie with every
|
||||
request until gitea replaces it. gitea cannot replace it while those
|
||||
requests are refused, so one refusal would keep the browser out of the
|
||||
whole site. gitea shows the message with any script removed and returns
|
||||
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.
|
||||
6. Responses are not inspected. A raw file from a repository, such as a shell
|
||||
`localhost` (931100, 934110). OAuth clients that run on the user's own
|
||||
machine, such as Git Credential Manager, ask to be sent back to
|
||||
`http://127.0.0.1` there. The server never fetches that address. The
|
||||
parameter's name is matched without regard to case, as Coraza matches it.
|
||||
4. `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.
|
||||
5. 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.
|
||||
|
||||
The parameter and cookie names in these changes are matched without regard to
|
||||
case, as Coraza matches them.
|
||||
|
||||
### Ban lengths, response and scope
|
||||
|
||||
`SWWAF_BAN_RESPONSE` is `403` by default, which makes a ban easy to recognise
|
||||
@@ -836,7 +802,7 @@ and the default file.
|
||||
runs. It reads the files again once the directory has had no change for 2
|
||||
seconds, so that a file still being written is read whole. A file whose name
|
||||
starts with `.`, such as an editor's lock file, is not a rule file.
|
||||
- Files are read in name order (`00-default.rules` before `50-gitea.rules`),
|
||||
- Files are read in name order (`00-default.rules` before `50-site.rules`),
|
||||
rules in line order.
|
||||
- A rule's regex is Go's RE2 syntax. It has no backreferences or lookaround; in
|
||||
exchange matching time is linear in the input, so no rule can be made to stall
|
||||
@@ -865,14 +831,15 @@ and the default file.
|
||||
|
||||
- The image ships a default file, kept short and limited to patterns that are
|
||||
wrong for every app, with its path rules anchored at the site root. In front
|
||||
of gitea, `/.env` is a probe, while
|
||||
of a code forge, `/.env` is a probe, while
|
||||
`/<owner>/<repo>/src/branch/main/.env.example` is a file in a repository that
|
||||
any visitor may open. Anchor the paths of your own `ban` rules at the site
|
||||
root too. Anything else, such as a rule banning WordPress probes in front of
|
||||
gitea, belongs in a file the deployer adds to the image `servewaf` runs from,
|
||||
or mounts. A rule has one target, so a `path` rule applies on every site
|
||||
behind that `servewaf`. A directory mounted over `SWWAF_RULES_DIR` replaces
|
||||
the default file; single files mounted into it add to it.
|
||||
an app that is not WordPress, belongs in a file the deployer adds to the image
|
||||
`servewaf` runs from, or mounts. A rule has one target, so a `path` rule
|
||||
applies on every site behind that `servewaf`. A directory mounted over
|
||||
`SWWAF_RULES_DIR` replaces the default file; single files mounted into it add
|
||||
to it.
|
||||
- Rules meant to be edited while `servewaf` runs belong in a mounted directory,
|
||||
not a mounted file. Many editors save a file by replacing it, and a container
|
||||
keeps seeing the old one when the file alone is mounted. The lookup database
|
||||
@@ -1695,8 +1662,7 @@ with no change to upaas. upaas builds the app's Dockerfile and runs the one
|
||||
container as it runs any app. The traefik labels route to port 8080, and the
|
||||
`SWRP_` settings go with the app's environment variables. `servewaf` runs in a
|
||||
container of its own, on a network the app's container can reach, with its state
|
||||
volume and the directory of any token file. `docs/gitea.md` records what the
|
||||
defaults do in front of gitea, and `script/gitea-requests` checks those notes.
|
||||
volume and the directory of any token file.
|
||||
|
||||
## Planned, not built
|
||||
|
||||
|
||||
Reference in new issue
Block a user