Huge increase in WordPress xmlrpc.php POST requests

Published on Monday, 7 July 2014

WordPress xmlprc.php DDoS and brute-force attacks. How to identify, block, mitigate and leverage these xmlrpc.php scans, brute-force, and user enumeration attacks on WordPress sites... Secure WordPress xmlprc.php interface and reduce service disruption.


WordPress xmlrpc.php attack characteristics (WordPress <= 3.9.2 XML-PRC brute-force)

Over the course of the last days, I notice a huge increase in HTTP POST requests on the WordPress xmlrpc.php file. This could be a new type of XML-RPC bruteforce or (D)DoS attack. The attacks are targeted at different WordPress versions, so let's do some investigation.

All attacked WordPress sites share the same characteristics: HTTP POST requests to /xmlrpc.php, all with an HTTP_USER_AGENT Mozilla/4.0+(compatible;+Win32;+WinHttp.WinHttpRequest.5), by random IP addresses (probably hacked and part of a botnet).

As shown below:

2014-07-07 09:16:59 77.94.248.174 POST /xmlrpc.php - 80 - 
  203.126.164.157 Mozilla/4.0+(compatible;+Win32;+WinHttp.WinHttpRequest.5) - 
  www.example.com 200 0 64 0 471 32901
2014-07-07 09:16:59 77.94.248.174 POST /xmlrpc.php - 80 -
  122.167.183.21 Mozilla/4.0+(compatible;+Win32;+WinHttp.WinHttpRequest.5) - 
  www.example.com 500 0 64 0 471 30108
2014-07-07 09:17:00 77.94.248.174 POST /xmlrpc.php - 80 - 
  39.51.10.73 Mozilla/4.0+(compatible;+Win32;+WinHttp.WinHttpRequest.5) - 
  www.example.com 200 0 64 0 471 32292
2014-07-07 09:17:00 77.94.248.174 POST /xmlrpc.php - 80 - 
  94.67.115.29 Mozilla/4.0+(compatible;+Win32;+WinHttp.WinHttpRequest.5) - 
  www.example.com 200 0 64 0 471 32105
2014-07-07 09:17:02 77.94.248.174 GET / - 80 - 
  203.126.164.157 Mozilla/4.0+(compatible;+Win32;+WinHttp.WinHttpRequest.5) - 
  www.example.com 500 0 64 0 145 30405
2014-07-07 09:17:02 77.94.248.174 GET / - 80 - 
  76.71.193.113 Mozilla/4.0+(compatible;+Win32;+WinHttp.WinHttpRequest.5) - 
  www.example.com 500 0 64 0 145 30155

Did you notice the POST payload is about 10 times bigger than the wp.getUsersBlogs bruteforce attacks mentioned further down below?

The attacks come at such high rates that the webservers sometimes return HTTP 500 Service Unavailable error messages for those websites. How to reduce this service disruption is explained in the next section.

A note: attacked websites don't run the same WordPress version. I've seen these attacks on WordPress versions 3.0.2 (sigh yep...) through 3.9.1.

About WordPress XML-RPC support

WordPress uses an XML-RPC interface. With WordPress XML-RPC support, you can post to your WordPress blog using many popular Weblog Clients. The XML-RPC system can be extended by WordPress Plugins to modify its behavior. All built-in XML-RPC methods use the action xmlrpc_call, with a parameter equal to the method's name (e.g., wp.newPost). The action is performed after the user has been authenticated but before the rest of the method logic begins.

The Internet Storm Center (ISC) also reports on this WordPress brute force attack via wp.getUsersBlogs.

2014-07-07 10:46:23 77.94.250.138 POST /xmlrpc.php - 80 - 
  93.174.93.204 Mozilla/4.0+(compatible:+MSIE+7.0;+Windows+NT+6.0) - 
  www.example.net 200 0 64 0 417 359
2014-07-07 10:55:59 77.94.250.138 POST /xmlrpc.php - 80 - 
  93.174.93.204 Mozilla/4.0+(compatible:+MSIE+7.0;+Windows+NT+6.0) - 
  www.example.net 200 0 64 0 417 343
