Back to Articles

How the JSON API and XMLRPC are used for Brute Force Attacks Against WordPress

By Daniel Cid (@dcid) Posted in: security-research, wordpress-security

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