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.
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