Compare commits

...
1 Commits
Author SHA1 Message Date
clawbot 52b5fcb9d2 Core Rule Set: a repository's name passes in the adopt form's id (closes #365)
check / check (push) Waiting to run
While bodies are read, the forms that adopt or delete an unadopted
repository were refused when the repository's name held a command, a
system file's name or a shell path, such as alice/python3-app,
alice/.profile or robin/assets. id there now skips 930120, 932120, 932160
and 932260. The paths are gitea's /-/admin/repos/unadopted, Forgejo's
/admin/repos/unadopted, and /user/settings/repos/unadopted, which both
serve.

Model: opus-5-5
2026-11-12 01:39:59 +00:00
4 changed files with 100 additions and 10 deletions

No files matched your search

+4 -4
View File
@@ -587,11 +587,11 @@ client's limits by tag. See "Request tags" in [`SPEC.md`](SPEC.md).
- These apps work out of the box at the default settings, and their recorded - These apps work out of the box at the default settings, and their recorded
ordinary requests pass the Core Rule Set: ordinary requests pass the Core Rule Set:
- gitea: git over HTTP and sign-in through Git Credential Manager get - gitea: git over HTTP and sign-in through Git Credential Manager get
through. On gitea's own paths, its searches, file names and branch names through. On gitea's own paths, its searches, file names, branch names and
do too. repository names do too.
- Forgejo: git over HTTP and its container and package registries get - Forgejo: git over HTTP and its container and package registries get
through. On Forgejo's own paths, its searches, file names and branch names through. On Forgejo's own paths, its searches, file names, branch names
do too. and repository names do too.
- WordPress: saving a post from the block editor gets through. On - WordPress: saving a post from the block editor gets through. On
WordPress's own paths, its searches, names and file names do too. WordPress's own paths, its searches, names and file names do too.
- Drupal: on Drupal's own paths, the page to return to, its searches and its - Drupal: on Drupal's own paths, the page to return to, its searches and its
+12 -3
View File
@@ -812,9 +812,9 @@ The changes hold in front of every app, and no setting undoes them:
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 paths, some fields skip the rules that refused ordinary gitea 8. 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 `../`. `SELECT * FROM users WHERE` or `../`.
@@ -884,6 +884,15 @@ The changes hold in front of every app, and no setting undoes them:
- 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
+61
View File
@@ -2421,6 +2421,67 @@ func TestGiteaBodiesTheCoreRuleSetRefuses(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
}{
{"/-/admin/repos/unadopted", ownerAndName}, // gitea's
{"/admin/repos/unadopted", ownerAndName}, // Forgejo's
{
"/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 TestTypesEndingInXMLOrJSONAndTextJSONAreRead(t *testing.T) { func TestTypesEndingInXMLOrJSONAndTextJSONAreRead(t *testing.T) {
t.Parallel() t.Parallel()
+23 -3
View File
@@ -1,8 +1,9 @@
# The eighth change: on gitea's paths, some fields skip the rules that refused # The eighth change: on gitea's paths, some fields skip the rules that refused
# ordinary gitea requests there. They are the fields where gitea sends a search, # ordinary gitea requests there. They are the fields where gitea sends a search,
# a file name or a branch name. Each exclusion runs with the request, before the # a file name, a branch name or a repository's name. Each exclusion runs with
# rules it switches off, those of phase 1 included, so this file is loaded before # the request, before the rules it switches off, those of phase 1 included, so
# the Core Rule Set's rules. Each comment names a request the rule refused. # this file is loaded before the Core Rule Set's rules. Each comment names a
# request the rule refused.
# gitea's searches of code, of issues and pull requests, and of commits send the # gitea's searches of code, of issues and pull requests, and of commits send the
# text searched for in q, which skips these rules there: # text searched for in q, which skips these rules there:
@@ -322,6 +323,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