In my introduction post, I said I would write about topics in order of interest. Securing WordPress blogs from hackers isn’t exactly fun or interesting but it is very necessary in this day and age. Hackers are constantly probing sites on the internet for insecurities. They’re constantly trying to log into WordPress sites with easily guessed passwords (hint: don’t use ‘password’ as your password). Here are some hints on how to secure WordPress blogs from hackers.
If you prefer a video version, check out my first ever YouTube video (!) covering this same content here – https://youtu.be/wKgm_684acM.
When I set this site up, the first 24 hours were pretty quiet. After that, the attacks started ramping up. I decided to take action and lock down access. There are three main things I did to secure this WordPress blog installation and VPS it is hosted on:
- Disable password-based SSH authentication for logins
- Install and enable Fail2Ban
- Install WordPress specific Fail2Ban filters
#1 – Disable password-based SSH authentication
Step 0 – Enable SSH Key Authentication
Before you disable password-based authentication, you need to enable SSH key based authentication. I have posted a SSH key tutorial here – SSH Key Tutorial.
Password-based SSH authentication
SSH stands for secure shell. It is how 99% of Linux/Unix servers on the public internet and private intranets are administered. There are two main methods of logging in with SSH: 1) password and 2) key. Password is pretty straight-forward and is what most people are familiar with. You have a username and password. If you enter the right password for the username, you get in. Hackers are constantly testing common usernames (root, admin, user, guest) with common passwords (password, password1, password123, test, etc.). Further – they aren’t testing just one combination of user/pass at a time, they keep trying passwords until they give up or are banned. I had my VPS for a few weeks before activating austinsnerdythings.com on it and here is a random sample starting a minute after midnight for about six minutes:
$sudo head -n 100 /var/log/auth.log.1
Feb 28 00:01:52 austinsnerdythings.com sshd: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=184.108.40.206 user=root Feb 28 00:01:54 austinsnerdythings.com sshd: Failed password for root from 220.127.116.11 port 45182 ssh2 Feb 28 00:01:54 austinsnerdythings.com sshd: Received disconnect from 18.104.22.168 port 45182:11: Bye Bye [preauth] Feb 28 00:01:54 austinsnerdythings.com sshd: Disconnected from authenticating user root 22.214.171.124 port 45182 [preauth] <snip> Feb 28 00:04:59 austinsnerdythings.com sshd: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=126.96.36.199 user=root Feb 28 00:05:02 austinsnerdythings.com sshd: Failed password for root from 188.8.131.52 port 53437 ssh2 Feb 28 00:05:04 austinsnerdythings.com sshd: Connection closed by authenticating user root 184.108.40.206 port 53437 [preauth] Feb 28 00:06:06 austinsnerdythings.com sshd: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=220.127.116.11 user=root Feb 28 00:06:07 austinsnerdythings.com sshd: Failed password for root from 18.104.22.168 port 37354 ssh2 Feb 28 00:06:08 austinsnerdythings.com sshd: Received disconnect from 22.214.171.124 port 37354:11: Bye Bye [preauth] Feb 28 00:06:08 austinsnerdythings.com sshd: Disconnected from authenticating user root 126.96.36.199 port 37354 [preauth] <snip> Feb 28 00:06:48 austinsnerdythings.com sshd: Received disconnect from 188.8.131.52 port 37056:11: [preauth] Feb 28 00:06:48 austinsnerdythings.com sshd: Disconnected from 184.108.40.206 port 37056 [preauth] Feb 28 00:06:56 austinsnerdythings.com sshd: Connection reset by 220.127.116.11 port 53318 [preauth] Feb 28 00:08:00 austinsnerdythings.com sshd: Received disconnect from 18.104.22.168 port 61081:11: [preauth] Feb 28 00:08:00 austinsnerdythings.com sshd: Disconnected from authenticating user root 22.214.171.124 port 61081 [preauth]
Each login attempt is 3-4 lines, so that’s 10 attempts in 6 minutes. Also notice the repeating IP addresses – 126.96.36.199 tried 4 separate times to log in across 6 minutes!
Hackers try user/pass logins because they’re relatively easy. And they get lucky often enough it is worth it.
Key-based SSH authentication
The other method to logging in with SSH is via public/private key. How this works is you generate a public/private keypair. Then you put the contents of the public key on the server you want to log in to. When logging in, your SSH client says “hello, I am user austin and I have a key to login and here it is”! The public key that’s copied to the remote server looks like this:
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCuzcK6yIyqJabWprjaZZI9mXpVaSewoGZROcYTf/iB6OvJklIYmM/j/YHPWq1fV30QcGPpUBwKFk8DrJNn5bIk3fow67TVC0Wr2tWy7DDweTNUpk7L01MBRhjLG2xpO9RU9F4hDyzAFI4NcrSOb6J9FL6ItrfQS/LZ7H3IrmBGIjp4OooQOhR4iw5KFEdgvNgs8rAaxSl2FziTRrxhISTzkQY0BUMBkUNjsJid4x3rTXJ9UyUDYwN2/WMfzf9aGJdRzPLIiNKsxbDeTzC3vd8TCfFOUJ+hmS8gSOY0vhLS/1wQp91jR10FF4d67z9FTwAyh+o6uKJfmvNpTXIhN [email protected]
And the private key (that should never be shared! this a throwaway key) looks like this:
-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEA5DZd/sSEHr9/Z74KF6t832ggMNc5I6aCm/2BV9HvU2P5tqeZ uw8qU6keLLO8BCCqcmiCjJXT/9ZRxoaRCP8Zde9Aq/ElhmXbbpUuGRLicbt9HYcu VCJmGP2GNhpAXdFlLU+efG3Alqu13Eynyhj47PB/JMj0BCWMASqkAI8h7ORNUbeQ NHiSTPRdXBVEHa5CW/wvqOzH+sHdrclQNCWAWfOn7wRsS78YH2Dw9yK3bD4RKxqO aIQ+UfKOl889/b/5E56I7Yup+VeN2o7PyV7+TOGrx+aKV7ax0ePBbrFjT1mXbQ/r OkOEyFC4r1S5pduxjwDWA6mbHM+v6xl/V65YzwIDAQABAoIBABmDloioMdk6MaVI ktpImuJjQs4TEdlRgWKtOeu2ldot4DoyjLZkIKhPzQbUZV3UxRmbY5USHyyIKoZW fxqRYqhTwlg20qou8xRu60N0YAq1GmzVszFG00FR/tJHpxCWG4iwURi6MIDn26Iw k8W9ev8KeDyFlvprtDZhLQq+9d0FByjLjD6mmeyhHTQXN7f+agUzQjx1pQMcecnV 9OuOqSgav0pmerzWGDWJDYtisIIyByoj1kMyYLKAAvBd2xtwv8rg9iA+TxKnvMCY fFDMeRQ45F+SLEMqARDxeRGCqPZ0hFswLeVs/uYiAyhoY7eBYtvyIlx9b9tAWb3t K+B0TDkCgYEA+a5iYXzTECk7xs6KbwI6Runo5n8J104yGLebaQkfA60LzuidaMqw aPL2DEb9wbHK4oCqwIOXOPY2L4qO8+7VJeMl+g5oxelmHhkTMecjmWmnCO1gmwBq 1mNLEg0nc89GZsUJm24y7FsPk2hspZmEnKpnViFqcZ17PWohWNcZtaUCgYEA6fzk bp1F0xX0IWT2jo62ioAcdaaxluOBB0zoUAC6gQ0P1uN4nkYn1sXKsalSsvahrILZ ud9z9ceaf/OLAgDOjQtWzn0mwhFP98wzb3CDF/83ZqMXJflnuwuPSTgcPqI6kr28 +KMwjbIoZgE38fN+p7I3qKvIkO2yHqPOi2uBkmMCgYAUqwPH0B5kmxUwqs44zDVo w1odImz9HqL0+tXphvDDTCLLGORW1VhvB5WohIPi8cW6pC3+S6ZL982ad9zHgoCw ZzIwldrEb0KdwTOekOSYgW9rRMMXcZxmbMe9EcuvQXwxa6QU8rVSbWNHr4A24RNi KJTvQ0rdZszZ05w5D204ZQKBgQCCS46wged16c2uItig/ZtseHZglVhi24DoHc1n b2BrqGhfkv+BszNQB4gdclpYybmxpJO1S1b5UBMamPWZQfXC2MOX7Fz+yEEtjYo+ zfpSDI4/GyYywTUgFQnPDe28ev3+5KUsF0NcRA727krG8n5ex4Dy7eWbvqDnKvRC 8rSOXQKBgF9ZG5xbV96I9ThMLRSwschlCo9EN3UjC1o3k3b4mzJLfc16c/I/tIdQ oDcMK6KbysVvUF1124vfSOv/V5GmCQZeaT7XgG4WlUcbO6ktdkwCnyE3xnyFnQpB zeeTrkPYBqkJO29lZcPkUNX4uANtjtdNiniew5UCVhrDjMgjnofS -----END RSA PRIVATE KEY-----
As you might imagine, it’s a lot harder to guess that key than it is a password. In fact, cracking a 2048 bit key like the one above would take 300 trillion years with a quantum supercomputer (which doesn’t yet exist)! Source. The universe is 15 billion years old. That means it would require 300 trillion / 15 billion = 20,000 universe lifetimes to crack.
Before you disable password-authentication, you need to be 100% sure that key-based authentication is working or else you will lock yourself out of your server!
To disable password-based authentication, you need to edit
/etc/ssh/sshd_config, find PasswordAuthentication and put no after it. If it is commented out (there is a # at the front of the line) delete the #. It will look like this when finished:
Then you need to restart the SSH daemon (service) for the change to take effect:
sudo systemctl restart ssh.service. Now you password-based SSH authentication has been disabled!
My failed authentication attempts dropped dramatically after disabling password-based SSH authentication. Below is the same general timeframe from the morning of when this post was written:
Mar 13 00:00:24 austinsnerdythings.com sshd: Invalid user ftpuser from 188.8.131.52 port 59060 Mar 13 00:00:24 austinsnerdythings.com sshd: Received disconnect from 184.108.40.206 port 59060:11: Normal Shutdown, Thank you for playing [preauth] Mar 13 00:00:24 austinsnerdythings.com sshd: Disconnected from invalid user ftpuser 220.127.116.11 port 59060 [preauth] Mar 13 00:03:09 austinsnerdythings.com sshd: Received disconnect from 18.104.22.168 port 5402:11: disconnected by user Mar 13 00:03:09 austinsnerdythings.com sshd: Disconnected from user austin 22.214.171.124 port 5402 Mar 13 00:03:09 austinsnerdythings.com sshd: pam_unix(sshd:session): session closed for user austin Mar 13 00:12:33 austinsnerdythings.com sshd: Invalid user postgres from 126.96.36.199 port 46444 Mar 13 00:12:33 austinsnerdythings.com sshd: Received disconnect from 188.8.131.52 port 46444:11: Normal Shutdown, Thank you for playing [preauth] Mar 13 00:12:33 austinsnerdythings.com sshd: Disconnected from invalid user postgres 184.108.40.206 port 46444 [preauth] Mar 13 00:12:44 austinsnerdythings.com sshd: Received disconnect from 220.127.116.11 port 11758:11: [preauth] Mar 13 00:12:44 austinsnerdythings.com sshd: Disconnected from authenticating user root 18.104.22.168 port 11758 [preauth] Mar 13 00:17:40 austinsnerdythings.com sshd: Received disconnect from 22.214.171.124 port 32827:11: [preauth] Mar 13 00:17:40 austinsnerdythings.com sshd: Disconnected from authenticating user root 126.96.36.199 port 32827 [preauth] Mar 13 00:24:51 austinsnerdythings.com sshd: Invalid user postgres from 188.8.131.52 port 33830 Mar 13 00:24:52 austinsnerdythings.com sshd: Received disconnect from 184.108.40.206 port 33830:11: Normal Shutdown, Thank you for playing [preauth] Mar 13 00:24:52 austinsnerdythings.com sshd: Disconnected from invalid user postgres 220.127.116.11 port 33830 [preauth]
Most of these are just disconnects. The hackers see that my server is not accepting passwords and they just disconnect – they don’t even try to log in.
#2 – Install Fail2Ban
Fail2Ban is a helpful tool that monitors various logs and if it sees too many failed attempts, it will issue a ban on the offending IP address.
It is simple enough to install. First, update your package cache. On Ubuntu/Debian, this is done with apt:
sudo apt update.
Then install fail2ban:
sudo apt install -y fail2ban. This automatically enables Fail2ban so that it starts on boot. It has a bunch of out-of-the-box rules and will handle many services without any additional configuration. This is what my Fail2ban log looks like as of right now. This is all SSH bans. Notice that the duration is increasing for IP 18.104.22.168. The default ban duration is 10 minutes and I have it configured to double (plus some randomness) every extra attempt.
#3 – Add WordPress specific Fail2ban jails and plugin
Attempts to log into WordPress look like normal web traffic in web logs. Failed logins aren’t recorded specifically. We can change that by adding a plugin to WordPress that writes to /var/log/auth.log for a number of activities. Fail2ban monitors /var/log/auth.log for failed logins so it can act appropriately. I am using WP-Fail2Ban-Redux which does exactly what it says and without any nonsense. To finish the install, I copied the files from wp-content/plugins/wp-fail2ban-redux/config/filters and /jail to my fail2ban filter.d/ and jail.d/ folders:
cp /var/www/wordpress/wp-content/plugins/wp-fail2ban-redux/config/filters/wordpress-hard.conf /etc/fail2ban/filter.d/wordpress-hard.conf cp /var/www/wordpress/wp-content/plugins/wp-fail2ban-redux/config/filters/wordpress-soft.conf /etc/fail2ban/filter.d/wordpress-soft.conf cp /var/www/wordpress/wp-content/plugins/wp-fail2ban-redux/config/jail/wordpress.conf /etc/fail2ban/jail.d/wordpress.conf
Restart fail2ban so the changes take effect:
sudo systemctl restart fail2ban
View all the bans in your log! Congrats, you’ve now applied some top notch security practices to your blog.
#4 – ALWAYS KEEP YOUR WORDPRESS INSTALL UPDATED
That is the entirety of #4.
#5 – To disable XMLRPC or not, that is the question
I haven’t disabled XML-RPC yet. XML-RPC is a way to programmatically interact with WordPress blogs. Hackers can use it to rapidly try user/password combinations and other things like that. Installing the WordPress specific Fail2Ban components will effectively ban offenders while still allowing access to the underlying services.
It isn’t too hard to make these three changes to secure your WordPress blog and doing so will increase the security drastically. If you would like assistance doing this on your site, please use the contact form to get in touch with me. Lastly, always keep your WordPress install up to date. Every so often, security researchers find holes in the base WordPress code. Automatic updates will prevent your site from being a target.