Core Rule Set: the package registries' searches of gitea and Forgejo pass (closes #354)
check / check (push) Failing after 1m54s
check / check (push) Failing after 1m54s
Recorded from gitea 1.27.3 and Forgejo 17.0.1 with each registry's own client, and curl for the Chef cookbook list and NuGet's Packages(), with python3, powershell, ls -la, .htaccess, file names and plain words. q on the Cargo, Chef, Composer and Conan searches and NuGet's search service joins 900603. 900605 lets /bin/sh through on Composer and Conan. New: 900609 for npm's text, 900611 lets a Conan pattern such as zlib/* skip 942100, and 900612 for NuGet's Search() and Packages(): searchTerm, $filter, and the names $filter, $orderby and $top for 942290. Model: opus-5-5
This commit is contained in:
1 parent
b84a33fb57
commit
119b9356e5
5 files changed
+360
-21
No files matched your search
@@ -271,15 +271,28 @@ 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(
|
||||
checkSearchesRefuseAttacks(
|
||||
t,
|
||||
giteaAPISearches,
|
||||
[]string{giteaAPIPackageSearch},
|
||||
)
|
||||
}
|
||||
|
||||
for _, search := range searches {
|
||||
// checkSearchesRefuseAttacks checks that SQL injection, a path traversal,
|
||||
// script injection and a shell command stay refused in the field of each
|
||||
// search, in the query string and in a form data body. Each search ends in
|
||||
// its field. /bin/sh stays refused too in the field of each of
|
||||
// refusingBinSh, but not of lettingBinSh.
|
||||
func checkSearchesRefuseAttacks(
|
||||
t *testing.T, refusingBinSh, lettingBinSh []string,
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
||||
|
||||
defaults, bodies := atDefaults(t), readingBodies(t)
|
||||
|
||||
for _, search := range slices.Concat(refusingBinSh, lettingBinSh) {
|
||||
path, field, _ := strings.Cut(search, "?")
|
||||
|
||||
for _, attack := range []string{
|
||||
@@ -303,8 +316,7 @@ func TestGiteaAttacksAreRefusedInItsAPISearches(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// /bin/sh stays refused on the searches that are not of packages.
|
||||
for _, search := range giteaAPISearches {
|
||||
for _, search := range refusingBinSh {
|
||||
if got := inspect(t, defaults, get(search+binSh)); got.Score < threshold {
|
||||
t.Errorf("%s%s let through: %+v", search, binSh, got)
|
||||
}
|
||||
@@ -314,14 +326,9 @@ func TestGiteaAttacksAreRefusedInItsAPISearches(t *testing.T) {
|
||||
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{
|
||||
// Each search's field on paths near the searches, and another field on
|
||||
// them.
|
||||
checkValuesStayRefused(t, []string{
|
||||
"/api/v1/topics/search/x?q=",
|
||||
"/api/v1/orgs/acme/teams?q=",
|
||||
"/api/v1/orgs/acme/teams/search/x?q=",
|
||||
@@ -337,7 +344,19 @@ func TestGiteaAPISearchExclusionsAreNarrow(t *testing.T) {
|
||||
"/api/v1/admin/unadopted?q=",
|
||||
"/api/v1/repos/alice/tools/milestones?q=",
|
||||
"/api/v1/packages/alice?other=",
|
||||
} {
|
||||
})
|
||||
}
|
||||
|
||||
// checkValuesStayRefused checks that /bin/sh, a shell command after a ";", a
|
||||
// path traversal and the hard values stay refused at the end of each target.
|
||||
func checkValuesStayRefused(t *testing.T, targets []string) {
|
||||
t.Helper()
|
||||
|
||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
||||
|
||||
crs := atDefaults(t)
|
||||
|
||||
for _, target := range targets {
|
||||
for _, value := range slices.Concat(
|
||||
[]string{binSh, commandInjection, pathTraversal},
|
||||
hardValues,
|
||||
@@ -348,3 +367,86 @@ func TestGiteaAPISearchExclusionsAreNarrow(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// giteaRegistrySearches are the package registries' searches 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 giteaRegistrySearches = []string{
|
||||
"/api/packages/alice/cargo/api/v1/crates?q=",
|
||||
"/api/packages/alice/chef/api/v1/search?q=",
|
||||
"/api/packages/alice/chef/api/v1/cookbooks?q=",
|
||||
"/api/packages/alice/nuget/query?q=",
|
||||
"/api/packages/alice/nuget/Search()?searchTerm=",
|
||||
"/api/packages/alice/nuget/Packages()?$filter=",
|
||||
}
|
||||
|
||||
// giteaRegistryNameSearches are the package registries' searches whose field
|
||||
// also lets /bin/sh through, since a Composer, Conan or npm name can hold a
|
||||
// slash.
|
||||
//
|
||||
//nolint:gochecknoglobals // a constant cannot be a list
|
||||
var giteaRegistryNameSearches = []string{
|
||||
"/api/packages/alice/composer/search.json?q=",
|
||||
"/api/packages/alice/conan/v1/conans/search?q=",
|
||||
"/api/packages/alice/conan/v2/conans/search?q=",
|
||||
"/api/packages/alice/npm/-/v1/search?text=",
|
||||
}
|
||||
|
||||
func TestGiteaAttacksAreRefusedInItsRegistrySearches(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checkSearchesRefuseAttacks(
|
||||
t,
|
||||
giteaRegistrySearches,
|
||||
giteaRegistryNameSearches,
|
||||
)
|
||||
}
|
||||
|
||||
func TestGiteaRegistrySearchExclusionsAreNarrow(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Each search's field on paths near the searches, and another field on
|
||||
// them.
|
||||
checkValuesStayRefused(t, []string{
|
||||
"/api/packages/alice/cargo/api/v1/crates/tools-cli?q=",
|
||||
"/api/packages/alice/chef/api/v1/cookbooks/tools?q=",
|
||||
"/api/packages/alice/composer/packages.json?q=",
|
||||
"/api/packages/alice/conan/v1/conans/tools/1.0/alice/stable/search?q=",
|
||||
"/api/packages/alice/nuget/query/x?q=",
|
||||
"/api/packages/alice/nuget/FindPackagesById()?id=",
|
||||
"/api/packages/alice/npm/tools-cli?text=",
|
||||
"/api/packages/alice/cargo/api/v1/crates?other=",
|
||||
"/api/packages/alice/chef/api/v1/search?other=",
|
||||
"/api/packages/alice/composer/search.json?type=",
|
||||
"/api/packages/alice/conan/v2/conans/search?other=",
|
||||
"/api/packages/alice/nuget/query?other=",
|
||||
"/api/packages/alice/nuget/Search()?targetFramework=",
|
||||
"/api/packages/alice/nuget/Packages()?$orderby=",
|
||||
"/api/packages/alice/npm/-/v1/search?q=",
|
||||
})
|
||||
|
||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
||||
|
||||
crs := atDefaults(t)
|
||||
|
||||
// The rule for MongoDB operators (942290) still refuses the name of
|
||||
// another operator on NuGet's searches, and $filter on NuGet's other
|
||||
// paths. The rule for SQL injection (942100) still refuses tools/* in a
|
||||
// field other than a Conan search's, and SQL injection in a second q
|
||||
// after a Conan pattern.
|
||||
for _, target := range []string{
|
||||
"/api/packages/alice/nuget/Search()?$where=1",
|
||||
"/api/packages/alice/nuget/Packages()?$ne=1",
|
||||
"/api/packages/alice/nuget/FindPackagesById()?$filter=IsLatestVersion",
|
||||
"/api/packages/alice/composer/search.json?q=tools%2F%2A",
|
||||
"/api/packages/alice/conan/v2/conans/search?other=tools%2F%2A",
|
||||
"/api/packages/alice/conan/v2/conans/search?q=tools%2F%2A&q=" +
|
||||
sqlInjection,
|
||||
} {
|
||||
if got := inspect(t, crs, get(target)); got.Score < threshold {
|
||||
t.Errorf("%s let through: %+v", target, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user