1 Commits
Author SHA1 Message Date
sneak ff9b06f373 Core Rule Set: MediaWiki's ordinary requests pass by default (closes #237)
check / check (push) Successful in 4m22s
Adds mediawiki.conf, the eleventh change: on MediaWiki's paths, the fields
that carry a search, a title, a name, a file name or an address skip the
four rules that read them as commands or system files, and the wikitext
fields skip the rules that refused saving a page. The reasons on the admin
pages skip only 930120 and 932120. /index.php counts as MediaWiki's only
with a page title in its query or after its path, since WordPress serves it
too. The requests recorded from MediaWiki 1.46.2, with and without short
URLs and on the admin pages, pass at the defaults and while bodies are
read. The app tests share the check of recorded requests and the list of
hard values.

Model: opus-5-5
2026-11-09 23:00:08 +00:00
4 changed files with 16 additions and 37 deletions

No files matched your search

+4 -11
View File
@@ -994,22 +994,15 @@ requests. The changes hold in front of every app, and no setting undoes them:
and command names (930120, 932120, 932250, 932260), in the query or the
body:
- On MediaWiki's pages, which are `/index.php` and the paths under
`/index.php/` and `/wiki/`:
- the searches `search` and `ilsearch`;
- the titles `title`, `page` and `target`;
- `from`, `to` and `prefix`, which limit a list of pages;
- the user names `user`, `username` and `wpTarget`;
- the file names `filename` and `wpDestFile`;
- `returnto` and `returntoquery`, the page and query to go back to after
signing in.
- On `/api.php`:
`/index.php/` and `/wiki/`: `title`, `search`, `target`, `page`, `user`,
`username`, `from`, `to`, `prefix`, `returnto`, `returntoquery`,
`ilsearch`, `filename`, `wpDestFile` and `wpTarget`.
- On `/api.php`, with or without the `g` of a generator:
- the searches `search`, `srsearch` and `pssearch`;
- the titles `titles`, `title`, `page` and `bltitle`;
- `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`;
- `euquery`, an address;
- the user names `ucuser`, `bkusers` and `user`;
- `summary`, `sectiontitle`, `filename`, `comment` and `reason`;
+6 -7
View File
@@ -46,7 +46,8 @@ SecRule REQUEST_FILENAME "@rx ^/(?:w/)?index\.php(?:$|/.)|^/wiki/" \
ctl:ruleRemoveTargetById=932260;ARGS:/^(?:title|search|target|page|user|username|from|to|prefix|returnto|returntoquery|ilsearch|filename|wpdestfile|wptarget)$/,\
ctl:ruleRemoveTargetById=932180;FILES"
# The API's searches, titles, names and file names, in the query or the body:
# The API's searches, titles, names and file names, in the query or the body,
# with or without the g of a generator:
# search: /api.php?action=opensearch&search=python3
# srsearch, pssearch: /api.php?action=query&list=search&srsearch=python3
# titles, title, page: /api.php?action=parse&page=Python3+notes
@@ -55,8 +56,6 @@ 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
# euquery: /api.php?action=query&list=exturlusage&euquery=example.com/python3
# ucuser: /api.php?action=query&list=usercontribs&ucuser=python3
# bkusers: /api.php?action=query&list=blocks&bkusers=Python3
@@ -71,10 +70,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:/^g?(?:search|srsearch|pssearch|titles?|page|from|to|apfrom|apto|apprefix|aifrom|aiprefix|aufrom|auprefix|bltitle|euquery|ucuser|bkusers|user|summary|sectiontitle|filename|comment|reason|lgname|lgpassword|username|password)$/,\
ctl:ruleRemoveTargetById=932120;ARGS:/^g?(?:search|srsearch|pssearch|titles?|page|from|to|apfrom|apto|apprefix|aifrom|aiprefix|aufrom|auprefix|bltitle|euquery|ucuser|bkusers|user|summary|sectiontitle|filename|comment|reason|lgname|lgpassword|username|password)$/,\
ctl:ruleRemoveTargetById=932250;ARGS:/^g?(?:search|srsearch|pssearch|titles?|page|from|to|apfrom|apto|apprefix|aifrom|aiprefix|aufrom|auprefix|bltitle|euquery|ucuser|bkusers|user|summary|sectiontitle|filename|comment|reason|lgname|lgpassword|username|password)$/,\
ctl:ruleRemoveTargetById=932260;ARGS:/^g?(?:search|srsearch|pssearch|titles?|page|from|to|apfrom|apto|apprefix|aifrom|aiprefix|aufrom|auprefix|bltitle|euquery|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:
+5 -18
View File
@@ -48,10 +48,8 @@ func TestOrdinaryMediaWikiRequestsPass(t *testing.T) {
wantRecordedRequestsPass(t, "mediawiki-requests.txt")
}
// 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
// path where it skips them, ending in "=".
// mediawikiQueryFields are a query field of each exclusion of a query
// field, on a path it applies to, each ending in "=".
//
//nolint:gochecknoglobals // a constant cannot be a list
var mediawikiQueryFields = []string{
@@ -78,19 +76,13 @@ var mediawikiQueryFields = []string{
"/api.php?action=query&list=search&srsearch=",
"/api.php?action=query&list=prefixsearch&pssearch=",
"/api.php?action=query&generator=prefixsearch&gpssearch=",
"/api.php?action=query&generator=search&gsrsearch=",
"/api.php?action=query&titles=",
"/api.php?action=parse&page=",
"/api.php?action=query&list=allpages&apfrom=",
"/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&list=allimages&aifrom=",
"/api.php?action=query&list=allimages&aiprefix=",
"/api.php?action=query&list=allusers&aufrom=",
"/api.php?action=query&list=allusers&auprefix=",
"/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&list=usercontribs&ucuser=",
"/api.php?action=query&list=blocks&bkusers=",
@@ -129,10 +121,8 @@ func TestMediaWikiAttacksAreRefusedInTheExcludedQueryFields(t *testing.T) {
// mediawikiBodyField is a field of a form data body, sent to a path.
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
// a path where it skips them.
// mediawikiBodyFields are a field of each exclusion of a body field, other
// than those that hold wikitext, on a path it applies to.
//
//nolint:gochecknoglobals // a constant cannot be a list
var mediawikiBodyFields = []mediawikiBodyField{
@@ -149,7 +139,6 @@ var mediawikiBodyFields = []mediawikiBodyField{
{"/w" + mediawikiSignInPage, "wpPassword="},
{mediawikiUploadPage, "wpDestFile="},
{"/w" + mediawikiUploadPage, "wpUploadDescription="},
{mediawikiAPI, "title="},
{mediawikiAPI, "from="},
{mediawikiAPI, "to="},
{mediawikiAPI, "summary="},
@@ -158,9 +147,7 @@ var mediawikiBodyFields = []mediawikiBodyField{
{mediawikiAPI, "comment="},
{mediawikiAPI, "reason="},
{mediawikiAPI, "user="},
{mediawikiAPI, "lgname="},
{mediawikiAPI, "lgpassword="},
{mediawikiAPI, "username="},
{mediawikiAPI, "password="},
}
@@ -228,7 +215,7 @@ func TestMediaWikiCommandAttackIsRefusedOutsideWikitext(t *testing.T) {
crs := atDefaults(t)
for _, target := range append([]string{"/load.php?modules="}, mediawikiQueryFields...) {
for _, target := range mediawikiQueryFields {
got := inspect(t, crs, get(target+mediawikiCommandAttack))
if got.Score < mediawikiThreshold {
t.Errorf("%s let through: %+v", target, got)
+1 -1
View File
@@ -2,7 +2,7 @@
# the defaults. They were recorded from a wiki driven by a headless Chrome and
# an API client. Each line is one request as JSON. Host names, passwords and
# session cookies are invented.
{"method": "GET", "target": "/images/README", "headers": ["Host: example.com", "User-Agent: MediaWiki/1.46.2 (https://\ud83c\udf3b.invalid)"], "body": ""}
{"method": "GET", "target": "/images/README", "headers": ["Host: example.com", "User-Agent: MediaWiki/1.46.2 (https://\u00f0\u009f\u008c\u00bb.invalid)"], "body": ""}
{"method": "GET", "target": "/wiki/Main_Page", "headers": ["Host: example.com", "User-Agent: Wget", "Accept: */*"], "body": ""}
{"method": "GET", "target": "/", "headers": ["Host: example.com", "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/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": "/load.php?lang=en&modules=skins.vector.icons%2Cstyles%7Cskins.vector.search.codex.styles&only=styles&skin=vector-2022", "headers": ["Host: example.com", "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/css,*/*;q=0.1", "Referer: http://example.com/wiki/Main_Page", "Accept-Encoding: gzip, deflate"], "body": ""}