2014-07-07 11:01:28 77.94.250.138 POST /xmlrpc.php - 80 - 
  93.174.93.204 Mozilla/4.0+(compatible:+MSIE+7.0;+Windows+NT+6.0) - 
  www.example.net 200 0 64 0 417 406
2014-07-07 11:06:57 77.94.250.138 POST /xmlrpc.php - 80 - 
  93.174.93.204 Mozilla/4.0+(compatible:+MSIE+7.0;+Windows+NT+6.0) - 
  www.example.net 200 0 64 0 417 343

As you can see, the XML-RPC payload is much smaller here, and it originates from the same IP address.

The IP address is known to be a comment spammer, so I suspect this to be a different kind of spamming attack. But I'll keep an close eye on it.

Before we can tell anything about this attack (these attacks), we need to log some data first. We then can analyze the gathered data.

Log XMLRPC.php POST data to a log file - create a quick PHP logger

Log WordPress XML-RPC brute-force data to a log file for analysis and inspection. On a number of WordPress blogs I run, I wanted to log the HTTP POST information send to xmlrpc.php. Just to see what is POST'ed.

I made a quick 'n dirty logger in WordPress' xmlrpc.php file, it's ugly... Open up xmlrcp.php, and add on line 2, after the PHP open tag <?php:

// Quick 'n dirty XMLRPC logger
// Follow me on Twitter: @Jan_Reilink, support me: https://www.paypal.me/jreilink
$xmlrpc_log = "../database/xmlrpc_log.txt";
$text = "";
if ( $_SERVER['REQUEST_METHOD'] === 'POST' )
{
    foreach ( $_POST as $key => $value ) {
        $text .= $key ." : ". $value ."\r\n";
    }
    if( file_exists( $xmlrpc_log ) && is_writable( $xmlrpc_log ) )
    {
        // our XMLRPC "honeypot" logfile is writeable, let's continue
        $f = fopen( $xmlrpc_log,"ab" );
        fwrite( $f, $text );
        fclose( $f );
        clearstatcache();
    }
}

Make sure the path to $xmlrpc_log exists, and now we wait...

XMLRPC-logger result
The result of the above XMLRPC-logger is quite interesting. It appears it could be a WordPress user blog enumeration scan. On one of the websites I enabled the logger, I noticed HTTP POST requests in the HTTP logfile:

2014-07-09 09:51:28 77.94.248.175 POST /xmlrpc.php - 80 - 
  84.40.123.39 Mozilla/4.0+(compatible;+Win32;+WinHttp.WinHttpRequest.5) - 
  example.com 200 0 0 588 500 202
2014-07-09 09:51:29 77.94.248.175 POST /xmlrpc.php - 80 - 
  182.186.89.187 Mozilla/4.0+(compatible;+Win32;+WinHttp.WinHttpRequest.5) - 
  example.com 200 0 0 588 503 592
2014-07-09 09:51:42 77.94.248.175 POST /xmlrpc.php - 80 - 
  77.31.97.165 Mozilla/4.0+(compatible;+Win32;+WinHttp.WinHttpRequest.5) - 
  example.com 200 0 0 588 502 249
2014-07-09 09:51:45 77.94.248.175 POST /xmlrpc.php - 80 - 
  162.198.57.86 Mozilla/4.0+(compatible;+Win32;+WinHttp.WinHttpRequest.5) - 
  example.com 200 0 0 588 499 343
2014-07-09 09:51:52 77.94.248.175 POST /xmlrpc.php - 80 - 
  213.233.104.10 Mozilla/4.0+(compatible;+Win32;+WinHttp.WinHttpRequest.5) - 
  example.com 200 0 0 588 515 265
2014-07-09 09:52:05 77.94.248.175 POST /xmlrpc.php - 80 - 
  112.135.143.66 Mozilla/4.0+(compatible;+Win32;+WinHttp.WinHttpRequest.5) - 
  example.com 200 0 0 588 501 390
2014-07-09 09:52:05 77.94.248.175 POST /xmlrpc.php - 80 - 
  109.93.159.232 Mozilla/4.0+(compatible;+Win32;+WinHttp.WinHttpRequest.5) - 
  example.com 200 0 0 588 503 234
2014-07-09 09:52:08 77.94.248.175 POST /xmlrpc.php - 80 - 
  111.68.38.156 Mozilla/4.0+(compatible;+Win32;+WinHttp.WinHttpRequest.5) - 
  example.com 200 0 0 588 503 686
