Jay Gould

A digestible WordPress security checklist for web developers

December 07, 2019

WordPress websites are sometimes thought to be insecure. There are a bunch of reasons for this - firstly WordPress is extremely popular, so it makes sense that there will be so many reports of the sites getting compromised. Also because there are so many sites built on the popular CMS, the likelihood of a hacker stumbling upon one is quite high, meaning they can try their luck at a few common techniques in their repertoire. There are so many other reasons though such as weak passwords, default usernames, out of date plugins and core WordPress software - the list is huge.

Choosing an alternative system seems like the perfect answer. Maybe a different CMS like Craft CMS? Maybe a custom built CMS with a Laravel foundation? Or a super fancy Node or Go based solution? The harsh reality is that WordPress is often the better choice because of it’s widespread adoption in the dev community, the intuitive UI for the admin area which makes end users and admins so comfortable, and the speed that sites can be developed.

I’ve experienced the breach of a WordPress website first hand recently, and it can be damaging. Of course, it depends on the content/data within the site, but generally speaking a breach could mean the access and exfiltration of data, deletion of data, and defacing of a website - some of which can be catastrophic to a business or brand.

The checklist

This post aims to provide a simple checklist with brief but concise reasons for each item. This post is aimed at developers, as access to coding techniques will be required. The items in this list are aimed at general WordPress sites, leveraging techniques for both WordPress and non-WordPress sites, in order of ease of implementation.

Install WordFence

The first item on the list is to install the popular WordPress security plugin called WordFence. This is such an easy solution and provides so many benefits, including:

  • Brute force protection - stops scripted login systems from trying hundreds of passwords per minute on your login system. This can be tweaked to allow a certain number of password attempts in a certain time frame.
  • Site scan - checks website for strange looking files which could be used for malicious purposes.
  • 2 factor authentication - one of the most useful parts of WF, allowing all users or just admins to log in with their password and an additional step which requires the user to enter a unique code found on their phone. This will help stop logins for users who may have guessed a password.
  • Weak password detection - checks the passwords in the site to see if any are listed on a database of frequently used passwords. You don’t want your admins using common or weak passwords!
  • User login message changes - perhaps a less important feature, but updates the feedback message on an incorrect password attempt so the potential hacker does not know the username/email exists in the database. By default, WordPress responds to an incorrect password attempt to say that the password for [email protected] is incorrect. At this point the hacker can see that the email address does in fact exist. WordFence stops this insecure response, and changes it to a generic message no matter what username/password combination is used.

Avoid using the “admin” username

Simple.

Keeping software up to date

The most common reason for WordPress hacks is due to out of date software. This is for both WordPress core software and also plugins. Over time, people are able to find vulnerabilities in certain software which exposes the site to a large number of potential threats. A popular software service called Metasploit allows a beginner hacker or penetration tester to deploy exploits to outdated software in WordPress extremely easily.

Keeping WordPress and plugins up to date isn’t always as simple as you might think. The site may break on update, so it’s important the process is carried out manually with a human checking. Here’s a few tips:

  • Set yourself a weekly reminder to log in to each site and check the WordPress core and plugin versions.
  • Back up the site before updating. You really should have daily backups anyway, especially for important sites.
  • Test updates on a staging site first.
  • Enable auto updates.

Use strong passwords!

Use a strong password for everything - wp-config.php database, CMS login, email account, everything! one weak link can ruin the chain. I recommend using LastPass to help keep your CMS logins secure.

Moving the wp-config.php file

The wp-config.php file contains the crown jewels of your website - the database credentials. With these details, a hacker can do literally anything to the site they want. To protect this file a bit more, move it to the directory above the web root. WordPress automatically detects this location of the wp-config.php file as it looks in the directory above first, so there’s no reason not to do this! It decreases the risk of a user being able to access the file.

Change your wp-config.php database prefix

By default the WordPress database prefix is set to “wp_“. This is obviously a slight risk as potential hackers will easily be able to target “wp_users” in a SQL injection attack for example. It’s easy to update the config file to use a prefix of your choice:

prefix

Restrict your login page to known IP addresses

If you take a glimpse at your HTTP access logs of a WordPress site, you’ll be surprised ay just how many attempts can be made at submitting unauthorized login credentials to your wp-login.php page. It can help to restrict access to this page completely using your .htaccess file (or similar).

For .htaccess, just add the following snippet at the top of the page:

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

Of course, if you’re an on-the-go blogger who needs access to your site where ever you are, this might not be ideal, but it is certainly a huge advantage. Perhaps you could invest in setting up a VPN from your home or office which you can connect to from anywhere in the world to ensure you’re connecting with the same IP? That’s what I do anyway!

