diff --git a/SPEC.md b/SPEC.md index 9225940..9ce13cf 100644 --- a/SPEC.md +++ b/SPEC.md @@ -829,9 +829,9 @@ The changes hold in front of every app, and no setting undoes them: - packages, on its pages and in its API. A user's or an organisation's page is any path of one segment, so on - every such path, on any app, `q` skips 930120, 932120 and 932260, for - example on `/search`. It keeps 932250 there, so `ls -la` is refused on - those pages. + every such path, on any app, `q` skips these rules, for example on + `/search`. A path that ends in `.php`, such as `/index.php`, keeps + 932250, so `ls -la` is refused there. Some of these searches also skip the rule for Java class names (944130), which refused `org.apache.commons.logging`. diff --git a/internal/coreruleset/coreruleset_test.go b/internal/coreruleset/coreruleset_test.go index eae74e7..0927a92 100644 --- a/internal/coreruleset/coreruleset_test.go +++ b/internal/coreruleset/coreruleset_test.go @@ -1063,6 +1063,41 @@ func TestGiteaAttacksAreRefused(t *testing.T) { } } +func TestGiteaSearchOnAUsersOrAnOrganisationsPageLetsLsThrough(t *testing.T) { + t.Parallel() + + crs := atDefaults(t) + + // The searches of a user's and of an organisation's repositories. + for _, target := range []string{ + "/alice?tab=repositories&q=ls%20-la", + "/acme?q=ls%20-la&sort=recentupdate", + "/jane.doe?tab=repositories&q=ls%20-la", + } { + wantResult(t, crs, get(target), coreruleset.Result{}) + } + + // A path that ends in .php, another field, and a path of two segments + // keep 932250. + for _, target := range []string{ + "/index.php?q=ls%20-la", + "/INDEX.PHP?q=ls%20-la", + "/index%2Ephp?q=ls%20-la", + "/alice?tab=ls%20-la", + "/alice/tools?q=ls%20-la", + } { + wantResult(t, crs, get(target), matched(932250)) + } + + // A command after a ";" is still refused on those pages. + const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default + + target := "/alice?tab=repositories&q=" + commandInjection + if got := inspect(t, crs, get(target)); got.Score < threshold { + t.Errorf("%s let through: %+v", target, got) + } +} + // WordPress's paths: signing in, posting a comment, admin-ajax.php, and the // REST API routes of a post and of the tags. const ( diff --git a/internal/coreruleset/gitea.conf b/internal/coreruleset/gitea.conf index abd381c..5a0ac7b 100644 --- a/internal/coreruleset/gitea.conf +++ b/internal/coreruleset/gitea.conf @@ -135,8 +135,8 @@ SecRule REQUEST_FILENAME "@rx ^/(?:repo/search|\ ctl:ruleRemoveTargetById=932260;ARGS:q" # The repositories on a user's or an organisation's page are searched with q -# too. Their path has one segment, as other apps' pages such as /index.php do, -# so q keeps 932250 there and skips only these rules: +# too. Their path has one segment, so q skips these rules on every path of one +# segment, other apps' pages such as /index.php included: # 930120: .profile # 932120: powershell-scripts # 932260: python3-app @@ -146,6 +146,13 @@ SecRule REQUEST_FILENAME "@rx ^/[^/]+$" \ ctl:ruleRemoveTargetById=932120;ARGS:q,\ ctl:ruleRemoveTargetById=932260;ARGS:q" +# On those pages q skips 932250 too, which refused ls -la. A path that ends in +# .php, such as /index.php, keeps it. +SecRule REQUEST_FILENAME "@rx ^/[^/]+$" \ + "id:900605,phase:1,pass,nolog,chain" + SecRule REQUEST_FILENAME "!@rx \.php$" \ + "t:lowercase,ctl:ruleRemoveTargetById=932250;ARGS:q" + # These requests name a file in the path, which skips these rules there: # 920500: notes.txt~ # 934100: process.env.d.ts diff --git a/internal/coreruleset/testdata/forgejo-requests.txt b/internal/coreruleset/testdata/forgejo-requests.txt index cb36ae4..dbb0702 100644 --- a/internal/coreruleset/testdata/forgejo-requests.txt +++ b/internal/coreruleset/testdata/forgejo-requests.txt @@ -4,7 +4,9 @@ # the API and the generic package registry, and by crane on the container # registry. Each line is one request as JSON. Names, addresses and passwords # are invented. The browser's User-Agent is an ordinary Chrome's. A body or a -# multipart part that is not text is written as base64. +# multipart part that is not text is written as base64. The searches for ls -la +# on a user's and an organisation's page are the recorded searches for python3 +# there, with q changed. {"method": "GET", "target": "/", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate"], "body": ""} {"method": "GET", "target": "/assets/js/webcomponents.js?v=17.0.1~gitea-1.22.0", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate"], "body": ""} {"method": "GET", "target": "/assets/css/index.css?v=17.0.1~gitea-1.22.0", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/css,*/*;q=0.1", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate"], "body": ""} @@ -102,6 +104,7 @@ {"method": "GET", "target": "/explore/users?q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""} {"method": "GET", "target": "/explore/organizations?q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""} {"method": "GET", "target": "/alice?tab=repositories&q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""} +{"method": "GET", "target": "/alice?tab=repositories&q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""} {"method": "GET", "target": "/avatars/53bce4f1dfa0fe8e7ca126f91b35d3a6?size=64", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/alice?tab=repositories&q=python3", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""} {"method": "GET", "target": "/alice/tools/commits/branch/main/search?q=python3&all=true", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""} {"method": "GET", "target": "/alice/-/packages?q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""} @@ -468,3 +471,4 @@ {"method": "GET", "target": "/repo-avatars/f9e66d43eb4888aac5d9d93745661631a09b4c73969226a3e89b2911ece215ba", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/alice", "Accept-Encoding: gzip, deflate", "Cookie: session=00a162ad65e95a87; lang=en-US"], "body": ""} {"method": "GET", "target": "/avatars/53bce4f1dfa0fe8e7ca126f91b35d3a6?size=64", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/alice", "Accept-Encoding: gzip, deflate", "Cookie: session=00a162ad65e95a87; lang=en-US"], "body": ""} {"method": "GET", "target": "/acme?q=python3&sort=recentupdate", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/acme", "Accept-Encoding: gzip, deflate", "Cookie: session=00a162ad65e95a87; lang=en-US"], "body": ""} +{"method": "GET", "target": "/acme?q=ls%20-la&sort=recentupdate", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/acme", "Accept-Encoding: gzip, deflate", "Cookie: session=00a162ad65e95a87; lang=en-US"], "body": ""}