Two Ways to Fully Disable WordPress XML-RPC

Back several years ago when XML-RPC attacks on WordPress were prevalent, I shared some techniques here for selectively countering such attacks. Most users, however, just want to shut XML-RPC off completely. They often land on the widely installed Disable XML-RPC plugin. This plugin unfortunately does not fully work. Let me show you why, share some better solutions, and update my unit testing code for Python 3 in the process.

XML-RPC test code

The Disable XML-RPC plugin does not work they way people assume

It’s not the plugin author’s fault, but the plugin by this name only disables a portion of the defined XML-RPC method space. This is because all it does is call add_filter( 'xmlrpc_enabled', '__return_false' ); – which by the way you could do from anywhere, such as your theme’s functions.php.

The problem is that after debating how the hook should work, WordPress Core devs tied developers’ hands here, and the xmlrpc_enabled filter in core was left deliberately ineffective. For compatibility with an earlier extant toggle control long since removed from the wp-admin UI, it only disables authenticated XML-RPC methods, not all XML-RPC methods.

You would think a hook named xmlrpc_enabled would completely shut off the XML-RPC subsystem, right? Wrong.

So let’s say you have no use for the XML-RPC subsystem, think it’s a security hazard (it is), and just want it gone. Most developers end up blocking it from .htaccess, but I’ll show you two ways.

Alternative 1: Unregister the whole XML-RPC method space from functions.php

This technique hooks the xmlrpc_methods filter and empties the method space at runtime:

// disable xmlrpc
function remove_xmlrpc_methods( $methods ) {
  return array();
}
add_filter( 'xmlrpc_methods', 'remove_xmlrpc_methods' );

Any subsequent request for any method will return an XML-RPC Fault object informing “requested method does not exist”. Ugly, perhaps, but it effectively closes the service, and it’s easily deployed in your theme’s or child theme’s functions.php.

Alternative 2: Block xmlrpc.php from .htaccess

Most users end up going this route because it’s the primary advice you get from Googling the problem:

# disable xmlrpc
<FilesMatch "^xmlrpc\.php$">
  Require all denied
</FilesMatch>

That goes in .htaccess at the root of your WordPress installation. Apache then responds 403 Forbidden for all requests to xmlrpc.php. (I actually block a couple more things I don’t want attackers loading this way too.) Notice the use of Apache 2.4 mod_authz style Require as the old Apache 2.2 style Allow and Deny directives are now deprecated.

You can safely deploy both techniques. They won’t interfere with each other, and you’ll have defense in depth in case either one gets accidentally removed.

Testing that XML-RPC blocking is working

Now we just need to test whether the blocking is working or not.

The code snippets in my old post for sending specially crafted attack requests to xmlrpc.php were written for Python 2 and are now outdated. Here’s some new code good for Python 3.

If we just want to know if the XML-RPC subsystem is available, there’s a demo method in the core XML-RPC subsystem called demo.sayHello() that’ll do the job:

#!/usr/bin/python3

import ssl
import xmlrpc.client

# this disables ssl certificate verification so you can use this on dev targets
c = ssl.create_default_context()
c.check_hostname = False
c.verify_mode = ssl.CERT_NONE

proxy = xmlrpc.client.ServerProxy("https://your_server_address_or_hostname/xmlrpc.php", context=c)
print(proxy.demo.sayHello())

The imports are in the standard library.

For an unblocked XML-RPC subsystem, this will succeed and print “Hello!”. If you run this and see “Hello!”, XML-RPC is still on and responding to requests.

Under alternative technique 1 above (xmlrpc_methods filter), it will exit with a traceback to an xmlrpc.client.Fault reporting “requested method demo.sayHello does not exist”.

Under alternative technique 2 above (.htaccess blocking) or the combined application of both techniques, it will exit with a traceback to an xmlrpc.client.ProtocolError reporting 403 Forbidden.

Resources

Countering WordPress XML-RPC Attacks with fail2ban, from 2014

WordPress Code Reference: Hooks: xmlrpc_enabled