In order to effectively reduce costs, several businesses are moving away from complex designs and bespoke hosting to a simpler platform based on WordPress - often self-hosted. According to WordPress, “Code Is Poetry”, and literally anyone with a basic knowledge can create a website within a short space of time. With a potentially massive reduction in cost, who can blame anyone wanting to leverage such an opportunity ? Code may well be poetry, but without the requisite steps needed to harden the WordPress platform against external attacks, that same code can be exploited to gain access to your site – and where’s the poetic justice in that ? As an experienced WordPress developer, there are a number of attacks I would personally attempt on a WordPress site, and in this article, I will walk you through the most common (and some not so common) issues and explain why they are classed as vulnerabilities. I will also show you how to reduce the risk that each one poses with real world examples.
A quick thought. Would “poetry” still sound nice if you used it as an acronym ?
- Open To
- Yelp (when you realise what’s happened)
Only yesterday I was reviewing security on a WordPress site that belongs to a local distribution company. I didn’t expect the result to be perfect, but it was certainly well below the bar in terms of what is considered acceptable and secure. A perfect level of security is unobtainable in my view – the threat landscape changes on a daily (sometimes hourly) basis, and so keeping up with each new trend really isn’t feasible unless you are a security analyst dedicated to this particular function. Businesses and individuals alike have evolved, and are looking at ways to do more with less – more about that a little further down the line.
What I did see startled me. The design of the website looks great to the normal user, but even the basic hardening steps have not been applied on this site. Having nearly choked on my coffee, I decided that awareness around this particular issue needed to be raised to help other people avoid similar pitfalls. Now, if you’ve gone down the WordPress.com route for your blog, this article isn’t for you. It really only applies to those who have chosen to run a self hosted site. Let’s get started
WordPress Hardening – a walk-through
OK. You’ve concluded you will use WordPress as your chosen CMS and blogging platform. WordPress is undoubtedly the most popular blogging platform in the world with an astonishing number of websites being solely based on it. For those who are blissfully unaware of the dangers that an unsecured WordPress platform can present, this information is for you.
Out of the box, WordPress is essentially ready to go from the blogging perspective. However, it’s a misconception that the same applies to security. As great a platform as WordPress is, mainly attributed to its vast array of plugins that extend the core functionality above and beyond, installing it then leaving the product in it’s vanilla state is a disaster waiting to happen. Even in it’s plainest form, there are a number of settings and best practice steps that should be taken in order to further harden the platform against attack.
IMPORTANT: Before you proceed any further, ensure you have a full backup (both files and database) of your WordPress installation !
Step 1 - Rename the admin account
WordPress uses an account to perform functions that require administrative access to the core and associated plugins. On all new self hosted installations, the account is named “admin” (unless you make use of an installer provided by the ISP, in which case, they will probably change this as part of the installation process). Any attacker with even a basic knowledge will target this account, and attempt to use either a dictionary attack, or brute force to gain access.
If you have an “admin” account in your WordPress installation, then you should seriously consider removing it. Renaming it is possible, and there is also a fairly common argument for stripping out the permissions but leaving the account intact – supposedly kind of a honey pot (albeit without the honey), but this just keeps an attacker engaged until they break into your site. The best course of action is to create a new user, give that user administration rights, then delete the previous one. Note, that on deleting the old admin account, you’ll be asked to attribute any content to another user – simply provide the new one you’ve created. See the below steps
Create the new user as shown below
Logout of the WordPress admin console, and log back in as the new user
Drop the old admin account
- Assign any content to the new admin user
Step 2 - Change the admin account ID
The second part of securing the admin account against brute force is to change the ID that the account uses. This requires changing the database entry, so be careful.
NOTE – if you have posts written by the admin account that you are going to change the ID of, then it is important to reassign the author of these to another user. This site provides an excellent walk through of how to do just that, so I won’t be reinventing the wheel here ? Once you have a good database backup completed, you can then proceed to execute the below statements in MySQL to change the admin ID
UPDATE wp_users SET ID = 1024 WHERE ID = 1;
The above syntax will change the ID of the admin user from 1 to 1024. Note, that for administrator accounts, the generally accepted mechanism is to choose a higher value – mainly due to the amount of time it will take to discover by any would-be attacker
UPDATE wp_usermeta SET user_id = 1024 WHERE user_id = 1;
The above syntax will change the associated ID of the admin user in the table wp_usermeta. Note, that the above commands make some assumptions – mainly that you are using the default wp_ prefix for your WordPress installation. This in itself isn’t recommended by most sites, and there are a multitude of plugins that can address this issue if need be. You can complete the process manually, although this is a very advanced change, and you really need to be familiar with the platform in order to get things working again.
One of the major flaws with WordPress over the years is the ability to use a weak password. Sites that make use of a simple dictionary based password will find themselves compromised very quickly. The user may also find that they have been locked out of their installation by the same perpetrator who broke into it, and will effectively have lost control. This is known as a take over – kind of like squatting, but with the intention of applying malicious content (usually malware), and then turning the site into a command and control (or C2) centre that the attacker has control over.
A weak password also opens the floodgates for bruteforce attacks in WordPress.
Essentially, you keep hammering on the door with millions of passwords per second until you gain access. In this scenario, a successful attacker would then gain control over the system, and could install a plugin that allows direct access to the database from the GUI – and yes, these do exist. Once this access has been established, cyber criminals are in a position to steal confidential data. I can hear the wheels turning in your head now – confidential data on WordPress ? Yes. Think Mossac Fonseca, the Panamanian law firm. Using an outdated version of WordPress, the attackers were able to gain access to other systems using the compromised platform as a launchpad.
Directory and file permissions
The accepted platform for WordPress installations is generally Linux using the LAMP stack. Installations under Windows are perfectly feasible and possible, although extra effort is required in order to make PHP and MySQL work with Microsoft IIS. If you are intending on using Windows, then you should at least gain an understanding of what the numbers below actually mean
- All files should be 664
- All folders should be 775
- wp-config.php should be 660
Those coming from windows environments should be already familiar with the dangers that Full Control presents – particular for public facing servers. The same applies to Linux – and not just the WordPress elements. Finding a balance is important, and the settings above are the accepted standard for all WordPress sites at the very least. It’s important to think of security as a sliding scale. The more you apply, the higher chance there is of unintentionally reducing or breaking the functionality that you set out to provide in the first place.
Very little attention is paid to the WordPress file system by the new or average user. Importantly, by default, the level of access afforded exceeds what is required for the site to function.
Seeing as most shared hosting facilities that offer WordPress platforms commonly leverage Linux, it makes sense to explain at this point how the operating system differs in terms of permissions in relation to Windows.
Linux uses a particular permissions structure known as “classes“. These are split into three distinct areas
- Owner – Usually always the creator of the files and/or folders.
- Group – The Group contains a selection of users who share the same permissions and privileges
- Others – The permissions that should be set for anyone outside of the above criteria (for example, public)
To understand how permissions function from a Linux perspective, look at the below
0 – no permission
1 – execute
2 – write
3 – write and execute
4 – read
5 – read and execute
6 – read and write
7 – read, write, and execute
Essentially, this dictates that 664 (for example) would equal read and write for owners, read and write for groups, and read only for everyone else. Similarly, 755 for a directory would equal read, write, and execute for owners, read and execute for groups, then finally, read and execute for everyone else.
Below is an example of what the default permissions (should) look like on a newly installed platform (the left hand side is an individual file, and the right is a directory). Under no circumstances should you grant 777 permission to files or folders – on a Linux system, this is the Windows equivalent of Everyone\Full Control. If you are not familiar with this term, it offers literally the highest level of permissions with no access control whatsoever. In this case, “everyone” literally means everyone – even those who you do not want to have unrestricted access. Leaving permissions set at 777 will leave your platform open to abuse and takeover. Think of it this way; “777” signifies a jackpot in gambling. In security terms, the jackpot is what any potential hacker hits when they find this unintentional gem.
Certain directories should be secured using .htaccess configurations to prevent unauthorised access outside of the WordPress core. Some examples of how to configure depending on your needs are shown below
Step 3 - Block Bad IP Addresses
# Block one or more IP address.
# Replace THISBADADDRESS with the one you really want to block
<Limit GET POST> order allow,deny
deny from THISHADADDRESS
deny from THISBADADDRESS
allow from all
Step 4 - Prevent directory browsing
One of the worst security offenders in WordPress is the ability to browse the file structure from the internet. Using this technique, an attacker can then gather reconnaissance information concerning the plugins in use on your site, then use this information to leverage known vulnerabilities against each of the plugins. Even worse, if the permissions allow, an attacker can also upload malicious content such as malware or ransomware. Look out for plugins (such as backup utilities) that typically place a plugins.txt file in your directory. This is invaluable to an attacker, as they now have a list of all plugins running on your site, and can formulate custom attacks based on these.
Such an example of a poorly secured blog chosen at random (identity hidden) is as below
Attackers typically target the wp-content folder looking for the directory browsing vulnerability. This can easily be prevented by modifying (or creating if it does not exist) the .htaccess file within the wp-config directory so that it looks like the below
# Disable directory browsing
Options All -Indexes
This on it’s own is more than enough to prevent all directory browsing, but will also prevent various other media types from showing in your blog. To work around this, we need to flex the permissions a bit to make the files accessible to the blog, but not subject to directory browsing. An example of how we achieve this is shown below
Deny from all
<Files ~ ".(xml|css|jpe?g|png|gif|js|mp4|woff|woff2|ttf)$">
Allow from all
Step 5 - Secure wp-config.php
This file is the first one to be read by WordPress during each site load. It also contains significant sensitive information that could be used by an attacker to gain access to your site and underlying database. For security reasons, it is always a good idea to move the wp-config.php file out of the public HTML area so that it cannot be parsed by a directory listing. If you’ve taken the steps above to secure WordPress against a directory listing probe, then arguably, a good portion of the risk is mitigated.
But, if it isn’t there at all, then it’s not subject to the above vulnerability. As a platform, WordPress will tolerate the wp-config.php file being moved up one level, as it will automatically search one level above if the config file cannot be located in the expected position. Best practice is actually to place this file outside of the www root. There are a number of plugins that will stop working as a result of the wp-config file being relocated – to work around this, my personal preference is below
- Move the wp-config config file to a location that only you can access.
- Rename the original wp-config.php file to something else – let’s say wp-config-main.php for the purposes of simplicity
- Create a new file named wp-config.php that contains the below
/** Absolute path to the WordPress directory. */
if ( !defined('ABSPATH') )
define('ABSPATH', dirname(__FILE__) . '/');
/** Location of your WordPress configuration. */
require_once(ABSPATH . '../wp-config-main.php');
What are we doing here ? In a nutshell, we are satisfying 2 requirements. We are addressing the security vulnerability, and at the same time, ensuring that our plugins will continue to function. All the code above does is include the real wp-config.php content when it is requested by the core, or a plugin. This is referred to as an SSI or Server Side Include.
Step 6 - Security Services
Sites such as CloudFlare can offer security services (mostly for free for small sites – although the function set is reduced), and is a great way to bolster the security services that may already be present on your site. A word of warning though – the CloudFlare curve isn’t an easy one to learn in a short space of time, and there are a number of plugins that conflict with (rather than compliment) this additional layer of security
Step 7 - Remove unnecessary plugins
This is kind of obvious, but only use the plugins that you absolutely cannot do without. In fact, most plugins offer functionality that can be written into a custom
functions.php file inside a child theme. If you are not using child themes, then you should look into doing this, as it means any custom changes you have made will survive an upgrade if the main theme you are using changes (updated, for example). It’s bad practice to change the core files directly – don’t do this, no matter how great the temptation is. If you have no use for a plugin, remove it. If a plugin hasn’t been updated in a long time, then this is a great indicator that you really should be looking for an alternative that is actively maintained.
There are a number of security plugins on the WordPress scene which do an excellent job of securing the entire platform. A couple of examples are WordFence and iThemes Security (to name the most popular ones). These extend the core to a higher plane in terms of security, but they are not for the novice user in most aspects, and you really need to understand why the changes are being highlighted in the first place – which is really outside of the scope of this article. If you’d like more information about using functions.php or a detailed guide as to security plugins, do let me know.
Have I missed anything (or better still, confused anyone) ? Let me know !