Wordpress and fail2ban with Apache

Hackers suck. As a web hosting provider with 40+ people running Wordpress that don’t always keep it up to date, hackers are always trying to get in. Last month they were pretty successful, so I tightened some things down a little bit further. I don’t manage any of the PHP files, so coordinating things among customers is a lot harder than it could be, and I won’t be able to get them to standardize on plugins (e.g. a way to log failed logins to a consistent place).

First up was trying to get everyone upgraded to the newest version which keeps most of the automated tools out, but still leaves everyone exposed to brute-force password guessing attempts.

One can go the route of configuring Wordpress plugins to handle everything, but because I use fail2ban to protect SSH access I figured it wouldn’t be too bad to get some help there. Turns out, I was right!

I added two new jails, one for wp-login.php requests and one for xmlrpc.php requests:

[wordpress-xmlrpc]

enabled  = true
filter   = wordpress-xmlrpc
action   = iptables[name=WordPressXMLRPC, port=http, protocol=tcp]
logpath  = /var/log/apache2/access_log
maxretry = 5

[wordpress-login]

enabled  = true
filter   = wordpress-login
action   = iptables[name=WordPressLogin, port=http, protocol=tcp]
logpath  = /var/log/apache2/access_log
maxretry = 5

And the filter.d files to go along with it:

# wordpress-login.conf
[INCLUDES]
before = common.conf

[Definition]
_daemon = wordpress
failregex = ^[a-zA-Z0-9\.]+ <HOST> .*POST.*/wp-login\.php HTTP.*
ignoreregex =
# wordpress-xmlrpc.conf
[INCLUDES]
before = common.conf

[Definition]
_daemon = wordpress
failregex = ^[a-zA-Z0-9\.]+ <HOST> .*/xmlrpc\.php.*
ignoreregex =

Are these a little overkill? Definitely. From my global config for limits, if someone POSTS to wp-login.php 5 times in a 10 minute period, they get firewalled off for a day. If they hit xmlrpc.php 5 times in a 10 minute period, they get firewalled off for a day.

That said, I don’t image any regular usage from anyone will run into these limits. If they do, my hosting customers know how to contact me. After ~3 hours of running this, hackers are getting slowed down already:

Chain fail2ban-WordPressLogin (1 references)
target     prot opt source               destination
REJECT     all  --  202.177.25.123       0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  177.70.21.29         0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  212.82.217.9         0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  141.105.66.179       0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  88.190.45.37         0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  216.222.148.52       0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  80.97.64.148         0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  94.23.28.193         0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  91.123.193.54        0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  193.109.248.66       0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  108.162.216.31       0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  50.22.232.106        0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  37.98.197.5          0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  117.18.73.66         0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  108.163.165.66       0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  95.163.121.138       0.0.0.0/0            reject-with icmp-port-unreachable
RETURN     all  --  0.0.0.0/0            0.0.0.0/0

Chain fail2ban-WordPressXMLRPC (1 references)
target     prot opt source               destination
REJECT     all  --  155.109.35.51        0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  89.248.168.164       0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  117.18.73.66         0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  173.245.62.107       0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  198.204.243.115      0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  80.82.78.57          0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  93.174.93.204        0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  89.248.174.101       0.0.0.0/0            reject-with icmp-port-unreachable
RETURN     all  --  0.0.0.0/0            0.0.0.0/0

Some gotchas specific to my setup:

  • I didn’t have multiport support for iptables rules compiled into my kernel which when using iptables-multi in jails.conf gave the cryptic error message on restarting iptables: “iptables: No chain/target/match by that name.” I’m fine with just blocking http.
  • I use multiple vhosts going to the same log file with a standard, but less common log format. This meant adding the “[a-zA-Z0-9.]+”” regex before the HOST named capture so that the request vhost wasn’t used for blocking. Hint: Use the “fail2ban-regex” tool to test out rules on a log file!
  • Make sure your actions have separate names. When you name them the same thing, you won’t get what you are expecting.

comments powered by Disqus