WordPress is the most popular CMS—and because of that popularity, it’s also heavily targeted. Brute-force attacks (guessing credentials) remain common. Every brute-force requires two halves of the same pie: a username and a password. So how do attackers find valid usernames when it isn’t admin? And how do they test lots of passwords efficiently?
Finding Users via the WordPress JSON API
The WP REST API can expose author data. In the wild, we see automated attempts to enumerate users through /wp-json/wp/v2/users/
, then fall back to author ID probes:
2021-06-21 20:21:46 [site] cdn-edge-usa-east-atlanta1 129.213.160.208 403 196 HIT waf:spam_bot1 GET //wp-json/wp/v2/users/ HTTP/1.1 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36
2021-06-14 20:24:04 [site] cdn-edge-usa-east-atlanta1 129.213.160.208 403 210 HIT waf:virtual_hardening1 GET //?author=1 HTTP/1.1 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36
With a candidate username in hand, attackers pivot to credential testing—commonly through xmlrpc.php
.
How XMLRPC is Used in Brute-Force Attacks
We observe two prevalent patterns against /xmlrpc.php
:
1) Single-user credential checks via wp.getUsersBlogs
The attacker submits a POST payload that validates credentials:
<methodCall>
<methodName>wp.getUsersBlogs</methodName>
<params>
<param><value>userx</value></param>
<param><value>author</value></param>
</params>
</methodCall>
2) Multi-guess batching with system.multicall
Far more dangerous, this bundles many attempts in one request to reduce rate limits and logging volume:
<methodCall>
<methodName>system.multicall</methodName>
<params>
<param><value><array><data>
<value><struct>
<member><name>methodName</name><value><string>wp.getUsersBlogs</string></value></member>
<member><name>params</name><value><array><data>
<value><array><data>
<value><string>4seasons</string></value>
<value><string>z43218765z</string></value>
</data></array>
</data></array></value></member>
</struct></value>
</data></array></value></param>
</params>
</methodCall>
In parallel you’ll see POSTs to /xmlrpc.php
like:
2021-06-21 20:54:36 [site] cdn-edge-usa-east-atlanta1 129.213.160.208 200 726 HIT waf: POST //xmlrpc.php HTTP/1.1 - Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Hardening XMLRPC and WP-JSON
The most effective baseline: block access to xmlrpc.php
(just like you’d protect /wp-admin
and /wp-login.php
):
<Files xmlrpc.php>
order deny,allow
deny from all
allow from xxx.xxx.xxx.xxx
</Files>
If you must keep XMLRPC (e.g., Jetpack), apply deeper inspection to POST payloads and strip/deny calls such as wp.getUsersBlogs
and system.multicall
. That typically requires a WAF or advanced server tuning.
We also recommend restricting the WP REST API if you don’t need it:
# Block WP REST API (wp-json) for key methods
RewriteCond %{REQUEST_METHOD} ^(GET|POST|PUT|PATCH|DELETE) [NC]
RewriteCond %{REQUEST_URI} ^.*wp-json/wp/ [NC]
RewriteRule ^(.*)$ - [F]
Prefer not to wrangle rules? A cloud WAF can provide virtual hardening and patching at the edge, reducing noise and blocking automated brute-force attempts before they reach your origin.
NOC — Authoritative DNS, CDN & WAF
Accelerate and protect your sites with global DNS, edge caching, and an always-on web application firewall.
See Plans