WordPress has evolved from a blogging platform into
a development framework. It is becoming increasingly common to see
it used as a basis for a variety of software projects, including
e-commerce stores. Its ease of use and low barrier to entry means
it has rapidly become a favourite.
Unfortunately, as with every popular software
system, it has become a target for hackers, spammers and other
nefarious types on the Internet. WordPress sites
are responsible for a large number of the
phishing and malware pages online.
If you're running a website, your site will be a
target eventually, no matter how big or small. Malicious users run
automated web crawlers (like what Google uses to discover content)
to discover and scan zillions of websites looking for
vulnerabilities. WordPress is relatively easy to detect, so it's
not if your site is targeted by hackers -
it's when.
Is WordPress Insecure?
Why is this the case? Is WordPress fundamentally
broken or insecure?
The short answer is "no". The longer answer is "no,
but… sometimes".
There seem to be two major factors that influence
WordPress security: plugins and
passwords.
The Risk of Plugins
One of the best features of WordPress is that it is
easily extensible: it can be modified and
adapted easily by means of powerful plugin and theming
systems.
If you run a WordPress site, you almost certainly
have some plugins installed to extend the functionality - maybe a
contact form so people can send you email, maybe a spam filter.
There is simply a staggering number of plugins, made available
because WordPress is easy to develop for, and they're dead easy for
anyone to install.
Unfortunately, as with all great power, it comes
with great responsibility. It is very important to note that any
random WordPress plugin can wield enormous power on your site. The
very features that make them so useful also make them so risky. A
malicious plugin could track user or admin activity and report it
to remote sites.
Genuinely malicious plugins, however, are
(probably) pretty rare. Much more common - and certainly
responsible for the majority of websites getting hacked or
otherwise compromised - are plain old buggy plugins. A plugins with
a simple coding error - a character in the wrong spot, or the wrong
function call used, or one not used - can mean your entire site is
an open book to malicious users. They can read all your data, and -
possibly worse - write over it.
Many plugins simply increase the attack
surface of a WordPress site - it creates more
opportunities for a malicious user to compromise your
website.
(It should also be noted that a lot of the problems
that apply to plugins also apply to WordPress themes. A theme is
basically just a bunch of code. Many themes include plugins, but
don't forget that a theme with a bug in it can also cause a lot of
problems.)
The Risk of Passwords
Everyone knows this one, so I won't spend too much
time on it. Passwords are hard. Everyone wants a password that's
easy to remember, but these are typically very weak. Longer
passwords are better, but harder to remember. There's the correct horse battery
staple method, but then you've got people
saying
that's no good either. Argh!
Putting that aside for a minute, let's take a look
at a real-world example of what is going on when people try to hack
your WordPress site by brute forcing your password.
The below is a (small!) excerpt from a
authentication log file showcasing a concerted attempt to brute
force the passwords on a (small, non-descript, personal) WordPress
blog. The sheer volume of requests is interesting - the excerpt
doesn't show them all, but there were thousands over the course of
a 24 hour period - as is the fact that they come from a large
number of different IP addresses (presumably some sort of
botnet).
Sep 18 06:36:33 testvps wordpress(example.com)[11218]:
Authentication failure for Administrator from 85.97.168.XX
Sep 18 06:36:50 testvps wordpress(example.com)[11225]:
Authentication failure for Administrator from 124.120.10.XX
Sep 18 06:36:59 testvps wordpress(example.com)[11232]:
Authentication failure for Administrator from 186.18.60.XX
Sep 18 06:37:01 testvps wordpress(example.com)[11239]:
Authentication failure for example.com from 173.161.124.XX
Sep 18 06:37:14 testvps wordpress(example.com)[11246]:
Authentication failure for example.com from 216.195.240.XX
Sep 18 06:37:50 testvps wordpress(example.com)[11247]:
Authentication failure for admin from 123.176.17.XX
Sep 18 06:37:55 testvps wordpress(example.com)[11254]:
Authentication failure for admin from 121.96.42.XX
Sep 18 06:37:58 testvps wordpress(example.com)[11255]:
Authentication failure for example.com from 109.74.73.XX
Sep 18 06:38:06 testvps wordpress(example.com)[11256]:
Authentication failure for example.com from 95.12.97.XX
Sep 18 06:38:06 testvps wordpress(example.com)[11257]:
Authentication failure for example.com from 95.12.97.XX
Sep 18 06:38:10 testvps wordpress(example.com)[11258]:
Authentication failure for admin from 189.133.143.XX
Sep 18 06:38:10 testvps wordpress(example.com)[11259]:
Authentication failure for admin from 189.133.143.XX
Sep 18 06:38:32 testvps wordpress(example.com)[11266]:
Authentication failure for admin from 111.91.86.XX
Sep 18 06:39:29 testvps wordpress(example.com)[11274]:
Authentication failure for admin from 166.147.104.XX
Sep 18 06:40:11 testvps wordpress(example.com)[11291]:
Authentication failure for example.com from 187.199.59.XX
Sep 18 06:40:33 testvps wordpress(example.com)[11292]:
Authentication failure for admin from 197.34.170.XX
Sep 18 06:42:19 testvps wordpress(example.com)[11440]:
Authentication failure for example.com from 98.89.84.XX
Sep 18 06:42:31 testvps wordpress(example.com)[11448]:
Authentication failure for example.com from 177.138.120.XX
Sep 18 06:42:49 testvps wordpress(example.com)[11556]:
Authentication failure for Administrator from 76.76.172.XX
Sep 18 06:43:01 testvps wordpress(example.com)[11614]:
Authentication failure for admin from 216.195.240.XX
Sep 18 06:43:06 testvps wordpress(example.com)[11630]:
Authentication failure for Administrator from 119.35.25.XX
Sep 18 06:43:14 testvps wordpress(example.com)[11637]:
Authentication failure for admin from 95.12.97.XX
Sep 18 06:43:14 testvps wordpress(example.com)[11638]:
Authentication failure for admin from 95.12.97.XX
Sep 18 06:43:53 testvps wordpress(example.com)[11653]:
Authentication failure for example.com from 173.26.69.XX
Sep 18 06:43:53 testvps wordpress(example.com)[11652]:
Authentication failure for example.com from 173.26.69.XX
Sep 18 06:44:29 testvps wordpress(example.com)[11717]:
Authentication failure for {domain} from 218.19.118.XX
Sep 18 06:44:30 testvps wordpress(example.com)[11718]:
Authentication failure for {domain} from 218.19.118.XX
Sep 18 06:44:32 testvps wordpress(example.com)[11719]:
Authentication failure for example.com from 41.68.153.XX
Sep 18 06:44:37 testvps wordpress(example.com)[11720]:
Authentication failure for example.com from 188.169.172.XX
Sep 18 06:44:37 testvps wordpress(example.com)[11721]:
Authentication failure for Administrator from 119.139.169.XX
Sep 18 06:44:38 testvps wordpress(example.com)[11722]:
Authentication failure for Administrator from 213.6.0.XX
Sep 18 06:44:55 testvps wordpress(example.com)[11735]:
Authentication failure for admin from 41.35.26.XX
Sep 18 06:45:00 testvps wordpress(example.com)[11736]:
Authentication failure for admin from 187.199.59.XX
Sep 18 06:45:28 testvps wordpress(example.com)[11746]:
Authentication failure for admin from 176.121.227.XX
Sep 18 06:46:00 testvps wordpress(example.com)[11747]:
Authentication failure for example.com from 41.233.203.XX
Sep 18 06:46:45 testvps wordpress(example.com)[11749]:
Authentication failure for Administrator from 197.34.170.XX
Sep 18 06:47:47 testvps wordpress(example.com)[11757]:
Authentication failure for Administrator from 216.195.240.XX
Sep 18 06:47:52 testvps wordpress(example.com)[11765]:
Authentication failure for example.com from 125.22.195.XX
Sep 18 06:48:14 testvps wordpress(example.com)[11768]:
Authentication failure for admin from 108.12.237.XX
Sep 18 06:48:44 testvps wordpress(example.com)[11771]:
Authentication failure for admin from 173.26.69.XX
Sep 18 06:49:15 testvps wordpress(example.com)[11778]:
Authentication failure for admin from 188.169.172.XX
Sep 18 06:49:34 testvps wordpress(example.com)[11781]:
Authentication failure for Administrator from 187.199.59.XX
Sep 18 06:49:37 testvps wordpress(example.com)[11788]:
Authentication failure for Administrator from 175.17.156.XX
Sep 18 06:49:41 testvps wordpress(example.com)[11795]:
Authentication failure for admin from 177.138.120.XX
Sep 18 06:50:09 testvps wordpress(example.com)[11804]:
Authentication failure for example.com from 79.12.237.XX
Sep 18 06:50:24 testvps wordpress(example.com)[11806]:
Authentication failure for example.com from 151.240.180.XX
Sep 18 06:51:27 testvps wordpress(example.com)[11809]:
Authentication failure for Administrator from 63.227.69.XX
Sep 18 06:51:44 testvps wordpress(example.com)[11817]:
Authentication failure for Administrator from 108.12.237.XX
Sep 18 06:52:10 testvps wordpress(example.com)[11824]:
Authentication failure for admin from 109.74.73.XX
Sep 18 06:53:12 testvps wordpress(example.com)[11826]:
Authentication failure for example.com from 212.252.101.XX
Sep 18 06:53:12 testvps wordpress(example.com)[11827]:
Authentication failure for example.com from 212.252.101.XX
Sep 18 06:53:32 testvps wordpress(example.com)[11829]:
Authentication failure for Administrator from 188.169.172.XX
Sep 18 06:53:54 testvps wordpress(example.com)[11838]:
Authentication failure for admin from 125.22.195.XX
Sep 18 06:55:03 testvps wordpress(example.com)[11852]:
Authentication failure for admin from 203.193.153.XX
Sep 18 06:55:03 testvps wordpress(example.com)[11853]:
Authentication failure for admin from 203.193.153.XX
Sep 18 06:55:21 testvps wordpress(example.com)[11860]:
Authentication failure for admin from 41.68.153.XX
Sep 18 06:55:46 testvps wordpress(example.com)[11861]:
Authentication failure for example.com from 2.50.40.XX
Sep 18 06:56:13 testvps wordpress(example.com)[11864]:
Authentication failure for Administrator from 177.138.120.XX
Sep 18 06:56:55 testvps wordpress(example.com)[11871]:
Authentication failure for admin from 218.19.118.XX
Sep 18 06:56:55 testvps wordpress(example.com)[11872]:
Authentication failure for admin from 218.19.118.XX
Sep 18 06:58:36 testvps wordpress(example.com)[11886]:
Authentication failure for Administrator from 121.96.42.XX
Sep 18 06:59:37 testvps wordpress(example.com)[11897]:
Authentication failure for Administrator from 125.22.195.XX
Sep 18 07:00:18 testvps wordpress(example.com)[11913]:
Authentication failure for admin from 151.240.180.XX
Sep 18 07:00:42 testvps wordpress(example.com)[11914]:
Authentication failure for admin from 41.233.195.XX
Sep 18 07:00:52 testvps wordpress(example.com)[11915]:
Authentication failure for admin from 2.50.40.XX
Sep 18 07:01:23 testvps wordpress(example.com)[11916]:
Authentication failure for admin from 91.121.86.XX
Sep 18 07:01:31 testvps wordpress(example.com)[11918]:
Authentication failure for admin from 203.195.184.XX
Sep 18 07:03:10 testvps wordpress(example.com)[11919]:
Authentication failure for Administrator from 176.121.227.XX
Sep 18 07:03:23 testvps wordpress(example.com)[11927]:
Authentication failure for Administrator from 41.68.153.XX
Sep 18 07:03:26 testvps wordpress(example.com)[11934]:
Authentication failure for Administrator from 109.74.73.XX
Sep 18 07:05:15 testvps wordpress(example.com)[11956]:
Authentication failure for Administrator from 79.12.237.XX
Sep 18 07:05:35 testvps wordpress(example.com)[11958]:
Authentication failure for Administrator from 2.50.40.XX
Sep 18 07:08:37 testvps wordpress(example.com)[11973]:
Authentication failure for example.com from 201.68.2.XX
Sep 18 07:09:03 testvps wordpress(example.com)[11982]:
Authentication failure for Administrator from 41.35.26.XX
Sep 18 07:09:12 testvps wordpress(example.com)[11989]:
Authentication failure for Administrator from 151.240.180.XX
Sep 18 07:09:21 testvps wordpress(example.com)[11997]:
Authentication failure for example.com from 205.144.215.XX
Sep 18 07:09:21 testvps wordpress(example.com)[11996]:
Authentication failure for example.com from 205.144.215.XX
Sep 18 07:10:21 testvps wordpress(example.com)[12007]:
Authentication failure for example.com from 113.161.230.XX
Sep 18 07:13:43 testvps wordpress(example.com)[12013]:
Authentication failure for admin from 205.144.215.XX
Sep 18 07:14:36 testvps wordpress(example.com)[12023]:
Authentication failure for example.com from 98.166.154.XX
Sep 18 07:16:48 testvps wordpress(example.com)[12035]:
Authentication failure for example.com from 200.36.176.XX
Sep 18 07:18:00 testvps wordpress(example.com)[12039]:
Authentication failure for admin from 98.166.154.XX
Sep 18 07:18:20 testvps wordpress(example.com)[12041]:
Authentication failure for example.com from 200.36.176.XX
Sep 18 07:19:07 testvps wordpress(example.com)[12044]:
Authentication failure for example.com from 27.54.168.XX
Sep 18 07:19:07 testvps wordpress(example.com)[12043]:
Authentication failure for example.com from 201.127.74.XX
Sep 18 07:21:04 testvps wordpress(example.com)[12056]:
Authentication failure for Administrator from 98.166.154.XX
Sep 18 07:23:13 testvps wordpress(example.com)[12067]:
Authentication failure for example.com from 201.92.48.XX
Sep 18 07:23:49 testvps wordpress(example.com)[12069]:
Authentication failure for admin from 201.127.74.XX
Sep 18 07:25:27 testvps wordpress(example.com)[12080]:
Authentication failure for admin from 27.54.168.XX
Sep 18 07:25:36 testvps wordpress(example.com)[12081]:
Authentication failure for admin from 113.161.230.XX
Sep 18 07:27:59 testvps wordpress(example.com)[12087]:
Authentication failure for Administrator from 201.127.74.XX
Sep 18 07:29:25 testvps wordpress(example.com)[12102]:
Authentication failure for example.com from 124.123.194.XX
Sep 18 07:31:24 testvps wordpress(example.com)[12121]:
Authentication failure for example.com from 78.177.148.XX
Sep 18 07:31:45 testvps wordpress(example.com)[12124]:
Authentication failure for Administrator from 27.54.168.XX
Sep 18 07:32:04 testvps wordpress(example.com)[12131]:
Authentication failure for example.com from 108.211.163.XX
Sep 18 07:32:04 testvps wordpress(example.com)[12132]:
Authentication failure for example.com from 108.211.163.XX
Sep 18 07:36:18 testvps wordpress(example.com)[12153]:
Authentication failure for example.com from 142.177.17.XX
Sep 18 07:36:20 testvps wordpress(example.com)[12155]:
Authentication failure for admin from 78.177.148.XX
Sep 18 07:36:38 testvps wordpress(example.com)[12156]:
Authentication failure for example.com from 85.49.21.XX
Sep 18 07:36:56 testvps wordpress(example.com)[12157]:
Authentication failure for admin from 108.211.163.XX
Sep 18 07:36:56 testvps wordpress(example.com)[12158]:
Authentication failure for admin from 108.211.163.XX
Sep 18 07:37:36 testvps wordpress(example.com)[12167]:
Authentication failure for example.com from 148.251.16.XX
The really scary thing about this is that owners of
many WordPress blogs will probably never know that these attempts
are taking place. Unless you have some sort of logging (like in the
above example), the vast majority of these attempts will happen
without you noticing. There may be no effect on your site at all,
although if the logins are very frequent it may cause your site's
performance to suffer.
But remember: attackers (basically) have unlimited
time and resources to continue these attempts. If they finally get
the password right, your site has been hacked, and is now
compromised.
How do you tell if your site has been
compromised?
Many of these attacks are trying to add your site
into a network that serves spam pages trying to sell CH3AP
PH4RMACEUT1CALS, or to make it so you serve malware to users. Or
they might simply get added to a botnet that tries to find more
vulnerable sites.
In the case of spamming or malware, unless you keep
a really close eye on the contents of every part of your site and
the log files, it's often pretty hard to notice. The first you'll
hear about it will probably be something like an email from your
site host saying your site is doing weird stuff. Your customers
might tell you that they get a warning in Firefox or Chrome when
they visit your site (like in the below images).

