Dealing With Hacked WordPress on EC2
I was going to start this article by saying that I’m a big fan of WordPress, but I’m hesitating. WordPress is super popular, and it gets the job done for most people, but let’s be honest – it is a huge target. Bots are constantly beating at it trying to slip into cracks and wreak havoc. I’ve recently had to deal with 2 big holes in WordPress, that I’m sure most big sites using WordPress have had to deal with. Those 2 holes are the login page, and the plugin directories. In the first case I had a bots hammering the default /wp-admin page, trying to brute force the login for admin access. WordPress is pretty terrible when it comes to limiting any kind of logins, correct or incorrect, so any script trying to break in can continue on forever undeterred. The second hole had to do with security holes in the nggallery plugin. While I’m not sure I was actually being hit with a hack attempt, I did have server issues with Google bots trying to access rss feeds in my plugin subdirectories inside the wp-content/plugins directory. Whatever the case, both security holes ended with my server consuming vast amounts of requests, and server cpu cycles trying to deal with the barrage of requests; which ended up with my server hanging ever so often.
Diagnosing the issue
I knew I had a problem when I had to restart my server because it had become unresponsive. The most common issue making it unresponsive was a locked table in Mysql. I use AWS Ec2 to serve my WordPress sites – so the easiest method to get it up and running again is just to reboot the instance. You can do that from inside the EC2 console or from AWSs’ phone app. It isn’t hard – you can also use Cloudwatch to automate rebooting. In my case I set up an alarm to reboot the EC2 when the cpu cycles drop below .3 % for 15 minutes. Basically if the server isn’t serving anything for 15 mins just restart. This means that ever once in awhile it might restart because of no activity, but it also means that if the server is in some hung state it can recover. I also set up some additional Cloudwatch alarms to text my phone if the CPU is above 20% utilization over 10 minutes. It is rare that site traffic is high on that server for any extended time for normal usage – most visitors to sites hosted on that server request a few pages then leave, all of which doesn’t require much CPU, so if the CPU is over 20% for 10mins plus it usually means it is being hit with some kind of script. Ec2 monitoring and Cloudwatch were really my first line of defense in dealing with these attacks.
My second line was the Apache Mod_status extension. It comes installed by default in AWS Amazon Linux distribution – you just have to enable it. I wrote up another post about that process here. What this extension allowed me to do was pinpoint what site was getting hammered and what urls were the most suspect.
Stop the Attacks!
After I figured out what was making the server go down it was a matter of stopping it from happen. There were some options to consider when handling these issues.
Fix Brute Force on Wp-Admin / Wp-login.php
Let’s look at the first issues; the brute force attacks on wp-admin. The reason it was getting hit was that my login were in their default location, domain.com/wp-admin. There are some options out there to change the location of wp-admin and wp-login.
Option 1: Plugin to hide wp-admin
There are a few WordPress plugins that you can use that hide/move wp-admin. I didn’t try to fix it with a plugin because that is just another thing I have to maintain and worry about becoming compromised and/or unsupported. Also it would mean maintaining database configurations that would compromise my ability to setup local installs of the site that directly mimick my production setup. It isn’t ideal to have to worry with db configurations when trying to replicate the site.
Option2: Rename wp-login.php
Another solution for hiding the login was to change wp-login.php to another file name (wp-login-somethingelse.php) and changing all of the references in core wordpress to that new file name. This means that you would have to change core wordpress files. That change would work at hiding the login and only require code changes. This would solve my ability to setup local environments without database dependencies, but it might compromise my ability to update core wordpress files, which would make it hard to update Core wordpress. Boo
Option3: Restrict access to wp-admin with Apache
What I ended up doing to remedy the issue was to setup a user/password htpasswd authentication using Apache and authorized access in the Vhost setting in Apache. It is a simple htpasswd file that restricts access to wp-admin until you specify the user and password to Apache, once a user logs into apache it can then login to wordpress. This isn’t that inconvenient and it stops scripts from hitting WordPress by stopping them at the htpasswd login. While this could possibly be compromised it is just another barrier making it harder to simply hack it. The reason why I liked this solution best is that my code and repos still remain the same and there aren’t any special considerations when setting up the site for development – the only special situation is on the production server itself.
Here is the code for the Apache Virtual Host Entry
ServerName domain.com ServerAlias www.domain.com DocumentRoot "/var/www/html/WP-website" ErrorLog "/var/log/WP-domain.error.log" CustomLog "/var/log/WP-domain.access.log" common DirectoryIndex index.php # Protect wp-login AuthUserFile /etc/httpd/conf.d/mydomain_htpasswd AuthGroupFile /dev/null AuthName ByPassword AuthType Basic Require valid-user
Problem 2: Restricting Plugin Hacks
Like the brute force attacks on wp-admin, the plugins problems stem from access to areas of WordPress that aren’t important to normal users. In this case the affected area was domain.com/gallery/nggallery/feed/ – While I’m not sure that isn’t be hit by bots, I know it is an area of the site that just shouldn’t be accessed by a normal user. Taking the same play as before I knew I wanted to have Apache handle the restriction – keeping any special setup out of my code. In this case I just wanted to restrict access to the gallery directory using a 403 header response. I added this expression to the Virtual host configuration in Apache.
RedirectMatch 403 ^/gallery/nggallery/(.*)?$
These actions have restored by EC2 to a usage level that keeps the server up and running. It is amazing how a few simple scripts can take down a server – but a little restriction can stop those same scripts. After implementing these two solutions the server has been humming along without issue. . . until some bot wriggles in through another crack! Hopefully this helps!