2014-07-09 09:52:15 77.94.248.175 POST /xmlrpc.php - 80 - 
  181.188.88.148 Mozilla/4.0+(compatible;+Win32;+WinHttp.WinHttpRequest.5) - 
  example.com 200 0 64 0 501 374
2014-07-09 09:52:16 77.94.248.175 POST /xmlrpc.php - 80 - 
  96.36.130.121 Mozilla/4.0+(compatible;+Win32;+WinHttp.WinHttpRequest.5) - 
  example.com 200 0 0 588 501 265
2014-07-09 09:52:18 77.94.248.175 POST /xmlrpc.php - 80 - 
  176.73.72.206 Mozilla/4.0+(compatible;+Win32;+WinHttp.WinHttpRequest.5) - 
  example.com 200 0 0 588 501 218
2014-07-09 09:52:36 77.94.248.175 POST /xmlrpc.php - 80 - 
  196.29.199.46 Mozilla/4.0+(compatible;+Win32;+WinHttp.WinHttpRequest.5) - 
  example.com 200 0 0 588 502 358
2014-07-09 09:52:41 77.94.248.175 POST /xmlrpc.php - 80 - 
  121.54.54.50 Mozilla/4.0+(compatible;+Win32;+WinHttp.WinHttpRequest.5) - 
  example.com 200 0 0 588 503 873

Notice that, in this case, the POST payload is much smaller, so this could be the result from a different type of attack. The logged POST data is:

<?xml_version"1.0" encoding="iso-8859-1"?>
<methodCall>
  <methodName>wp.getUsersBlogs</methodName>
  <params>
    <param>
      <value>
        <string>example_com</string>
      </value>
    </param>
    <param>
      <value>
        <string>nickie</string>
      </value>
    </param>
  </params>
</methodCall>
<?xml_version"1.0" encoding="iso-8859-1"?>
<methodCall>
  <methodName>wp.getUsersBlogs</methodName>
  <params>
    <param>
      <value>
        <string>example_com</string>
      </value>
    </param>
    <param>
    <value>
      <string>nicola</string>
    </value>
    </param>
  </params>
</methodCall>
<?xml_version"1.0" encoding="iso-8859-1"?>
<methodCall>
  <methodName>wp.getUsersBlogs</methodName>
  <params>
    <param>
      <value>
        <string>example_com</string>
      </value>
    </param>
    <param>
      <value>
        <string>nick</string>
      </value>
    </param>
  </params>
</methodCall>
<?xml_version"1.0" encoding="iso-8859-1"?>
<methodCall>
  <methodName>wp.getUsersBlogs</methodName>
  <params>
    <param>
      <value>
        <string>example_com</string>
      </value>
    </param>
    <param>
      <value>
        <string>nicolas</string>
      </value>
    </param>
  </params>
</methodCall>
<?xml_version"1.0" encoding="iso-8859-1"?>
<methodCall>
  <methodName>wp.getUsersBlogs</methodName>
  <params>
    <param>
      <value>
        <string>example_com</string>
      </value>
    </param>
    <param>
      <value>
        <string>nico</string>
      </value>
    </param>
  </params>
</methodCall>
<?xml_version"1.0" encoding="iso-8859-1"?>
<methodCall>
  <methodName>wp.getUsersBlogs</methodName>
  <params>
    <param>
      <value>
        <string>example_com</string>
      </value>
    </param>
    <param>
      <value>
        <string>nicole1</string>
      </value>
    </param>
  </params>
</methodCall>
[...]

wp.getUsersBlogs is used to retrieve the blogs of the users.

Mitigate WordPress XML-RPC attacks and wp.getUserBlogs user enumeration scans: how to disable XML-RPC in WordPress

Here is how to mitigate XMLRPC and user enumeration attacks, and to resolve degraded web server performance.

Unfortunately you can't just simply remove the xmlrpc.php file. It is used in many places within Wordpress. However, it's possible to mitigate against this wp.getUserBlogs enumeration scan with a filter.

This filter needs to be put your THEME functions.php file. Basically it's the same as the filter below to disable the pingback.ping function.

