Gitea setup: Core Rule Set blocks attacks, not ordinary Gitea traffic (closes #198)
check / check (push) Successful in 3m16s
check / check (push) Successful in 3m16s
The Gitea setup runs the Core Rule Set in block mode. Rules that refused ordinary Gitea requests are switched off only where they misfired: - deploy/gitea/crs-exclusions.conf, loaded through the new setting SWWAF_CORE_RULE_SET_EXCLUSIONS_FILE before the Core Rule Set's rules, covers Gitea's search, file-name and branch-name fields, Referer, file paths, API attachment names and Actions artifact uploads; - smallwebwaf's defaults gain a seventh change: the registry upload path's from parameter skips 932260. A file that cannot be read, does not parse or names an id from 900000 to 900999 stops servewaf at start, in every mode. internal/waf's tests and script/gitea-setup send the recorded requests in internal/waf/testdata/gitea-requests.txt and three attacks. Model: opus-5-5
This commit is contained in:
1 parent
21cac70b50
commit
866b948ec0
17 files changed
+1048
-242
No files matched your search
+337
@@ -0,0 +1,337 @@
|
||||
# Ordinary gitea requests that the Core Rule Set must let through with
|
||||
# deploy/gitea/crs-exclusions.conf. Each line is a path and query, and may end
|
||||
# with one header after a space. The tests of internal/waf inspect each as a GET,
|
||||
# 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.
|
||||
/owner/repo/search?q=package.json
|
||||
/owner/repo/search?q=.gitignore
|
||||
/owner/repo/search?q=docker-compose.yml
|
||||
/owner/repo/search?q=python3
|
||||
/owner/repo/search?q=ssh%20key
|
||||
/owner/repo/search?q=feat%2Fdocker-support
|
||||
/owner/repo/search?q=ls%20-la
|
||||
/owner/repo/search?q=sed%20-i
|
||||
/owner/repo/search?q=%2Fbin%2Fsh
|
||||
/owner/repo/search?q=%60make%20check%60
|
||||
/owner/repo/search?q=%60whoami%60
|
||||
/owner/repo/search?q=fetch%28
|
||||
/owner/repo/search?q=%24%7BVAR%7D
|
||||
/owner/repo/search?q=%24%28pwd%29
|
||||
/owner/repo/search?q=process.env
|
||||
/owner/repo/search?q=%3Cimg%20src%3D
|
||||
/owner/repo/search?q=%3Cscript%3E
|
||||
/owner/repo/search?q=SELECT%20%2A%20FROM%20users%20WHERE
|
||||
/owner/repo/search?q=union%20select
|
||||
/owner/repo/search?q=http%3A%2F%2F127.0.0.1%3A8080
|
||||
/owner/repo/search?q=..%2F
|
||||
/owner/repo/search?q=%2Fetc%2Fpasswd
|
||||
/owner/repo/search?q=eval%28
|
||||
/owner/repo/search?q=chmod%20%2Bx
|
||||
/owner/repo/search?q=echo%20%24PATH
|
||||
/owner/repo/search?q=java.lang.Runtime
|
||||
/owner/repo/search?q=document.cookie
|
||||
/owner/repo/search?q=alert%281%29
|
||||
/owner/repo/search?q=__proto__
|
||||
/owner/repo/search?q=C%3A%5CWindows%5CSystem32
|
||||
/owner/repo/search?q=Invoke-WebRequest
|
||||
/owner/repo/search?q=if%20%28a%20%3D%3D%20b%29%20%7B
|
||||
/owner/repo/search?q=sudo%20apt%20install
|
||||
/owner/repo/search?q=~%2F.ssh%2Fconfig
|
||||
/owner/repo/search?q=.htaccess
|
||||
/owner/repo/search?q=%24_GET%5B
|
||||
/owner/repo/search?q=%24_POST
|
||||
/owner/repo/search?q=%24_SERVER%5B%27REQUEST_URI%27%5D
|
||||
/owner/repo/search?q=%3C%3Fphp%20echo
|
||||
/owner/repo/search?q=information_schema
|
||||
/owner/repo/search?q=LIKE%20%27%25foo%25%27
|
||||
/owner/repo/search?q=onClick%3D%7B
|
||||
/explore/repos?q=SELECT%20%2A%20FROM%20users%20WHERE
|
||||
/explore/repos?q=%3Cimg%20src%3D
|
||||
/explore/code?q=SELECT%20%2A%20FROM%20users%20WHERE
|
||||
/explore/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=%3Cimg%20src%3D
|
||||
/owner/repo/pulls?q=SELECT%20%2A%20FROM%20users%20WHERE
|
||||
/owner/repo/pulls?q=%3Cimg%20src%3D
|
||||
/issues?type=your_repositories&q=SELECT%20%2A%20FROM%20users%20WHERE
|
||||
/issues?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=%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=%3Cimg%20src%3D
|
||||
/owner/repo/releases?q=SELECT%20%2A%20FROM%20users%20WHERE
|
||||
/owner/repo/releases?q=%3Cimg%20src%3D
|
||||
/owner/-/packages?q=SELECT%20%2A%20FROM%20users%20WHERE
|
||||
/owner/-/packages?q=%3Cimg%20src%3D
|
||||
/user/search?q=SELECT%20%2A%20FROM%20users%20WHERE
|
||||
/user/search?q=%3Cimg%20src%3D
|
||||
/api/v1/repos/search?q=SELECT%20%2A%20FROM%20users%20WHERE
|
||||
/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=%3Cimg%20src%3D
|
||||
/api/v1/repos/owner/repo/issues?q=SELECT%20%2A%20FROM%20users%20WHERE
|
||||
/api/v1/repos/owner/repo/issues?q=%3Cimg%20src%3D
|
||||
|
||||
# Pages, raw files and edit pages of files, and the API's call for their
|
||||
# contents.
|
||||
/owner/repo/src/branch/main/package.json
|
||||
/owner/repo/raw/branch/main/package.json
|
||||
/owner/repo/_edit/main/package.json
|
||||
/api/v1/repos/owner/repo/contents/package.json
|
||||
/owner/repo/src/branch/main/.gitignore
|
||||
/owner/repo/raw/branch/main/.gitignore
|
||||
/owner/repo/_edit/main/.gitignore
|
||||
/api/v1/repos/owner/repo/contents/.gitignore
|
||||
/owner/repo/src/branch/main/docker-compose.yml
|
||||
/owner/repo/raw/branch/main/docker-compose.yml
|
||||
/owner/repo/_edit/main/docker-compose.yml
|
||||
/api/v1/repos/owner/repo/contents/docker-compose.yml
|
||||
/owner/repo/src/branch/main/Dockerfile
|
||||
/owner/repo/raw/branch/main/Dockerfile
|
||||
/owner/repo/_edit/main/Dockerfile
|
||||
/api/v1/repos/owner/repo/contents/Dockerfile
|
||||
/owner/repo/src/branch/main/.htaccess
|
||||
/owner/repo/raw/branch/main/.htaccess
|
||||
/owner/repo/_edit/main/.htaccess
|
||||
/api/v1/repos/owner/repo/contents/.htaccess
|
||||
/owner/repo/src/branch/main/rootfs/etc/passwd
|
||||
/owner/repo/raw/branch/main/rootfs/etc/passwd
|
||||
/owner/repo/_edit/main/rootfs/etc/passwd
|
||||
/api/v1/repos/owner/repo/contents/rootfs/etc/passwd
|
||||
/owner/repo/src/branch/main/bin/docker-entrypoint
|
||||
/owner/repo/raw/branch/main/bin/docker-entrypoint
|
||||
/owner/repo/_edit/main/bin/docker-entrypoint
|
||||
/api/v1/repos/owner/repo/contents/bin/docker-entrypoint
|
||||
/owner/repo/src/branch/main/scripts/python3-setup.sh
|
||||
/owner/repo/raw/branch/main/scripts/python3-setup.sh
|
||||
/owner/repo/_edit/main/scripts/python3-setup.sh
|
||||
/api/v1/repos/owner/repo/contents/scripts/python3-setup.sh
|
||||
/owner/repo/src/branch/main/src/java.base/share/classes/java/lang/Runtime.java
|
||||
/owner/repo/raw/branch/main/src/java.base/share/classes/java/lang/Runtime.java
|
||||
/owner/repo/_edit/main/src/java.base/share/classes/java/lang/Runtime.java
|
||||
/api/v1/repos/owner/repo/contents/src/java.base/share/classes/java/lang/Runtime.java
|
||||
/owner/repo/src/branch/main/java.lang.String.md
|
||||
/owner/repo/raw/branch/main/java.lang.String.md
|
||||
/owner/repo/_edit/main/java.lang.String.md
|
||||
/api/v1/repos/owner/repo/contents/java.lang.String.md
|
||||
/owner/repo/src/branch/main/META-INF/services/org.apache.commons.logging.LogFactory
|
||||
/owner/repo/raw/branch/main/META-INF/services/org.apache.commons.logging.LogFactory
|
||||
/owner/repo/_edit/main/META-INF/services/org.apache.commons.logging.LogFactory
|
||||
/api/v1/repos/owner/repo/contents/META-INF/services/org.apache.commons.logging.LogFactory
|
||||
/owner/repo/src/branch/main/process.env.d.ts
|
||||
/owner/repo/raw/branch/main/process.env.d.ts
|
||||
/owner/repo/_edit/main/process.env.d.ts
|
||||
/api/v1/repos/owner/repo/contents/process.env.d.ts
|
||||
/owner/repo/src/branch/main/document.write.js
|
||||
/owner/repo/raw/branch/main/document.write.js
|
||||
/owner/repo/_edit/main/document.write.js
|
||||
/api/v1/repos/owner/repo/contents/document.write.js
|
||||
/owner/repo/src/branch/main/test/fixtures/__proto__.json
|
||||
/owner/repo/raw/branch/main/test/fixtures/__proto__.json
|
||||
/owner/repo/_edit/main/test/fixtures/__proto__.json
|
||||
/api/v1/repos/owner/repo/contents/test/fixtures/__proto__.json
|
||||
/owner/repo/src/branch/main/install-script.sh
|
||||
/owner/repo/raw/branch/main/install-script.sh
|
||||
/owner/repo/_edit/main/install-script.sh
|
||||
/api/v1/repos/owner/repo/contents/install-script.sh
|
||||
/owner/repo/src/branch/main/src/backend/catalog/information_schema.sql
|
||||
/owner/repo/raw/branch/main/src/backend/catalog/information_schema.sql
|
||||
/owner/repo/_edit/main/src/backend/catalog/information_schema.sql
|
||||
/api/v1/repos/owner/repo/contents/src/backend/catalog/information_schema.sql
|
||||
/owner/repo/src/branch/main/docs/a%20b.xhtml
|
||||
/owner/repo/raw/branch/main/docs/a%20b.xhtml
|
||||
/owner/repo/_edit/main/docs/a%20b.xhtml
|
||||
/api/v1/repos/owner/repo/contents/docs/a%20b.xhtml
|
||||
/owner/repo/src/branch/main/%5Bdraft%5D.md
|
||||
/owner/repo/raw/branch/main/%5Bdraft%5D.md
|
||||
/owner/repo/_edit/main/%5Bdraft%5D.md
|
||||
/api/v1/repos/owner/repo/contents/%5Bdraft%5D.md
|
||||
/owner/repo/src/branch/main/notes.txt~
|
||||
/owner/repo/raw/branch/main/notes.txt~
|
||||
/owner/repo/_edit/main/notes.txt~
|
||||
/api/v1/repos/owner/repo/contents/notes.txt~
|
||||
|
||||
# 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
|
||||
# sign-in page on the way to its edit page.
|
||||
/owner/repo/graph?file=package.json
|
||||
/api/v1/repos/owner/repo/commits?path=package.json
|
||||
/api/v1/repos/owner/repo/file-contents?body=%7B%22files%22%3A%5B%22package.json%22%5D%7D
|
||||
/owner/repo/issues/new?template=.gitea%2FISSUE_TEMPLATE%2Fpackage.json
|
||||
/user/login?redirect_to=%2Fowner%2Frepo%2F_edit%2Fmain%2Fpackage.json
|
||||
/owner/repo/graph?file=.gitignore
|
||||
/api/v1/repos/owner/repo/commits?path=.gitignore
|
||||
/api/v1/repos/owner/repo/file-contents?body=%7B%22files%22%3A%5B%22.gitignore%22%5D%7D
|
||||
/owner/repo/issues/new?template=.gitea%2FISSUE_TEMPLATE%2F.gitignore
|
||||
/user/login?redirect_to=%2Fowner%2Frepo%2F_edit%2Fmain%2F.gitignore
|
||||
/owner/repo/graph?file=docker-compose.yml
|
||||
/api/v1/repos/owner/repo/commits?path=docker-compose.yml
|
||||
/api/v1/repos/owner/repo/file-contents?body=%7B%22files%22%3A%5B%22docker-compose.yml%22%5D%7D
|
||||
/owner/repo/issues/new?template=.gitea%2FISSUE_TEMPLATE%2Fdocker-compose.yml
|
||||
/user/login?redirect_to=%2Fowner%2Frepo%2F_edit%2Fmain%2Fdocker-compose.yml
|
||||
/owner/repo/graph?file=.htaccess
|
||||
/api/v1/repos/owner/repo/commits?path=.htaccess
|
||||
/api/v1/repos/owner/repo/file-contents?body=%7B%22files%22%3A%5B%22.htaccess%22%5D%7D
|
||||
/owner/repo/issues/new?template=.gitea%2FISSUE_TEMPLATE%2F.htaccess
|
||||
/user/login?redirect_to=%2Fowner%2Frepo%2F_edit%2Fmain%2F.htaccess
|
||||
/owner/repo/graph?file=rootfs%2Fetc%2Fpasswd
|
||||
/api/v1/repos/owner/repo/commits?path=rootfs%2Fetc%2Fpasswd
|
||||
/api/v1/repos/owner/repo/file-contents?body=%7B%22files%22%3A%5B%22rootfs%2Fetc%2Fpasswd%22%5D%7D
|
||||
/owner/repo/issues/new?template=.gitea%2FISSUE_TEMPLATE%2Frootfs%2Fetc%2Fpasswd
|
||||
/user/login?redirect_to=%2Fowner%2Frepo%2F_edit%2Fmain%2Frootfs%2Fetc%2Fpasswd
|
||||
/owner/repo/graph?file=bin%2Fdocker-entrypoint
|
||||
/api/v1/repos/owner/repo/commits?path=bin%2Fdocker-entrypoint
|
||||
/api/v1/repos/owner/repo/file-contents?body=%7B%22files%22%3A%5B%22bin%2Fdocker-entrypoint%22%5D%7D
|
||||
/owner/repo/issues/new?template=.gitea%2FISSUE_TEMPLATE%2Fbin%2Fdocker-entrypoint
|
||||
/user/login?redirect_to=%2Fowner%2Frepo%2F_edit%2Fmain%2Fbin%2Fdocker-entrypoint
|
||||
/owner/repo/graph?file=scripts%2Fpython3-setup.sh
|
||||
/api/v1/repos/owner/repo/commits?path=scripts%2Fpython3-setup.sh
|
||||
/api/v1/repos/owner/repo/file-contents?body=%7B%22files%22%3A%5B%22scripts%2Fpython3-setup.sh%22%5D%7D
|
||||
/owner/repo/issues/new?template=.gitea%2FISSUE_TEMPLATE%2Fscripts%2Fpython3-setup.sh
|
||||
/user/login?redirect_to=%2Fowner%2Frepo%2F_edit%2Fmain%2Fscripts%2Fpython3-setup.sh
|
||||
/owner/repo/graph?file=src%2Fjava.base%2Fshare%2Fclasses%2Fjava%2Flang%2FRuntime.java
|
||||
/api/v1/repos/owner/repo/commits?path=src%2Fjava.base%2Fshare%2Fclasses%2Fjava%2Flang%2FRuntime.java
|
||||
/api/v1/repos/owner/repo/file-contents?body=%7B%22files%22%3A%5B%22src%2Fjava.base%2Fshare%2Fclasses%2Fjava%2Flang%2FRuntime.java%22%5D%7D
|
||||
/owner/repo/issues/new?template=.gitea%2FISSUE_TEMPLATE%2Fsrc%2Fjava.base%2Fshare%2Fclasses%2Fjava%2Flang%2FRuntime.java
|
||||
/user/login?redirect_to=%2Fowner%2Frepo%2F_edit%2Fmain%2Fsrc%2Fjava.base%2Fshare%2Fclasses%2Fjava%2Flang%2FRuntime.java
|
||||
/owner/repo/graph?file=java.lang.String.md
|
||||
/api/v1/repos/owner/repo/commits?path=java.lang.String.md
|
||||
/api/v1/repos/owner/repo/file-contents?body=%7B%22files%22%3A%5B%22java.lang.String.md%22%5D%7D
|
||||
/owner/repo/issues/new?template=.gitea%2FISSUE_TEMPLATE%2Fjava.lang.String.md
|
||||
/user/login?redirect_to=%2Fowner%2Frepo%2F_edit%2Fmain%2Fjava.lang.String.md
|
||||
/owner/repo/graph?file=META-INF%2Fservices%2Forg.apache.commons.logging.LogFactory
|
||||
/api/v1/repos/owner/repo/commits?path=META-INF%2Fservices%2Forg.apache.commons.logging.LogFactory
|
||||
/api/v1/repos/owner/repo/file-contents?body=%7B%22files%22%3A%5B%22META-INF%2Fservices%2Forg.apache.commons.logging.LogFactory%22%5D%7D
|
||||
/owner/repo/issues/new?template=.gitea%2FISSUE_TEMPLATE%2FMETA-INF%2Fservices%2Forg.apache.commons.logging.LogFactory
|
||||
/user/login?redirect_to=%2Fowner%2Frepo%2F_edit%2Fmain%2FMETA-INF%2Fservices%2Forg.apache.commons.logging.LogFactory
|
||||
/owner/repo/graph?file=process.env.d.ts
|
||||
/api/v1/repos/owner/repo/commits?path=process.env.d.ts
|
||||
/api/v1/repos/owner/repo/file-contents?body=%7B%22files%22%3A%5B%22process.env.d.ts%22%5D%7D
|
||||
/owner/repo/issues/new?template=.gitea%2FISSUE_TEMPLATE%2Fprocess.env.d.ts
|
||||
/user/login?redirect_to=%2Fowner%2Frepo%2F_edit%2Fmain%2Fprocess.env.d.ts
|
||||
/owner/repo/graph?file=document.write.js
|
||||
/api/v1/repos/owner/repo/commits?path=document.write.js
|
||||
/api/v1/repos/owner/repo/file-contents?body=%7B%22files%22%3A%5B%22document.write.js%22%5D%7D
|
||||
/owner/repo/issues/new?template=.gitea%2FISSUE_TEMPLATE%2Fdocument.write.js
|
||||
/user/login?redirect_to=%2Fowner%2Frepo%2F_edit%2Fmain%2Fdocument.write.js
|
||||
/owner/repo/graph?file=test%2Ffixtures%2F__proto__.json
|
||||
/api/v1/repos/owner/repo/commits?path=test%2Ffixtures%2F__proto__.json
|
||||
/api/v1/repos/owner/repo/file-contents?body=%7B%22files%22%3A%5B%22test%2Ffixtures%2F__proto__.json%22%5D%7D
|
||||
/owner/repo/issues/new?template=.gitea%2FISSUE_TEMPLATE%2Ftest%2Ffixtures%2F__proto__.json
|
||||
/user/login?redirect_to=%2Fowner%2Frepo%2F_edit%2Fmain%2Ftest%2Ffixtures%2F__proto__.json
|
||||
/owner/repo/graph?file=install-script.sh
|
||||
/api/v1/repos/owner/repo/commits?path=install-script.sh
|
||||
/api/v1/repos/owner/repo/file-contents?body=%7B%22files%22%3A%5B%22install-script.sh%22%5D%7D
|
||||
/owner/repo/issues/new?template=.gitea%2FISSUE_TEMPLATE%2Finstall-script.sh
|
||||
/user/login?redirect_to=%2Fowner%2Frepo%2F_edit%2Fmain%2Finstall-script.sh
|
||||
/owner/repo/graph?file=src%2Fbackend%2Fcatalog%2Finformation_schema.sql
|
||||
/api/v1/repos/owner/repo/commits?path=src%2Fbackend%2Fcatalog%2Finformation_schema.sql
|
||||
/api/v1/repos/owner/repo/file-contents?body=%7B%22files%22%3A%5B%22src%2Fbackend%2Fcatalog%2Finformation_schema.sql%22%5D%7D
|
||||
/owner/repo/issues/new?template=.gitea%2FISSUE_TEMPLATE%2Fsrc%2Fbackend%2Fcatalog%2Finformation_schema.sql
|
||||
/user/login?redirect_to=%2Fowner%2Frepo%2F_edit%2Fmain%2Fsrc%2Fbackend%2Fcatalog%2Finformation_schema.sql
|
||||
/owner/repo/graph?file=docs%2Fa%20b.xhtml
|
||||
/api/v1/repos/owner/repo/commits?path=docs%2Fa%20b.xhtml
|
||||
/api/v1/repos/owner/repo/file-contents?body=%7B%22files%22%3A%5B%22docs%2Fa%20b.xhtml%22%5D%7D
|
||||
/owner/repo/issues/new?template=.gitea%2FISSUE_TEMPLATE%2Fdocs%2Fa%20b.xhtml
|
||||
/user/login?redirect_to=%2Fowner%2Frepo%2F_edit%2Fmain%2Fdocs%2Fa%20b.xhtml
|
||||
/owner/repo/graph?file=%5Bdraft%5D.md
|
||||
/api/v1/repos/owner/repo/commits?path=%5Bdraft%5D.md
|
||||
/api/v1/repos/owner/repo/file-contents?body=%7B%22files%22%3A%5B%22%5Bdraft%5D.md%22%5D%7D
|
||||
/owner/repo/issues/new?template=.gitea%2FISSUE_TEMPLATE%2F%5Bdraft%5D.md
|
||||
/user/login?redirect_to=%2Fowner%2Frepo%2F_edit%2Fmain%2F%5Bdraft%5D.md
|
||||
|
||||
# File names of attachments uploaded through the API, to a release, an issue and
|
||||
# a comment.
|
||||
/api/v1/repos/owner/repo/releases/1/assets?name=package.json
|
||||
/api/v1/repos/owner/repo/releases/1/assets?name=.gitignore
|
||||
/api/v1/repos/owner/repo/releases/1/assets?name=docker-compose.yml
|
||||
/api/v1/repos/owner/repo/releases/1/assets?name=.htaccess
|
||||
/api/v1/repos/owner/repo/releases/1/assets?name=docker-entrypoint
|
||||
/api/v1/repos/owner/repo/releases/1/assets?name=python3-setup.sh
|
||||
/api/v1/repos/owner/repo/releases/1/assets?name=Runtime.java
|
||||
/api/v1/repos/owner/repo/releases/1/assets?name=org.apache.commons.logging.LogFactory
|
||||
/api/v1/repos/owner/repo/releases/1/assets?name=%5Bdraft%5D.md
|
||||
/api/v1/repos/owner/repo/releases/1/assets?name=notes.txt~
|
||||
/api/v1/repos/owner/repo/releases/1/assets?name=install-script.sh
|
||||
/api/v1/repos/owner/repo/issues/1/assets?name=install-script.sh
|
||||
/api/v1/repos/owner/repo/issues/comments/1/assets?name=install-script.sh
|
||||
/api/v1/repos/owner/repo/releases/1/assets?name=process.env.d.ts
|
||||
/api/v1/repos/owner/repo/issues/1/assets?name=process.env.d.ts
|
||||
/api/v1/repos/owner/repo/issues/comments/1/assets?name=process.env.d.ts
|
||||
/api/v1/repos/owner/repo/releases/1/assets?name=java.lang.String.md
|
||||
/api/v1/repos/owner/repo/issues/1/assets?name=java.lang.String.md
|
||||
/api/v1/repos/owner/repo/issues/comments/1/assets?name=java.lang.String.md
|
||||
/api/v1/repos/owner/repo/releases/1/assets?name=information_schema.sql
|
||||
/api/v1/repos/owner/repo/issues/1/assets?name=information_schema.sql
|
||||
/api/v1/repos/owner/repo/issues/comments/1/assets?name=information_schema.sql
|
||||
/api/v1/repos/owner/repo/releases/1/assets?name=document.write.js
|
||||
/api/v1/repos/owner/repo/issues/1/assets?name=document.write.js
|
||||
/api/v1/repos/owner/repo/issues/comments/1/assets?name=document.write.js
|
||||
/api/v1/repos/owner/repo/releases/1/assets?name=__proto__.json
|
||||
/api/v1/repos/owner/repo/issues/1/assets?name=__proto__.json
|
||||
/api/v1/repos/owner/repo/issues/comments/1/assets?name=__proto__.json
|
||||
/api/v1/repos/owner/repo/releases/1/assets?name=index.xhtml
|
||||
/api/v1/repos/owner/repo/issues/1/assets?name=index.xhtml
|
||||
/api/v1/repos/owner/repo/issues/comments/1/assets?name=index.xhtml
|
||||
|
||||
# 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
|
||||
|
||||
# Branch and tag names in the query string: deleting a branch, starting a
|
||||
# release from a tag, a branch protection rule, a directory listing's last
|
||||
# commits, and a container image push that reuses a layer.
|
||||
/owner/repo/branches?name=docker-build
|
||||
/owner/repo/releases/new?tag=docker-build
|
||||
/owner/repo/settings/branches/edit?rule_name=docker-build
|
||||
/owner/repo/lastcommit/main/src?refSubUrl=branch%2Fdocker-build
|
||||
/v2/owner/app/blobs/uploads/?mount=sha256%3A0000&from=owner%2Fdocker-build
|
||||
/owner/repo/branches?name=feat%2Fdocker-support
|
||||
/owner/repo/releases/new?tag=feat%2Fdocker-support
|
||||
/owner/repo/settings/branches/edit?rule_name=feat%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
|
||||
/owner/repo/branches?name=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/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
|
||||
/owner/repo/branches?name=python3-migration
|
||||
/owner/repo/releases/new?tag=python3-migration
|
||||
/owner/repo/settings/branches/edit?rule_name=python3-migration
|
||||
/owner/repo/lastcommit/main/src?refSubUrl=branch%2Fpython3-migration
|
||||
/v2/owner/app/blobs/uploads/?mount=sha256%3A0000&from=owner%2Fpython3-migration
|
||||
/owner/repo/branches?name=ansible-roles
|
||||
/owner/repo/releases/new?tag=ansible-roles
|
||||
/owner/repo/settings/branches/edit?rule_name=ansible-roles
|
||||
/owner/repo/lastcommit/main/src?refSubUrl=branch%2Fansible-roles
|
||||
/v2/owner/app/blobs/uploads/?mount=sha256%3A0000&from=owner%2Fansible-roles
|
||||
/owner/repo/branches?name=whoami
|
||||
/owner/repo/releases/new?tag=whoami
|
||||
/owner/repo/settings/branches/edit?rule_name=whoami
|
||||
/owner/repo/lastcommit/main/src?refSubUrl=branch%2Fwhoami
|
||||
/v2/owner/app/blobs/uploads/?mount=sha256%3A0000&from=owner%2Fwhoami
|
||||
|
||||
# Pages a signed-in user opens.
|
||||
/
|
||||
/owner/repo
|
||||
/owner/repo/issues
|
||||
/owner/repo/pulls
|
||||
/owner/repo/releases
|
||||
/owner/repo/wiki
|
||||
/owner/repo/activity
|
||||
/owner/repo/branches
|
||||
/owner/repo/tags
|
||||
/owner/repo/settings
|
||||
/owner/repo/actions
|
||||
/owner/repo/compare/main...main
|
||||
/owner/repo/commits/branch/main
|
||||
/owner/repo/archive/main.zip
|
||||
/notifications
|
||||
/user/settings
|
||||
/user/settings/applications
|
||||
/explore/repos
|
||||
/-/admin
|
||||
+94
-13
@@ -1,8 +1,9 @@
|
||||
// Package waf runs the OWASP Core Rule Set 4.25.0, through Coraza, on the
|
||||
// method, the URL with its query and the headers of a request, and on its
|
||||
// body while SWWAF_WAF_BODY_LIMIT is set, with the six changes smallwebwaf
|
||||
// makes to it, as "Attack detection" under "Settings" in SPEC.md describes
|
||||
// them. It reads no response.
|
||||
// body while SWWAF_WAF_BODY_LIMIT is set, with the seven changes smallwebwaf
|
||||
// makes to it and the exclusions in SWWAF_CORE_RULE_SET_EXCLUSIONS_FILE, as
|
||||
// "Attack detection" under "Settings" in SPEC.md describes them. It reads no
|
||||
// response.
|
||||
//
|
||||
// smallwebwaf writes only to its state directory, so Coraza is built with
|
||||
// its no_fs_access tag, as the Dockerfile and script/build build it: of a
|
||||
@@ -11,10 +12,13 @@
|
||||
package waf
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -26,12 +30,13 @@ import (
|
||||
)
|
||||
|
||||
// directives are the Core Rule Set as smallwebwaf runs it, with the
|
||||
// paranoia level for %d, and bodyDirectives for %s while
|
||||
// SWWAF_WAF_BODY_LIMIT is set. Each rule smallwebwaf adds has an id from
|
||||
// 900000 to 900999, the ids the Core Rule Set keeps for the rules that set
|
||||
// it up, which SWWAF_WAF_DISABLED_RULES refuses, so that no setting
|
||||
// switches one off. Coraza joins a line ending in \ to the next, without
|
||||
// the spaces at the start of the next.
|
||||
// paranoia level for %d, bodyDirectives for the first %s while
|
||||
// SWWAF_WAF_BODY_LIMIT is set, and the exclusions file for the second.
|
||||
// Each rule smallwebwaf adds has an id from 900000 to 900999, the ids the
|
||||
// Core Rule Set keeps for the rules that set it up, which
|
||||
// SWWAF_WAF_DISABLED_RULES and the exclusions file refuse, so that no
|
||||
// setting switches one off. Coraza joins a line ending in \ to the next,
|
||||
// without the spaces at the start of the next.
|
||||
const directives = `
|
||||
# The engine only detects. smallwebwaf compares the request's anomaly
|
||||
# score with SWWAF_WAF_ANOMALY_THRESHOLD itself, in block and detect mode
|
||||
@@ -69,6 +74,11 @@ SecArgumentsLimit 1000
|
||||
SecRule ARGUMENTS_LIMIT_REACHED "@eq 1" "id:900300,phase:2,pass,\
|
||||
severity:'CRITICAL',setvar:'tx.inbound_anomaly_score_pl1=+5'"
|
||||
|
||||
# The exclusions file comes before the rules, where the Core Rule Set puts
|
||||
# its own file of exclusions that run before the rules, so that they run
|
||||
# before the rules of phase 1 too.
|
||||
%s
|
||||
|
||||
# The sixth: only the rules for requests are loaded, and no response is
|
||||
# inspected.
|
||||
Include @owasp_crs/REQUEST-*.conf
|
||||
@@ -100,6 +110,13 @@ SecRuleUpdateTargetById 932260 "!ARGS:path|!ARGS:files|!ARGS:skip-to|\
|
||||
# Inspect.
|
||||
SecRuleUpdateTargetById 932340 "!REQUEST_HEADERS:Referer"
|
||||
SecRuleUpdateTargetById 944110 "!REQUEST_HEADERS:Referer"
|
||||
|
||||
# The seventh: a container image push that reuses a layer names the image
|
||||
# it takes the layer from in the query parameter from, which is not
|
||||
# checked for command names. 932260 refused the push of a layer from
|
||||
# owner/python3-app.
|
||||
SecRule REQUEST_FILENAME "@rx ^/v2/.+/blobs/uploads/$" \
|
||||
"id:900500,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932260;ARGS:from"
|
||||
`
|
||||
|
||||
// bodyDirectives have the Core Rule Set read the part of a request body
|
||||
@@ -160,6 +177,51 @@ type Params struct {
|
||||
// BodyLimit is the most of a request body the Core Rule Set reads
|
||||
// (SWWAF_WAF_BODY_LIMIT), 0 while it is off and it reads none.
|
||||
BodyLimit int64
|
||||
// ExclusionsFile is the path of a file of exclusions loaded before the
|
||||
// Core Rule Set's rules (SWWAF_CORE_RULE_SET_EXCLUSIONS_FILE), "" while
|
||||
// there is none.
|
||||
ExclusionsFile string
|
||||
}
|
||||
|
||||
// The ids of smallwebwaf's own rules, which an exclusions file may not
|
||||
// name.
|
||||
const (
|
||||
firstOwnRuleID = 900000
|
||||
lastOwnRuleID = 900999
|
||||
)
|
||||
|
||||
// numberOrRange matches a number, or a range of numbers such as
|
||||
// 900000-900999.
|
||||
var numberOrRange = regexp.MustCompile(`(\d+)(?:-(\d+))?`)
|
||||
|
||||
// errSmallwebwafRuleID is the error of an exclusions file that names an
|
||||
// id of smallwebwaf's own rules.
|
||||
var errSmallwebwafRuleID = errors.New("an id of smallwebwaf's own rules")
|
||||
|
||||
// ownRuleNamed returns the first number or range in exclusions, the text
|
||||
// of an exclusions file, that takes in an id of smallwebwaf's own rules,
|
||||
// or "" if none does. Comment lines are skipped, as Coraza skips them.
|
||||
func ownRuleNamed(exclusions string) string {
|
||||
for line := range strings.Lines(exclusions) {
|
||||
if strings.HasPrefix(strings.TrimSpace(line), "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, match := range numberOrRange.FindAllStringSubmatch(line, -1) {
|
||||
first, _ := strconv.Atoi(match[1])
|
||||
|
||||
last := first
|
||||
if match[2] != "" {
|
||||
last, _ = strconv.Atoi(match[2])
|
||||
}
|
||||
|
||||
if first <= lastOwnRuleID && last >= firstOwnRuleID {
|
||||
return match[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// CoreRuleSet is the Core Rule Set, ready to inspect requests. It is safe
|
||||
@@ -169,16 +231,35 @@ type CoreRuleSet struct {
|
||||
bodyLimit int64
|
||||
}
|
||||
|
||||
// New returns the Core Rule Set with the six changes, at params'
|
||||
// paranoia level, without the rules it switches off, and reading request
|
||||
// bodies up to params' limit.
|
||||
// New returns the Core Rule Set with the seven changes, at params'
|
||||
// paranoia level, without the rules it switches off, reading request
|
||||
// bodies up to params' limit, and with params' exclusions file loaded
|
||||
// before the Core Rule Set's rules. An exclusions file that cannot be
|
||||
// read, does not parse or names an id of smallwebwaf's own rules is an
|
||||
// error.
|
||||
func New(params Params) (*CoreRuleSet, error) {
|
||||
body := ""
|
||||
if params.BodyLimit > 0 {
|
||||
body = fmt.Sprintf(bodyDirectives, params.BodyLimit)
|
||||
}
|
||||
|
||||
text := fmt.Sprintf(directives, params.ParanoiaLevel, body)
|
||||
exclusions := ""
|
||||
|
||||
if params.ExclusionsFile != "" {
|
||||
file, err := os.ReadFile(params.ExclusionsFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read the exclusions file: %w", err)
|
||||
}
|
||||
|
||||
exclusions = string(file)
|
||||
|
||||
if named := ownRuleNamed(exclusions); named != "" {
|
||||
return nil, fmt.Errorf("the exclusions file names %s: %w", named,
|
||||
errSmallwebwafRuleID)
|
||||
}
|
||||
}
|
||||
|
||||
text := fmt.Sprintf(directives, params.ParanoiaLevel, body, exclusions)
|
||||
|
||||
if len(params.DisabledRules) > 0 {
|
||||
ids := make([]string, len(params.DisabledRules))
|
||||
|
||||
+163
-1
@@ -5,6 +5,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
@@ -119,7 +120,7 @@ func atDefaults(t *testing.T) *waf.CoreRuleSet {
|
||||
}
|
||||
|
||||
// wantChange checks that crs lets through passes, a gitea request one of
|
||||
// the six 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.
|
||||
func wantChange(
|
||||
t *testing.T, crs *waf.CoreRuleSet, passes, refused request, result waf.Result,
|
||||
@@ -362,6 +363,167 @@ func TestEachDisabledRuleIsSwitchedOff(t *testing.T) {
|
||||
wantResult(t, newCoreRuleSet(t, 1, 920350, 911100), r, waf.Result{})
|
||||
}
|
||||
|
||||
func TestImagePushThatReusesALayerIsNotCheckedForCommandNames(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
crs := atDefaults(t)
|
||||
reuse := "?mount=sha256:0000&from=owner/python3-app"
|
||||
|
||||
wantChange(t, crs, get("/v2/owner/app/blobs/uploads/"+reuse), get("/upload/"+reuse),
|
||||
matched(932260))
|
||||
|
||||
// A command after the image's name is still refused.
|
||||
wantResult(t, crs,
|
||||
get("/v2/owner/app/blobs/uploads/?from=owner/app;cat%20/etc/passwd"),
|
||||
matched(930120, 932160))
|
||||
}
|
||||
|
||||
// withExclusions returns the Core Rule Set at the defaults, with the
|
||||
// exclusions file at path.
|
||||
func withExclusions(path string) (*waf.CoreRuleSet, error) {
|
||||
return waf.New(waf.Params{
|
||||
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, ExclusionsFile: path,
|
||||
})
|
||||
}
|
||||
|
||||
// exclusionsFile writes text to a file of exclusions and returns its path.
|
||||
func exclusionsFile(t *testing.T, text string) string {
|
||||
t.Helper()
|
||||
|
||||
path := filepath.Join(t.TempDir(), "exclusions.conf")
|
||||
|
||||
err := os.WriteFile(path, []byte(text), 0o600)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
// backupFileExclusion is an exclusions file that switches 920500, a rule
|
||||
// of phase 1, off for the path of a backup file under /files/.
|
||||
const backupFileExclusion = `SecRule REQUEST_FILENAME "@beginsWith /files/" ` +
|
||||
`"id:1,phase:1,pass,nolog,ctl:ruleRemoveTargetById=920500;REQUEST_FILENAME"` + "\n"
|
||||
|
||||
func TestExclusionsFileIsLoadedBeforeTheRules(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
crs, err := withExclusions(exclusionsFile(t, backupFileExclusion))
|
||||
if err != nil {
|
||||
t.Fatalf("load the exclusions: %v", err)
|
||||
}
|
||||
|
||||
wantChange(t, crs, get("/files/notes.txt~"), get("/notes.txt~"), matched(920500))
|
||||
}
|
||||
|
||||
func TestRuleSwitchedOffAndNamedInTheExclusionsFileIsOff(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
crs, err := waf.New(waf.Params{
|
||||
ParanoiaLevel: 1, DisabledRules: []int{920500},
|
||||
ExclusionsFile: exclusionsFile(t, backupFileExclusion),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("load the exclusions: %v", err)
|
||||
}
|
||||
|
||||
wantResult(t, crs, get("/notes.txt~"), waf.Result{})
|
||||
}
|
||||
|
||||
func TestExclusionsFileMayNameAnyNumberInAComment(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := withExclusions(exclusionsFile(t,
|
||||
"# 942100: GET /issues/900123\n"+backupFileExclusion))
|
||||
if err != nil {
|
||||
t.Errorf("the exclusions did not load: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExclusionsFileThatCannotBeUsedIsAnError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, path := range []string{
|
||||
filepath.Join(t.TempDir(), "missing.conf"),
|
||||
exclusionsFile(t, "SecNoSuchDirective On\n"),
|
||||
// The Core Rule Set's rules are not loaded yet.
|
||||
exclusionsFile(t, "SecRuleUpdateTargetById 942100 \"!ARGS:id\"\n"),
|
||||
// smallwebwaf's own rules may not be switched off or changed, by
|
||||
// id or in a range.
|
||||
exclusionsFile(t, "SecRuleRemoveById 900300\n"),
|
||||
exclusionsFile(t, "SecRuleRemoveById 899999-901000\n"),
|
||||
exclusionsFile(t, "SecAction \"id:1,phase:1,pass,nolog,"+
|
||||
"ctl:ruleRemoveById=899999-901000\"\n"),
|
||||
exclusionsFile(t, "SecAction \"id:1,phase:1,pass,nolog,"+
|
||||
"ctl:ruleRemoveTargetById=900500;ARGS:from\"\n"),
|
||||
} {
|
||||
_, err := withExclusions(path)
|
||||
if err == nil {
|
||||
t.Errorf("%s loaded", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// giteaRequests returns the recorded ordinary gitea requests in testdata,
|
||||
// which script/gitea-setup sends too. Each has the Referer of a page that
|
||||
// names the same, as a page opened from it has.
|
||||
func giteaRequests(t *testing.T) []request {
|
||||
t.Helper()
|
||||
|
||||
text, err := os.ReadFile(filepath.Join("testdata", "gitea-requests.txt"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var requests []request
|
||||
|
||||
for line := range strings.Lines(string(text)) {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
target, header, _ := strings.Cut(line, " ")
|
||||
|
||||
headers := []string{"Referer: http://git.example" + target}
|
||||
if header != "" {
|
||||
headers = append(headers, header)
|
||||
}
|
||||
|
||||
requests = append(requests, get(target, headers...))
|
||||
}
|
||||
|
||||
return requests
|
||||
}
|
||||
|
||||
func TestGiteaExclusionsLetOrdinaryGiteaRequestsThroughAndRefuseAttacks(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
crs, err := withExclusions(filepath.Join("..", "..", "deploy", "gitea",
|
||||
"crs-exclusions.conf"))
|
||||
if err != nil {
|
||||
t.Fatalf("load gitea's exclusions: %v", err)
|
||||
}
|
||||
|
||||
const threshold = 5 // SWWAF_WAF_ANOMALY_THRESHOLD's default
|
||||
|
||||
for _, r := range giteaRequests(t) {
|
||||
if got := inspect(t, crs, r); got.Score >= threshold {
|
||||
t.Errorf("%s %q refused: %+v", r.target, r.headers[1:], got)
|
||||
}
|
||||
}
|
||||
|
||||
for _, attack := range []string{
|
||||
"/owner/repo?id=1'%20OR%20'1'='1",
|
||||
"/owner/repo?id=..%2Fcustom%2Fconf%2Fapp.ini",
|
||||
"/owner/repo?id=%3Cscript%3Ealert(1)%3C%2Fscript%3E",
|
||||
} {
|
||||
if got := inspect(t, crs, get(attack)); got.Score < threshold {
|
||||
t.Errorf("%s let through: %+v", attack, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// bodyLimit is SWWAF_WAF_BODY_LIMIT in the tests that read bodies.
|
||||
const bodyLimit = 8 << 10
|
||||
|
||||
|
||||
Reference in new issue
Block a user