Introduction

In this article we’re going to take a look at how to secure a WordPress installation against attackers in an IaaS virtual machine. Virtual machines can be rented with various IaaS cloud providers, and only a credit card is needed to actually rent a virtual machine, which is quite cheap nowadays.

There are multiple reasons to run your own WordPress website on an IaaS environment rather than using a WordPress hosting with one of multiple providers. One of the advantages is greater flexibility, since you can do practically anything with your instance of virtual machine. Additionally, you can setup additional services on the same virtual host if the need arises.

On the other hand, there are some risks when running your own version of WordPress website, since you have to protect it by yourself and you can’t rely on the hosting provider to do it for you. At the same time, you should also be aware of the fact that some providers don’t actually secure WordPress the way they should, and you can’t do it by yourself, since you don’t have appropriate access. The problem is that they don’t know your needs and leave all the dangerous files like xmlrpc.php intact in case you might need them. Therefore it’s best to assume that we alone are the most appropriate person to secure the WordPress installation in the most secure way.

Next we’ll take a look at the security threats of WordPress installations and determine how to protect against them. Let’s take a look at what an attacker can gather from a WordPress website, and later on we’ll provide security mitigations we have to apply to secure ourselves.

  • Discovering WordPress Version: the wpscan program scans for the /readme.html file, which is present by default when installing WordPress and contains a lot of information regarding its version. There are also other files that disclose the version of installed WordPress, which an attacker can use to determine whether it contains any vulnerabilities. To mitigate this, we have to remove the readme.html file and install the All in One WP Security plugin, which will hide the WordPress version in other files.
  • Enumerate WordPress Users: the wpscan program can be used to enumerate all users on the WordPress website. By enumerating existing users, an attacker might issue a bruteforce/dictionary attack on the users to determine their password, in which case he can login to the administrative interface provided by WordPress.
  • Enumerate WordPress Plugins: the wpscan program can be used to enumerate all plugins on the WordPress website. By enumerating the installed plugins, an attacker can find a vulnerability in an unpatched version of a plugin to gain complete access to the WordPress website.
  • WordPress Vulnerabilities: we have to ensure the latest version of WordPress is installed in order to keep it up-to-date. Unpatched versions of WordPress installations may contain a different vulnerability an attacker might use to exploit the website.
  • Using XML-RPC Interface: attackers have lately been using xml-rpc interface for various attacks, from bruteforcing passwords of users as well as using it for DoS attacks.
  • Denial of Service: an attacker can issue multiple subsequent requests to the WordPress website in order to cause a denial of service, in which case the website won’t be accessible anymore. DoS can disrupt business procedures, which causes the enterprise to lose money every time the website is inaccessible.
  • Clickjacking: a clickjacking attack is an attack where an attacker uses an iframe element to embed a web page into another page and tricking the user into clicking on the button or link of an embedded page while viewing the contents of the top page.
  • Administrative Interface: the /wp-admin/ interface is usually enabled on WordPress installation and allows user to login to the WordPress and administer the web site. In order to secure WordPress, we have to enable logging into the web interface only from specific IP addresses and prevent access from arbitrary IP addresses.

Protecting against Discovering WordPress Version

We’ve already said that WordPress version can be discovered simply by visiting the http://192.168.1.2/readme.html, which is presented on the picture below, where it’s evident that we’re running WordPress Version 3.5.

To mitigate the issue, we have to remove the readme.html file from the DocumentRoot of the WordPress website. But even after deleting that file, the WordPress version can still be discovered by visiting the http://192.168.1.2/ website and viewing the website source code. On the picture below we can see that the WordPress version is disclosed.

There are different resources on the web where it’s clearly presented how a version can be removed from a source code: one such resource is here.

Protecting against Enumerating WordPress Users

The wpscan tool can be used to enumerate users of the WordPress website, which can later be bruteforced easily. The tool needs to be run with the following parameters to successfully enumerate the users:

# wpscan --enumerate u --url http://192.168.1.2

A successfully enumerated username admin can be seen on the picture below.

