Blocking WordPress Brute Force Attacks against wp-login.php

We had a support request recently that indicated a server was under heavy load due to a wordpress wp-login.php brute force login attack. This attack was impacting 3 customer servers from more than 500 different IP addresses. We needed a solution fast.

Others on the internet have reported tens of thousands of unique IPs involved in this attack.

There is currently a significant attack being launched at a large number of WordPress blogs across the Internet. The attacker is brute force attacking the WordPress administrative portals, using the username “admin” and trying thousands of passwords. It appears a botnet is being used to launch the attack and more than tens of thousands of unique IP addresses have been recorded attempting to hack WordPress installs.[1]

mod_security seemed like the perfect fit for this type of problem, and we came across a great blog post: http://www.frameloss.org/2011/07/29/stopping-brute-force-logins-against-wordpress/.

A few minor tweaks were required based on different mod_security versions, this seemed to work unless the attacker is only using a single IP address every few hours:

[code]
<IfModule mod_security2.c>
# This has to be global, cannot exist within a directory or location clause . . .
SecAction phase:1,nolog,pass,initcol:ip=%{REMOTE_ADDR},initcol:user=%{REMOTE_ADDR}
<Location /wp-login.php>
# Setup brute force detection.

# React if block flag has been set.
SecRule user:bf_block "@gt 0" "deny,status:401,log,msg:’ip address blocked for 5 minutes, more than 15 login attempts in 3 minutes.’"

# Setup Tracking. On a successful login, a 302 redirect is performed, a 200 indicates login failed.
SecRule RESPONSE_STATUS "^302" "phase:5,t:none,nolog,pass,setvar:ip.bf_counter=0"
SecRule RESPONSE_STATUS "^200" "phase:5,chain,t:none,nolog,pass,setvar:ip.bf_counter=+1,deprecatevar:ip.bf_counter=1/180"
SecRule ip:bf_counter "@gt 15" "t:none,setvar:user.bf_block=1,expirevar:user.bf_block=300,setvar:ip.bf_counter=0"
</location>
</IfModule>
[/code]

If you receive complaints about ModSecurity: No action id present within the rule, then you may need a modified ruleset as shown below. cPanel has a post about this change here:
[code]
<IfModule mod_security2.c>
# This has to be global, cannot exist within a directory or location clause . . .
SecAction phase:1,nolog,pass,initcol:ip=%{REMOTE_ADDR},initcol:user=%{REMOTE_ADDR},id:10011
<Location /wp-login.php>
# Setup brute force detection.

# React if block flag has been set.
SecRule user:bf_block "@gt 0" "deny,status:401,log,msg:’ip address blocked for 5 minutes, more than 15 login attempts in 3 minutes.’,id:10011"

# Setup Tracking. On a successful login, a 302 redirect is performed, a 200 indicates login failed.
SecRule RESPONSE_STATUS "^302" "phase:5,t:none,nolog,pass,setvar:ip.bf_counter=0,id:10012"
SecRule RESPONSE_STATUS "^200" "phase:5,chain,t:none,nolog,pass,setvar:ip.bf_counter=+1,deprecatevar:ip.bf_counter=1/180,id:10013"
SecRule ip:bf_counter "@gt 15" "t:none,setvar:user.bf_block=1,expirevar:user.bf_block=300,setvar:ip.bf_counter=0"
</location>
</IfModule>

[/code]

Update 4/11/2013
After seeing a huge surge in search traffic to this page, and reviewing a thread over at webhostingtalk.com, it seems the attacker has adjusted it’s pattern to render this mod_security method ineffective at times.

A few additional options

Patrick on webhostingtalk.com suggested a good solution:

[code]
Edit /usr/local/apache/conf/httpd.conf and add the following near the other <Files></Files> lines:

<Files ~ "^wp-login.php">
Order allow,deny
Deny from all

Satisfy All
</Files>
ErrorDocument 403 "Not acceptable"

Restart Apache. It’s not a fix, but if your server is going down because of the attacks it’s better than nothing. I’m still trying to come up with a better solution than flat out blocking…
[/code]

Cloudflare has posted a solution using thier DDoS protection services (this is also covered in their free plan).

[1]: http://blog.cloudflare.com/patching-the-internet-fixing-the-wordpress-br