check / check (push) Waiting to run
`testdata/forgejo-requests.txt` records these Forgejo 17.0.1 searches with the hard values, file names and plain words, and the code search with code and a page opened from each search. `forgejo.conf` lets them through: - the code search at a branch or tag, where the home page's search box sends its search without a code indexer: `q` skips what it skips on gitea's code search, and `Referer` what it skips after gitea's searches; - the release search, page and API: `q` skips 930120, 932120, 932160, 932250 and 932260, since it matches tag names; - the job log search in the API: the same, without 932160. In `gitea.conf`, `q` on a repository's branches page and an owner's packages page also skips 932160, so `/bin/sh` passes there. `SPEC.md` lists both. Model: opus-5-5
116 lines
3.8 KiB
Go
116 lines
3.8 KiB
Go
package coreruleset_test
|
|
|
|
import (
|
|
"net/http"
|
|
"slices"
|
|
"testing"
|
|
)
|
|
|
|
// forgejoSearches are Forgejo's searches whose q skips some rules: those of
|
|
// a wiki, of releases and of a job's log, from the fifteenth change, and the
|
|
// searches for a name, from the eighth. The search of code at a branch or tag
|
|
// is not one of them: there, as on gitea's search of code, q lets these
|
|
// attacks through.
|
|
//
|
|
//nolint:gochecknoglobals // a constant cannot be a list
|
|
var forgejoSearches = []string{
|
|
"/alice/tools/wiki/search",
|
|
"/alice/tools/releases",
|
|
"/api/v1/repos/alice/tools/releases",
|
|
"/api/v1/repos/alice/tools/actions/jobs/1/logs",
|
|
"/explore/repos",
|
|
"/explore/users",
|
|
"/alice/tools/branches",
|
|
"/alice/-/packages",
|
|
"/api/v1/packages/alice",
|
|
"/alice",
|
|
}
|
|
|
|
// forgejoAttacks are an SQL injection, a path traversal, a script injection
|
|
// and a shell command after a ";", as query values.
|
|
//
|
|
//nolint:gochecknoglobals // a constant cannot be a list
|
|
var forgejoAttacks = []string{
|
|
sqlInjection, "..%2Fcustom%2Fconf%2Fapp.ini", scriptInjection, commandInjection,
|
|
}
|
|
|
|
func TestForgejoAttacksAreRefusedInItsSearches(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
|
|
|
defaults, bodies := atDefaults(t), readingBodies(t)
|
|
|
|
for _, path := range forgejoSearches {
|
|
for _, attack := range forgejoAttacks {
|
|
if got := inspect(t, defaults, get(path+"?q="+attack)); got.Score < threshold {
|
|
t.Errorf("GET %s?q=%s let through: %+v", path, attack, got)
|
|
}
|
|
|
|
// The same field in a form data body.
|
|
r := request{http.MethodPost, path, []string{formData}}
|
|
if got, _ := inspectBody(t, bodies, r, "q="+attack); got.Score < threshold {
|
|
t.Errorf("POST %s q=%s let through: %+v", path, attack, got)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestForgejoExclusionsAreNarrow(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
|
|
|
crs := atDefaults(t)
|
|
|
|
// /bin/sh and the hard values, which q lets through on the searches of
|
|
// the fifteenth change and on those of branches and packages, stay
|
|
// refused in q on paths near them and in another field on them.
|
|
// /alice/wiki/search would be the code search of a repository named
|
|
// wiki, where the eighth change lets q through. Forgejo has no owner
|
|
// named api, so the paths under /api/ here are not searches.
|
|
for _, target := range []string{
|
|
"/alice/tools/wiki/Home?q=",
|
|
"/alice/tools/wiki/search/x?q=",
|
|
"/api/wiki/search?q=",
|
|
"/alice/tools/wiki/search?other=",
|
|
"/alice/tools/search/branch?q=",
|
|
"/alice/tools/search/commit/0123abc?q=",
|
|
"/alice/tools/src/branch/main?q=",
|
|
"/api/tools/search/branch/main?q=",
|
|
"/alice/tools/search/branch/main?other=",
|
|
"/alice/tools/releases/tag/v1.0?q=",
|
|
"/alice/tools/releases/latest?q=",
|
|
"/api/v1/releases?q=",
|
|
"/alice/tools/releases?other=",
|
|
"/api/v1/repos/alice/tools/actions/jobs/1?q=",
|
|
"/api/v1/repos/alice/tools/actions/runs/1/logs?q=",
|
|
"/alice/tools/actions/runs/1/jobs/0/logs?q=",
|
|
"/api/v1/repos/alice/tools/actions/jobs/1/logs?other=",
|
|
"/alice/tools/branches/x?q=",
|
|
"/alice/-/packages/npm/x?q=",
|
|
"/alice/tools/branches?other=",
|
|
} {
|
|
for _, value := range slices.Concat([]string{"%2Fbin%2Fsh"}, hardValues) {
|
|
if got := inspect(t, crs, get(target+value)); got.Score < threshold {
|
|
t.Errorf("%s%s let through: %+v", target, value, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Referer keeps its rules on a page opened from a path near the code
|
|
// search at a branch or tag.
|
|
for _, path := range []string{
|
|
"/alice/tools/search/branch",
|
|
"/alice/tools/search/commit/0123abc",
|
|
"/api/tools/search/branch/main",
|
|
} {
|
|
for _, attack := range []string{scriptTag, injection} {
|
|
referer := "Referer: http://example.com" + path + "?q=" + attack
|
|
if got := inspect(t, crs, get("/", referer)); got.Score < threshold {
|
|
t.Errorf("%s let through: %+v", referer, got)
|
|
}
|
|
}
|
|
}
|
|
}
|