OK, I know this is somewhat late to come to the party, but I recently had to implement some rules to protect servers against wordpress wp-login brute force attacks. Although the attacks were somewhat distributed, many clients were sending hundreds of login requests for wp-login.php.
Most of the solutions I have seen overlook the fact that a login attempt uses the “POST” REQUEST_METHOD – so anyone who simply loads the login page a few times without even submitting any credentials is going to get blocked, which is probably going to result in customers/visitors complaining.
They also seem to rely on the USER persistent collection for storing the blocking flag variable (flag_b, below). This is going to cause namespace collisions without SecWebAppId directives in the <VirtualHost hostname> section of the apache configuration file. Essentially without application namespaces everyone will share the same USER application namespace – which basically means that once one person gets locked out, everyone does. There really is no reason not to use the simple IP collection for both variables used in the rules (count_a and flag_b).
The rules work on the basis that WordPress responds with a http status code 200 only when the login fails. A successful login is responded to with a status code 302 (redirect) which redirects the user to the wp-admin directory.
The rules are as follows:
# The following line is only required once in your rules - you may get away without it
# SecRule SERVER_NAME "theflyingschool" phase:2,pass,ctl:debugLogLevel=5,id:690019
SecRule ip:FLAG_B "@gt 0" "phase:2,deny,status:403,log,msg:'WAF Rules : WordPress, 6 auth failures in 90 seconds - blocked for 5 minutes',id:690020"
SecRule RESPONSE_STATUS "^302" "phase:5,t:none,nolog,pass,setvar:ip.count_a=0,id:690021"
SecRule RESPONSE_STATUS "^200" "phase:5,chain,t:none,nolog,pass,id:690022"
This will keep count of failed logins per IP address in count_a. Each 90 seconds count_a is reduced in value by the integer 6. If count_a every exceeds 5 then flag_b is set to 1. For the next 300 seconds requests from the same IP received by the server will be responded to with a 403 – after which the counting begins again!
Don’t forget – for these kinds of rules to work (that rely on persistent storage) you must have configured, at least, the SecDataDir directive in your apache configuration file, and ensured that the web server has permissions to read/write to the path you provide!