Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5ff3ef763 |
No files matched your search
@@ -589,8 +589,8 @@ client's limits by tag. See "Request tags" in [`SPEC.md`](SPEC.md).
|
|||||||
- Each supported app also has its own fixes, which let its ordinary requests
|
- Each supported app also has its own fixes, which let its ordinary requests
|
||||||
through. Its recorded ordinary requests pass the Core Rule Set at the default
|
through. Its recorded ordinary requests pass the Core Rule Set at the default
|
||||||
settings. The supported apps, by their names in `SWW_APPS`, are:
|
settings. The supported apps, by their names in `SWW_APPS`, are:
|
||||||
- `gitea`, whose fixes let its container registry, searches, file names and
|
- `gitea`, whose fixes let its container registry, searches, file names,
|
||||||
branch names through;
|
branch names and repository names through;
|
||||||
- `forgejo`, a fork of gitea, which gets gitea's fixes too, and whose own
|
- `forgejo`, a fork of gitea, which gets gitea's fixes too, and whose own
|
||||||
fixes let its searches through;
|
fixes let its searches through;
|
||||||
- `wordpress`, whose fixes let through its searches, its names, its file
|
- `wordpress`, whose fixes let through its searches, its names, its file
|
||||||
|
|||||||
@@ -826,9 +826,9 @@ one entry of `apps` in `internal/coreruleset/coreruleset.go`. gitea's fixes are:
|
|||||||
with any script removed and returns only to its own pages. Every other cookie
|
with any script removed and returns only to its own pages. Every other cookie
|
||||||
is read in full.
|
is read in full.
|
||||||
3. On gitea's paths, some fields skip the rules that refused ordinary gitea
|
3. On gitea's paths, some fields skip the rules that refused ordinary gitea
|
||||||
requests there. They are the fields where gitea sends a search, a file name
|
requests there. They are the fields where gitea sends a search, a file name,
|
||||||
or a branch name. `internal/coreruleset/gitea.conf` lists each rule and the
|
a branch name or a repository's name. `internal/coreruleset/gitea.conf` lists
|
||||||
request it refused. The fields are:
|
each rule and the request it refused. The fields are:
|
||||||
- `q` on gitea's searches of code, of issues and pull requests, and of
|
- `q` on gitea's searches of code, of issues and pull requests, and of
|
||||||
commits. A code search holds code, such as `<script>`,
|
commits. A code search holds code, such as `<script>`,
|
||||||
`SELECT * FROM users WHERE` or `../`. The searches of issues and pull
|
`SELECT * FROM users WHERE` or `../`. The searches of issues and pull
|
||||||
@@ -924,6 +924,15 @@ one entry of `apps` in `internal/coreruleset/coreruleset.go`. gitea's fixes are:
|
|||||||
- to start a release from a tag;
|
- to start a release from a tag;
|
||||||
- to edit a branch protection rule;
|
- to edit a branch protection rule;
|
||||||
- to list a directory's last commits.
|
- to list a directory's last commits.
|
||||||
|
- `id` of the forms that adopt or delete an unadopted repository, for the
|
||||||
|
rules that refused repository names (930120, 932120, 932160, 932260). `id`
|
||||||
|
holds the repository's name, such as `python3-app` or `.profile`. The
|
||||||
|
admin forms put the owner in front. There the rule for shell paths refused
|
||||||
|
`robin/assets`, which holds `bin/as`. The forms post to these paths:
|
||||||
|
- `/-/admin/repos/unadopted`, gitea's admin form;
|
||||||
|
- `/admin/repos/unadopted`, Forgejo's admin form;
|
||||||
|
- `/user/settings/repos/unadopted`, the form in a user's settings, on
|
||||||
|
both.
|
||||||
- The `Content-Range` header of an artifact upload from
|
- The `Content-Range` header of an artifact upload from
|
||||||
`actions/upload-artifact@v3` (920450).
|
`actions/upload-artifact@v3` (920450).
|
||||||
- The passwords in a body, for each rule that random passwords set off. The
|
- The passwords in a body, for each rule that random passwords set off. The
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# gitea's fixes: some fields skip the rules that refused ordinary gitea requests.
|
# gitea's fixes: some fields skip the rules that refused ordinary gitea requests.
|
||||||
# They are the fields where gitea sends a search, a file name or a branch name.
|
# They are the fields where gitea sends a search, a file name, a branch name or
|
||||||
# Each exclusion runs with the request, before the rules it switches off, those
|
# a repository's name. Each exclusion runs with the request, before the rules it
|
||||||
# of phase 1 included, so this file is loaded before the Core Rule Set's rules.
|
# switches off, those of phase 1 included, so this file is loaded before the
|
||||||
# Each comment names a request the rule refused.
|
# Core Rule Set's rules. Each comment names a request the rule refused.
|
||||||
|
|
||||||
# Every rule below is skipped unless Inspect has set tx.app_gitea. It sets it
|
# Every rule below is skipped unless Inspect has set tx.app_gitea. It sets it
|
||||||
# while SWW_APPS is unset, or lists gitea or forgejo for the request's hostname.
|
# while SWW_APPS is unset, or lists gitea or forgejo for the request's hostname.
|
||||||
@@ -399,6 +399,25 @@ SecRule REQUEST_FILENAME \
|
|||||||
"id:900640,phase:1,pass,nolog,\
|
"id:900640,phase:1,pass,nolog,\
|
||||||
ctl:ruleRemoveTargetById=920450;REQUEST_HEADERS_NAMES:Content-Range"
|
ctl:ruleRemoveTargetById=920450;REQUEST_HEADERS_NAMES:Content-Range"
|
||||||
|
|
||||||
|
# The forms that adopt or delete an unadopted repository send its name in id.
|
||||||
|
# The admin forms put its owner in front, as alice/python3-app. id skips these
|
||||||
|
# rules there:
|
||||||
|
# 930120: alice/.profile
|
||||||
|
# 932120: alice/powershell-scripts
|
||||||
|
# 932160: robin/assets, which holds bin/as
|
||||||
|
# 932260: alice/python3-app
|
||||||
|
# The forms post to these paths:
|
||||||
|
# /-/admin/repos/unadopted: gitea's admin form
|
||||||
|
# /admin/repos/unadopted: Forgejo's admin form
|
||||||
|
# /user/settings/repos/unadopted: the form in a user's settings, on both
|
||||||
|
SecRule REQUEST_FILENAME \
|
||||||
|
"@rx ^/(?:(?:-/)?admin|user/settings)/repos/unadopted$" \
|
||||||
|
"id:900645,phase:1,pass,nolog,\
|
||||||
|
ctl:ruleRemoveTargetById=930120;ARGS:id,\
|
||||||
|
ctl:ruleRemoveTargetById=932120;ARGS:id,\
|
||||||
|
ctl:ruleRemoveTargetById=932160;ARGS:id,\
|
||||||
|
ctl:ruleRemoveTargetById=932260;ARGS:id"
|
||||||
|
|
||||||
# The passwords skip each rule that random passwords set off:
|
# The passwords skip each rule that random passwords set off:
|
||||||
# password: signing in, signing up, linking an account, activating an account,
|
# password: signing in, signing up, linking an account, activating an account,
|
||||||
# resetting or changing the password, deleting an account, and creating or
|
# resetting or changing the password, deleting an account, and creating or
|
||||||
|
|||||||
@@ -8,6 +8,13 @@ import (
|
|||||||
"sneak.berlin/go/smallwebwaf/internal/coreruleset"
|
"sneak.berlin/go/smallwebwaf/internal/coreruleset"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// giteaAdminUnadoptedRepos and forgejoAdminUnadoptedRepos are the admin pages
|
||||||
|
// of unadopted repositories. The forms that adopt or delete one post there.
|
||||||
|
const (
|
||||||
|
giteaAdminUnadoptedRepos = "/-/admin/repos/unadopted"
|
||||||
|
forgejoAdminUnadoptedRepos = "/admin/repos/unadopted"
|
||||||
|
)
|
||||||
|
|
||||||
// giteaSearches are searches of the pages gitea and Forgejo share whose q
|
// giteaSearches are searches of the pages gitea and Forgejo share whose q
|
||||||
// skips the lists of system files and command names, in the eighth change.
|
// skips the lists of system files and command names, in the eighth change.
|
||||||
// gitea serves its admin pages under /-/admin/ and Forgejo under /admin/.
|
// gitea serves its admin pages under /-/admin/ and Forgejo under /admin/.
|
||||||
@@ -30,12 +37,12 @@ var giteaSearches = []string{
|
|||||||
"/-/admin/users",
|
"/-/admin/users",
|
||||||
"/-/admin/orgs",
|
"/-/admin/orgs",
|
||||||
"/-/admin/repos",
|
"/-/admin/repos",
|
||||||
"/-/admin/repos/unadopted",
|
giteaAdminUnadoptedRepos,
|
||||||
"/-/admin/emails",
|
"/-/admin/emails",
|
||||||
"/-/admin/emails/activate",
|
"/-/admin/emails/activate",
|
||||||
"/admin/actions/runners",
|
"/admin/actions/runners",
|
||||||
"/admin/users",
|
"/admin/users",
|
||||||
"/admin/repos/unadopted",
|
forgejoAdminUnadoptedRepos,
|
||||||
"/admin/emails/activate",
|
"/admin/emails/activate",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +67,7 @@ func TestGiteaAdminFormsLetHardValuesThroughInTheBody(t *testing.T) {
|
|||||||
for _, value := range hardValues {
|
for _, value := range hardValues {
|
||||||
adopt := "id=alice%2Flegacy-site&action=adopt&q=" + value + "&page=1"
|
adopt := "id=alice%2Flegacy-site&action=adopt&q=" + value + "&page=1"
|
||||||
|
|
||||||
for _, path := range []string{"/-/admin/repos/unadopted", "/admin/repos/unadopted"} {
|
for _, path := range []string{giteaAdminUnadoptedRepos, forgejoAdminUnadoptedRepos} {
|
||||||
r := request{http.MethodPost, path, []string{formData}}
|
r := request{http.MethodPost, path, []string{formData}}
|
||||||
wantBody(t, crs, r, adopt, coreruleset.Result{}, adopt)
|
wantBody(t, crs, r, adopt, coreruleset.Result{}, adopt)
|
||||||
}
|
}
|
||||||
@@ -75,6 +82,67 @@ func TestGiteaAdminFormsLetHardValuesThroughInTheBody(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGiteaFormsThatAdoptARepositoryLetItsNameThrough(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
||||||
|
|
||||||
|
crs := readingBodies(t)
|
||||||
|
|
||||||
|
// The admin forms send the repository's owner and name in id, and the
|
||||||
|
// form in a user's settings only its name. robin/assets holds bin/as, a
|
||||||
|
// shell path.
|
||||||
|
ownerAndName := []string{
|
||||||
|
"alice%2Fpython3-app",
|
||||||
|
"alice%2Fpowershell-scripts",
|
||||||
|
"alice%2F.profile",
|
||||||
|
"robin%2Fassets",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, form := range []struct {
|
||||||
|
path string
|
||||||
|
ids []string
|
||||||
|
}{
|
||||||
|
{giteaAdminUnadoptedRepos, ownerAndName},
|
||||||
|
{forgejoAdminUnadoptedRepos, ownerAndName},
|
||||||
|
{
|
||||||
|
"/user/settings/repos/unadopted", // gitea's and Forgejo's
|
||||||
|
[]string{"python3-app", "powershell-scripts", ".profile"},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
r := request{http.MethodPost, form.path, []string{formData}}
|
||||||
|
|
||||||
|
for _, action := range []string{"adopt", "delete"} {
|
||||||
|
for _, id := range form.ids {
|
||||||
|
body := "id=" + id + "&action=" + action
|
||||||
|
wantBody(t, crs, r, body, coreruleset.Result{}, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Another field keeps those rules.
|
||||||
|
body := "id=" + form.ids[0] + "&action=adopt&page=python3"
|
||||||
|
wantBody(t, crs, r, body, matched(932260), body)
|
||||||
|
|
||||||
|
// Attacks in id are still refused.
|
||||||
|
for _, attack := range []string{
|
||||||
|
sqlInjection,
|
||||||
|
scriptInjection,
|
||||||
|
commandInjection,
|
||||||
|
"..%2F..%2Fetc%2Fpasswd",
|
||||||
|
} {
|
||||||
|
body := "id=" + attack + "&action=adopt"
|
||||||
|
if got, _ := inspectBody(t, crs, r, body); got.Score < threshold {
|
||||||
|
t.Errorf("POST %s %s let through: %+v", form.path, body, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// On another path, id keeps those rules.
|
||||||
|
r := request{http.MethodPost, "/-/admin/repos", []string{formData}}
|
||||||
|
body := "id=alice%2Fpython3-app&action=adopt&q=&page=1"
|
||||||
|
wantBody(t, crs, r, body, matched(932260), body)
|
||||||
|
}
|
||||||
|
|
||||||
func TestGiteaAttacksAreRefusedInItsSearches(t *testing.T) {
|
func TestGiteaAttacksAreRefusedInItsSearches(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user