Authors can be enumerated by using the http://192.168.1.2/?author=1 request, where the author ID has to be increased by 1 in every iteration. The website displayed under such URL is presented below, where it’s evident that a user admin has been discovered.

To prevent attackers from enumerating users, we have to enable rewrite conditions, where have to add the following to the .htaccess file:

RewriteCond %{REQUEST_URI} ^/$
RewriteCond %{QUERY_STRING} ^/?author=([0-9]*)
RewriteRule ^(.*)$ http://192.168.1.1/ [L,R=301]

Then we have to enable the mod_rewrite module and restart Apache by running the commands below:

# a2enmod rewrite
# /etc/init.d/apache2 restart

Protecting against Enumerating WordPress Plugins

The wpscan tool can be used to enumerate users of the WordPress website, which can later be used to identify different vulnerabilities in the website. A vulnerability can be used by an attacker to steal sensitive information from a website, to use the vulnerability for a social engineering attack, or possibly take total control of the website. The wpscan tool searches for readme.html files in the plugin’s home directory under /wp-content/plugins/ directory.

To enumerate plugins on the WordPress website, we have to use the command below:

# wpscan --enumerate u --url http://192.168.1.2

A successfully enumerated plugin can be seen below, where a number of plugins were identified: akismet, my-category-order, nospampti and upm-polls. We can see that wpscan automatically displayed a number of security advisories notifying us about existing vulnerabilities present in the installed version of WordPress.


[!] The WordPress 'http://192.168.1.2/readme.html' file exists
[+] WordPress version 3.5 identified from meta generator

[!] 5 vulnerabilities identified from the version number:
 |
 | * Title: WordPress 3.4 - 3.5.1 /wp-admin/users.php Malformed s Parameter Path Disclosure
 | * Reference: http://seclists.org/fulldisclosure/2013/Jul/70
 | * Reference: http://osvdb.org/95060
 | * Fixed in: 3.5.2
 |
 | * Title: WordPress 3.4 - 3.5.1 DoS in class-phpass.php
 | * Reference: http://seclists.org/fulldisclosure/2013/Jun/65
 | * Reference: http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-2173
 | * Reference: http://secunia.com/advisories/53676
 | * Reference: http://osvdb.org/94235
 |
 | * Title: WordPress 3.3.2 - 3.5 Cross-Site Scripting (XSS) (Issue 3)
 | * Reference: https://github.com/wpscanteam/wpscan/wiki/WordPress-3.5-Issues
 |
 | * Title: XMLRPC Pingback API Internal/External Port Scanning
 | * Reference: https://github.com/FireFart/WordPressPingbackPortScanner
 |
 | * Title: WordPress XMLRPC pingback additional issues
 | * Reference: http://lab.onsec.ru/2013/01/WordPress-xmlrpc-pingback-additional.html

[+] WordPress theme in use: twentytwelve v1.1

 | Name: twentytwelve v1.1
 | Location: http://192.168.1.2/wp-content/themes/twentytwelve/

[+] Enumerating installed plugins  ...

[+] We found 4 plugins:

 | Name: akismet v2.5.6
 | Location: http://192.168.1.2/wp-content/plugins/akismet/
 | Readme: http://192.168.1.2/wp-content/plugins/akismet/readme.txt

 | Name: my-category-order
 | Location: http://192.168.1.2/wp-content/plugins/my-category-order/
 |
 | * Title: My Category Order <= 2.8 - SQL Injection Vulnerability
 | * Reference: http://www.exploit-db.com/exploits/9150/

 | Name: nospampti v1.0
 | Location: http://192.168.1.2/wp-content/plugins/nospampti/
 | Directory listing enabled: Yes
 | Readme: http://192.168.1.2/wp-content/plugins/nospampti/readme.txt
 |
 | * Title: NOSpamPTI 2.1 - wp-comments-post.php comment_post_ID Parameter SQL Injection
 | * Reference: http://packetstormsecurity.com/files/123331/
 | * Reference: http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-5917
 | * Reference: http://osvdb.org/97528
 | * Reference: http://www.exploit-db.com/exploits/28485/

 | Name: upm-polls v1.0.4
 | Location: http://192.168.1.2/wp-content/plugins/upm-polls/
 | Directory listing enabled: Yes
 | Readme: http://192.168.1.2/wp-content/plugins/upm-polls/readme.txt
 |
 | * Title: UPM-POLLS 1.0.4 - BLIND SQL injection
 | * Reference: http://www.exploit-db.com/exploits/18231/

