Core Rule Set: the API searches gitea and Forgejo share pass (closes #353)
check / check (push) Waiting to run
check / check (push) Waiting to run
Recorded with python3, powershell, ls -la, .htaccess, file names and plain words from gitea 1.27.3 and Forgejo 17.0.1. q on the API's searches of topics, of an organisation's teams and of emails joins 900603, which skips 930120, 932120, 932250 and 932260. The same four rules are skipped by query on the search of unadopted repositories (900607) and by name on the search of a repository's milestones (900608). 900605 also lets /bin/sh through in q on the API's search of packages, since a package name can hold a slash. The searches of repositories, users and issues already passed. Model: opus-5-5
This commit is contained in:
1 parent
4dad4fff43
commit
7caad07de3
7 files changed
+271
-11
No files matched your search
@@ -3,6 +3,7 @@ package coreruleset_test
|
||||
import (
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sneak.berlin/go/smallwebwaf/internal/coreruleset"
|
||||
@@ -182,7 +183,7 @@ func TestGiteaSearchExclusionsAreNarrow(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
attacks := []string{"%2Fbin%2Fsh", commandInjection, pathTraversal}
|
||||
attacks := []string{binSh, commandInjection, pathTraversal}
|
||||
|
||||
// /bin/sh, a shell command after a ";", a path traversal and the hard
|
||||
// values stay refused in q on paths near the searches, and in another
|
||||
@@ -248,3 +249,102 @@ func TestGiteaSearchExclusionsAreNarrow(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// giteaAPISearches are the API's searches that gitea and Forgejo share whose
|
||||
// field skips the lists of system files and command names. Each ends in the
|
||||
// field it sends its words in.
|
||||
//
|
||||
//nolint:gochecknoglobals // a constant cannot be a list
|
||||
var giteaAPISearches = []string{
|
||||
"/api/v1/users/search?q=",
|
||||
"/api/v1/topics/search?q=",
|
||||
"/api/v1/orgs/acme/teams/search?q=",
|
||||
"/api/v1/admin/emails/search?q=",
|
||||
"/api/v1/admin/unadopted?query=",
|
||||
"/api/v1/repos/alice/tools/milestones?name=",
|
||||
}
|
||||
|
||||
// giteaAPIPackageSearch is the API's search of an owner's packages, whose q
|
||||
// also lets /bin/sh through, since a package name can hold a slash.
|
||||
const giteaAPIPackageSearch = "/api/v1/packages/alice?q="
|
||||
|
||||
func TestGiteaAttacksAreRefusedInItsAPISearches(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
||||
|
||||
defaults, bodies := atDefaults(t), readingBodies(t)
|
||||
searches := slices.Concat(
|
||||
giteaAPISearches,
|
||||
[]string{giteaAPIPackageSearch},
|
||||
)
|
||||
|
||||
for _, search := range searches {
|
||||
path, field, _ := strings.Cut(search, "?")
|
||||
|
||||
for _, attack := range []string{
|
||||
sqlInjection, pathTraversal, scriptInjection, commandInjection,
|
||||
} {
|
||||
if got := inspect(t, defaults, get(search+attack)); got.Score < threshold {
|
||||
t.Errorf("GET %s%s let through: %+v", search, attack, got)
|
||||
}
|
||||
|
||||
// The same field in a form data body.
|
||||
r := request{http.MethodPost, path, []string{formData}}
|
||||
if got, _ := inspectBody(t, bodies, r, field+attack); got.Score < threshold {
|
||||
t.Errorf(
|
||||
"POST %s %s%s let through: %+v",
|
||||
path,
|
||||
field,
|
||||
attack,
|
||||
got,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// /bin/sh stays refused on the searches that are not of packages.
|
||||
for _, search := range giteaAPISearches {
|
||||
if got := inspect(t, defaults, get(search+binSh)); got.Score < threshold {
|
||||
t.Errorf("%s%s let through: %+v", search, binSh, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGiteaAPISearchExclusionsAreNarrow(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
||||
|
||||
crs := atDefaults(t)
|
||||
|
||||
// /bin/sh, a shell command after a ";", a path traversal and the hard
|
||||
// values stay refused in each search's field on paths near the
|
||||
// searches, and in another field on them.
|
||||
for _, target := range []string{
|
||||
"/api/v1/topics/search/x?q=",
|
||||
"/api/v1/orgs/acme/teams?q=",
|
||||
"/api/v1/orgs/acme/teams/search/x?q=",
|
||||
"/api/v1/admin/emails?q=",
|
||||
"/api/v1/admin/unadopted/alice/legacy-site?query=",
|
||||
"/api/v1/admin/users?query=",
|
||||
"/api/v1/repos/alice/tools/milestones/1?name=",
|
||||
"/api/v1/repos/alice/tools/labels?name=",
|
||||
"/api/v1/packages/alice/generic/tools-cli?q=",
|
||||
"/api/v1/topics/search?other=",
|
||||
"/api/v1/orgs/acme/teams/search?other=",
|
||||
"/api/v1/admin/emails/search?other=",
|
||||
"/api/v1/admin/unadopted?q=",
|
||||
"/api/v1/repos/alice/tools/milestones?q=",
|
||||
"/api/v1/packages/alice?other=",
|
||||
} {
|
||||
for _, value := range slices.Concat(
|
||||
[]string{binSh, commandInjection, pathTraversal},
|
||||
hardValues,
|
||||
) {
|
||||
if got := inspect(t, crs, get(target+value)); got.Score < threshold {
|
||||
t.Errorf("%s%s let through: %+v", target, value, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user