Wordpress, XML-RPC and Security

XML-RPC is for sure one of the two Achille's heels of Wordpress.

It is a notorious target for hackers who like to do one of these three things or a combination of them all with the xmlrpc.php script:

  • DOSing your website
  • Using your website to stage a DDOS on someone else's website
  • Try to gather more information about your website for further hacking

It's a very well known problem, and the web is full of blogs and forum posts stating that the best course of action is to shut of that endpoint completely.

Different people goes about different ways to do that, but if your Wordpress run behind a Nginx web server, the most common solution I've seen is to add the following restriction in the configuration of the web server

location = /xmlrpc.php {
	deny all;
	access_log off;
	log_not_found off;
}

If you were to hit the xmlrpc.php endpoint on the server with the above configuration you will get a 403 HTTP Error response.

Another way of turning the XML-RPC interface is to add the following filter to Wordpress

add_filter( 'xmlrpc_enabled', '__return_false' );

Apparently it has the advantage of allowing Automatic's Jetpack plugin to still work which I cannot verify as I'm not using that plugin on my websites.

However, what happen when you actually need to use that endpoint, either because your client want to be able to access the website from his smartphone app or there is a requirement to integrate Wordpress with automation services like Zapier.

It may be interesting to have a look at what XML-RPC do and how it works.

Clients POST an xml document to the endpoint that contains a method and parameters

<?xml version="1.0"?>
<methodCall>
    <methodName>system.listMethods</methodName>
    <params>
        <param>
            <value>
                <string/>
            </value>
        </param>
    </params>
</methodCall>

and the endpoint return an XML response in case of success

HTTP/1.1 200 OK
<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
    <params>
        <param>
            <value>
                <array>
                    <data>
                        <value>
                            <string>system.multicall</string>
                        </value>
                        <value>
                            <string>system.listMethods</string>
                        </value>
                        <value>
                            <string>system.getCapabilities</string>
                        </value>
                        <value>
                            <string>demo.addTwoNumbers</string>
                        </value>
                        <value>
                            <string>demo.sayHello</string>
                        </value>
                        <value>
...

On this page, you can see all the methods Wordpress accepts.

Strategy 1: unset risky methods

To this day, there seems to be three of them that have been exploited for nefarious purposes, and one strategy then is to deactivate these methods from the XML-RPC interface.

you can do so with another Wordpress filter

add_filter( 'xmlrpc_methods', 'unset_risky_methods' );

function unset_risky_methods( $methods ) {
  unset( $methods['pingback.ping'] );
  unset( $methods['pingback.extensions.getPingbacks'] );
  unset( $methods['wp.getUsersBlogs'] ); 
  return $methods;
}

There is actually a Wordpress plugin that will implements the code above.

However that doesn't stop bots to continue hammering your XML-RPC (in particular there's a fake Google Bot that like to POST data to Wordpress websites XML-RPC endpoints).

Also you have to maintain awareness of new methods that can hackers can exploits.

It's still a good first step as it reduces the attack surface.

Strategy 2: IP whitelisting

Another approach is to whitelist the services you want to communicate with, which is easier said than done.

The reason is because the services we are likely to interface with are massively scalable and their range of IPs is large and likely to change from time to time.

So at some point when I needed to use Jetpack, the Nginx restriction block using that strategy looked like:

location = /xmlrpc.php {
        # Automattic's netblocks
        allow 216.151.209.64/26;
        allow 66.135.48.128/25;
        allow 69.174.248.128/25;
        allow 76.74.255.0/25;
        allow 216.151.210.0/25;
        allow 76.74.248.128/25;
        allow 76.74.254.0/25;
        allow 207.198.112.0/23;
        allow 207.198.101.0/25;
        allow 198.181.116.0/22;
        allow 192.0.64.0/18;
        allow 66.155.8.0/22;
        allow 66.155.38.0/24;
        allow 72.233.119.192/26;
        allow 209.15.21.0/24;
        deny all;
}

However I'll have to be aware those IPs may change.

This github ticket is where I sourced the list and it has some additional explanation.

If I were to integrate with Zapier, I'd have to add the whole list of AWS IP addresses.

If my client who wants to update the Wordpress website from her mobile app has the habit of working from various public wifi hotspots, it's going to be hard to pin and IP address to white list

Strategy 3: IP blacklisting

Another approach is to black list IP addresses from where malicious activities originate.
How do we know them ? by looking at our server logs

185.81.157.204 - - [19/Aug/2016:12:42:39 +0000] "POST /xmlrpc.php HTTP/1.1" 301 184 "-" "Googlebot/2.1 (+http://www.google.com/bot.html)"

or

52.18.74.217 - - [25/Apr/2016:09:28:25 +0000] "GET /xmlrpc.php HTTP/1.1" 403 135 "-" "-"

so these IPs can be blacklisted

location  /xmlrpc.php {
        allow all;
        deny 185.81.157.204;
        deny 52.18.74.217;
}

The trouble with such approach is that IPs change and new actors pop up every time so you need to scour your logs fairly regularly to catch any new dodgy IPs.

There is a slight potential for collateral damage too as the IP may be shared with many users, not all of them bent on ill-intent.

This approach, nonetheless, have the potential advantage of a good balance between usability, security and resource frugality: It doesn't reduce usability blocking the service our web service need to access to while allowing us to identify and keep at bay bad actors.
If only there was an easy way to scour the log for malicious activities (repeated login attempt, dosing the xml-rpc interface, comment spam,...) and prevent the connection to happen automatically...

It just happens that there is such a tool, it's called Fail2Ban.

And there's a Fail2Ban Wordpress plugin to make its use even easier.

Strategy 4: Throttling

Usually the symptom of many of these attack is degradation of performance of the server as bots keep hitting on the XML-RPC interface.

So you can configure rate limit to prevent large amount of short-repeated, concurrent requests.

In the context of Wordpress running with Nginx and php-fpm, I found this article very helpful for setting up such configuration.

You can achieve similar effect by using services like Cloudflare.

Final words

I wrote this article to document the process I'm going through for my current and next Wordpress projects with relation to web security.

There's no silver bullet, but a combination of removal of unnecessary methods, throttling and IP blacklisting are what work for my current use cases.

Next on that topic, I might write a post about Fail2ban, its use with Wordpress and Nginx and its deployment in the context of containers.