In order to prevent the wpscan tool from enumerating the plugin version, we have to know how the wpscan tool finds that information. If you look at the source code of the tool, you can quickly determine that it tries to find the readme.html, readme.txt and changelog.txt files available in every plugin directory. The tool uses a dictionary file /usr/share/wpscan/data/plugins.txt, which contains 2205 number of plugin names to determine whether the URL address http://site/wp-content/plugins/<plugin_name>/readme.html is accessible (the <plugin_name> variable needs to be replaced by the actual name of the plugin as contained in the plugins.txt file).

Therefore, to protect against the wpscan tool, we have to delete every occurrence of the readme.html, readme.txt, as well as the changelog.txt file in the DocumentRoot of the WordPress installation. We can do that effectively by running the following command.

# find /var/www/ -name readme.html -exec rm {} \;
# find /var/www/ -name readme.txt -exec rm {} \;
# find /var/www/ -name changelog.txt -exec rm {} \;

After that we can rescan the WordPress website by using the same wpscan command at which time we’ll find out the tool wasn’t able to determine the versions of available plugins. It’s important to understand that the wpscan can still find the available plugins, but won’t be able to determine their versions with 100% accuracy. There are other techniques an attacker might use in order to determine the version of installed plugins, which are the following:

  • Matching File Names: an attacker can look at the SVN repository of the plugin to determine if any files have been added/removed when moving from one version to the next. He can see information about the presence of certain files to determine the plugin version.
  • Matching Response Body: an attacker can directly access all the files available by the plugin and remember the returned results. After that he can compare the returned results with the results returned from different versions of the plugin in question, which he can download from the SVN repository. By matching the responses of one version of the WordPress plugin against the other version, he can determine whether the results differ, in which case he can determine the version of the plugin.

It’s imperative to understand that by masking the plugin versions from the attacker, he will still be able to exploit vulnerabilities in plugins to gain access to the website; this is true, because if an old version of a plugin is installed, the vulnerability is contained in the same source file. We have to understand that the only possible solution to prevent an attacker from using the vulnerabilities is by constantly updating the plugins to the latest version available.

Protecting against WordPress Vulnerabilities

WordPress has a number of vulnerabilities which keep getting discovered from time to time. It’s impossible to predict how many vulnerabilities will be discovered in the future, so we must constantly upgrade WordPress to a new release when it becomes available to mitigate the newly discovered vulnerabilities.

One such vulnerability is the CVE-2013-0235 vulnerability and is present in WordPress versions prior to 3.5.1. The vulnerability affects XML-RPC interface, which is enabled by default, which consequentially made a lot of WordPress installations vulnerable. We can determine if the XML-RPC interface is enabled by visiting the web address http://www.domain.com/xmlrpc.com, which should return the message “XML-RPC server accepts POST requests only.”

If we have an older version of WordPress, the XML-RPC interface can be exploited by using the WordPress_pingback_access Metasploit module. We can use that module to discover whether we’re running a WordPress site with the Pingback API enabled.


To check whether the Pingback API is enabled by the XML-RPC we have to set the RHOSTS variable to the IP/Domain of the WordPress blog and run the exploit with the run command. To see the vulnerability in action, we can download WordPress 3.5, install and configure it appropriately. Then we can use the WordPress_pingback_access module to scan the site to determine whether it’s vulnerable to the vulnerability or not. Below we can see that pingback is enabled on the WordPress site, which was installed on the server with IP address 192.168.1.2.

