Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
866b948ec0 |
No files matched your search
@@ -272,7 +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_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_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_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_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; `deploy/gitea/crs-exclusions.conf` is gitea's. |
|
||||||
| `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_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_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:<percent>` of every limit, or `log`. |
|
| `SWWAF_BLOCKLIST_ACTION` | `deny` | What a client on a blocklist gets: `deny`, `limit:<percent>` of every limit, or `log`. |
|
||||||
@@ -637,13 +637,14 @@ 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
|
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
|
front of a code forge, bodies carry what people write and publish, which the
|
||||||
rules take for attacks.
|
rules take for attacks.
|
||||||
- `smallwebwaf` changes the Core Rule Set in eight ways so that it lets ordinary
|
- `smallwebwaf` changes the Core Rule Set in seven ways so that it lets ordinary
|
||||||
requests to a code forge through, such as git over HTTP and sign-in through
|
requests to a code forge through, such as git over HTTP and sign-in through
|
||||||
Git Credential Manager. On gitea's own paths, its searches, file names and
|
Git Credential Manager. No setting undoes them; "Attack detection" in
|
||||||
branch names get through too. No setting undoes the changes; "Attack
|
[`SPEC.md`](SPEC.md) lists them.
|
||||||
detection" in [`SPEC.md`](SPEC.md) lists them.
|
|
||||||
- `SWWAF_CORE_RULE_SET_EXCLUSIONS_FILE` switches single rules off for an app's
|
- `SWWAF_CORE_RULE_SET_EXCLUSIONS_FILE` switches single rules off for an app's
|
||||||
own fields or paths.
|
own fields or paths. gitea's exclusions, in
|
||||||
|
`deploy/gitea/crs-exclusions.conf`, let its searches, file names and branch
|
||||||
|
names through.
|
||||||
- Rule files are plain text, one rule to a line, edited by hand and read again
|
- 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
|
while `servewaf` runs. The image ships one file of probes no real visitor
|
||||||
sends (see [`docs/rules.md`](docs/rules.md)).
|
sends (see [`docs/rules.md`](docs/rules.md)).
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ to them.
|
|||||||
of bans is full.
|
of bans is full.
|
||||||
- `internal/rules`: reads the rule files at start and again when they
|
- `internal/rules`: reads the rule files at start and again when they
|
||||||
change, and tells which rules a request matches.
|
change, and tells which rules a request matches.
|
||||||
- `internal/waf`: the Core Rule Set, with the eight changes listed under
|
- `internal/waf`: the Core Rule Set, with the seven changes listed under
|
||||||
"Attack detection", as Coraza directives.
|
"Attack detection", as Coraza directives.
|
||||||
- `internal/lookup`: looks clients up through GeoJS or in the lookup
|
- `internal/lookup`: looks clients up through GeoJS or in the lookup
|
||||||
database.
|
database.
|
||||||
@@ -587,8 +587,8 @@ sends it.
|
|||||||
|
|
||||||
- `SWWAF_CORE_RULE_SET_EXCLUSIONS_FILE` names a file of exclusions, written as
|
- `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
|
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
|
only, where `SWWAF_WAF_DISABLED_RULES` switches it off everywhere.
|
||||||
holds an app's own exclusions; the eighth change below holds gitea's.
|
`deploy/gitea/crs-exclusions.conf` holds gitea's.
|
||||||
- `servewaf` loads the file after `smallwebwaf`'s own setup rules and before
|
- `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
|
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.
|
exclusions that run before the rules in the same place.
|
||||||
@@ -632,7 +632,7 @@ sends it.
|
|||||||
scanner from an ordinary client. In front of gitea, registry and git clients
|
scanner from an ordinary client. In front of gitea, registry and git clients
|
||||||
get `404` and `401` by design, hundreds of times a minute.
|
get `404` and `401` by design, hundreds of times a minute.
|
||||||
|
|
||||||
`smallwebwaf` changes the Core Rule Set 4.25.0 in eight ways, since in front of
|
`smallwebwaf` changes the Core Rule Set 4.25.0 in seven ways, since in front of
|
||||||
gitea it would otherwise refuse ordinary requests. The changes hold in front of
|
gitea it would otherwise refuse ordinary requests. The changes hold in front of
|
||||||
every app, and no setting undoes them:
|
every app, and no setting undoes them:
|
||||||
|
|
||||||
@@ -664,10 +664,9 @@ every app, and no setting undoes them:
|
|||||||
or `docker-build`. In these parameters, what only these three rules would
|
or `docker-build`. In these parameters, what only these three rules would
|
||||||
catch reaches the app, such as the command `|cat /etc/passwd`. Path
|
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
|
traversal, SQL and script injection, and PHP, Java and Node.js code are
|
||||||
still refused there. Other parameters keep all three rules, except on the
|
still refused there. Every other parameter keeps all three rules. An app
|
||||||
gitea paths the eighth change names. An app that uses one of these
|
that uses one of these parameters as a file on the server, or passes it to a
|
||||||
parameters as a file on the server, or passes it to a shell, must check the
|
shell, must check the value itself. A rule file can refuse it instead.
|
||||||
value itself. A rule file can refuse it instead.
|
|
||||||
|
|
||||||
5. Two cookies are left out, and `Referer` keeps fewer rules:
|
5. Two cookies are left out, and `Referer` keeps fewer rules:
|
||||||
- The Core Rule Set reads the request without the `gitea_flash` and
|
- The Core Rule Set reads the request without the `gitea_flash` and
|
||||||
@@ -681,47 +680,19 @@ every app, and no setting undoes them:
|
|||||||
only to its own pages. Every other cookie is read in full. An app that
|
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.
|
reads a cookie of either name must check it itself.
|
||||||
- `Referer` is not checked for a Unix command given without arguments
|
- `Referer` is not checked for a Unix command given without arguments
|
||||||
(932340), for Java starting a process (944110) or for a Java class name
|
(932340) or for Java starting a process (944110). `Referer` names the page
|
||||||
(944130). `Referer` names the page a request comes from, on everything
|
a request comes from, on everything that page loads and on every link
|
||||||
that page loads and on every link followed from it. The Core Rule Set
|
followed from it. The Core Rule Set takes some page addresses for attacks,
|
||||||
takes some page addresses for attacks, such as a search for `env`,
|
such as a search for `env`, or OpenJDK's `Runtime.java`, whose path holds
|
||||||
OpenJDK's `Runtime.java`, whose path holds both `runtime` and `java.`, or
|
both `runtime` and `java.`. All of those requests would be refused.
|
||||||
a file named `java.lang.String.md`. All of those requests would be
|
`Referer` keeps the other rules. An app that passes `Referer` to a shell
|
||||||
refused. `Referer` keeps the other rules, except after a gitea search (the
|
must check it itself.
|
||||||
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
|
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.
|
script, looks to the response rules like source code leaking from the server.
|
||||||
7. The `from` parameter of a container registry's upload path,
|
7. The `from` parameter of a container registry's upload path,
|
||||||
`/v2/<image>/blobs/uploads/`, is not checked for command names (932260). When
|
`/v2/<image>/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
|
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.
|
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 `<script>`, `SELECT * FROM users WHERE` or `../`.
|
|
||||||
- `Referer` of a page opened from such a search, for script and SQL
|
|
||||||
injection and Java code (941110, 941120, 941160, 942100, 944100).
|
|
||||||
- The path of a file's page, raw file, download, blame, history, edit and
|
|
||||||
delete pages, and of the API's call for its contents. Names such as
|
|
||||||
`notes.txt~`, `process.env.d.ts` and `java.lang.String.md` were refused.
|
|
||||||
- A file name in a query parameter: `file` of a commit graph, `path` of the
|
|
||||||
API's list of commits, `body` of the API's call for several files,
|
|
||||||
`template` of a new issue, `redirect_to` of the sign-in page, and `name`
|
|
||||||
of an attachment uploaded through the API.
|
|
||||||
- A branch or tag name in a query parameter, for command names (932260).
|
|
||||||
gitea sends one to delete or restore a branch, to start a release from a
|
|
||||||
tag, to edit a branch protection rule, and to list a directory's last
|
|
||||||
commits.
|
|
||||||
- The `Content-Range` header of an artifact upload from
|
|
||||||
`actions/upload-artifact@v3` (920450).
|
|
||||||
|
|
||||||
Elsewhere these fields keep every rule. The paths are those of gitea at the
|
|
||||||
site root. An app other than gitea that serves one of them must check these
|
|
||||||
fields itself.
|
|
||||||
|
|
||||||
The parameter and cookie names in these changes are matched without regard to
|
The parameter and cookie names in these changes are matched without regard to
|
||||||
case, as Coraza matches them.
|
case, as Coraza matches them.
|
||||||
|
|||||||
@@ -104,11 +104,12 @@ services:
|
|||||||
# address of an automation fleet on another host here too; without
|
# address of an automation fleet on another host here too; without
|
||||||
# that, it is counted like any client.
|
# that, it is counted like any client.
|
||||||
SWWAF_ALLOW_NETS: 10.99.0.128/25,fd10:99:0:0:8000::/65
|
SWWAF_ALLOW_NETS: 10.99.0.128/25,fd10:99:0:0:8000::/65
|
||||||
# In block mode the Core Rule Set refuses everyday Gitea searches
|
# The Core Rule Set's exclusions for Gitea's searches, file names
|
||||||
# and file names, and 31 refusals in a minute ban the address.
|
# and branch names.
|
||||||
SWWAF_WAF_MODE: detect
|
SWWAF_CORE_RULE_SET_EXCLUSIONS_FILE: /etc/smallwebwaf/crs-exclusions.conf
|
||||||
volumes:
|
volumes:
|
||||||
- servewaf-state:/var/lib/smallwebwaf
|
- servewaf-state:/var/lib/smallwebwaf
|
||||||
|
- ./crs-exclusions.conf:/etc/smallwebwaf/crs-exclusions.conf:ro
|
||||||
networks:
|
networks:
|
||||||
- shared
|
- shared
|
||||||
logging: *logging
|
logging: *logging
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
# The Core Rule Set's exclusions for Gitea, which servewaf loads from
|
||||||
|
# SWWAF_CORE_RULE_SET_EXCLUSIONS_FILE. servewaf loads this file before the Core
|
||||||
|
# Rule Set's rules, so it holds only exclusions made while a request is checked:
|
||||||
|
# SecAction and SecRule lines with ctl:ruleRemoveTargetById or
|
||||||
|
# ctl:ruleRemoveById. Each switches one rule off for the Gitea fields or paths
|
||||||
|
# where it refused ordinary Gitea requests. A SecAction applies on every path, a
|
||||||
|
# SecRule on the paths it matches. Gitea sends these in the query string:
|
||||||
|
# - q: the text of every search;
|
||||||
|
# - file, path, template, body and redirect_to: a file name, alone or in
|
||||||
|
# JSON or a link;
|
||||||
|
# - name, tag, rule_name and refSubUrl: a branch or tag name.
|
||||||
|
# docs/gitea.md says how to add an exclusion.
|
||||||
|
|
||||||
|
# Searches.
|
||||||
|
# 930110: a search for ../
|
||||||
|
SecAction "id:10001,phase:1,pass,nolog,ctl:ruleRemoveTargetById=930110;ARGS:q"
|
||||||
|
# 931100: a search for http://127.0.0.1:8080
|
||||||
|
SecAction "id:10002,phase:1,pass,nolog,ctl:ruleRemoveTargetById=931100;ARGS:q"
|
||||||
|
# 932140: a search for if (a == b) {
|
||||||
|
SecAction "id:10003,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932140;ARGS:q"
|
||||||
|
# 932235: a search for `make check`
|
||||||
|
SecAction "id:10004,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932235;ARGS:q"
|
||||||
|
# 932250: a search for ls -la
|
||||||
|
SecAction "id:10005,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932250;ARGS:q"
|
||||||
|
# 932380: a search for `whoami`
|
||||||
|
SecAction "id:10006,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932380;ARGS:q"
|
||||||
|
# 933100: a search for <?php echo
|
||||||
|
SecAction "id:10007,phase:1,pass,nolog,ctl:ruleRemoveTargetById=933100;ARGS:q"
|
||||||
|
# 933130: a search for $_GET[
|
||||||
|
SecAction "id:10008,phase:1,pass,nolog,ctl:ruleRemoveTargetById=933130;ARGS:q"
|
||||||
|
# 933135: a search for ${VAR}
|
||||||
|
SecAction "id:10009,phase:1,pass,nolog,ctl:ruleRemoveTargetById=933135;ARGS:q"
|
||||||
|
# 934110: a search for http://127.0.0.1:8080
|
||||||
|
SecAction "id:10010,phase:1,pass,nolog,ctl:ruleRemoveTargetById=934110;ARGS:q"
|
||||||
|
# 941100: a search for <script>
|
||||||
|
SecAction "id:10011,phase:1,pass,nolog,ctl:ruleRemoveTargetById=941100;ARGS:q"
|
||||||
|
# 941110: a search for <script>, or a page opened from its results
|
||||||
|
SecAction "id:10012,phase:1,pass,nolog,\
|
||||||
|
ctl:ruleRemoveTargetById=941110;ARGS:q,\
|
||||||
|
ctl:ruleRemoveTargetById=941110;REQUEST_HEADERS:Referer"
|
||||||
|
# 941120: a page opened from the results of a search for onClick={
|
||||||
|
SecAction "id:10013,phase:1,pass,nolog,\
|
||||||
|
ctl:ruleRemoveTargetById=941120;REQUEST_HEADERS:Referer"
|
||||||
|
# 941160: a search for <img src=, or a page opened from its results
|
||||||
|
SecAction "id:10014,phase:1,pass,nolog,\
|
||||||
|
ctl:ruleRemoveTargetById=941160;ARGS:q,\
|
||||||
|
ctl:ruleRemoveTargetById=941160;REQUEST_HEADERS:Referer"
|
||||||
|
# 941390: a search for fetch(
|
||||||
|
SecAction "id:10015,phase:1,pass,nolog,ctl:ruleRemoveTargetById=941390;ARGS:q"
|
||||||
|
# 942100: a search for SELECT * FROM users WHERE, or a page opened from the
|
||||||
|
# results of a search for LIKE '%foo%'
|
||||||
|
SecAction "id:10016,phase:1,pass,nolog,\
|
||||||
|
ctl:ruleRemoveTargetById=942100;ARGS:q,\
|
||||||
|
ctl:ruleRemoveTargetById=942100;REQUEST_HEADERS:Referer"
|
||||||
|
# 942190: a search for union select
|
||||||
|
SecAction "id:10017,phase:1,pass,nolog,ctl:ruleRemoveTargetById=942190;ARGS:q"
|
||||||
|
# 942230: a search for if (a == b) {
|
||||||
|
SecAction "id:10018,phase:1,pass,nolog,ctl:ruleRemoveTargetById=942230;ARGS:q"
|
||||||
|
# 944100: a search for java.lang.Runtime, or a page opened from its results
|
||||||
|
SecAction "id:10019,phase:1,pass,nolog,\
|
||||||
|
ctl:ruleRemoveTargetById=944100;ARGS:q,\
|
||||||
|
ctl:ruleRemoveTargetById=944100;REQUEST_HEADERS:Referer"
|
||||||
|
|
||||||
|
# Searches, and file, branch and tag names.
|
||||||
|
# 930120: a search for package.json, or the commit graph of .gitignore
|
||||||
|
SecAction "id:10020,phase:1,pass,nolog,ctl:ruleRemoveTargetById=930120;\
|
||||||
|
ARGS:/^(?:q|file|template|body|refsuburl|name|tag|rule_name)$/"
|
||||||
|
# 932160: a search for /bin/sh, or the API's contents of bin/docker-entrypoint
|
||||||
|
SecAction "id:10021,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932160;\
|
||||||
|
ARGS:/^(?:q|file|template|body|refsuburl|name|tag|rule_name)$/"
|
||||||
|
# 932260: a search for python3, or deleting the branch docker-build
|
||||||
|
SecAction "id:10022,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932260;\
|
||||||
|
ARGS:/^(?:q|file|template|body|refsuburl|name|tag|rule_name)$/"
|
||||||
|
|
||||||
|
# Searches, and file names in the query string.
|
||||||
|
# 932120: a search for Invoke-WebRequest, or the commits of install-script.sh
|
||||||
|
SecAction "id:10023,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932120;\
|
||||||
|
ARGS:/^(?:q|file|path|template|body|redirect_to)$/"
|
||||||
|
# 932130: a search for $(pwd), or an issue opened from the template [draft].md
|
||||||
|
SecAction "id:10024,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932130;\
|
||||||
|
ARGS:/^(?:q|file|path|template|body|redirect_to)$/"
|
||||||
|
# 934100: a search for process.env, or the commits of process.env.d.ts
|
||||||
|
SecAction "id:10025,phase:1,pass,nolog,ctl:ruleRemoveTargetById=934100;\
|
||||||
|
ARGS:/^(?:q|file|path|template|body|redirect_to)$/"
|
||||||
|
# 934130: a search for __proto__, or the commits of __proto__.json
|
||||||
|
SecAction "id:10026,phase:1,pass,nolog,ctl:ruleRemoveTargetById=934130;\
|
||||||
|
ARGS:/^(?:q|file|path|template|body|redirect_to)$/"
|
||||||
|
# 941130: the commits of index.xhtml
|
||||||
|
SecAction "id:10027,phase:1,pass,nolog,ctl:ruleRemoveTargetById=941130;\
|
||||||
|
ARGS:/^(?:file|path|template|body|redirect_to)$/"
|
||||||
|
# 941180: a search for document.cookie, or the commits of document.write.js
|
||||||
|
SecAction "id:10028,phase:1,pass,nolog,ctl:ruleRemoveTargetById=941180;\
|
||||||
|
ARGS:/^(?:q|file|path|template|body|redirect_to)$/"
|
||||||
|
# 942140: a search for information_schema, or the commits of
|
||||||
|
# information_schema.sql
|
||||||
|
SecAction "id:10029,phase:1,pass,nolog,ctl:ruleRemoveTargetById=942140;\
|
||||||
|
ARGS:/^(?:q|file|path|template|body|redirect_to)$/"
|
||||||
|
# 944110: a search for java.lang.Runtime, or the commits of Runtime.java
|
||||||
|
SecAction "id:10030,phase:1,pass,nolog,ctl:ruleRemoveTargetById=944110;\
|
||||||
|
ARGS:/^(?:q|file|path|template|body|redirect_to)$/"
|
||||||
|
# 944130: a search for java.lang.Runtime, the commits of java.lang.String.md,
|
||||||
|
# or any page opened from that file's page, which names it in the Referer
|
||||||
|
SecAction "id:10031,phase:1,pass,nolog,\
|
||||||
|
ctl:ruleRemoveTargetById=944130;\
|
||||||
|
ARGS:/^(?:q|file|path|template|body|redirect_to)$/,\
|
||||||
|
ctl:ruleRemoveTargetById=944130;REQUEST_HEADERS:Referer"
|
||||||
|
|
||||||
|
# File names in the path of a file's page, raw file, blame, history and edit
|
||||||
|
# page, and of the API's calls for its contents.
|
||||||
|
# 920500: the page of notes.txt~
|
||||||
|
SecRule REQUEST_FILENAME "@rx ^/(?:api/v1/repos/)?[^/]+/[^/]+/(?:src|raw|media|blame|commits|_edit|_delete|contents)/" \
|
||||||
|
"id:10032,phase:1,pass,nolog,ctl:ruleRemoveTargetById=920500;REQUEST_FILENAME"
|
||||||
|
# 934100: the page of process.env.d.ts
|
||||||
|
SecRule REQUEST_FILENAME "@rx ^/(?:api/v1/repos/)?[^/]+/[^/]+/(?:src|raw|media|blame|commits|_edit|_delete|contents)/" \
|
||||||
|
"id:10033,phase:1,pass,nolog,ctl:ruleRemoveTargetById=934100;REQUEST_FILENAME"
|
||||||
|
# 941130: the page of docs/a b.xhtml
|
||||||
|
SecRule REQUEST_FILENAME "@rx ^/(?:api/v1/repos/)?[^/]+/[^/]+/(?:src|raw|media|blame|commits|_edit|_delete|contents)/" \
|
||||||
|
"id:10034,phase:1,pass,nolog,ctl:ruleRemoveTargetById=941130;REQUEST_FILENAME"
|
||||||
|
# 944130: the page of java.lang.String.md
|
||||||
|
SecRule REQUEST_FILENAME "@rx ^/(?:api/v1/repos/)?[^/]+/[^/]+/(?:src|raw|media|blame|commits|_edit|_delete|contents)/" \
|
||||||
|
"id:10035,phase:1,pass,nolog,ctl:ruleRemoveTargetById=944130;REQUEST_FILENAME"
|
||||||
|
|
||||||
|
# The file name, in name, of an attachment uploaded through the API to a
|
||||||
|
# release, an issue or a comment.
|
||||||
|
# 932120: the attachment install-script.sh
|
||||||
|
SecRule REQUEST_FILENAME "@rx ^/api/v1/repos/[^/]+/[^/]+/(?:releases|issues|issues/comments)/[0-9]+/assets$" \
|
||||||
|
"id:10036,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932120;ARGS:name"
|
||||||
|
# 934100: the attachment process.env.d.ts
|
||||||
|
SecRule REQUEST_FILENAME "@rx ^/api/v1/repos/[^/]+/[^/]+/(?:releases|issues|issues/comments)/[0-9]+/assets$" \
|
||||||
|
"id:10037,phase:1,pass,nolog,ctl:ruleRemoveTargetById=934100;ARGS:name"
|
||||||
|
# 934130: the attachment __proto__.json
|
||||||
|
SecRule REQUEST_FILENAME "@rx ^/api/v1/repos/[^/]+/[^/]+/(?:releases|issues|issues/comments)/[0-9]+/assets$" \
|
||||||
|
"id:10038,phase:1,pass,nolog,ctl:ruleRemoveTargetById=934130;ARGS:name"
|
||||||
|
# 941130: the attachment index.xhtml
|
||||||
|
SecRule REQUEST_FILENAME "@rx ^/api/v1/repos/[^/]+/[^/]+/(?:releases|issues|issues/comments)/[0-9]+/assets$" \
|
||||||
|
"id:10039,phase:1,pass,nolog,ctl:ruleRemoveTargetById=941130;ARGS:name"
|
||||||
|
# 941180: the attachment document.write.js
|
||||||
|
SecRule REQUEST_FILENAME "@rx ^/api/v1/repos/[^/]+/[^/]+/(?:releases|issues|issues/comments)/[0-9]+/assets$" \
|
||||||
|
"id:10040,phase:1,pass,nolog,ctl:ruleRemoveTargetById=941180;ARGS:name"
|
||||||
|
# 942140: the attachment information_schema.sql
|
||||||
|
SecRule REQUEST_FILENAME "@rx ^/api/v1/repos/[^/]+/[^/]+/(?:releases|issues|issues/comments)/[0-9]+/assets$" \
|
||||||
|
"id:10041,phase:1,pass,nolog,ctl:ruleRemoveTargetById=942140;ARGS:name"
|
||||||
|
# 944130: the attachment java.lang.String.md
|
||||||
|
SecRule REQUEST_FILENAME "@rx ^/api/v1/repos/[^/]+/[^/]+/(?:releases|issues|issues/comments)/[0-9]+/assets$" \
|
||||||
|
"id:10042,phase:1,pass,nolog,ctl:ruleRemoveTargetById=944130;ARGS:name"
|
||||||
|
|
||||||
|
# The header Content-Range of an artifact upload.
|
||||||
|
# 920450: an artifact upload from actions/upload-artifact@v3
|
||||||
|
SecRule REQUEST_FILENAME "@rx ^/api/actions_pipeline/_apis/pipelines/workflows/[0-9]+/artifacts/[^/]+/upload$" \
|
||||||
|
"id:10043,phase:1,pass,nolog,ctl:ruleRemoveTargetById=920450;REQUEST_HEADERS_NAMES:Content-Range"
|
||||||
+50
-184
@@ -13,7 +13,8 @@ Four containers share one Docker network, the shared network:
|
|||||||
plain HTTP to HTTPS;
|
plain HTTP to HTTPS;
|
||||||
- `rproxy`, in front of gitea;
|
- `rproxy`, in front of gitea;
|
||||||
- gitea 1.27.3, as its image comes;
|
- gitea 1.27.3, as its image comes;
|
||||||
- `servewaf`, with a volume for its state files.
|
- `servewaf`, with a volume for its state files, and with
|
||||||
|
`deploy/gitea/crs-exclusions.conf` mounted read-only.
|
||||||
|
|
||||||
To start it:
|
To start it:
|
||||||
|
|
||||||
@@ -30,7 +31,8 @@ To start it:
|
|||||||
`docker run --rm <gitea's image> gitea generate secret SECRET_KEY`, with
|
`docker run --rm <gitea's image> gitea generate secret SECRET_KEY`, with
|
||||||
gitea's image as `deploy/gitea/compose.yaml` names it. Do this once per
|
gitea's image as `deploy/gitea/compose.yaml` names it. Do this once per
|
||||||
install, and keep the key.
|
install, and keep the key.
|
||||||
5. Run `docker compose up --detach` in `deploy/gitea`.
|
5. Run `docker compose up --detach` in `deploy/gitea`, with
|
||||||
|
`crs-exclusions.conf` beside `compose.yaml`.
|
||||||
6. Make gitea's first user with `gitea admin user create`, in gitea's container,
|
6. Make gitea's first user with `gitea admin user create`, in gitea's container,
|
||||||
as the user `git`.
|
as the user `git`.
|
||||||
|
|
||||||
@@ -46,6 +48,12 @@ under a name of its own, and checks:
|
|||||||
`rproxy`, and that `servewaf` checks them;
|
`rproxy`, and that `servewaf` checks them;
|
||||||
- that gitea logs the visitor's address, not `rproxy`'s;
|
- that gitea logs the visitor's address, not `rproxy`'s;
|
||||||
- that a request from a container on the shared network skips the checks;
|
- that a request from a container on the shared network skips the checks;
|
||||||
|
- that the Core Rule Set refuses none of the ordinary gitea requests in
|
||||||
|
`internal/waf/testdata/gitea-requests.txt`, sent by a signed-in user;
|
||||||
|
- that it refuses none of the API calls with JSON bodies;
|
||||||
|
- that it refuses no fetch;
|
||||||
|
- that it refuses SQL injection, path traversal and script injection in a query
|
||||||
|
string;
|
||||||
- that with `servewaf` stopped, gitea still answers through `rproxy`.
|
- that with `servewaf` stopped, gitea still answers through `rproxy`.
|
||||||
|
|
||||||
## Uploads and downloads
|
## Uploads and downloads
|
||||||
@@ -79,11 +87,8 @@ GiB a day.
|
|||||||
|
|
||||||
## The checks
|
## The checks
|
||||||
|
|
||||||
`SWWAF_WAF_MODE` is `detect`. The Core Rule Set then logs a match, with its rule
|
The Core Rule Set runs in `block` mode, the default, with gitea's exclusions
|
||||||
ids, and refuses nothing. In `block` mode, the default, it refuses everyday
|
(see "The Core Rule Set's exclusions" below).
|
||||||
gitea searches and file names. The section "What the Core Rule Set refuses in
|
|
||||||
`block` mode" below lists them. Each such refusal counts toward the error burst,
|
|
||||||
so 31 in one minute ban the address.
|
|
||||||
|
|
||||||
Every other check stays at its default. A client may make 120 requests a minute,
|
Every other check stays at its default. A client may make 120 requests a minute,
|
||||||
1,500 an hour and 6,000 a day. Archive downloads and blame and history pages are
|
1,500 an hour and 6,000 a day. Archive downloads and blame and history pages are
|
||||||
@@ -153,188 +158,49 @@ within two minutes. Its address is then banned, its running jobs fail, and each
|
|||||||
repeat within a day makes the ban longer. Newer runners ask for work less often.
|
repeat within a day makes the ban longer. Newer runners ask for work less often.
|
||||||
Put the runners' addresses in `SWWAF_RATE_LIMIT_EXEMPT_NETS`.
|
Put the runners' addresses in `SWWAF_RATE_LIMIT_EXEMPT_NETS`.
|
||||||
|
|
||||||
## What the Core Rule Set refuses in `block` mode
|
## The Core Rule Set's exclusions
|
||||||
|
|
||||||
These notes record what the Core Rule Set does in front of gitea in `block`
|
`deploy/gitea/crs-exclusions.conf` switches single Core Rule Set rules off where
|
||||||
mode, at the other defaults. They are observations, not requirements.
|
they refused ordinary gitea requests. `servewaf` loads it from
|
||||||
`script/gitea-requests` runs gitea behind `rproxy` and `servewaf` at the
|
`SWWAF_CORE_RULE_SET_EXCLUSIONS_FILE`, before the Core Rule Set's rules. Each
|
||||||
defaults. It sends through them a sample of gitea's ordinary use and the
|
exclusion covers one rule, in these places only:
|
||||||
requests named below. It reports each request the Core Rule Set refuses, so that
|
|
||||||
these notes can be checked again when gitea or the Core Rule Set changes
|
|
||||||
version. In `detect` mode, as the setup runs it, the same requests are logged
|
|
||||||
with their rule ids, and pass.
|
|
||||||
|
|
||||||
Each refusal answers only that request ("Bans" in `SPEC.md` says what it counts
|
- `q`, the text of every search;
|
||||||
toward). The request log names the rule in `waf_rule_ids`, which
|
- the query parameters that hold a file name: `file`, `path`, `template`, `body`
|
||||||
`SWWAF_WAF_DISABLED_RULES` can switch off.
|
and `redirect_to`;
|
||||||
|
- the query parameters that hold a branch or tag name: `name`, `tag`,
|
||||||
|
`rule_name` and `refSubUrl`;
|
||||||
|
- `Referer`, on a page opened from search results or from a file's page;
|
||||||
|
- the path of a file's page, raw file, blame, history and edit page, and of the
|
||||||
|
API's call for its contents;
|
||||||
|
- `name`, the file name of an attachment uploaded through the API;
|
||||||
|
- the header `Content-Range` of an Actions artifact upload.
|
||||||
|
|
||||||
### What it lets through
|
gitea reads these as text to search for, as names in a repository, or, for
|
||||||
|
`Content-Range`, as the part of a file an upload carries. It never runs them as
|
||||||
|
SQL, a command or a page's script. So an attack that only the excluded rules
|
||||||
|
would catch there reaches gitea and does nothing. Every other field and path
|
||||||
|
keeps every rule.
|
||||||
|
|
||||||
The Core Rule Set lets gitea's ordinary use through, apart from the refusals in
|
To add an exclusion for a request the Core Rule Set refuses:
|
||||||
the sections after this one:
|
|
||||||
|
|
||||||
- browsing and views of files in a repository, with their history, blame and the
|
1. Find the request in `servewaf`'s log. Its line gives the path and query, and
|
||||||
file tree;
|
names the rules in `waf_rule_ids`.
|
||||||
- diffs, including their hidden lines and large files, and pull request review;
|
2. For each rule, add a line with `ctl:ruleRemoveTargetById` for the field it
|
||||||
- git's clone, fetch and push over HTTP;
|
matched. For a field on every path, copy one of the file's `SecAction` lines.
|
||||||
- signing in, including the return to the page a visitor came from, and sign-in
|
For some paths only, copy one of its `SecRule REQUEST_FILENAME` lines. Give
|
||||||
with Git Credential Manager, git-credential-oauth or tea;
|
it a new id. An id from 900000 to 900999 stops the start.
|
||||||
- the API's calls for a file and its commits;
|
3. Above it, write a one-line comment naming the request it refused.
|
||||||
- pushing and pulling container images and packages;
|
4. Add the request to `internal/waf/testdata/gitea-requests.txt`, and run
|
||||||
- Actions runners, and artifacts uploaded with `actions/upload-artifact@v4`;
|
`make check`.
|
||||||
- posting issues, pull requests, comments, wiki pages and files saved in the web
|
5. Restart `servewaf`.
|
||||||
editor, code included, since no body is read;
|
|
||||||
- the page gitea shows after a change, with its message naming what was done.
|
|
||||||
|
|
||||||
### A query string that reads as an attack
|
The Core Rule Set still refuses the page of a file in a directory named `...`,
|
||||||
|
which reads as path traversal.
|
||||||
|
|
||||||
Most often this is a search. A search term is a query parameter, which the Core
|
With `SWWAF_WAF_BODY_LIMIT` set, it also refuses issue text, comments, wiki
|
||||||
Rule Set checks against its lists of system files and commands (see the fourth
|
pages and files saved in the web editor that hold shell commands or code.
|
||||||
change under "Attack detection" in `SPEC.md`). A release attachment uploaded
|
|
||||||
through the API as `docker-compose.yml` is refused the same way. Examples:
|
|
||||||
|
|
||||||
- a name on those lists, such as `package.json`, `.gitignore` or
|
`make gitea-requests` runs gitea behind `rproxy` and `servewaf` at the defaults,
|
||||||
`docker-compose.yml`;
|
without these exclusions. It reports each request the Core Rule Set refuses
|
||||||
- text that starts with a command name, such as `python3` or `ssh key`, or has
|
there.
|
||||||
one right after a `/` in its first word, such as `feat/docker-support`;
|
|
||||||
- a shell command with its options or a system path (`ls -la`, `sed -i`,
|
|
||||||
`/bin/sh`), or a command in backticks;
|
|
||||||
- script code (`fetch(`, `${VAR}`, `process.env`), HTML (`<img src=`) or an SQL
|
|
||||||
statement (`SELECT * FROM users WHERE`);
|
|
||||||
- a URL naming an IP address or `localhost`.
|
|
||||||
|
|
||||||
### A branch, tag or file name that starts with a command name
|
|
||||||
|
|
||||||
The Core Rule Set's list of commands (932260) holds names such as `docker-`,
|
|
||||||
`python3`, `ansible`, `base64` and `whoami`. A query parameter holding a branch,
|
|
||||||
tag or file name that starts with one is refused. So is one that has such a name
|
|
||||||
right after a `/`, such as `feat/docker-support`, `fix/docker-build` or
|
|
||||||
`renovate/docker-build-push-action-6.x`. gitea's own pages send the name in a
|
|
||||||
query parameter that 930120, 932160 and 932260 still check:
|
|
||||||
|
|
||||||
- `refSubUrl`, the branch or tag of a directory listing. A listing sends it when
|
|
||||||
it asks for its entries' last commits in a second request. It does so whenever
|
|
||||||
gitea takes more than a second to work them out;
|
|
||||||
- `name`, when a branch is deleted or restored on the branches page;
|
|
||||||
- `tag`, when a release is started from a tag;
|
|
||||||
- `template`, the file of an issue template, when an issue is opened from it;
|
|
||||||
- `rule_name`, when a branch protection rule is opened for editing in the
|
|
||||||
repository's settings or has just been saved.
|
|
||||||
|
|
||||||
What a visitor sees, for a branch named `docker-build`:
|
|
||||||
|
|
||||||
- A directory listing that makes that second request leaves those last commits
|
|
||||||
out and shows an error.
|
|
||||||
- The branches page shows an error instead of deleting or restoring the branch.
|
|
||||||
- A release started from such a tag gets the `403` answer of `smallwebwaf` in
|
|
||||||
place of its form. So does an issue opened from such a template, and such a
|
|
||||||
rule opened for editing.
|
|
||||||
- Saving such a rule stores it. gitea then returns to the branch settings page,
|
|
||||||
which gets the `403` answer, so the save looks as if it failed.
|
|
||||||
|
|
||||||
### A file name in a query parameter that trips the code rules
|
|
||||||
|
|
||||||
The Core Rule Set's rules for Java, script, PowerShell and SQL find what they
|
|
||||||
look for in some file names. This holds too in the ten query parameters that
|
|
||||||
930120, 932160 and 932260 do not check (see the fourth change under "Attack
|
|
||||||
detection" in `SPEC.md`). Examples:
|
|
||||||
|
|
||||||
- `runtime` in OpenJDK's `src/java.base/share/classes/java/lang/Runtime.java`
|
|
||||||
(944110);
|
|
||||||
- a Java class, as in `java.lang.String.md` (944130);
|
|
||||||
- `document.write.js` (941180);
|
|
||||||
- `process.env.d.ts` (934100);
|
|
||||||
- `__proto__` (934130);
|
|
||||||
- the PowerShell command `Install-Script`, as in `install-script.sh` (932120);
|
|
||||||
- a database's name, as in PostgreSQL's `information_schema.sql` (942140).
|
|
||||||
|
|
||||||
For such a file, these requests are refused:
|
|
||||||
|
|
||||||
- the API's list of its commits (`path`);
|
|
||||||
- the API's call for the contents of files that names it in its query (`body`);
|
|
||||||
- the commit graph of that file alone (`file`);
|
|
||||||
- the hidden lines of its diff (`path`), which stay hidden;
|
|
||||||
- the sign-in page, for a visitor who opens a page of the file that needs
|
|
||||||
signing in, such as its edit page (`redirect_to`). The visitor can still sign
|
|
||||||
in by opening the sign-in page itself.
|
|
||||||
|
|
||||||
### A path
|
|
||||||
|
|
||||||
- A file name ending in `~`, or an `.xhtml` file whose path holds a space.
|
|
||||||
- A path that holds what the Core Rule Set takes for a Java class (944130), as
|
|
||||||
`java.lang.String.md` and
|
|
||||||
`META-INF/services/org.apache.commons.logging.LogFactory` do.
|
|
||||||
- A path that holds what it takes for Node.js code (934100), as
|
|
||||||
`process.env.d.ts` does.
|
|
||||||
- A path with a directory named `...` (930100, 930110), such as `.../notes.txt`.
|
|
||||||
|
|
||||||
For such a file, these are refused:
|
|
||||||
|
|
||||||
- the page of the file;
|
|
||||||
- its raw file;
|
|
||||||
- its history and its blame;
|
|
||||||
- its edit page.
|
|
||||||
|
|
||||||
The directory that holds it lists it, and a clone carries it.
|
|
||||||
|
|
||||||
### A container image push that reuses a layer
|
|
||||||
|
|
||||||
A push can reuse a layer of an image docker has pushed to that gitea before.
|
|
||||||
docker then asks gitea to take the layer from that image, naming it in the query
|
|
||||||
parameter `from`, which 932260 checks. The request is refused when the image's
|
|
||||||
owner starts with an entry on the list of commands. It is refused too when the
|
|
||||||
image's name after the owner starts with one. Examples are `owner/python3-app`,
|
|
||||||
`owner/docker-base`, and any image of an owner named `ansible` or `docker-team`.
|
|
||||||
|
|
||||||
What happens next depends on docker's image store. A docker that keeps its
|
|
||||||
images in containerd's image store fails the push, and fails each new try the
|
|
||||||
same way. A new install of Docker Engine 29 does this. A docker that keeps them
|
|
||||||
in its own older image store uploads the layer instead. The push then succeeds
|
|
||||||
after one refusal for each layer it reuses, and each of those refusals counts
|
|
||||||
toward the error burst ("Attack detection" in `SPEC.md`).
|
|
||||||
|
|
||||||
### Artifact uploads from `actions/upload-artifact@v3`
|
|
||||||
|
|
||||||
This action sends the header `Content-Range`, which the Core Rule Set refuses
|
|
||||||
(920450). The action sends two files at a time, does not retry a `403`, and
|
|
||||||
sends no further file once one is refused. So an upload is refused once or
|
|
||||||
twice, however many files it holds, and the step fails. One upload does not ban
|
|
||||||
the runner. The error burst ("Attack detection" in `SPEC.md`) bans it after more
|
|
||||||
than 30 such refusals from its address in a minute, as when many jobs upload at
|
|
||||||
once. `actions/upload-artifact@v4` does not send the header.
|
|
||||||
|
|
||||||
### What body inspection refuses
|
|
||||||
|
|
||||||
With `SWWAF_WAF_BODY_LIMIT` set, the Core Rule Set reads request bodies too. In
|
|
||||||
front of gitea it then refuses these, until their rule ids, named in the request
|
|
||||||
log, are added to `SWWAF_WAF_DISABLED_RULES`:
|
|
||||||
|
|
||||||
- issue and comment text, wiki pages and files saved in the web editor that hold
|
|
||||||
shell commands or code (932125, 932235, 932250 and others);
|
|
||||||
- package descriptions that show code;
|
|
||||||
- PyPI uploads (922130);
|
|
||||||
- attachments named like `debug.log` or `config.yml` (932180).
|
|
||||||
|
|
||||||
### What `script/gitea-requests` sends
|
|
||||||
|
|
||||||
Of the requests above, the script sends:
|
|
||||||
|
|
||||||
- for each file named under "A file name in a query parameter that trips the
|
|
||||||
code rules", the API's list of its commits;
|
|
||||||
- for `Runtime.java` alone, the commit graph of that file, the hidden lines of
|
|
||||||
its diff, and the API's call for the contents of files that names it;
|
|
||||||
- the sign-in page for a visitor who opens the edit page of `document.write.js`;
|
|
||||||
- the pages of `java.lang.String.md`,
|
|
||||||
`META-INF/services/org.apache.commons.logging.LogFactory`, `process.env.d.ts`
|
|
||||||
and `.../notes.txt`;
|
|
||||||
- one container image push that reuses a layer, which it does not try again.
|
|
||||||
|
|
||||||
It does not send the raw file, history, blame or edit page of those four files,
|
|
||||||
or the pages of the directories `META-INF/services` and `...`. It also sends a
|
|
||||||
short sample of gitea's ordinary use:
|
|
||||||
|
|
||||||
- signing in;
|
|
||||||
- pages of files and commits, and a diff;
|
|
||||||
- the API;
|
|
||||||
- a push, and clone and fetch over HTTP;
|
|
||||||
- two container image pushes.
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
# The eighth change: on gitea's own paths, the fields in which gitea sends a
|
|
||||||
# search, a file name or a branch name skip the rules that refused ordinary
|
|
||||||
# gitea requests there. Each exclusion runs with the request, before the rules
|
|
||||||
# it switches off, those of phase 1 included, so this file is loaded before the
|
|
||||||
# Core Rule Set's rules. Each comment names a request the rule refused.
|
|
||||||
|
|
||||||
# gitea's searches send the text searched for in q: searches of code, of issues
|
|
||||||
# and pull requests, of commits, and of repositories, branches, tags, packages
|
|
||||||
# and users. q skips these rules there:
|
|
||||||
# 930110: ../
|
|
||||||
# 930120: .gitignore
|
|
||||||
# 931100: http://127.0.0.1:8080
|
|
||||||
# 932120: Invoke-WebRequest
|
|
||||||
# 932130: $(pwd)
|
|
||||||
# 932140: if (a == b) {
|
|
||||||
# 932160: /bin/sh
|
|
||||||
# 932235: `make check`
|
|
||||||
# 932250: ls -la
|
|
||||||
# 932260: python3
|
|
||||||
# 932380: `whoami`
|
|
||||||
# 933100: <?php echo
|
|
||||||
# 933130: $_GET[
|
|
||||||
# 933135: ${VAR}
|
|
||||||
# 934100: process.env
|
|
||||||
# 934110: http://127.0.0.1:8080
|
|
||||||
# 934130: __proto__
|
|
||||||
# 941100: <script>
|
|
||||||
# 941110: <script>
|
|
||||||
# 941160: <img src=
|
|
||||||
# 941180: document.cookie
|
|
||||||
# 941390: fetch(
|
|
||||||
# 942100: SELECT * FROM users WHERE
|
|
||||||
# 942140: information_schema
|
|
||||||
# 942190: union select
|
|
||||||
# 942230: if (a == b) {
|
|
||||||
# 944100: java.lang.Runtime
|
|
||||||
# 944110: java.lang.Runtime
|
|
||||||
# 944130: java.lang.Runtime
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/(?:explore/(?:repos|code)|issues(?:/search)?|pulls|\
|
|
||||||
user/search_candidates|[^/]+/-/(?:code|packages)|\
|
|
||||||
[^/]+/[^/]+/(?:search|issues(?:/search)?|pulls|branches|tags|\
|
|
||||||
commits/(?:branch|tag|commit)/.+/search)|\
|
|
||||||
api/v1/repos/(?:search|issues/search|[^/]+/[^/]+/issues))$" \
|
|
||||||
"id:900600,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=930110;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=931100;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932130;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932140;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932160;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932235;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932380;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=933100;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=933130;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=933135;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=934100;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=934110;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=934130;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=941100;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=941110;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=941160;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=941180;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=941390;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=942100;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=942140;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=942190;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=942230;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=944100;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=944110;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=944130;ARGS:q"
|
|
||||||
|
|
||||||
# A page opened from the results of one of those searches has the search's
|
|
||||||
# address in Referer, which skips these rules then:
|
|
||||||
# 941110: <script>
|
|
||||||
# 941120: onClick={
|
|
||||||
# 941160: <img src=
|
|
||||||
# 942100: LIKE '%foo%'
|
|
||||||
# 944100: java.lang.Runtime
|
|
||||||
SecRule REQUEST_HEADERS:Referer "@rx ^https?://[^/]+/(?:explore/(?:repos|code)|\
|
|
||||||
issues(?:/search)?|pulls|user/search_candidates|[^/]+/-/(?:code|packages)|\
|
|
||||||
[^/]+/[^/]+/(?:search|issues(?:/search)?|pulls|branches|tags|\
|
|
||||||
commits/(?:branch|tag|commit)/.+/search)|\
|
|
||||||
api/v1/repos/(?:search|issues/search|[^/]+/[^/]+/issues))\?" \
|
|
||||||
"id:900601,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=941110;REQUEST_HEADERS:Referer,\
|
|
||||||
ctl:ruleRemoveTargetById=941120;REQUEST_HEADERS:Referer,\
|
|
||||||
ctl:ruleRemoveTargetById=941160;REQUEST_HEADERS:Referer,\
|
|
||||||
ctl:ruleRemoveTargetById=942100;REQUEST_HEADERS:Referer,\
|
|
||||||
ctl:ruleRemoveTargetById=944100;REQUEST_HEADERS:Referer"
|
|
||||||
|
|
||||||
# A file's page, raw file and download, its blame, history, edit and delete
|
|
||||||
# pages, and the API's call for its contents name the file in the path, which
|
|
||||||
# skips these rules there:
|
|
||||||
# 920500: notes.txt~
|
|
||||||
# 934100: process.env.d.ts
|
|
||||||
# 941130: docs/a b.xhtml
|
|
||||||
# 944130: java.lang.String.md
|
|
||||||
SecRule REQUEST_FILENAME \
|
|
||||||
"@rx ^/(?:api/v1/repos/)?[^/]+/[^/]+/(?:src|raw|media|blame|commits|_edit|_delete|contents)/" \
|
|
||||||
"id:900610,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=920500;REQUEST_FILENAME,\
|
|
||||||
ctl:ruleRemoveTargetById=934100;REQUEST_FILENAME,\
|
|
||||||
ctl:ruleRemoveTargetById=941130;REQUEST_FILENAME,\
|
|
||||||
ctl:ruleRemoveTargetById=944130;REQUEST_FILENAME"
|
|
||||||
|
|
||||||
# The requests below name a file in a query parameter. These rules refused
|
|
||||||
# them for the file named:
|
|
||||||
# 930120: .gitignore
|
|
||||||
# 932120: install-script.sh
|
|
||||||
# 932130: [draft].md
|
|
||||||
# 932160: bin/docker-entrypoint
|
|
||||||
# 932260: docker-compose.yml
|
|
||||||
# 934100: process.env.d.ts
|
|
||||||
# 934130: __proto__.json
|
|
||||||
# 941130: index.xhtml
|
|
||||||
# 941180: document.write.js
|
|
||||||
# 942140: information_schema.sql
|
|
||||||
# 944110: java/lang/Runtime.java
|
|
||||||
# 944130: java.lang.String.md
|
|
||||||
# Each request skips the rules that refused it. The fourth change already has
|
|
||||||
# path and redirect_to skip 930120, 932160 and 932260 on every path.
|
|
||||||
|
|
||||||
# A file's commit graph, in file.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/[^/]+/[^/]+/graph$" \
|
|
||||||
"id:900620,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:file,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:file,\
|
|
||||||
ctl:ruleRemoveTargetById=932160;ARGS:file,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:file,\
|
|
||||||
ctl:ruleRemoveTargetById=934100;ARGS:file,\
|
|
||||||
ctl:ruleRemoveTargetById=934130;ARGS:file,\
|
|
||||||
ctl:ruleRemoveTargetById=941130;ARGS:file,\
|
|
||||||
ctl:ruleRemoveTargetById=941180;ARGS:file,\
|
|
||||||
ctl:ruleRemoveTargetById=942140;ARGS:file,\
|
|
||||||
ctl:ruleRemoveTargetById=944110;ARGS:file,\
|
|
||||||
ctl:ruleRemoveTargetById=944130;ARGS:file"
|
|
||||||
|
|
||||||
# The API's list of a file's commits, in path.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/api/v1/repos/[^/]+/[^/]+/commits$" \
|
|
||||||
"id:900621,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:path,\
|
|
||||||
ctl:ruleRemoveTargetById=934100;ARGS:path,\
|
|
||||||
ctl:ruleRemoveTargetById=934130;ARGS:path,\
|
|
||||||
ctl:ruleRemoveTargetById=941130;ARGS:path,\
|
|
||||||
ctl:ruleRemoveTargetById=941180;ARGS:path,\
|
|
||||||
ctl:ruleRemoveTargetById=942140;ARGS:path,\
|
|
||||||
ctl:ruleRemoveTargetById=944110;ARGS:path,\
|
|
||||||
ctl:ruleRemoveTargetById=944130;ARGS:path"
|
|
||||||
|
|
||||||
# The API's call for the contents of several files, in body, as JSON.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/api/v1/repos/[^/]+/[^/]+/file-contents$" \
|
|
||||||
"id:900622,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:body,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:body,\
|
|
||||||
ctl:ruleRemoveTargetById=932160;ARGS:body,\
|
|
||||||
ctl:ruleRemoveTargetById=934100;ARGS:body,\
|
|
||||||
ctl:ruleRemoveTargetById=934130;ARGS:body,\
|
|
||||||
ctl:ruleRemoveTargetById=941130;ARGS:body,\
|
|
||||||
ctl:ruleRemoveTargetById=941180;ARGS:body,\
|
|
||||||
ctl:ruleRemoveTargetById=942140;ARGS:body,\
|
|
||||||
ctl:ruleRemoveTargetById=944110;ARGS:body,\
|
|
||||||
ctl:ruleRemoveTargetById=944130;ARGS:body"
|
|
||||||
|
|
||||||
# An issue opened from a template, in template.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/[^/]+/[^/]+/issues/new$" \
|
|
||||||
"id:900623,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:template,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:template,\
|
|
||||||
ctl:ruleRemoveTargetById=932130;ARGS:template,\
|
|
||||||
ctl:ruleRemoveTargetById=932160;ARGS:template,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:template,\
|
|
||||||
ctl:ruleRemoveTargetById=934100;ARGS:template,\
|
|
||||||
ctl:ruleRemoveTargetById=934130;ARGS:template,\
|
|
||||||
ctl:ruleRemoveTargetById=941130;ARGS:template,\
|
|
||||||
ctl:ruleRemoveTargetById=941180;ARGS:template,\
|
|
||||||
ctl:ruleRemoveTargetById=942140;ARGS:template,\
|
|
||||||
ctl:ruleRemoveTargetById=944110;ARGS:template,\
|
|
||||||
ctl:ruleRemoveTargetById=944130;ARGS:template"
|
|
||||||
|
|
||||||
# The sign-in page on the way to a file's edit page, in redirect_to.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/user/login$" \
|
|
||||||
"id:900624,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:redirect_to,\
|
|
||||||
ctl:ruleRemoveTargetById=932130;ARGS:redirect_to,\
|
|
||||||
ctl:ruleRemoveTargetById=934100;ARGS:redirect_to,\
|
|
||||||
ctl:ruleRemoveTargetById=934130;ARGS:redirect_to,\
|
|
||||||
ctl:ruleRemoveTargetById=941130;ARGS:redirect_to,\
|
|
||||||
ctl:ruleRemoveTargetById=941180;ARGS:redirect_to,\
|
|
||||||
ctl:ruleRemoveTargetById=942140;ARGS:redirect_to,\
|
|
||||||
ctl:ruleRemoveTargetById=944110;ARGS:redirect_to,\
|
|
||||||
ctl:ruleRemoveTargetById=944130;ARGS:redirect_to"
|
|
||||||
|
|
||||||
# An attachment uploaded through the API to a release, an issue or a comment,
|
|
||||||
# in name.
|
|
||||||
SecRule REQUEST_FILENAME \
|
|
||||||
"@rx ^/api/v1/repos/[^/]+/[^/]+/(?:releases|issues|issues/comments)/[0-9]+/assets$" \
|
|
||||||
"id:900625,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:name,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:name,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:name,\
|
|
||||||
ctl:ruleRemoveTargetById=934100;ARGS:name,\
|
|
||||||
ctl:ruleRemoveTargetById=934130;ARGS:name,\
|
|
||||||
ctl:ruleRemoveTargetById=941130;ARGS:name,\
|
|
||||||
ctl:ruleRemoveTargetById=941180;ARGS:name,\
|
|
||||||
ctl:ruleRemoveTargetById=942140;ARGS:name,\
|
|
||||||
ctl:ruleRemoveTargetById=944130;ARGS:name"
|
|
||||||
|
|
||||||
# The requests below name a branch or tag in a query parameter, which skips
|
|
||||||
# the list of command names (932260) there. It refused names such as
|
|
||||||
# docker-build, python3-migration and feat/docker-support.
|
|
||||||
|
|
||||||
# Deleting or restoring a branch, in name.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/[^/]+/[^/]+/branches/(?:delete|restore)$" \
|
|
||||||
"id:900630,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932260;ARGS:name"
|
|
||||||
|
|
||||||
# A release started from a tag, in tag.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/[^/]+/[^/]+/releases/new$" \
|
|
||||||
"id:900631,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932260;ARGS:tag"
|
|
||||||
|
|
||||||
# A branch protection rule opened for editing, in rule_name.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/[^/]+/[^/]+/settings/branches/edit$" \
|
|
||||||
"id:900632,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932260;ARGS:rule_name"
|
|
||||||
|
|
||||||
# A directory listing's last commits, in refSubUrl.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/[^/]+/[^/]+/lastcommit/" \
|
|
||||||
"id:900633,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932260;ARGS:refSubUrl"
|
|
||||||
|
|
||||||
# An artifact upload from actions/upload-artifact@v3 sends the header
|
|
||||||
# Content-Range, which the Core Rule Set refused (920450).
|
|
||||||
SecRule REQUEST_FILENAME \
|
|
||||||
"@rx ^/api/actions_pipeline/_apis/pipelines/workflows/[0-9]+/artifacts/[^/]+/upload$" \
|
|
||||||
"id:900640,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=920450;REQUEST_HEADERS_NAMES:Content-Range"
|
|
||||||
+20
-50
@@ -1,7 +1,8 @@
|
|||||||
# Ordinary gitea 1.27 requests that the Core Rule Set must let through at the
|
# Ordinary gitea requests that the Core Rule Set must let through with
|
||||||
# defaults. Each line is a path and query, and may end with one header after a
|
# deploy/gitea/crs-exclusions.conf. Each line is a path and query, and may end
|
||||||
# space. The tests of internal/waf inspect each as a GET. The Core Rule Set reads
|
# with one header after a space. The tests of internal/waf inspect each as a GET,
|
||||||
# the query and the headers the same way for any method.
|
# and script/gitea-setup sends each to gitea through the setup as a GET. The Core
|
||||||
|
# Rule Set reads the query and the headers the same way for any method.
|
||||||
|
|
||||||
# Searches with ordinary words and symbols.
|
# Searches with ordinary words and symbols.
|
||||||
/owner/repo/search?q=package.json
|
/owner/repo/search?q=package.json
|
||||||
@@ -50,30 +51,24 @@
|
|||||||
/explore/repos?q=%3Cimg%20src%3D
|
/explore/repos?q=%3Cimg%20src%3D
|
||||||
/explore/code?q=SELECT%20%2A%20FROM%20users%20WHERE
|
/explore/code?q=SELECT%20%2A%20FROM%20users%20WHERE
|
||||||
/explore/code?q=%3Cimg%20src%3D
|
/explore/code?q=%3Cimg%20src%3D
|
||||||
/owner/-/code?q=SELECT%20%2A%20FROM%20users%20WHERE
|
|
||||||
/owner/-/code?q=%3Cimg%20src%3D
|
|
||||||
/owner/repo/issues?type=all&state=open&q=SELECT%20%2A%20FROM%20users%20WHERE
|
/owner/repo/issues?type=all&state=open&q=SELECT%20%2A%20FROM%20users%20WHERE
|
||||||
/owner/repo/issues?type=all&state=open&q=%3Cimg%20src%3D
|
/owner/repo/issues?type=all&state=open&q=%3Cimg%20src%3D
|
||||||
/owner/repo/issues/search?q=SELECT%20%2A%20FROM%20users%20WHERE
|
|
||||||
/owner/repo/issues/search?q=%3Cimg%20src%3D
|
|
||||||
/owner/repo/pulls?q=SELECT%20%2A%20FROM%20users%20WHERE
|
/owner/repo/pulls?q=SELECT%20%2A%20FROM%20users%20WHERE
|
||||||
/owner/repo/pulls?q=%3Cimg%20src%3D
|
/owner/repo/pulls?q=%3Cimg%20src%3D
|
||||||
/issues?type=your_repositories&q=SELECT%20%2A%20FROM%20users%20WHERE
|
/issues?type=your_repositories&q=SELECT%20%2A%20FROM%20users%20WHERE
|
||||||
/issues?type=your_repositories&q=%3Cimg%20src%3D
|
/issues?type=your_repositories&q=%3Cimg%20src%3D
|
||||||
/issues/search?q=SELECT%20%2A%20FROM%20users%20WHERE
|
|
||||||
/issues/search?q=%3Cimg%20src%3D
|
|
||||||
/pulls?type=your_repositories&q=SELECT%20%2A%20FROM%20users%20WHERE
|
|
||||||
/pulls?type=your_repositories&q=%3Cimg%20src%3D
|
|
||||||
/owner/repo/commits/branch/main/search?q=SELECT%20%2A%20FROM%20users%20WHERE
|
/owner/repo/commits/branch/main/search?q=SELECT%20%2A%20FROM%20users%20WHERE
|
||||||
/owner/repo/commits/branch/main/search?q=%3Cimg%20src%3D
|
/owner/repo/commits/branch/main/search?q=%3Cimg%20src%3D
|
||||||
|
/owner/repo/wiki/search?q=SELECT%20%2A%20FROM%20users%20WHERE
|
||||||
|
/owner/repo/wiki/search?q=%3Cimg%20src%3D
|
||||||
/owner/repo/branches?q=SELECT%20%2A%20FROM%20users%20WHERE
|
/owner/repo/branches?q=SELECT%20%2A%20FROM%20users%20WHERE
|
||||||
/owner/repo/branches?q=%3Cimg%20src%3D
|
/owner/repo/branches?q=%3Cimg%20src%3D
|
||||||
/owner/repo/tags?q=SELECT%20%2A%20FROM%20users%20WHERE
|
/owner/repo/releases?q=SELECT%20%2A%20FROM%20users%20WHERE
|
||||||
/owner/repo/tags?q=%3Cimg%20src%3D
|
/owner/repo/releases?q=%3Cimg%20src%3D
|
||||||
/owner/-/packages?q=SELECT%20%2A%20FROM%20users%20WHERE
|
/owner/-/packages?q=SELECT%20%2A%20FROM%20users%20WHERE
|
||||||
/owner/-/packages?q=%3Cimg%20src%3D
|
/owner/-/packages?q=%3Cimg%20src%3D
|
||||||
/user/search_candidates?q=SELECT%20%2A%20FROM%20users%20WHERE
|
/user/search?q=SELECT%20%2A%20FROM%20users%20WHERE
|
||||||
/user/search_candidates?q=%3Cimg%20src%3D
|
/user/search?q=%3Cimg%20src%3D
|
||||||
/api/v1/repos/search?q=SELECT%20%2A%20FROM%20users%20WHERE
|
/api/v1/repos/search?q=SELECT%20%2A%20FROM%20users%20WHERE
|
||||||
/api/v1/repos/search?q=%3Cimg%20src%3D
|
/api/v1/repos/search?q=%3Cimg%20src%3D
|
||||||
/api/v1/repos/issues/search?q=SELECT%20%2A%20FROM%20users%20WHERE
|
/api/v1/repos/issues/search?q=SELECT%20%2A%20FROM%20users%20WHERE
|
||||||
@@ -160,25 +155,6 @@
|
|||||||
/owner/repo/_edit/main/notes.txt~
|
/owner/repo/_edit/main/notes.txt~
|
||||||
/api/v1/repos/owner/repo/contents/notes.txt~
|
/api/v1/repos/owner/repo/contents/notes.txt~
|
||||||
|
|
||||||
# The download, blame, history and delete pages of the files above whose names
|
|
||||||
# the Core Rule Set checks in the path.
|
|
||||||
/owner/repo/media/branch/main/notes.txt~
|
|
||||||
/owner/repo/blame/branch/main/notes.txt~
|
|
||||||
/owner/repo/commits/branch/main/notes.txt~
|
|
||||||
/owner/repo/_delete/main/notes.txt~
|
|
||||||
/owner/repo/media/branch/main/process.env.d.ts
|
|
||||||
/owner/repo/blame/branch/main/process.env.d.ts
|
|
||||||
/owner/repo/commits/branch/main/process.env.d.ts
|
|
||||||
/owner/repo/_delete/main/process.env.d.ts
|
|
||||||
/owner/repo/media/branch/main/docs/a%20b.xhtml
|
|
||||||
/owner/repo/blame/branch/main/docs/a%20b.xhtml
|
|
||||||
/owner/repo/commits/branch/main/docs/a%20b.xhtml
|
|
||||||
/owner/repo/_delete/main/docs/a%20b.xhtml
|
|
||||||
/owner/repo/media/branch/main/java.lang.String.md
|
|
||||||
/owner/repo/blame/branch/main/java.lang.String.md
|
|
||||||
/owner/repo/commits/branch/main/java.lang.String.md
|
|
||||||
/owner/repo/_delete/main/java.lang.String.md
|
|
||||||
|
|
||||||
# File names in the query string: the commit graph of a file, the API's lists
|
# File names in the query string: the commit graph of a file, the API's lists
|
||||||
# of its commits and of its contents, an issue opened from a template, and the
|
# of its commits and of its contents, an issue opened from a template, and the
|
||||||
# sign-in page on the way to its edit page.
|
# sign-in page on the way to its edit page.
|
||||||
@@ -305,41 +281,35 @@
|
|||||||
# An artifact upload from actions/upload-artifact@v3, which sends Content-Range.
|
# An artifact upload from actions/upload-artifact@v3, which sends Content-Range.
|
||||||
/api/actions_pipeline/_apis/pipelines/workflows/1/artifacts/9b7c1f3a/upload?itemPath=artifact%2Freport.txt Content-Range: bytes 0-9/10
|
/api/actions_pipeline/_apis/pipelines/workflows/1/artifacts/9b7c1f3a/upload?itemPath=artifact%2Freport.txt Content-Range: bytes 0-9/10
|
||||||
|
|
||||||
# Branch and tag names in the query string: deleting and restoring a branch,
|
# Branch and tag names in the query string: deleting a branch, starting a
|
||||||
# starting a release from a tag, a branch protection rule, a directory listing's
|
# release from a tag, a branch protection rule, a directory listing's last
|
||||||
# last commits, and a container image push that reuses a layer.
|
# commits, and a container image push that reuses a layer.
|
||||||
/owner/repo/branches/delete?name=docker-build&page=1
|
/owner/repo/branches?name=docker-build
|
||||||
/owner/repo/branches/restore?branch_id=1&name=docker-build&page=1
|
|
||||||
/owner/repo/releases/new?tag=docker-build
|
/owner/repo/releases/new?tag=docker-build
|
||||||
/owner/repo/settings/branches/edit?rule_name=docker-build
|
/owner/repo/settings/branches/edit?rule_name=docker-build
|
||||||
/owner/repo/lastcommit/main/src?refSubUrl=branch%2Fdocker-build
|
/owner/repo/lastcommit/main/src?refSubUrl=branch%2Fdocker-build
|
||||||
/v2/owner/app/blobs/uploads/?mount=sha256%3A0000&from=owner%2Fdocker-build
|
/v2/owner/app/blobs/uploads/?mount=sha256%3A0000&from=owner%2Fdocker-build
|
||||||
/owner/repo/branches/delete?name=feat%2Fdocker-support&page=1
|
/owner/repo/branches?name=feat%2Fdocker-support
|
||||||
/owner/repo/branches/restore?branch_id=1&name=feat%2Fdocker-support&page=1
|
|
||||||
/owner/repo/releases/new?tag=feat%2Fdocker-support
|
/owner/repo/releases/new?tag=feat%2Fdocker-support
|
||||||
/owner/repo/settings/branches/edit?rule_name=feat%2Fdocker-support
|
/owner/repo/settings/branches/edit?rule_name=feat%2Fdocker-support
|
||||||
/owner/repo/lastcommit/main/src?refSubUrl=branch%2Ffeat%2Fdocker-support
|
/owner/repo/lastcommit/main/src?refSubUrl=branch%2Ffeat%2Fdocker-support
|
||||||
/v2/owner/app/blobs/uploads/?mount=sha256%3A0000&from=owner%2Ffeat%2Fdocker-support
|
/v2/owner/app/blobs/uploads/?mount=sha256%3A0000&from=owner%2Ffeat%2Fdocker-support
|
||||||
/owner/repo/branches/delete?name=renovate%2Fdocker-build-push-action-6.x&page=1
|
/owner/repo/branches?name=renovate%2Fdocker-build-push-action-6.x
|
||||||
/owner/repo/branches/restore?branch_id=1&name=renovate%2Fdocker-build-push-action-6.x&page=1
|
|
||||||
/owner/repo/releases/new?tag=renovate%2Fdocker-build-push-action-6.x
|
/owner/repo/releases/new?tag=renovate%2Fdocker-build-push-action-6.x
|
||||||
/owner/repo/settings/branches/edit?rule_name=renovate%2Fdocker-build-push-action-6.x
|
/owner/repo/settings/branches/edit?rule_name=renovate%2Fdocker-build-push-action-6.x
|
||||||
/owner/repo/lastcommit/main/src?refSubUrl=branch%2Frenovate%2Fdocker-build-push-action-6.x
|
/owner/repo/lastcommit/main/src?refSubUrl=branch%2Frenovate%2Fdocker-build-push-action-6.x
|
||||||
/v2/owner/app/blobs/uploads/?mount=sha256%3A0000&from=owner%2Frenovate%2Fdocker-build-push-action-6.x
|
/v2/owner/app/blobs/uploads/?mount=sha256%3A0000&from=owner%2Frenovate%2Fdocker-build-push-action-6.x
|
||||||
/owner/repo/branches/delete?name=python3-migration&page=1
|
/owner/repo/branches?name=python3-migration
|
||||||
/owner/repo/branches/restore?branch_id=1&name=python3-migration&page=1
|
|
||||||
/owner/repo/releases/new?tag=python3-migration
|
/owner/repo/releases/new?tag=python3-migration
|
||||||
/owner/repo/settings/branches/edit?rule_name=python3-migration
|
/owner/repo/settings/branches/edit?rule_name=python3-migration
|
||||||
/owner/repo/lastcommit/main/src?refSubUrl=branch%2Fpython3-migration
|
/owner/repo/lastcommit/main/src?refSubUrl=branch%2Fpython3-migration
|
||||||
/v2/owner/app/blobs/uploads/?mount=sha256%3A0000&from=owner%2Fpython3-migration
|
/v2/owner/app/blobs/uploads/?mount=sha256%3A0000&from=owner%2Fpython3-migration
|
||||||
/owner/repo/branches/delete?name=ansible-roles&page=1
|
/owner/repo/branches?name=ansible-roles
|
||||||
/owner/repo/branches/restore?branch_id=1&name=ansible-roles&page=1
|
|
||||||
/owner/repo/releases/new?tag=ansible-roles
|
/owner/repo/releases/new?tag=ansible-roles
|
||||||
/owner/repo/settings/branches/edit?rule_name=ansible-roles
|
/owner/repo/settings/branches/edit?rule_name=ansible-roles
|
||||||
/owner/repo/lastcommit/main/src?refSubUrl=branch%2Fansible-roles
|
/owner/repo/lastcommit/main/src?refSubUrl=branch%2Fansible-roles
|
||||||
/v2/owner/app/blobs/uploads/?mount=sha256%3A0000&from=owner%2Fansible-roles
|
/v2/owner/app/blobs/uploads/?mount=sha256%3A0000&from=owner%2Fansible-roles
|
||||||
/owner/repo/branches/delete?name=whoami&page=1
|
/owner/repo/branches?name=whoami
|
||||||
/owner/repo/branches/restore?branch_id=1&name=whoami&page=1
|
|
||||||
/owner/repo/releases/new?tag=whoami
|
/owner/repo/releases/new?tag=whoami
|
||||||
/owner/repo/settings/branches/edit?rule_name=whoami
|
/owner/repo/settings/branches/edit?rule_name=whoami
|
||||||
/owner/repo/lastcommit/main/src?refSubUrl=branch%2Fwhoami
|
/owner/repo/lastcommit/main/src?refSubUrl=branch%2Fwhoami
|
||||||
|
|||||||
+6
-20
@@ -1,6 +1,6 @@
|
|||||||
// Package waf runs the OWASP Core Rule Set 4.25.0, through Coraza, on the
|
// 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
|
// 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 eight changes smallwebwaf
|
// 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
|
// 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
|
// "Attack detection" under "Settings" in SPEC.md describes them. It reads no
|
||||||
// response.
|
// response.
|
||||||
@@ -12,7 +12,6 @@
|
|||||||
package waf
|
package waf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -32,8 +31,7 @@ import (
|
|||||||
|
|
||||||
// directives are the Core Rule Set as smallwebwaf runs it, with the
|
// directives are the Core Rule Set as smallwebwaf runs it, with the
|
||||||
// paranoia level for %d, bodyDirectives for the first %s while
|
// paranoia level for %d, bodyDirectives for the first %s while
|
||||||
// SWWAF_WAF_BODY_LIMIT is set, the exclusions file for the second, and
|
// SWWAF_WAF_BODY_LIMIT is set, and the exclusions file for the second.
|
||||||
// giteaExclusions for the third.
|
|
||||||
// Each rule smallwebwaf adds has an id from 900000 to 900999, the ids the
|
// 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
|
// Core Rule Set keeps for the rules that set it up, which
|
||||||
// SWWAF_WAF_DISABLED_RULES and the exclusions file refuse, so that no
|
// SWWAF_WAF_DISABLED_RULES and the exclusions file refuse, so that no
|
||||||
@@ -81,9 +79,6 @@ SecRule ARGUMENTS_LIMIT_REACHED "@eq 1" "id:900300,phase:2,pass,\
|
|||||||
# before the rules of phase 1 too.
|
# before the rules of phase 1 too.
|
||||||
%s
|
%s
|
||||||
|
|
||||||
# The eighth change comes before the rules for the same reason.
|
|
||||||
%s
|
|
||||||
|
|
||||||
# The sixth: only the rules for requests are loaded, and no response is
|
# The sixth: only the rules for requests are loaded, and no response is
|
||||||
# inspected.
|
# inspected.
|
||||||
Include @owasp_crs/REQUEST-*.conf
|
Include @owasp_crs/REQUEST-*.conf
|
||||||
@@ -111,12 +106,10 @@ SecRuleUpdateTargetById 932260 "!ARGS:path|!ARGS:files|!ARGS:skip-to|\
|
|||||||
!ARGS:artifactName|!ARGS:redirect_to"
|
!ARGS:artifactName|!ARGS:redirect_to"
|
||||||
|
|
||||||
# The fifth, for Referer: it is not checked for a Unix command without
|
# The fifth, for Referer: it is not checked for a Unix command without
|
||||||
# arguments, for Java starting a process, or for a Java class name. 944130
|
# arguments, or for Java starting a process. The cookies are left out in
|
||||||
# refused every page opened from the page of java.lang.String.md. The
|
# Inspect.
|
||||||
# cookies are left out in Inspect.
|
|
||||||
SecRuleUpdateTargetById 932340 "!REQUEST_HEADERS:Referer"
|
SecRuleUpdateTargetById 932340 "!REQUEST_HEADERS:Referer"
|
||||||
SecRuleUpdateTargetById 944110 "!REQUEST_HEADERS:Referer"
|
SecRuleUpdateTargetById 944110 "!REQUEST_HEADERS:Referer"
|
||||||
SecRuleUpdateTargetById 944130 "!REQUEST_HEADERS:Referer"
|
|
||||||
|
|
||||||
# The seventh: a container image push that reuses a layer names the image
|
# 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
|
# it takes the layer from in the query parameter from, which is not
|
||||||
@@ -168,13 +161,6 @@ SecRule MULTIPART_STRICT_ERROR "!@eq 0" "id:900450,phase:2,pass,\
|
|||||||
severity:'CRITICAL',setvar:'tx.inbound_anomaly_score_pl1=+5'"
|
severity:'CRITICAL',setvar:'tx.inbound_anomaly_score_pl1=+5'"
|
||||||
`
|
`
|
||||||
|
|
||||||
// giteaExclusions are the eighth change: the fields of gitea's own
|
|
||||||
// requests that skip the rules that refused them there, as Coraza
|
|
||||||
// directives.
|
|
||||||
//
|
|
||||||
//go:embed gitea.conf
|
|
||||||
var giteaExclusions string
|
|
||||||
|
|
||||||
// cookiesNotRead are the cookies the Core Rule Set reads a request
|
// cookiesNotRead are the cookies the Core Rule Set reads a request
|
||||||
// without, the rest of the fifth change.
|
// without, the rest of the fifth change.
|
||||||
//
|
//
|
||||||
@@ -245,7 +231,7 @@ type CoreRuleSet struct {
|
|||||||
bodyLimit int64
|
bodyLimit int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns the Core Rule Set with the eight changes, at params'
|
// New returns the Core Rule Set with the seven changes, at params'
|
||||||
// paranoia level, without the rules it switches off, reading request
|
// paranoia level, without the rules it switches off, reading request
|
||||||
// bodies up to params' limit, and with params' exclusions file loaded
|
// bodies up to params' limit, and with params' exclusions file loaded
|
||||||
// before the Core Rule Set's rules. An exclusions file that cannot be
|
// before the Core Rule Set's rules. An exclusions file that cannot be
|
||||||
@@ -273,7 +259,7 @@ func New(params Params) (*CoreRuleSet, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
text := fmt.Sprintf(directives, params.ParanoiaLevel, body, exclusions, giteaExclusions)
|
text := fmt.Sprintf(directives, params.ParanoiaLevel, body, exclusions)
|
||||||
|
|
||||||
if len(params.DisabledRules) > 0 {
|
if len(params.DisabledRules) > 0 {
|
||||||
ids := make([]string, len(params.DisabledRules))
|
ids := make([]string, len(params.DisabledRules))
|
||||||
|
|||||||
+19
-22
@@ -120,7 +120,7 @@ func atDefaults(t *testing.T) *waf.CoreRuleSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// wantChange checks that crs lets through passes, a gitea request one of
|
// wantChange checks that crs lets through passes, a gitea request one of
|
||||||
// the eight changes is for, and still finds result in refused, a request
|
// the seven changes is for, and still finds result in refused, a request
|
||||||
// like it that the change is not for.
|
// like it that the change is not for.
|
||||||
func wantChange(
|
func wantChange(
|
||||||
t *testing.T, crs *waf.CoreRuleSet, passes, refused request, result waf.Result,
|
t *testing.T, crs *waf.CoreRuleSet, passes, refused request, result waf.Result,
|
||||||
@@ -309,14 +309,13 @@ func TestCookiesGiteaFlashAndRedirectToAreNotRead(t *testing.T) {
|
|||||||
matched(930120))
|
matched(930120))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRefererIsNotCheckedForACommandOrJava(t *testing.T) {
|
func TestRefererIsNotCheckedForACommandOrJavaStartingAProcess(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const (
|
const (
|
||||||
search = "https://git.example/explore/repos?q=env"
|
search = "https://git.example/explore/repos?q=env"
|
||||||
runtimeJava = "https://git.example/openjdk/jdk/src/branch/master/src/" +
|
runtimeJava = "https://git.example/openjdk/jdk/src/branch/master/src/" +
|
||||||
"java.base/share/classes/java/lang/Runtime.java"
|
"java.base/share/classes/java/lang/Runtime.java"
|
||||||
stringMD = "https://git.example/owner/repo/src/branch/main/java.lang.String.md"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
crs := atDefaults(t)
|
crs := atDefaults(t)
|
||||||
@@ -325,8 +324,6 @@ func TestRefererIsNotCheckedForACommandOrJava(t *testing.T) {
|
|||||||
matched(932340))
|
matched(932340))
|
||||||
wantChange(t, crs, get("/", "Referer: "+runtimeJava),
|
wantChange(t, crs, get("/", "Referer: "+runtimeJava),
|
||||||
get("/", "X-Page: "+runtimeJava), matched(944110))
|
get("/", "X-Page: "+runtimeJava), matched(944110))
|
||||||
wantChange(t, crs, get("/", "Referer: "+stringMD), get("/", "X-Page: "+stringMD),
|
|
||||||
matched(944130))
|
|
||||||
|
|
||||||
// It is still checked for script and SQL injection.
|
// It is still checked for script and SQL injection.
|
||||||
wantResult(t, crs,
|
wantResult(t, crs,
|
||||||
@@ -467,9 +464,9 @@ func TestExclusionsFileThatCannotBeUsedIsAnError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// giteaRequests returns the recorded ordinary gitea requests in testdata.
|
// giteaRequests returns the recorded ordinary gitea requests in testdata,
|
||||||
// Each has the Referer of a page that names the same, as a page opened
|
// which script/gitea-setup sends too. Each has the Referer of a page that
|
||||||
// from it has.
|
// names the same, as a page opened from it has.
|
||||||
func giteaRequests(t *testing.T) []request {
|
func giteaRequests(t *testing.T) []request {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
@@ -499,12 +496,16 @@ func giteaRequests(t *testing.T) []request {
|
|||||||
return requests
|
return requests
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOrdinaryGiteaRequestsPassAndAttacksAreRefused(t *testing.T) {
|
func TestGiteaExclusionsLetOrdinaryGiteaRequestsThroughAndRefuseAttacks(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const threshold = 5 // SWWAF_WAF_ANOMALY_THRESHOLD's default
|
crs, err := withExclusions(filepath.Join("..", "..", "deploy", "gitea",
|
||||||
|
"crs-exclusions.conf"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("load gitea's exclusions: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
crs := atDefaults(t)
|
const threshold = 5 // SWWAF_WAF_ANOMALY_THRESHOLD's default
|
||||||
|
|
||||||
for _, r := range giteaRequests(t) {
|
for _, r := range giteaRequests(t) {
|
||||||
if got := inspect(t, crs, r); got.Score >= threshold {
|
if got := inspect(t, crs, r); got.Score >= threshold {
|
||||||
@@ -512,17 +513,13 @@ func TestOrdinaryGiteaRequestsPassAndAttacksAreRefused(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In q too, off gitea's search pages.
|
for _, attack := range []string{
|
||||||
for _, parameter := range []string{"id", "q"} {
|
"/owner/repo?id=1'%20OR%20'1'='1",
|
||||||
for _, attack := range []string{
|
"/owner/repo?id=..%2Fcustom%2Fconf%2Fapp.ini",
|
||||||
"1'%20OR%20'1'='1",
|
"/owner/repo?id=%3Cscript%3Ealert(1)%3C%2Fscript%3E",
|
||||||
"..%2Fcustom%2Fconf%2Fapp.ini",
|
} {
|
||||||
"%3Cscript%3Ealert(1)%3C%2Fscript%3E",
|
if got := inspect(t, crs, get(attack)); got.Score < threshold {
|
||||||
} {
|
t.Errorf("%s let through: %+v", attack, got)
|
||||||
target := "/owner/repo?" + parameter + "=" + attack
|
|
||||||
if got := inspect(t, crs, get(target)); got.Score < threshold {
|
|
||||||
t.Errorf("%s let through: %+v", target, got)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
# default but these: SWRP_UPSTREAM_URL names gitea's container, SWRP_WAF_BACKEND
|
# default but these: SWRP_UPSTREAM_URL names gitea's container, SWRP_WAF_BACKEND
|
||||||
# names servewaf's, and SWWAF_LOG_REQUEST_HEADERS adds the header that labels a
|
# names servewaf's, and SWWAF_LOG_REQUEST_HEADERS adds the header that labels a
|
||||||
# request. Then send through rproxy the gitea requests that reading gitea's
|
# request. Then send through rproxy the gitea requests that reading gitea's
|
||||||
# source found the Core Rule Set refuses, which docs/gitea.md records, and a
|
# source found the Core Rule Set refuses without the exclusions in
|
||||||
|
# deploy/gitea/crs-exclusions.conf, and a
|
||||||
# short sample of gitea's ordinary use: signing in, pages of files and commits,
|
# short sample of gitea's ordinary use: signing in, pages of files and commits,
|
||||||
# a diff, the API, a push, clone and fetch over HTTP, and two container image
|
# a diff, the API, a push, clone and fetch over HTTP, and two container image
|
||||||
# pushes. Last, print each request servewaf logged, with what it did with it and
|
# pushes. Last, print each request servewaf logged, with what it did with it and
|
||||||
|
|||||||
@@ -5,6 +5,12 @@
|
|||||||
# rproxy, and that servewaf checks them;
|
# rproxy, and that servewaf checks them;
|
||||||
# - that gitea logs the visitor's address, not rproxy's;
|
# - that gitea logs the visitor's address, not rproxy's;
|
||||||
# - that a request from a container on the shared network skips the checks;
|
# - that a request from a container on the shared network skips the checks;
|
||||||
|
# - that the Core Rule Set refuses none of the ordinary gitea requests in
|
||||||
|
# internal/waf/testdata/gitea-requests.txt;
|
||||||
|
# - that it refuses none of the API calls with JSON bodies;
|
||||||
|
# - that it refuses no fetch;
|
||||||
|
# - that it refuses SQL injection, path traversal and script injection in a
|
||||||
|
# query string;
|
||||||
# - that with servewaf stopped, gitea still answers through rproxy.
|
# - that with servewaf stopped, gitea still answers through rproxy.
|
||||||
# It publishes no port of traefik's, since this host's ports 80 and 443 may be
|
# It publishes no port of traefik's, since this host's ports 80 and 443 may be
|
||||||
# taken. It reaches traefik at its address on the shared network instead, so
|
# taken. It reaches traefik at its address on the shared network instead, so
|
||||||
@@ -136,6 +142,71 @@ passed_through() {
|
|||||||
"\"client_ip\":\"$gateway\"" '"action":"forward"' '"counts":' '"waf_score":'
|
"\"client_ip\":\"$gateway\"" '"action":"forward"' '"counts":' '"waf_score":'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# refused: print each request servewaf logged as refused, for any reason.
|
||||||
|
refused() {
|
||||||
|
compose logs --no-color --no-log-prefix servewaf 2>&1 |
|
||||||
|
grep -F '"servewaf_line":"report"' | grep -v -F '"action":"forward"' || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# ordinary_requests: sign in as the owner, save a file and open an issue
|
||||||
|
# through the API with JSON bodies, then send, signed in, each request in
|
||||||
|
# internal/waf/testdata/gitea-requests.txt, with the header its line ends with,
|
||||||
|
# if any, and that request's own address as its Referer, as a page opened from it
|
||||||
|
# has. One a second keeps them under the default limit of 120 requests a minute,
|
||||||
|
# since a request refused for a limit is never inspected. Fail if servewaf
|
||||||
|
# refuses any of them.
|
||||||
|
ordinary_requests() {
|
||||||
|
send /user/login --cookie-jar "$WORK/cookies" >/dev/null
|
||||||
|
status="$(send /user/login --cookie "$WORK/cookies" --cookie-jar "$WORK/cookies" \
|
||||||
|
--header "Origin: https://$SITE" --data-urlencode user_name=owner \
|
||||||
|
--data-urlencode "password=$PASSWORD")"
|
||||||
|
[ "$status" = 303 ] || fail "signing in was answered $status"
|
||||||
|
|
||||||
|
content="$(printf 'declare const env: Record<string, string>;\n' | base64)"
|
||||||
|
status="$(send /api/v1/repos/owner/repo/contents/process.env.d.ts \
|
||||||
|
--user "owner:$PASSWORD" --header "Content-Type: application/json" \
|
||||||
|
--data "{\"message\":\"Add process.env.d.ts\",\"content\":\"$content\"}")"
|
||||||
|
[ "$status" = 201 ] || fail "saving a file through the API was answered $status"
|
||||||
|
status="$(send /api/v1/repos/owner/repo/issues --user "owner:$PASSWORD" \
|
||||||
|
--header "Content-Type: application/json" \
|
||||||
|
--data '{"title":"ls -la fails","body":"SELECT * FROM users; <img src=x>"}')"
|
||||||
|
[ "$status" = 201 ] || fail "opening an issue through the API was answered $status"
|
||||||
|
|
||||||
|
grep -v -e '^#' -e '^$' "$ROOT/internal/waf/testdata/gitea-requests.txt" |
|
||||||
|
while read -r target header; do
|
||||||
|
if [ -n "$header" ]; then
|
||||||
|
send "$target" --globoff --cookie "$WORK/cookies" \
|
||||||
|
--header "Referer: https://$SITE$target" --header "$header" >/dev/null
|
||||||
|
else
|
||||||
|
send "$target" --globoff --cookie "$WORK/cookies" \
|
||||||
|
--header "Referer: https://$SITE$target" >/dev/null
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
# The last request's report is logged once its answer has ended.
|
||||||
|
send /api/v1/settings/api >/dev/null
|
||||||
|
wait_for "servewaf logged no report of the last ordinary request" logged servewaf \
|
||||||
|
'"servewaf_line":"report"' '"path":"/api/v1/settings/api"'
|
||||||
|
refusals="$(refused)"
|
||||||
|
[ -z "$refusals" ] || fail "servewaf refused ordinary requests:
|
||||||
|
$refusals"
|
||||||
|
}
|
||||||
|
|
||||||
|
# attacks: send SQL injection, path traversal and script injection in a query
|
||||||
|
# string, and fail unless the Core Rule Set refuses each.
|
||||||
|
attacks() {
|
||||||
|
# The traversal climbs one directory: the default rule file refuses two or
|
||||||
|
# more first, before the Core Rule Set runs.
|
||||||
|
for query in "id=1'%20OR%20'1'%3D'1" "id=..%2Fcustom%2Fconf%2Fapp.ini" \
|
||||||
|
"id=%3Cscript%3Ealert(1)%3C%2Fscript%3E"; do
|
||||||
|
status="$(send "/owner/repo?$query")"
|
||||||
|
[ "$status" = 403 ] || fail "the attack ?$query was answered $status"
|
||||||
|
wait_for "servewaf logged no refusal of ?$query" logged servewaf \
|
||||||
|
'"servewaf_line":"report"' '"action":"waf_blocked"' "\"query\":\"$query\""
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
@@ -204,6 +275,12 @@ main() {
|
|||||||
esac
|
esac
|
||||||
echo "gitea-setup: a request from the shared network skips the checks"
|
echo "gitea-setup: a request from the shared network skips the checks"
|
||||||
|
|
||||||
|
site_git -C "$WORK/clone" fetch --quiet || fail "the fetch failed"
|
||||||
|
ordinary_requests
|
||||||
|
echo "gitea-setup: the Core Rule Set refuses no ordinary gitea request"
|
||||||
|
attacks
|
||||||
|
echo "gitea-setup: the Core Rule Set refuses the attacks"
|
||||||
|
|
||||||
compose stop servewaf >/dev/null 2>&1 || fail "servewaf did not stop"
|
compose stop servewaf >/dev/null 2>&1 || fail "servewaf did not stop"
|
||||||
status="$(send /owner/repo)"
|
status="$(send /owner/repo)"
|
||||||
[ "$status" = 200 ] ||
|
[ "$status" = 200 ] ||
|
||||||
|
|||||||
Reference in new issue
Block a user