Received an site down alert this morning and discovered that one of a site was being password brunt forced with log entries below:-
104.245.97.218 - - [14/Apr/2015:10:10:26 +0800] "POST /xmlrpc.php HTTP/1.0" 200 394 "-" "Mozilla/4.0 (compatible: MSIE 7.0; Windows NT 6.0)"
104.245.97.218 - - [14/Apr/2015:10:10:27 +0800] "POST /xmlrpc.php HTTP/1.0" 200 394 "-" "Mozilla/4.0 (compatible: MSIE 7.0; Windows NT 6.0)"
104.245.97.218 - - [14/Apr/2015:10:10:28 +0800] "POST /xmlrpc.php HTTP/1.0" 200 394 "-" "Mozilla/4.0 (compatible: MSIE 7.0; Windows NT 6.0)"
104.245.97.218 - - [14/Apr/2015:10:10:28 +0800] "POST /xmlrpc.php HTTP/1.0" 200 394 "-" "Mozilla/4.0 (compatible: MSIE 7.0; Windows NT 6.0)"
104.245.97.218 - - [14/Apr/2015:10:10:29 +0800] "POST /xmlrpc.php HTTP/1.0" 200 394 "-" "Mozilla/4.0 (compatible: MSIE 7.0; Windows NT 6.0)"
104.245.97.218 - - [14/Apr/2015:10:10:30 +0800] "POST /xmlrpc.php HTTP/1.0" 200 394 "-" "Mozilla/4.0 (compatible: MSIE 7.0; Windows NT 6.0)"
104.245.97.218 - - [14/Apr/2015:10:10:30 +0800] "POST /xmlrpc.php HTTP/1.0" 200 394 "-" "Mozilla/4.0 (compatible: MSIE 7.0; Windows NT 6.0)"
104.245.97.218 - - [14/Apr/2015:10:10:31 +0800] "POST /xmlrpc.php HTTP/1.0" 200 394 "-" "Mozilla/4.0 (compatible: MSIE 7.0; Windows NT 6.0)"
Googling the solution and found fail2ban is the most suitable tool to be used in this case.
This is the brief introduction on fail2ban home page:-
"Fail2ban scans log files (e.g. /var/log/apache/error_log) and bans IPs that show the malicious signs -- too many password failures, seeking for exploits, etc. Generally Fail2Ban is then used to update firewall rules to reject the IP addresses for a specified amount of time, although any arbitrary other action (e.g. sending an email) could also be configured. Out of the box Fail2Ban comes with filters for various services (apache, courier, ssh, etc).
Fail2Ban is able to reduce the rate of incorrect authentications attempts however it cannot eliminate the risk that weak authentication presents. Configure services to use only two factor or public/private authentication mechanisms if you really want to protect services."
Configuration to ban POST request at abnormal rate
Add this to jail.conf file (usually in /etc/fail2ban/)
[apache-post]
enabled = true
filter = apache-post
action = iptables[name=httpd, port=80, protocol=tcp]
sendmail-whois[name=post_block, dest=email@example.com]
logpath = /var/log/httpd/access_log
findtime = 10
bantime = 86400
maxretry = 4
The above configure will block IP with 4 or more HTTP POST attempts in 10 sec for 183600(1 day)
Now create apache-post.conf in filter.d directory with following lines
# Fail2Ban configuration file
#
#
# $Revision: 1 $
#
[Definition]
# Option: failregex
# Notes.: Regexp to catch known spambots and software alike. Please verify
# that it is your intent to block IPs which were driven by
# abovementioned bots.
# Values: TEXT
#
failregex = ^<HOST> -.*"POST.*
# Option: ignoreregex
# Notes.: regex to ignore. If this regex matches, the line is ignored.
# Values: TEXT
#
ignoreregex =
Gotcha/Note
- *"POST.* -> make sure it is '"'
- To test the configuration, use curl with POST - curl --data "param1=value1¶m2=value2" http://sitename.com/wp-admin. Repeat the command 5 times in 10 sec.
- In Access Log, we have:-
162.243.18.229 - - [14/Apr/2015:13:47:53 +0800] "POST /wp-admin HTTP/1.1" 301 240 "-" "curl/7.37.1"
162.243.18.229 - - [14/Apr/2015:13:47:55 +0800] "POST /wp-admin HTTP/1.1" 301 240 "-" "curl/7.37.1"
162.243.18.229 - - [14/Apr/2015:13:47:56 +0800] "POST /wp-admin HTTP/1.1" 301 240 "-" "curl/7.37.1"
- The IP is now blocked in IPTABLES
num target prot opt source destination1 REJECT all -- 162.243.18.229 0.0.0.0/0 reject-with icmp-port-unreachable2 RETURN all -- 0.0.0.0/0 0.0.0.0/0
- Change http.conf LogFormat if your sites behind proxy or loadbalancer, in order to get true client IP
A default logging configuration in your httpd.conf looks like this:
1 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" combined |
2 | CustomLog log/acces_log combined |
There are several changes you are going to want to make to the default format in order to log the X-Forwarded-For client ip address or the real client ip address if the X-Forwarded-For header does not exist. Those changes are below:
1 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined |
2 | LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" proxy |
3 | SetEnvIf X-Forwarded-For "^.*\..*\..*\..*" forwarded |
4 | CustomLog "logs/access_log" combined env =!forwarded |
5 | CustomLog "logs/access_log" proxy env =forwarded |
You may also want to consider SecSign real two-factor authentication (2FA)plugin for Wordpress