Files
smallwebwaf/internal/coreruleset/wordpress.conf
T
clawbot c0071a7e8c
check / check (push) Waiting to run
Core Rule Set: random passwords pass at sign-in (closes #276)
A password manager's random password set off 35 Core Rule Set rules,
about 1 in 40 refused. The password fields the app exclusions name now
skip those rules on their paths: WordPress pwd, Drupal pass, MediaWiki
wpPassword, lgpassword and password, Mastodon's sign-in, sign-up,
password change, move and delete passwords, and Nextcloud's sign-in
password. The tests draw 2,000 random passwords from a fixed seed, plus
one rare password for each rule the 2,000 do not set off. Those that set
off a rule in a field no exclusion names are sent to every password
field. `' OR '1'='1`, `../../etc/passwd`, `; cat /etc/passwd` and
`<img src=x onerror=x()>` sent as a password now pass;
`<script>alert(1)</script>` and `javascript:alert(1)` are still refused.

Model: opus-5-5
2026-11-10 10:41:25 +00:00

232 lines
12 KiB
Plaintext

# The ninth change: on WordPress's paths, some fields skip the rules that refused
# ordinary WordPress requests there. Each exclusion runs with the request, before
# the rules it switches off, so this file is loaded before the Core Rule Set's
# rules. Each comment names a request the rule refused. An exclusion on a field
# of a body applies only when the query has no parameter of that name, so it
# matters only while SWW_WAF_BODY_LIMIT is set.
# WordPress takes a call to its REST API at any of four addresses:
# /wp-json/wp/v2/posts/1
# /index.php/wp-json/wp/v2/posts/1
# /index.php?rest_route=/wp/v2/posts/1
# /?rest_route=/wp/v2/posts/1
# These two rules put the route, here /wp/v2/posts/1, in tx.rest_route for the
# exclusions below. A call that names a route in both its path and its query
# gets none of them, since WordPress follows the query.
SecRule &ARGS_GET:rest_route "@eq 0" "id:900705,phase:1,pass,nolog,chain"
SecRule REQUEST_FILENAME "@rx ^/(?:index\.php/)?wp-json(/.*)$" \
"capture,setvar:tx.rest_route=%{tx.1}"
SecRule REQUEST_FILENAME "@rx ^/(?:index\.php)?$" "id:900706,phase:1,pass,nolog,chain"
SecRule ARGS_GET:rest_route "@rx ^(/.*)$" "capture,setvar:tx.rest_route=%{tx.1}"
# The block editor saves a post with POST and the header X-HTTP-Method-Override:
# PUT, which the Core Rule Set refused (920450). So does saving a user's editor
# preferences.
SecRule TX:rest_route "@beginsWith /" \
"id:900700,phase:1,pass,nolog,\
ctl:ruleRemoveTargetById=920450;REQUEST_HEADERS_NAMES:X-HTTP-Method-Override"
# The block editor and the site editor send HTML in the JSON field content, which
# skips these rules when saving or autosaving a post, a page, a template or a
# template part:
# 930120: cat /etc/hosts
# 932230: a code block with a shell pipeline and an SQL query
# 932250: the same code block
# 934100: console.log('ok')
# 941100: <iframe src="..." onload="...">
# 941110: <script>
# 941120: onload=
# 941160: <img src=
# 941180: <!-- wp:paragraph -->, the comment that starts every block
# 941390: console.log(
# 942190: how to select a table from the catalog
# 942270: how to select a table from the catalog
SecRule TX:rest_route \
"@rx ^/wp/v2/(?:(?:posts|pages)(?:/[0-9]+(?:/autosaves)?)?|template(?:s|-parts)(?:/[^/]+//[^/]+(?:/autosaves)?)?)$" \
"id:900701,phase:1,pass,nolog,chain"
SecRule &ARGS_GET:json.content "@eq 0" \
"ctl:ruleRemoveTargetById=930120;ARGS:json.content,\
ctl:ruleRemoveTargetById=932230;ARGS:json.content,\
ctl:ruleRemoveTargetById=932250;ARGS:json.content,\
ctl:ruleRemoveTargetById=934100;ARGS:json.content,\
ctl:ruleRemoveTargetById=941100;ARGS:json.content,\
ctl:ruleRemoveTargetById=941110;ARGS:json.content,\
ctl:ruleRemoveTargetById=941120;ARGS:json.content,\
ctl:ruleRemoveTargetById=941160;ARGS:json.content,\
ctl:ruleRemoveTargetById=941180;ARGS:json.content,\
ctl:ruleRemoveTargetById=941390;ARGS:json.content,\
ctl:ruleRemoveTargetById=942190;ARGS:json.content,\
ctl:ruleRemoveTargetById=942270;ARGS:json.content"
# A reply to a comment from the list of comments, in content:
# 932125: ls -la, in the reply's text
# 932230: ls -la, in the reply's text
# 932250: ls -la, in the reply's text
SecRule REQUEST_FILENAME "@streq /wp-admin/admin-ajax.php" \
"id:900702,phase:1,pass,nolog,chain"
SecRule &ARGS_GET:content "@eq 0" \
"ctl:ruleRemoveTargetById=932125;ARGS:content,\
ctl:ruleRemoveTargetById=932230;ARGS:content,\
ctl:ruleRemoveTargetById=932250;ARGS:content"
# Saving a plugin's or a theme's file from its file editor sends the file in
# newcontent, which skips these rules, each of which refused saving the plugin
# Hello Dolly's hello.php. Some of them refused only the whole file:
# 932140: if ( 'en_' !== substr(
# 932235: the whole file
# 933100: the whole file
# 933160: if ( 'en_' !== substr(
# 933210: $lyrics = explode(
# 941100: <style type='text/css'>
# 941160: <style type='text/css'>
# 941190: the whole file
# 942151: if ( 'en_' !== substr(
# 942540: Dolly'll never go away again";
SecRule REQUEST_FILENAME "@streq /wp-admin/admin-ajax.php" \
"id:900703,phase:1,pass,nolog,chain"
SecRule &ARGS_GET:newcontent "@eq 0" \
"ctl:ruleRemoveTargetById=932140;ARGS:newcontent,\
ctl:ruleRemoveTargetById=932235;ARGS:newcontent,\
ctl:ruleRemoveTargetById=933100;ARGS:newcontent,\
ctl:ruleRemoveTargetById=933160;ARGS:newcontent,\
ctl:ruleRemoveTargetById=933210;ARGS:newcontent,\
ctl:ruleRemoveTargetById=941100;ARGS:newcontent,\
ctl:ruleRemoveTargetById=941160;ARGS:newcontent,\
ctl:ruleRemoveTargetById=941190;ARGS:newcontent,\
ctl:ruleRemoveTargetById=942151;ARGS:newcontent,\
ctl:ruleRemoveTargetById=942540;ARGS:newcontent"
# Signing in, in pwd, which skips each rule that random passwords set off.
SecRule REQUEST_FILENAME "@streq /wp-login.php" \
"id:900704,phase:1,pass,nolog,chain"
SecRule &ARGS_GET:pwd "@eq 0" \
"ctl:ruleRemoveTargetById=920270;ARGS:pwd,\
ctl:ruleRemoveTargetById=920540;ARGS:pwd,\
ctl:ruleRemoveTargetById=921200;ARGS:pwd,\
ctl:ruleRemoveTargetById=930100;ARGS:pwd,\
ctl:ruleRemoveTargetById=930110;ARGS:pwd,\
ctl:ruleRemoveTargetById=930120;ARGS:pwd,\
ctl:ruleRemoveTargetById=932125;ARGS:pwd,\
ctl:ruleRemoveTargetById=932130;ARGS:pwd,\
ctl:ruleRemoveTargetById=932160;ARGS:pwd,\
ctl:ruleRemoveTargetById=932230;ARGS:pwd,\
ctl:ruleRemoveTargetById=932250;ARGS:pwd,\
ctl:ruleRemoveTargetById=932260;ARGS:pwd,\
ctl:ruleRemoveTargetById=932270;ARGS:pwd,\
ctl:ruleRemoveTargetById=932280;ARGS:pwd,\
ctl:ruleRemoveTargetById=932330;ARGS:pwd,\
ctl:ruleRemoveTargetById=932340;ARGS:pwd,\
ctl:ruleRemoveTargetById=932370;ARGS:pwd,\
ctl:ruleRemoveTargetById=932380;ARGS:pwd,\
ctl:ruleRemoveTargetById=933100;ARGS:pwd,\
ctl:ruleRemoveTargetById=933135;ARGS:pwd,\
ctl:ruleRemoveTargetById=933180;ARGS:pwd,\
ctl:ruleRemoveTargetById=933210;ARGS:pwd,\
ctl:ruleRemoveTargetById=941100;ARGS:pwd,\
ctl:ruleRemoveTargetById=941120;ARGS:pwd,\
ctl:ruleRemoveTargetById=941160;ARGS:pwd,\
ctl:ruleRemoveTargetById=941180;ARGS:pwd,\
ctl:ruleRemoveTargetById=942100;ARGS:pwd,\
ctl:ruleRemoveTargetById=942151;ARGS:pwd,\
ctl:ruleRemoveTargetById=942190;ARGS:pwd,\
ctl:ruleRemoveTargetById=942230;ARGS:pwd,\
ctl:ruleRemoveTargetById=942290;ARGS:pwd,\
ctl:ruleRemoveTargetById=942320;ARGS:pwd,\
ctl:ruleRemoveTargetById=942540;ARGS:pwd,\
ctl:ruleRemoveTargetById=942550;ARGS:pwd,\
ctl:ruleRemoveTargetById=944150;ARGS:pwd"
# Saving through the REST API, the JSON fields title, excerpt and name skip these
# rules. WordPress writes a slug such as htaccess or ls-la, so slug skips only
# 932120 and 932260:
# 930120: .htaccess
# 932120: powershell
# 932250: ls -la
# 932260: python3
SecRule TX:rest_route "@beginsWith /wp/v2/" "id:900712,phase:1,pass,nolog,chain"
SecRule &ARGS_GET:/^json\.(?:title|excerpt|slug|name)$/ "@eq 0" \
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.(?:title|excerpt|name)$/,\
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.(?:title|excerpt|slug|name)$/,\
ctl:ruleRemoveTargetById=932250;ARGS:/^json\.(?:title|excerpt|name)$/,\
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.(?:title|excerpt|slug|name)$/"
# A comment's author gives the address of their site in url, which skips these
# rules there:
# 930120: http://blog.example/.htaccess
# 932120: http://blog.example/powershell-basics/
SecRule REQUEST_FILENAME "@streq /wp-comments-post.php" \
"id:900713,phase:1,pass,nolog,chain"
SecRule &ARGS_GET:url "@eq 0" \
"ctl:ruleRemoveTargetById=930120;ARGS:url,\
ctl:ruleRemoveTargetById=932120;ARGS:url"
# The block editor's link previews and embeds send an address in url, which
# skips these rules there:
# 930120: http://blog.example/.htaccess
# 932120: http://blog.example/powershell-basics/
SecRule TX:rest_route "@rx ^/(?:wp-block-editor/v1/url-details|oembed/1\.0/proxy)$" \
"id:900714,phase:1,pass,nolog,\
ctl:ruleRemoveTargetById=930120;ARGS:url,\
ctl:ruleRemoveTargetById=932120;ARGS:url"
# The plugin and theme file editors name the file open in file, which skips
# these rules there:
# 930120: akismet/views/config.php
# 932120: install-script.sh
# 932160: bin/install.sh
# 932260: docker-compose.yml
# 941180: document.write.js
SecRule REQUEST_FILENAME "@rx ^/wp-admin/(?:plugin|theme)-editor\.php$" \
"id:900709,phase:1,pass,nolog,\
ctl:ruleRemoveTargetById=930120;ARGS:file,\
ctl:ruleRemoveTargetById=932120;ARGS:file,\
ctl:ruleRemoveTargetById=932160;ARGS:file,\
ctl:ruleRemoveTargetById=932260;ARGS:file,\
ctl:ruleRemoveTargetById=941180;ARGS:file"
# Searches, the names of tags, categories, plugins, themes and plugins' settings
# pages, and addresses can hold any word. In the fields of the next three rules,
# each request skips the rules that refused such words:
# 930120: .htaccess
# 932120: powershell
# 932250: ls -la
# 932260: python3
# An address repeats the search it came from, so only the addresses of the first
# group also skip:
# 932235: python3, in the address of a page of results
# All of wp-admin, and signing in. A plugin's settings page is named in page.
# The site editor names a template in p and postId, and a plugin to install is
# named in slug. WordPress writes these three as slugs, such as htaccess or
# ls-la, so they skip only 932120 and 932260. Names are matched in lower case,
# so postId is written postid.
SecRule REQUEST_FILENAME "@rx ^/(?:wp-admin/|wp-login\.php$)" \
"id:900707,phase:1,pass,nolog,\
ctl:ruleRemoveTargetById=930120;ARGS:/^(?:s|search|q|term|tag|category_name|plugin|checked\[\]|theme|stylesheet|page|url|return|redirect_to|wp_http_referer|_wp_http_referer)$/,\
ctl:ruleRemoveTargetById=932120;ARGS:/^(?:s|search|q|term|tag|category_name|plugin|checked\[\]|theme|stylesheet|page|p|postid|slug|url|return|redirect_to|wp_http_referer|_wp_http_referer)$/,\
ctl:ruleRemoveTargetById=932235;ARGS:/^(?:url|return|redirect_to|wp_http_referer|_wp_http_referer)$/,\
ctl:ruleRemoveTargetById=932250;ARGS:/^(?:s|search|q|term|tag|category_name|plugin|checked\[\]|theme|stylesheet|page|url|return|redirect_to|wp_http_referer|_wp_http_referer)$/,\
ctl:ruleRemoveTargetById=932260;ARGS:/^(?:s|search|q|term|tag|category_name|plugin|checked\[\]|theme|stylesheet|page|p|postid|slug|url|return|redirect_to|wp_http_referer|_wp_http_referer)$/"
# Every route of WordPress's own REST API, and the embed address each post links
# to.
SecRule TX:rest_route "@rx ^/(?:wp/v2/|oembed/1\.0/embed$)" \
"id:900708,phase:1,pass,nolog,\
ctl:ruleRemoveTargetById=930120;ARGS:/^(?:search|term|slug|url)$/,\
ctl:ruleRemoveTargetById=932120;ARGS:/^(?:search|term|slug|url)$/,\
ctl:ruleRemoveTargetById=932250;ARGS:/^(?:search|term|slug|url)$/,\
ctl:ruleRemoveTargetById=932260;ARGS:/^(?:search|term|slug|url)$/"
# The site's own pages, and the later pages of a search.
SecRule REQUEST_FILENAME "@rx ^/(?:(?:index\.php/)?page/[0-9]+/?|index\.php)?$" \
"id:900710,phase:1,pass,nolog,\
ctl:ruleRemoveTargetById=930120;ARGS:/^(?:s|tag)$/,\
ctl:ruleRemoveTargetById=932120;ARGS:/^(?:s|tag)$/,\
ctl:ruleRemoveTargetById=932250;ARGS:/^(?:s|tag)$/,\
ctl:ruleRemoveTargetById=932260;ARGS:/^(?:s|tag)$/"
# The media list sends its own address in _wp_http_referer, which ends in &ps and
# so skips 932340 there.
SecRule REQUEST_FILENAME "@streq /wp-admin/upload.php" \
"id:900711,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932340;ARGS:_wp_http_referer"