Here is an example of removing wp.getUsersBlogs method:

function saotn_remove_xmlrpc_getUsersBlogs( $methods ) {
  unset( $methods['wp.getUsersBlogs'] );
  return $methods;   
}
add_filter( 'xmlrpc_methods', 'saotn_remove_xmlrpc_getUsersBlogs');

But of course HTTP POST requests on xmlrpc.php will still come in. Unfortunately, you just can't protect PHP with PHP.

Reduce web server load caused by XMLRPC.php attacks on websites

The performance of a web server might severely degrade when you host multiple WordPress sites that are under attack. Therefore we need to take action to reduce the disturbance caused by these attacks and the inconvenience for other sites.

More than one action to take is possible and recommended.

Disable WordPress WP_CRON in wp-config.php

A good starting point in reducing the service disruption is to disable WordPress' WP_CRON functionality. The wp_cron.php file is executed after each HTTP request and thus it's executed a lot. Really a lot. This is a drain on your database (it makes an extra database connection for each time it's executed) and PHP.

So first we disable WP_CRON. Open up your wp-config.php file and add:

define( 'DISABLE_WP_CRON', 'true' );

While you're at it, verify that WordPress WP_DEBUG is disabled too: define( 'WP_DEBUG', false );

Another good method to reduce server load during an attack is to optimize WordPress wp_options table and autoload feature. This micro-optimization retrieves autoloaded options much faster using less database resources.

Disable XMLRPC.PHP in WordPress completely

Secondly, you can disable the XML-RPC functionality in WordPress by adding to your theme's functions.php file:

add_filter( 'xmlrpc_enabled', '__return_false' );

But again, HTTP requests still come in to your server, degrading performance or causing a denial of service. Let's do something about that in the next section.

Deny WinHttpRequest User-Agent in .htaccess

We don't want shady user-agents requesting information on our website. Since this attack on mulitple sites has in common that the User-Agent is always Mozilla/4.0+(compatible;+Win32;+WinHttp.WinHttpRequest.5), I believe it's wise to block this User-Agent on the website under siege.

Add to your .htaccess file:

RewriteCond %{HTTP_USER_AGENT} .*WinHttp.WinHttpRequest.* [NC]
RewriteRule .* - [F,L]

WordPress .htaccess protection from XMLRPC-, comment- and trackback spam

when the visitor isn't coming from your website as referer

It has always been important to protect your WordPress site from comment- and trackback spammers, and to block spammers manually if necessary. Especially if they don't come from your website (e.g. don't have your domain name as referer).

To block all POST requests on xmlrpc.php, wp-comments-post.php and wp-trackback.php, without your domain name as referrer, add to your .htaccess file:

RewriteCond %{REQUEST_METHOD} POST [NC]
RewriteCond %{REQUEST_FILENAME} (xmlrpc|wp-comments-post|wp-trackback)\.php [NC]
RewriteCond %{HTTP_REFERER} !^http://www.example.com [NC]
Rewriterule .* - [F,L]

A fast and easy method is to secure WordPress with a captcha on comment- and log in forms.

Bonus: disable only XMLRPC pingback functionality

Another action is to disable WordPress' XMLRPC pingback function. You can achieve this by adding the following filter to your THEME's functions.php file:

function saotn_remove_xmlrpc_pingback( $methods ) {
    unset( $methods['pingback.ping'] );
    return $methods;   
}
add_filter( 'xmlrpc_methods', 'saotn_remove_xmlrpc_pingback' );

Update WordPress - security release 3.9.2 fixes XML-RPC DoS

Update 2014-08-07: Both WordPress and Drupal released security updates to fix an XML-RPC DoS vulnerability in the XML-RPC implementation. WordPress 3.9.2, Drupal 7.31 and Drupal 6.33 fixes this severe XML-RPC Denial of Service (DoS).

XML-RPC Brute-force amplification attacks through WordPress xmlrpc.php

Just a small note: Sucuri writes about ongoing amplified brute-force attacks through the WordPress xmlrpc.php file:

Attackers managed to find a way to send a brute-force attack, trying multiple (tens or hundreds) username/password combinations within one request. This way, an XML-RPC brute-force attack might stay under the radar of ordinary Web Application Firewalls or rate-limiting settings.