Brute force login: Simple protection techniques with the ModSecurity WAF
WAF Published on •5 mins Last updatedThe web-based login to your application is a juicy target for hackers. And once they get past the login, they can cause you some serious pain.
If you have a WAF (Web Application Firewall), though, the problem is pretty easy to mitigate — even when it's a distributed attack.
Am I in the right place?
If you are protecting the login page for a web-facing application, you should definitely also consider the following techniques (listed in order of strength):
- Two-factor authentication (2FA)
- Double login protection
- Honey Pot defense
- Account lock-out policy settings*
- User education? — Yes, I am joking...
*A great way to carry out a denial of service attack on your own business!
I really like double login protection, especially when proper 2FA is not an option. Automated attack bots get very confused by any kind of double login. And your average hacker will quickly move on to easier targets.
However, a persistent hacker might well get through the first login, and then try to brute force the second login.
'Brute force attack' sounds so unthreatening - why should I be worried?
Hackers can quickly scan the internet for vulnerable login forms. Simply moving or disguising your login form (obfuscation) won't help much. Once a hacker has found a potentially vulnerable form, they will quickly try to figure out if you have a lock-out policy in place.
Assuming they don't get locked out after the first few attempts, the hacker can use automated tools to check millions of common usernames and passwords — and they will gain access to your system quickly.
Don't believe me? Take a look at this excellent blog by Chris Castiglione, which shows just how easy it is to write a brute force login script.
What's with the WAF?
A WAF can see all of the traffic hitting your servers, so it's the ideal place to implement the following brute force protection techniques:
- Block brute force login attempts — from a single source IP
- Block brute force login attempts — for a single username
- Block brute force login attempts — for a single password
Obviously blocking by IP address is only useful if you can actually see the hacker's IP address. Make sure you don't have any NAT firewalls or Proxy servers obscuring this information.
Also be aware that when using just a username or password to identify a hacker, they can only be blocked for a short time — otherwise your valid users won't be able to log in!
OK, so I need protection - what should I do?
This is going to get technical! I'm making the bold assumption that you already know how to set up a functional WAF based on ModSecurity — or that you already use a Loadbalancer.org appliance which includes an industrial-strength WAF.
Either way you'll want to test your WAF thoroughly and customize it for your application before you try it in a live environment!
# Global defaults
SecAction phase:1,nolog,pass,initcol:IP=%{REMOTE_ADDR},id:5000132
# Always collect IP address information for all users.
# Block this IP address > IF ip:bf_block flag has already been set.
SecRule ip:bf_block "@gt 0" \
"block,status:401,log,setvar:tx.anomaly_score=+%{tx.critical_anomaly_score},setvar:tx.%{rule.id}-CustomRule,msg:'IP ADDRESS BLOCKED for 10 minutes, more than 10 login attempts in 3 minutes.',id:5000133"
# Block this Username > IF user:bf_block flag has already been set.
SecRule user:bf_block "@gt 0" \
"block,status:401,log,setvar:tx.anomaly_score=+%{tx.critical_anomaly_score},setvar:tx.%{rule.id}-CustomRule,msg:'USER BLOCKED for 10 minutes, more than 2 login attempts in 3 minutes.',id:5000140"
The section above ensures that any malicious source IPs or suspicious usernames are blocked globally. But we only want to trigger block events on the specific login page:
#Only action brute force rule for the actual login.php page
<Location /test2/login.php>
# Setup Tracking.
# On a successful login, a 302 redirect is performed > So reset counter for this IP.
SecRule RESPONSE_STATUS "^302" "phase:5,t:none,nolog,pass,setvar:ip.bf_counter=0,id:5000135"
# 200 response indicates login failed > so count failed attempts - but reset after 3 minutes
SecRule RESPONSE_STATUS "^200" "phase:5,chain,t:none,nolog,pass,setvar:ip.bf_counter=+1,deprecatevar:ip.bf_counter=1/180,id:5000136"
For this particular application a 302 response indicates a succesful login, and 200 indicates a failure (which is the exact opposite of WordPress!). Keep track for a maximum of 3 minutes.
# IF > 10 failures then set the block flag for this IP address
SecRule ip:bf_counter "@gt 10" "t:none,setvar:ip.bf_block=1,expirevar:ip.bf_block=600,setvar:ip.bf_counter=0"
This is the important bit. If you count more than 10 failed login attempts in 3 minutes, then set the bf_block counter for this IP address for 10 minutes.
# What if they are brute forcing the USERNAME from different IPs?
SecAction phase:2,nolog,pass,initcol:USER=%{ARGS.user},id:5000137
Configure a new global counter for username (we are simply looking for the value of a field in the header from a GET or POST called USER).
# If the user logs in succesfuly, then clear the counters.
SecRule RESPONSE_STATUS "^302" "phase:5,t:none,nolog,pass,setvar:user.bf_counter=0,id:5000138"
# Count each unique user failed login over a 3 minute period:
SecRule RESPONSE_STATUS "^200" "phase:5,chain,t:none,nolog,pass,setvar:user.bf_counter=+1,deprecatevar:user.bf_counter=1/180,id:5000139"
This is exactly the same kind of structure as before, but now allows you to block both the username and the source IP address for 10 minutes:
# Block the source IP as before but also independently block the USERNAME
SecRule user:bf_counter "@gt 2" "t:none,setvar:ip.bf_block=1,expirevar:ip.bf_block=600,setvar:user.bf_block=1,expirevar:user.bf_block=600,setvar:user.bf_counter=0"
</Location>
This ensures that even if the hacker is cycling through source IPs they will still get blocked if they try to brute force a username.
I'll leave you with an exercise: how would you stop a brute force attack on a single common password against multple usernames?
Hint: initcol:RESOURCE=%{ARGS.password}
Thanks, but what if I'm scared of that configuration file?
Using a WAF to stop a brute force login is complex - but it's also kind of simple!
Your aim is to frustrate the hacker enough so that they move on to an easier target. You can't completely secure any system, but a well-configured WAF is a great defensive weapon in the fight.
At Loadbalancer.org we understand that security can be a major headache, and we want to help eliminate the anxiety it causes. That's why we offer our partners and customers - people like Metaswitch - full consultancy around protecting their applications. Peace of mind should be part of the package when it comes to your load balancing solution.
Thoughts? Questions? Objections? Let me know below.