Compare commits

..
1 Commits
Author SHA1 Message Date
clawbot 33b780098a Core Rule Set: the package registries' searches of gitea and Forgejo pass (closes #354)
check / check (push) Successful in 4m29s
Recorded from gitea 1.27.3 and Forgejo 17.0.1 with each registry's own
client, and curl for the Chef cookbook list, NuGet's Packages() and the
counts at Search()/$count and Packages()/$count, 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, in phase 2, lets one q shaped as a Conan
reference with no +, */ or --, such as zlib/*, skip 942100; 900612 for
NuGet's Search(), Packages() and their counts: searchTerm, $filter, and
the names $filter, $orderby and $top for 942290.

Model: opus-5-5
2026-11-12 13:34:35 +00:00
3 changed files with 5 additions and 22 deletions

No files matched your search

+1 -3
View File
@@ -940,9 +940,7 @@ one entry of `apps` in `internal/coreruleset/coreruleset.go`. gitea's fixes are:
version, then optionally `@` and a user, then optionally `/` and a version, then optionally `@` and a user, then optionally `/` and a
channel. Each part is made of letters, digits, `_`, `.`, `-` and `*`. channel. Each part is made of letters, digits, `_`, `.`, `-` and `*`.
A part holds no `+`, since 942100 reads `+` as a space. `q` also holds A part holds no `+`, since 942100 reads `+` as a space. `q` also holds
no `*/`, which ends an SQL comment, and no `--`, which starts one. And no `*/`, which ends an SQL comment, and no `--`, which starts one.
942100 finds no SQL injection in `q` once each comment that `/*`
starts, an unclosed one included, is replaced by a space.
- The path of these requests, which names a file: - The path of these requests, which names a file:
- a file's page; - a file's page;
+2 -6
View File
@@ -274,16 +274,13 @@ SecRule REQUEST_FILENAME "@rx ^/api/packages/[^/]+/npm/-/v1/search$" \
# and a user, then optionally / and a channel. Each part is made of letters, # and a user, then optionally / and a channel. Each part is made of letters,
# digits, _, ., - and *. A part holds no +, since 942100 reads + as a space. # digits, _, ., - and *. A part holds no +, since 942100 reads + as a space.
# q must also hold no */, which ends an SQL comment, and no --, which starts # q must also hold no */, which ends an SQL comment, and no --, which starts
# one. Last, 942100 must find no SQL injection in q once each comment that /* # one. Such a q has no quote, no space and no comment between two words, so
# starts, an unclosed one included, is replaced by a space. So zlib/* passes, # SQL injection such as these stays refused:
# and SQL injection such as these stays refused:
# 1' OR '1'='1 # 1' OR '1'='1
# 1/**/or/**/1 # 1/**/or/**/1
# 1/*@*/or-1 # 1/*@*/or-1
# 1+or+1-- # 1+or+1--
# 1-or-1-- # 1-or-1--
# x/1.or.1
# 1.having.1
# This rule is in phase 2, because a phase 1 rule runs before the body is read # This rule is in phase 2, because a phase 1 rule runs before the body is read
# and cannot see a q there. It checks tx.app_gitea, since the skip above does # and cannot see a q there. It checks tx.app_gitea, since the skip above does
# not reach phase 2. # not reach phase 2.
@@ -293,7 +290,6 @@ SecRule REQUEST_FILENAME "@rx ^/api/packages/[^/]+/conan/v[12]/conans/search$" \
SecRule &ARGS:q "@eq 1" "chain" SecRule &ARGS:q "@eq 1" "chain"
SecRule ARGS:q "!@contains */" "chain" SecRule ARGS:q "!@contains */" "chain"
SecRule ARGS:q "!@contains --" "chain" SecRule ARGS:q "!@contains --" "chain"
SecRule ARGS:q "!@detectSQLi" "t:none,t:replaceComments,chain"
SecRule ARGS:q "@rx ^[\w.*-]+(?:/[\w.*-]+)?(?:@[\w.*-]+(?:/[\w.*-]+)?)?$" \ SecRule ARGS:q "@rx ^[\w.*-]+(?:/[\w.*-]+)?(?:@[\w.*-]+(?:/[\w.*-]+)?)?$" \
"ctl:ruleRemoveTargetById=942100;ARGS:q" "ctl:ruleRemoveTargetById=942100;ARGS:q"
+2 -13
View File
@@ -441,8 +441,8 @@ func TestGiteaRegistrySearchExclusionsAreNarrow(t *testing.T) {
// another operator on NuGet's searches, and $filter on NuGet's other // another operator on NuGet's searches, and $filter on NuGet's other
// paths. The rule for SQL injection (942100) still refuses tools/* in a // paths. The rule for SQL injection (942100) still refuses tools/* in a
// field other than a Conan search's, SQL injection in a second q after a // field other than a Conan search's, SQL injection in a second q after a
// Conan pattern, SQL comments or dots that stand in for spaces, and SQL // Conan pattern, SQL comments that stand in for spaces, and SQL injection
// injection holding + or --. // holding + or --.
for _, target := range []string{ for _, target := range []string{
"/api/packages/alice/nuget/Search()?$where=1", "/api/packages/alice/nuget/Search()?$where=1",
"/api/packages/alice/nuget/Packages()?$ne=1", "/api/packages/alice/nuget/Packages()?$ne=1",
@@ -457,9 +457,6 @@ func TestGiteaRegistrySearchExclusionsAreNarrow(t *testing.T) {
"/api/packages/alice/conan/v2/conans/search?q=1%2F%2A%40%2A%2For-1", "/api/packages/alice/conan/v2/conans/search?q=1%2F%2A%40%2A%2For-1",
"/api/packages/alice/conan/v2/conans/search?q=1%2Bor%2B1--", "/api/packages/alice/conan/v2/conans/search?q=1%2Bor%2B1--",
"/api/packages/alice/conan/v2/conans/search?q=1-or-1--", "/api/packages/alice/conan/v2/conans/search?q=1-or-1--",
"/api/packages/alice/conan/v2/conans/search?q=x%2F1.or.1",
"/api/packages/alice/conan/v2/conans/search?q=x%2F1.and.1",
"/api/packages/alice/conan/v2/conans/search?q=1.having.1",
} { } {
if got := inspect(t, crs, get(target)); got.Score < threshold { if got := inspect(t, crs, get(target)); got.Score < threshold {
t.Errorf("%s let through: %+v", target, got) t.Errorf("%s let through: %+v", target, got)
@@ -479,14 +476,6 @@ func TestGiteaRegistrySearchExclusionsAreNarrow(t *testing.T) {
t.Errorf("POST %s %s let through: %+v", search.target, body, got) t.Errorf("POST %s %s let through: %+v", search.target, body, got)
} }
// So is SQL injection with dots for spaces, in a q sent only in the body.
search.target = "/api/packages/alice/conan/v2/conans/search"
body = "q=x%2F1.or.1"
if got, _ := inspectBody(t, readingBodies(t), search, body); got.Score < threshold {
t.Errorf("POST %s %s let through: %+v", search.target, body, got)
}
// The rule that lets a Conan pattern through is in phase 2, where it // The rule that lets a Conan pattern through is in phase 2, where it
// checks tx.app_gitea itself: a hostname that does not serve gitea // checks tx.app_gitea itself: a hostname that does not serve gitea
// refuses the pattern. // refuses the pattern.