Stop the xmlrpc.php exploit

By the same token, there’s a file called xmlrpc.php in a WordPress installation. This file can be hit hundreds of times by an attacker in order to brute force a system. This file isn’t commonly used in WordPress, so the majority of people can restrict access to it completely! Again, using the .htaccess file:

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

Secure your cookies

This can be done many ways, but as we’re on a roll with the .htaccess file:

Header always edit Set-Cookie (.*) "$1; HttpOnly"
Header always edit Set-Cookie (.*) "$1; Secure"

Add important security headers

There are a few headers, some recently released, which greatly increase the security of any website, WordPress or otherwise. Headers are defined in different ways depending on the CMS, platform, or language. Headers can also be set in the .htaccess file, but for these headers I prefer to define them in PHP, and integrate “the WordPress way”. I like this because it’s easy to grab a PHP class, file or plugin and add to other projects easily.

Create the following action in the functions.php file:

add_action( 'send_headers', 'security_headers' );
function security_headers() {
  // headers go here
}

I like to create a simple plugin which I can install on all sites, but that’s a little out of scope of this post.

Here are the headers:

  • Content Security Policy (CSP) - prevents XSS attacks by whitelisting sources of content. For example, allowing scripts from Google Analytics, but nowhere else. This is potentially the most verbose header I’ll cover here because each script source must be whitelisted. The CSP also stops inline embeds of JS and CSS by default, so it can be tricky to implement safely if your WordPress site has loads of plugins which embed JS or CSS in the page directly. Here’s an example one:
header( "Content-Security-Policy: frame-src 'self' *.google.com; script-src 'self' 'unsafe-inline' *.google-analytics.com *.googletagmanager.com;" );

This header stops iFrame embeds from a source other than the current site, allows scripts to be embedded only from Google Tag Manager and Google Analytics, and also allows inline JavaScript. As mentioned above, allowing inline JS isn’t advised, but often can’t be helped without considerable effort if you have plugins which don’t follow best practices.

  • HTTP Strict Transport Security (HSTS) - security enhancement to ensure connections to the site are sent over HTTPS. Here’s the header:
header( "Strict-Transport-Security: max-age=31536000; includeSubDomains; preload" );
  • Referrer Policy - controls the amount of information in the referer header which is included when navigating to the site:
header( "Referrer-Policy: no-referrer-when-downgrade" );
  • X Frame Options - controls whether the the browser should allow the site to be embedded in an iFrame or Object element. This helps prevent clickjacking attacks:
header( "X-Frame-Options: SAMEORIGIN" );
  • X-XSS-Protection - a solid cross site scripting filter:
header( "X-XSS-Protection: 1; mode=block" );
  • X-Content-Type-Options - stops the browser from trying to “sniff” a file type, helping to prevent malicious code embedded in to an image file type for example:
header( "X-Content-Type-Options: nosniff" );

Implement Subresource Integrity (SRI)

This is a technique which stops JS or CSS files embedded from external locations from being added to the site if they contain different content than intended. Any script you add to a site as a developer can be executed with full access to your site, potential APIs, browser features etc., so it’s important than when embedding jQuery library for example, it doesn’t contain malicious code integrated by a “man in the middle” or code having been compromised at the source.

This technique is becoming increasingly popular among hackers lately, especially on sites which contain a payment system, as a key logger can capture and send users credit card information to anothet location with only a few lines of JS at the bottom of an embedded library.

Here’s an example:

<script
  src="https://cdn.some-lib.com/lib.js"
  integrity="sha384-eaASDfvfdhuRKap7fdgcCY5uykM6+R9GqQ8K/uxyHNQlDSFrEGrfGBDsfegfseFG"
  crossorigin="anonymous"
></script>

The contents of the embedded file are hashed to produce a trusted base64 hash value, which is then added to the “integrity” attribute of the script. All future requests to the file must resolve to this same hash value in order to run on the page.

Do a Mozilla Observatory scan

Run your site through Mozilla Observatory scanner. This will perform a quick scan of your site to check for some of the above mentioned (non-WordPress) security enhancements. It really is quite easy to get this score to an A+.

Do WP Scan

Run a WP Scan scan of your site. Specific to WordPress sites, this scan will help find vulnerabilities in WordPress core and common plugins. It requires installation via the Terminal using Gems, and gives a fantastic overview of your WordPress site.

Thanks for reading

This post is designed to provide a checklist which is quick and relatively easy to implement, but there are a huge amount of other ways to secure a website. This is by no means everything, and depending on the type of site you have and the data within the site, you may need to push the boat out and dig much deeper than this. It’s a starting point though!


Senior Engineer at Haven

© Jay Gould 2023, Built with love and tequila.