WordPress security essentials
By: Daniel Ran
Tags: PHP, Nginx, Security, Server, Fail2ban

The more you work with WordPress, the more you learn about how people try to crack and abuse it. It’s sad that some people will always try to spoil our fun, and it tarnishes an otherwise great platform. That said, once something becomes so popular, it’s entirely expected that a lot of attention will be devoted to compromising its security.

Luckily, protecting WordPress from undesirables doesn’t take long. It does require configuring the server with root-level access, but even someone with a basic understanding of system administration can pull it off without a hitch.

Usernames and passwords

Every user account should be unique and reflect the person to whom it belongs. First name and last name combinations are perfectly acceptable. What’s most important here is that no generic names are used; ‘admin’, ‘administrator’ or ‘webmaster’ are all out of the question. Using them will only leave you more vulnerable to automated hacking attempts.

Passwords should all be at least eight characters long, preferably more than twelve, and must include at least one of the following; uppercase letter, lowercase letter, number and symbol. A good way to come up with such a password is to think of a unique phrase, something personal, and then change it.

As an example, extremesportsman becomes eXtreme$portsm4n. With billions of possible combinations for such a password, brute force hacking attempts won’t be able to crack it.

Customise your cookie names

By default, WordPress suffixes its cookie names with an MD5 hash (a 32-character string consisting of numbers and lowercase letters A-F) of the site’s URL. We don’t want potential hackers knowing our cookie names.

Add this line to your wp-config.php, making sure to change the text to something else. Anything else. Going nuts on your keyboard is fine.

define( 'COOKIEHASH', md5( 'Something random here' ) );

Block spamming IP addresses

Aside from the spam comments every WordPress site receives scattered throughout the day, like little bits of skin on your coffee, there is the occasional deluge of ads for sexy Russian women and ED medication. Although Akismet will stop them from appearing on your site, it wastes server resources dealing with all the junk requests.

We’re going to use a log monitor called Fail2Ban to block these IP addresses in order to reserve the server’s processing time for legit users. (These commands are for a Debian-like distribution, like Ubuntu.)

sudo aptitude install fail2ban

sudo service fail2ban stop

This will install Fail2Ban and then stop it. We’ll start it back up once we’ve configured it. Create a new file at /etc/fail2ban/filter.d/wordpress.conf. Paste or type the following into it.

#

# WordPress / Akismet

#

[Definition]

failregex = Akismet caught spam from <HOST>

Next, edit the main Fail2Ban configuration file at /etc/fail2ban/jail.conf. Add the following lines at the bottom (or amongst the other similar-looking blocks).

[wordpress]

enabled = true

port = http,https

filter = wordpress

logpath = /var/log/auth.log

maxretry = 0

bantime = 86400

findtime = 86400

Afterwards, start Fail2Ban.

sudo service fail2ban start

Now we just need to edit our active theme’s functions.php. Add the following anywhere.

add_action( 'akismet_spam_caught', function() {

openlog( 'WordPress', LOG_NDELAY | LOG_PID, LOG_AUTH );

syslog( LOG_NOTICE, "Akismet caught spam from {$_SERVER['REMOTE_ADDR']} ({$_SERVER['HTTP_HOST']})." );

});

The above code will write an entry in the server’s authorisation log file (located at /var/log/auth.log by default) whenever Akismet catches a spam comment.

Fail2Ban will monitor said log file for these entries. Every IP address that appears will be banned from reaching the server for 24 hours.

Brute force and backdoor protection

Brute force attacks are endless streams of automated login attempts using popular username and password combinations. By following the recommendations so far in this article, they already have next to no chance of ever gaining access, but every brute force hacking attempt has the side-effect of a denial-of-service attack. It takes up a lot of server resources.

Backdoors, on the other hand, are pieces of code which allow a user who knows about them to bypass security. They come in two types; hacker-injected and unintentional. The former is what would happen if someone cracked your user account and pasted some malicious code into the theme by way of the file editor.* The latter happens when a theme or plugin developer makes a mistake.

* It’s often suggested that you disable the file editor for this reason. However, this wouldn’t stop a hacker from uploading and installing a plugin instead.

Which, sadly, isn’t all that uncommon. Every once in a while, a plugin with hundreds of thousands of downloads is found to have a piece of code in it which could be exploited by a hacker. Since the source is available for everyone to peruse, there’s nothing stopping anyone from picking ten of the most popular plugins and then looking at their code for ways to hack them.

This does not mean you should suspect every plugin of being poorly coded. Even the best developers make mistakes, and we’ve nothing to gain from being vindictive. Instead, we should take the necessary precautions to protect ourselves.

HTTP authentication for WP Admin

This will stop brute force attacks in their tracks and will protect you from the majority of backdoor attacks. It will also stop the former from wasting any server resources.

First things first, we need to create a file which will hold our credentials. We’re only going to use a single set of login details here since everybody still has to sign into WP Admin using their own account.

In order to generate this file, we need the htpasswd application which is bundled in apache2-utils. In this example, we’re creating the username MySite. Note that the username and password are both case-sensitive.

sudo aptitude install apache2-utils

cd /<one directory above public_html>

htpasswd -Bc .htpasswd MySite

## Type your password when prompted

Now we need to add the following block in our nginx site configuration. Make sure that it’s right below your root location block, location / { … }, so that it takes precedence over your default PHP block.

location ~ /(wp-login|wp-admin/.*)\.php$ {

auth_basic "Restricted";

auth_basic_user_file /<one directory above public_html>/.htpasswd;



try_files $uri =404;

fastcgi_pass php.mysite; ## Change this to your own

}

Reload nginx, sudo service nginx reload, and then try to access your site’s WP Admin. You’ll be greeted with a username and password prompt.

We’ll also want to enable the Fail2Ban filter for IP addresses that fail HTTP authentications. Find the [nginx-http-auth] block in /etc/fail2ban/jail.conf and change enabled to true.

Then, sudo service fail2ban reload.

Explicit PHP location blocks

The typical PHP configuration for nginx is to add a regex block for all URIs matching \.php$, in other words any file with the PHP extension. An undesirable side-effect of this is that we grant people access to scripts that shouldn’t be accessed directly.

WordPress’s entry script is index.php, along with a few others in the root. We can explicitly grant access to these and deny all others (such as theme and plugin files which may contain unintentional backdoors).

Replace any PHP blocks you may have (other than the WP Admin one we just added) with the code below.

## Valid scripts

## Note the absence of wp-login.php; that one's covered by the WP Admin block

location ~ /(index|xmlrpc|wp-(comments-post|cron|links-opml|mail|trackback|signup))\.php$ {

try_files $uri =404;

fastcgi_pass php.mysite; ## Change this to your own

} ## Invalid scripts

## All scripts not declared above will be denied by this block

location ~ \.php$ {

deny all;

}

In closing

To fully secure a CMS like WordPress, you must always configure the server. There are limits to how much PHP can protect itself, especially when you’re installing third-party plugins. At the very least, make sure your site isn’t hosted using a one-size-fits-all configuration, like that of shared hosting.

Don’t hesitate to ask someone with experience in the area if you need help. Many of us will answer questions for free over at Server Fault.

Good luck out there.