Core Rule Set: WordPress's names and addresses pass on more fields (closes #245)
check / check (push) Failing after 2m54s

The four rules on system files and commands (930120, 932120, 932250,
932260) now skip:

- page, p, postId and slug on all of wp-admin;
- url on the block editor's link previews and embeds, by widening the
  REST group to /wp-block-editor/v1/ and /oembed/1.0/;
- the JSON fields title, excerpt, slug and name on /wp/v2/ routes.

A commenter's url skips 930120 and 932120 only. Every field keeps 932235.
Add 61 requests recorded from WordPress 7.1.4 with these fields.

Model: opus-5-5
This commit is contained in:
clawbot committed 2026-11-09 19:27:26 +00:00
1 parent decd9b4658
commit 1e39603125
5 files changed
+238 -17

No files matched your search

+126 -1
View File
@@ -665,6 +665,15 @@ func wordpressAttackTargets(attack string) []string {
"/wp-admin/plugin-editor.php?plugin=" + attack,
"/wp-admin/plugins.php?plugin=" + attack,
"/wp-admin/update.php?plugin=" + attack,
"/wp-admin/admin.php?page=" + attack,
"/wp-admin/options-general.php?page=" + attack,
"/wp-admin/tools.php?page=" + attack,
"/wp-admin/site-editor.php?p=" + attack,
"/wp-admin/site-editor.php?postId=" + attack,
"/wp-admin/admin-ajax.php?slug=" + attack,
"/wp-json/wp-block-editor/v1/url-details?url=" + attack,
"/wp-json/oembed/1.0/proxy?url=" + attack,
"/?rest_route=/oembed/1.0/proxy&url=" + attack,
}
}
@@ -717,6 +726,24 @@ func TestOrdinaryWordPressRequestsPassAndAttacksAreRefused(t *testing.T) {
t.Errorf("POST %s %q let through: %+v", target, body, got)
}
}
// In the body fields that skip some rules.
decoded, _ := url.QueryUnescape(attack)
for _, tt := range []struct{ target, contentType, body string }{
{"/wp-admin/admin-ajax.php", formData, "action=install-plugin&slug=" + attack},
{"/wp-comments-post.php", formData, "url=" + attack},
{"/wp-json/wp/v2/posts/1", jsonBody, `{"title":` + strconv.Quote(decoded) + `}`},
{"/wp-json/wp/v2/posts/1", jsonBody, `{"excerpt":` + strconv.Quote(decoded) + `}`},
{"/wp-json/wp/v2/posts/1", jsonBody, `{"slug":` + strconv.Quote(decoded) + `}`},
{"/wp-json/wp/v2/tags", jsonBody, `{"name":` + strconv.Quote(decoded) + `}`},
} {
got, _ := inspectBody(t, bodies,
request{http.MethodPost, tt.target, []string{tt.contentType}}, tt.body)
if got.Score < threshold {
t.Errorf("POST %s %q let through: %+v", tt.target, tt.body, got)
}
}
}
}
@@ -829,7 +856,7 @@ func TestWordPressGroupsSkipRulesOnTheirFields(t *testing.T) {
"/wp-admin/options.php?",
[]string{
"s", "search", "q", "term", "tag", "category_name", "plugin",
"checked%5B%5D", "theme", "stylesheet",
"checked%5B%5D", "theme", "stylesheet", "page", "p", "postId", "slug",
},
[]string{"url", "return", "redirect_to", "wp_http_referer", "_wp_http_referer"},
},
@@ -863,6 +890,9 @@ func TestWordPressGroupsSkipRulesOnTheirFields(t *testing.T) {
"/?rest_route=/wp/v2/tags&slug=python3",
"/wp-json/oembed/1.0/embed?url=python3",
"/?rest_route=/oembed/1.0/embed&url=python3",
"/wp-json/oembed/1.0/proxy?url=python3",
"/wp-json/wp-block-editor/v1/url-details?url=python3",
"/?rest_route=/wp-block-editor/v1/url-details&url=python3",
"/index.php?tag=python3",
"/page/2/?tag=python3",
"/index.php/page/2/?s=python3",
@@ -891,9 +921,35 @@ func TestWordPressCommandAfterASeparatorIsRefusedOnSearchesAndNames(t *testing.T
"/?tag=",
"/wp-admin/edit.php?tag=",
"/wp-admin/admin-ajax.php?q=",
"/wp-admin/admin.php?page=",
"/wp-admin/options-general.php?page=",
"/wp-admin/tools.php?page=",
"/wp-admin/site-editor.php?p=",
"/wp-admin/site-editor.php?postId=",
"/wp-admin/admin-ajax.php?slug=",
"/wp-json/wp-block-editor/v1/url-details?url=",
"/wp-json/oembed/1.0/proxy?url=",
} {
wantResult(t, crs, get(target+command), matched(932235))
}
bodies := readingBodiesUpTo64K(t)
decoded, _ := url.QueryUnescape(command)
for _, tt := range []struct{ target, contentType, body string }{
{"/wp-admin/admin-ajax.php", formData, "action=install-plugin&slug=" + command},
{"/wp-json/wp/v2/posts/1", jsonBody, `{"title":"` + decoded + `"}`},
{"/wp-json/wp/v2/posts/1", jsonBody, `{"excerpt":"` + decoded + `"}`},
{"/wp-json/wp/v2/posts/1", jsonBody, `{"slug":"` + decoded + `"}`},
{"/wp-json/wp/v2/tags", jsonBody, `{"name":"` + decoded + `"}`},
} {
r := request{http.MethodPost, tt.target, []string{tt.contentType}}
wantBody(t, bodies, r, tt.body, matched(932235), tt.body)
}
// A commenter's address keeps 932260 too.
comment := request{http.MethodPost, "/wp-comments-post.php", []string{formData}}
wantBody(t, bodies, comment, "url="+command, matched(932235, 932260), "url="+command)
}
// TestWordPressOtherFieldsAreExcludedOnlyThere checks each field and rule
@@ -946,6 +1002,16 @@ func TestWordPressOtherFieldsAreExcludedOnlyThere(t *testing.T) {
{"/wp-admin/plugin-editor.php?plugin=docker-helper", 932260},
{"/wp-admin/plugins.php?plugin=powershell-tools", 932120},
{"/wp-admin/update.php?plugin=docker-helper", 932260},
{"/wp-admin/admin.php?page=docker-helper", 932260},
{"/wp-admin/options-general.php?page=powershell-tools", 932120},
{"/wp-admin/tools.php?page=ls+-la", 932250},
{"/wp-admin/tools.php?page=.htaccess", 930120},
{"/wp-admin/site-editor.php?p=%2Fwp_template%2Fpython3-theme%2F%2Findex", 932260},
{"/wp-admin/site-editor.php?postId=powershell-theme%2F%2Findex", 932120},
{"/wp-admin/admin-ajax.php?slug=docker-helper", 932260},
{"/wp-json/wp-block-editor/v1/url-details?url=" +
"http%3A%2F%2Fblog.example%2Fpowershell-basics%2F", 932120},
{"/wp-json/oembed/1.0/proxy?url=http%3A%2F%2Fblog.example%2F.htaccess", 930120},
} {
_, query, _ := strings.Cut(tt.target, "?")
wantChange(t, crs, get(tt.target), get(elsewhere+"?"+query), matched(tt.rule))
@@ -1002,6 +1068,65 @@ func TestWordPressBodyFieldsAreCheckedInTheQuery(t *testing.T) {
wantResult(t, crs,
get("/wp-json/wp/v2/posts/1?json.content="+url.QueryEscape(wordpressBlock)),
matched(941180))
wantResult(t, crs, get("/wp-json/wp/v2/posts/1?json.title=python3"), matched(932260))
wantResult(t, crs, get("/wp-comments-post.php?url=python3"), matched(932260))
}
// TestWordPressBodyNamesAndAddressesAreExcludedOnlyThere checks the JSON
// fields of a REST API save and a commenter's address, against the same
// values in another field and off their paths.
func TestWordPressBodyNamesAndAddressesAreExcludedOnlyThere(t *testing.T) {
t.Parallel()
crs := readingBodiesUpTo64K(t)
for _, v := range []struct {
value string
rule int
}{
{".htaccess", 930120},
{"powershell", 932120},
{"ls -la", 932250},
{"python3", 932260},
} {
for _, name := range []string{"title", "excerpt", "slug", "name"} {
body := `{"` + name + `":"` + v.value + `"}`
for _, address := range wordpressRESTAddresses("/wp/v2/posts/1") {
r := request{http.MethodPost, address, []string{jsonBody, wordpressOverride}}
wantBody(t, crs, r, body, coreruleset.Result{}, body)
}
r := request{http.MethodPost, "/wp-json/example/v1/things", []string{jsonBody}}
wantBody(t, crs, r, body, matched(v.rule), body)
}
other := `{"description":"` + v.value + `"}`
r := request{http.MethodPost, "/wp-json/wp/v2/tags", []string{jsonBody}}
wantBody(t, crs, r, other, matched(v.rule), other)
}
comment := request{http.MethodPost, "/wp-comments-post.php", []string{formData}}
elsewhere := request{http.MethodPost, "/example/things", []string{formData}}
// A commenter's address skips the two rules that refused an address.
for _, v := range []struct {
address string
rule int
}{
{"http://blog.example/.htaccess", 930120},
{"http://blog.example/powershell-basics/", 932120},
} {
body := "url=" + url.QueryEscape(v.address)
wantBody(t, crs, comment, body, coreruleset.Result{}, body)
wantBody(t, crs, elsewhere, body, matched(v.rule), body)
author := "author=" + url.QueryEscape(v.address)
wantBody(t, crs, comment, author, matched(v.rule), author)
}
// The rules on Unix commands keep it.
wantBody(t, crs, comment, "url=python3", matched(932260), "url=python3")
wantBody(t, crs, comment, "url=ls+-la", matched(932250), "url=ls+-la")
}
func TestWordPressExclusionsAreNarrow(t *testing.T) {