Protecting WordPress with Fail2Ban

When you host a WordPress site, you should consider that you are pretty much opening a backdoor to your server. It’s not quite that simple, but, as one of the most widely-used Blogging and “CMS” platforms, it is regular target for hackers, and when you consider the vast ecosystem of 3rd party Plugins and Themes (one of the main driving points of it’s popularity), hackers have a massive surface area to attack. Because of this, it is important to do what you can to protect WordPress installations from exploit and abuse. In this post I am not going to go over securing the code that runs on WordPress, but I am going to mention two things you can do using Fail2Ban to protect against unauthorized logins, and abuse of xmlrpc.php.

First, a very brief explanation of fail2ban:

Fail2ban is an intrusion prevention software framework which protects computer servers from brute-force attacks. Written in the Python programming language, it is able to run on POSIX systems that have an interface to a packet-control system or firewall installed locally, for example, iptables or TCP Wrapper.

Or, more generically: It watches configured logs, and triggers configured actions when configured patterns are detected.

Protecting xmlrpc.php

The xmlrpc.php script in WordPress handles inter-blog communications (think, trackbacks). In previous versions of WordPress, there was a security vulnerability in this script, which has since been patched. However, just because it no longer contains a known vulnerability does not mean we can. Tin-foil hats would say that there may be a 0-day we do not know about. I am not sure about that (“don’t know” and “not sure” are about the same) but I do know that one of our servers experienced a large volume of traffic on this endpoint, and the resulting resource exhaustion nearly took our site off-line. I did not get a chance to do any payload inspection, but I assume the attacker was either trying to use the previous exploits, or was successfully executing a DoS attack.

Use of xmlrpc.php is not a volume thing. Typical usage results from another blog linking to your blog, which triggers a trackback. In so far as volume is concerned, our attack was seeing “a lot” of traffic to this endpoint, every second. It is safe to say that this kind of traffic would not result from common behavior patterns, and can be safely blocked. “Large volume” traffic to this endpoint is, again, either actively attacking, or exploring for an attack, so blocking these high-volume users is something we can do without remorse. And, even better, it is easy.

After installing fail2ban find the directory where jail.conf exists, this will put you in the correct place for the rest of the game.

Create a file in ./filter.d/ named apache-xmlrpc.conf, with the following content:

1

2

3

[Definition]

failregex=^<HOST>.*POST.*xmlrpc\.php.*

ignoreregex=

Pretty straight forward. This filter simply matches log lines that indicate POST requests to a file named xmlrpc.php, and stores the part of the line that would contain the HOST (the user executing the request). Easy.

Next, in jail.local (create this next to jail.conf, if it does not exist already) add the following jail to the end:

If you are using a version of fail2ban greater than or equal to version 9, or if you have added the [DEFAULT] shortcuts outlined here, you can limit the above to just:

1

2

[apache-xmlrpc]

logpath=/var/www/clients/client*/web*/log/access.log

Now, my logpath may not be the same as yours. I am using ISPConfig, and as such, the logpath indicates to look in every access log for every domain for every client. If you have a single access.log, you can point to that here (say, /etc/httpd/logs/access_log), or if you need to specify multiple logs individually, you can do it like this:

1

2

3

logpath=/var/www/vhosts/site1/log/access.log

/var/www/vhosts/site3/log/access.log

/var/www/vhosts/site4/log/access.log

Although, the above could also be shortened to:

1

logpath=/var/www/vhosts/site*/log/access.log

Which is basically what I did.

And that is it. You can customize maxretry, and the specific bantime, etc, but this should work immediately once you restart the application:

1

sudo service fail2ban restart

At which point, according to the above jail, you will start banning people who hit the xmlrpc.php endpoint of the configured Access Logs 3 or more times in 10 minutes, for 1 hour.

Protecting WP-Login

One of the other “low-hanging” fruits available to us while protecting WordPress, is user-authentication. Obviously, WordPress handles logging in internally, fantastic, but by default it will let you attempt to login with no limit, and because of this, it is possible to enumerate usernames (detect what usernames exist) as well as brute-force passwords. There are plugins for both, but anything that uses WordPress it’s self do do the blocking is not as efficient as it an be, and if you rely on cookies, or sessions, or headers, etc, to identify the user, it can be spoofed. Much better: block them at the port, and not at the code.

First, we need to log the Login Attempts and Enumeration Attempts, then we need to set up a fail2ban jail that handles these logs. Luckily, there is a plugin that handles the logging, and even comes with the requisite fail2ban filter, WP Fail2Ban. Setup and install is simple: Install the Plugin, and copy the jail and filters.

Add the Fail Configuration to jail.local:

1

2

3

4

5

[wordpress]

enabled=true

port=http,https

filter=wordpress

logpath=/var/log/auth.log

And add the filter to ./filter.d/wordpress.conf:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

# Fail2Ban configuration file

#

# Author: Charles Lecklider

#

[INCLUDES]

# Read common prefixes. If any customization available -- read them from

# common.local

before=common.conf

[Definition]

_daemon=wordpress

# Option: failregex

# Notes.: regex to match the password failures messages in the logfile. The

# host must be matched by a group named "host". The tag "<HOST>" can

# be used for standard IP/hostname matching and is only an alias for

# (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)

# Values: TEXT

#

failregex=^%(__prefix_line)sAuthentication failure for.*from<HOST>$

^%(__prefix_line)sBlocked authentication attempt for.*from<HOST>$

^%(__prefix_line)sBlocked user enumeration attempt from<HOST>$

^%(__prefix_line)sPingback requested from<HOST>$

# Option: ignoreregex

# Notes.: regex to ignore. If this regex matches, the line is ignored.

# Values: TEXT

#

ignoreregex=

And restart:

1

sudo service fail2ban restart

It is worth noting that, on my server, I had to change the jail’s logpath to point to the correct log:

1

logpath=/var/log/secure

Now you will be blocking Clients that hit the logs. You can configure what type of events to log, and where to, by setting constants in your wp-config.php. You can see the options available in the plugin’s FAQ. I have to set my logger to LOG_AUTHPRIV, and I activated enumeration:

1

2

3

4

<?php

define('WP_FAIL2BAN_BLOCK_USER_ENUMERATION',true);

define('WP_FAIL2BAN_AUTH_LOG',LOG_AUTHPRIV);

Note, changing these settings, or even adding this plugin to more sites on the same server, does not require restarting fail2ban. Once the jail is configured, it will block and clients that show up in the logs matching the provided patterns.

Done!

And there you go. With the two above fail2ban jails, you have helped protect yourself against to different attacks, and it was all pretty simple too! To be sure, this shouldn’t be the entirety of your WordPress security setup. Be sure to check out the fairly extensive list of ways you can harden WordPress and protect against code insertion and exploitation at the Codex, and check back here in the future, I have a few more tricks up my sleeve to share.