The worst case scenario is if you have a big site
with a lot of interesting data - names, email addresses, phone
numbers, payment information - anything that real criminals can use
for identity theft or for other scary purposes.
Unfortunately, in this case, if your site contains
data that is more valuable than the opportunity presented by simply
using it to serve spam, it might be very hard to ever notice that
it's been compromised. A clever malicious user might simply choose
to just sit behind the scenes in your website taking advantage of
the information that they now have access to. (This is probably a
pretty low risk scenario for most users.)
What happens if I've been hacked?
This is a big topic and it is dependent on precise
details. There are two basic things that I'd recommend
doing:
-
Find out how the hack occurred. Was it a brute
force? Was it a vulnerable plugin? If you don't know how it
happened, stopping it from happening again can be very difficult.
Unfortunately, finding how it happened can be difficult - you'll
need to end up spending a lot of time poring over log files looking
for clues.
-
Reinstall WordPress from scratch. This is painful.
You need to import your old database - but first you need to make
sure there's nothing in the database that might lead to another
compromise (e.g., an attacker may add themselves their own user
account, which you might not notice if you have a lot of different
users).
You can reinstall WordPress from a "last known
good" backup - but you need to really
know that it's good (i.e., not already compromised), and
you really need to have closed whatever
hole that let them in.
What can I do to lower the risks?
Here are some simple things you can do to help
maximise WordPress security. Before we get into it though, two
important notes:
-
Software, and thus security of software, is a
moving target. Things are changing all the time - software is
modified creating new exploits, more research is done on old code
revealing new weaknesses. There is no silver
bullet.
-
Prevention is better than the cure. There is always
a strong temptation to postpone security measures. Don't. Do what
you can right from the start.
The easy stuff:
-
Keep the WordPress core
up-to-date
While very few vulnerabilities have been found in WordPress
core recently, it is still very important to ensure you're running
the latest release. This has been somewhat simplified by a recent
change that offers automatic updating, but the onus is on you to
keep it updated. If you're not logging into your administration
section regularly (weekly at the very least), the WordPress Development
Blog is a great way to stay up-to-date via RSS, or if you
prefer email notifications they also have an
announcement mailing list.
-
Keep your plugins and themes up-to-date.
Plugins are updated regularly, but they won't
auto-update in WordPress. Many attackers are out there looking for
holes in WordPress plugins, so it's vitally important that these
are kept as current as possible.
-
Use as few plugins as possible.
Reduce the attack surface as much as you can. Use
plugins that have high ratings. If you can, read the plugin source
code to see what it does. Look for plugins that just do the one
thing you need - the larger and more complex the plugin, the bigger
the attack surface, and the more risk associated.
-
Use strong passwords on your WordPress
accounts.
Come up with a strong password that is long and has a
lot of funny characters. Use your browser's password system to
remember it. Get another password management system like PasswordSafe
and start using it to create and store strong
passwords.
-
Use strong passwords on your FTP
accounts.
FTP brute force attempts are also pretty common. If you
can, disable FTP altogether.
-
Sign up for Google Webmaster
Tools.
Google's Webmaster
Tools has a bunch of great features that give you
interesting insights into how your site is working. One of these
tools is a malware scanner, and it will notify you if they detect
anything scary on your site.
Harder stuff:
-
Add a separate .htaccess-based password on your
/wp-admin directory.
If an attacker gets into your /wp-admin directory,
they can basically do anything - edit your site, look at your
users, read your database. Putting an additional layer of security
by password protecting this directory via a .htaccess will
significantly cut down risk. Setting this up only takes a few
minutes, and in most cases you'll be able to save the password in
your browser, so it will barely be an inconvenience.
-
Remove write permissions.
As noted in WordPress's own "
Hardening WordPress" guide, many of the neat
features in WordPress exist because it can write to the disk,
allowing you to easily install plugins and themes with a single
click. Unfortunately this is a massive attack vector and probably
the most commonly exploited part of WordPress as a whole - one bug
in the wrong spot and a malicious user can spray whatever files
they want all over your site.
In many cases, disabling write permissions will
flat-out stop this from happening - in the event of an exploit in a
plugin or theme, the web server will simply be unable to write to
the disk, which will stop many common attacks.
This is a tricky one though, as it comes with a big
downside: it will stop a lot of WordPress functionality from
working. You won't be able to install plugins and themes from the
administration interface. You won't be able to update WordPress.
Some plugins that need to write to the disk (e.g., WP Super Cache)
might not be able to function. You won't be able to upload
images.
In short, anything that needs to write to the disk
won't be able to. The site will generally operate fine though - as
long as your code doesn't need to write to the disk, users will
still be able to access everything and make comments and place
orders and all of that stuff.
You can, of course, simply change the permissions
back if you need to do any of these things. But with the loss of
convenience comes improved security.
For VPS users:
If you're running your WordPress setup on your own
VPS (as opposed to shared hosting), there's a few more things you
can do to reduce your risk:
-
Install Fail2ban. Now!
Fail2ban is a neat piece of software that can scan
log files and perform actions based on things it sees. One very
useful practical application of this is that you can catch failed
login attempts to your WordPress site and then ban the IP addresses
involved - massively reducing the effectiveness of brute force
attacks.
Fail2ban is good to have on your VPS in general (it
can stop other kinds of brute force attempts, including SSH and
FTP). It takes a few minutes to install and set up by itself, and
there's already a good WordPress plugin called WP
fail2ban that is dead easy to get working.
As a bonus, in addition to increasing security it's
possible you'll see some performance improvements as well as
large-volume brute forces are cut off early and stop hammering your
site.
-
Install Tripwire.
Tripwire
monitors changes to files on your disk. It's a useful
security tool for WordPress - you can check to see if any of your
files have been edited, or if new files have been added, without
you doing something.
It's a little more fiddly, but there are plenty of
guides online that can walk you through the process.
WordPress is no longer just a great platform for blogging
- it's a flexible piece of software that can be used as the basis
for many projects. When handled with care it can be just as
reliable and secure as anything else, but it's vitally important to
remember that - as with any popular
software that is Internet-connected - it will be a target for
hackers.