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) } } } }