msf auxiliary(WordPress_pingback_access) > run<strong>[+] 192.168.1.2 - Pingback enabled: </strong>
	[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
msf auxiliary(WordPress_pingback_access) >

The vulnerability can also be used to port scan internal and external targets, which attackers use to hide their activity, because the scans are coming from WordPress website and not from the attacker.

To mitigate the vulnerability, we have to update to the latest WordPress version, but we have to keep in mind that new vulnerabilities are being discovered regularly, so it’s imperative that we constantly update WordPress to keep ourselves from being vulnerable.

We can also install the All In One WP Security & Firewall plugin, which helps us harden the WordPress security.

Protecting against using XML-RPC Interface

Most of the XML-RPC API functions are available here. In order to call most of the functions, we have to be authenticated, as they cannot be called without authentication. Let’s use Burp in order to invoke the sayHello function on the server. The request that needs to be sent to the server can be seen below.

The answer where a “Hello!” string has been written into the response can be seen below.

An attacker can use the request presented below in order to bruteforce the password of the enumerated username. If an attacker used the wpscan tool to enumerate the admin user, it can further use the XML-RPC API to bruteforce the password of the administrator. The request that needed to be sent to the server can be seen below.

If the username and password exist in the system, the actual post will be returned in the response as presented below.

We’ve established that the xmlrpc.php file is dangerous for various things and access to it should be restricted. There are a number of solutions which one can use to protect against xmlrpc.php attacks, which are presented below.

Using .htaccess to Restrict Access Completely

We can use the .htaccess file to restrict access to the xmlrpc.php file. First we have to set the AllowOverride option in /etc/apache2/sites-available/default to the correct value. The AllowOverride option specifies the types of directives that are allowed in the .htaccess files.

We can see that AllowOverride is set to None by default in Apache versions 2.3.9 and earlier. In order to allow .htaccess to overwrite certain directives, we have to set it to one of the directive types, but in most cases we can set it to All; We can restrict the options available in certain directory by using the Options directive.

The Options directive can be set to one of the following values:

  • None: none of the extra features are enabled.
  • All: all options except MultiViews are enabled.
  • FollowSymlinks: the server will follow symlinks in this directory.
  • Includes: enables server-side includes provided by the mod_include module.
  • IncludesNOEXEC: enables server-side includes provided by the mod_include module, but disables cmd/cgi.
  • Indexes: if a directory is requests a directory listing is returned in the absence of DirectoryIndex (e.g. index.html).
  • MultiViews: enables mod_negotiation.
  • SymLinksIfOwnerMatch: the server will only follow symlinks for which the file/directory are owned by the same user as the symlink itself.

When the “AllowOverride None” is set, the server will respond with 200 OK messages as presented below. This means that the server is not denying access to the xmlrpc.php file, because the .htaccess is not able to override the options specified in the Apache default configuration file.

When the “AllowOverride All” is set, we have to add appropriate .htaccess file with the following contents to the /var/www/ directory (the same directory, which contains the xmlrpc.php file).

<Files xmlrpc.php>
    Order Deny,Allow
    Deny from all
</Files>

Since the .htaccess is able to override the options specified in the default Apache configuration file, the access to the xmlrpc.php is denied. The server will respond with 403 forbidden messages as presented below.

Using .htaccess to Allow Access for Trusted IPs

A large portion of the previous section also applies here: we have to set the AllowOverride appropriately, but use a different .htaccess in order to deny access to anybody but specific IP addresses. We can use the following .htaccess file to allow IP address 1.2.3.4 to call the xmlrpc.php file.

<Files xmlrpc.php>
    Order Deny,Allow
    Deny from all
    Allow from 1.2.3.4
</Files>

Deleting the File Completely

Protecting the .htaccess from arbitrary IP addresses but allowing it from certain IP addresses is the best solution that we can use. In any case, if you don’t want to go into all this trouble, you can also delete the xmlrpc.php file altogether. The file is optional and the WordPress installation doesn’t depend on it, so it can safely be removed.
Keep in mind that by removing the file you’ll not be able to use some XMLRPC functionality, which is why you should delete the file only when you’re absolutely sure you’ll not be using it.

If we take a look at the returned response HTTP headers, we can see the X-Pingback header is still being set as presented below. Various worms use the value of the HTTP header to scan whether XMLRPC interface is enabled or not. In cases where the interface is enabled, the attack progresses further, otherwise the attackers move to the next WordPress website.

HTTP/1.1 200 OK

Date: Mon, 13 Oct 2014 21:08:05 GMT

Server: Apache
X-Pingback: <strong>http://192.168.1.2/xmlrpc.php
</strong>
	Vary: Accept-Encoding

Content-Length: 6882

Content-Type: text/html; charset=UTF-8

In order to ensure the XMLRCP interface is disclosed in X-Pingback HTTP header, the following can be added to the .htaccess file, which will remove the X-Pingback HTTP header from every response.

<IfModule mod_headers.c>
  Header unset X-Pingback
</IfModule>

Protecting against Clickjacking

To protect against a clickjacking attack, the WordPress web site has to return the X-FRAME-OPTIONS HTTP header in every response, which will prevent the webpage to be included into another page by using iframe elements. In order to do that, we can use the mod_headers module, which we can enable by running the following commands. The X-Content-Type-Options is used to prevent content spoofing in older web browsers.

# a2enmod headers
# /etc/init.d/apache2 restart

Then we need to edit the .htaccess file and add the following options in order for relevant HTTP headers to be added in every response. The X-XSS-Protection is used to enable the XSS protections in the web browser itself – if the value of X-XSS-Protection HTTP header is set to 0, the anti-xss filter in Chrome web browser will be effectively disabled (as if we passed the –disable-xss-auditor as a command-line parameter when starting Chrome)”. The X-Frame-Options is set to SAMEORIGIN, which allows only the current domain to frame content of the web page, while framing content from an arbitrary domain is not allowed.

<IfModule mod_headers.c>
    Header set X-XSS-Protection "1"
    Header always append X-Frame-Options "SAMEORIGIN"
    Header set X-Content-Type-Options: "nosniff"
</IfModule>

Protecting against Accessible Administrative Interface

When accessing the /wp-admin/ URL in a web browser, WordPress will automatically redirect us to /wp-login.php. In order to protect against attackers trying to guess our password by using a bruteforce or dictionary attack, we can either enable administrative interface to certain IPs only or enable a secondary Basic authentication.

To protect the login web page wp-login.php, we have to put the following into the /var/www/.htaccess file, which allows only the IP address 1.2.3.4 to access the wp-login.php file.

<Files wp-login.php>
    Order Deny,Allow
    Deny from all
    Allow from 1.2.3.4
</Files>

Additionally, we can also protect /wp-admin/ directory by placing the following content into the /var/www/wp-admin/.htaccess file.

<FilesMatch ".*">
   Order Deny,Allow
   Deny from all
   Allow from 1.2.3.4
</FilesMatch>

If we would like to use Basic authentication, we can place the following into .htaccess file. Whenever accessing the /wp-login.php, a popup will show asking us for the basic password. Note that the “/var/.htpasswd” file can be created by using “htpasswd -c /var/.htpasswd user” command.

<Files wp-login.php>
    AuthUserFile /var/.htpasswd
    AuthType Basic
    AuthName "Authentication required"
    Require valid-user
    Order Allow,Deny
    Allow from all
</Files>

Alternatively, we can use Basic authentication together with IP address filtering by slightly changing the .htaccess file into the following.

<Files wp-login.php>
    AuthUserFile /var/.htpasswd
    AuthType Basic
    AuthName "Authentication required"
    Require valid-user
    Order Deny,Allow
    Deny from all
    Allow from 1.2.3.4
</Files>

Conclusion

We’ve seen that protecting every WordPress installation is not an easy task and should not be taken lightly. In order to protect our WordPress installation, we have to follow the guidelines presented above; most importantly, we have to restrict access to sensitive web pages like /wp-admin/ administrative interface, which could allow attackers to bruteforce the password and gain administrative rights.
After performing all the steps outlined in the article above, we have to realize that security is an ongoing process. Whenever installing a new version of a plugin, the readme.html, readme.txt and changelog.txt files will be brought with it, which is why we should delete them after updating the plugin. The same is true when updating WordPress itself.

Remember that security is not a once in a lifetime process, which we have to apply after installation of a WordPress website, but is a process which is alive by itself. Therefore, we must be constantly on alert when it comes to security, because there’s no telling which techniques attackers will come up with in order to further attack the WordPress website.