Preventing Brute Force Password Guessing Attacks with APM - Part 4
F5er and DevCentral community member ystephie is back with another great solution (check out her first solution here: BIG-IP APM Customized Logon Page), this time tackling brute force attacks utilizing customizations with the BIG-IP Access Policy Manager. This solution requires BIG-IP 10.2.2 Hotfix 1 or later.
Introduction
Exposing applications or services to the Internet opens inherent security risks. BIG-IP Access Policy Manager (APM) provides edge authentication and access control services for applications, BIG-IP Edge Gateway provides secure SSL VPN services, and BIG-IP Application Security Manager (ASM) provides protection against a variety of attacks. In this series of APM deployment examples, we cover a couple techniques for protecting against brute force password-guessing attacks.
In our first example (Part 1), we walked through the process of including a CAPTCHA on the APM logon page via a web service (Google reCAPTCHA project), to provide some protection against script based or other automated attacks.
In our second example (Part 2), we modified our configuration to only display the CAPTCHA challenge if a user has previously failed authentication (by checking the user’s badPwdCount attribute from Active Directory).
In our third example (Part 3), we kept track of authentication failures ourselves on box (through use of an iRules Session Table). This removed the dependency on Active Directory and solved the issue around tracking failures for invalid users (providing the same external behavior, whether a user is valid or invalid).
In this final example, we’ll modify our policy to also temporarily lock out users from attempting login to APM after failing authentication too many times. This can further protect against both automated and manual credentials guessing attacks, and also prevent intentional or unintentional internal account lockout (for example, an Active Directory domain lockout from too many failures).
Locking Out Users After Failed Logon Attempts
Our previous example allowed the user to try authentication combined with the CAPTCHA challenge as many times as they liked, but that opens the possibility of an attacker locking a legitimate user’s account within an internal domain authentication server (e.g. Active Directory). Also allows for the possibility of a manual password guessing attack (by those really good at entering CAPTCHA challenges!). To prevent these possibilities, we can set a lockout variable in our example iRules and set the reset timer to the time we’d like to temporarily lock out a user (after a defined number of authentication failures). As with Part 3, we use iRules to create a Session Table that keeps track of all the usernames and number of failed authentication attempts for each user. Before we begin, please start with the access policy we created in Part 3.
Policy Additions
To incorporate lockout functionality, we simply need to add some branch rules and edit a bit of the iRule. The changes are described in each section below. The overall access policy isn’t changing much. The only modifications I’ve made are in the branch rules and some of the endings.
The places we need to change are where a user’s information is first accessed or updated. This happens in more than five places. But, luckily with our use of macros, we only need to add branch rules in three places.
Macro: AD Auth & iRule
Since the Empty block (compares badpwdcount with maxtries) in the AD Auth and iRule macro checks the updated badpwdcount of the user against our CAPTCHA challenge variable (maxtries), this is a place we also want to check the badpwdcount against the lockout variable. Please see the modified iRule below (updates in red). We will add a new rule branch that compares the bad password count with a preset variable, lockout. Lockout is defined in the iRule and it is very easy to customize how many authentication failures should be allowed before temporarily locking them out.
when ACCESS_POLICY_AGENT_EVENT {# Maxtries is a variable that sets how many times you want the regular logon page to show before showing the captcha.
# Timeout is a variable that sets how long a user entry will persist in the session table. Lockout is a variable that sets how
# many tries a user gets before getting locked out. This is the only place you need to change these variables.
set maxtries 2
set timeout 900
set lockout 5
set user [ACCESS::session data get session.logon.last.username]ACCESS::session data set session.custom.maxtries $maxtriesACCESS::session data set session.custom.lockout $lockoutif {[ACCESS::policy agent_id] eq "set session vars"} {ACCESS::session data set session.custom.badpwdcount [table lookup -notouch $user]}if {[ACCESS::policy agent_id] eq "session lookups"} {set badpwd [table lookup $user]if {$badpwd == {}} {
set badpwd [table add $user 0 $timeout]}ACCESS::session data set session.custom.badpwdcount $badpwd# log local0. "$user has this number of incorrect logons(lookups): $badpwd"
}if {[ACCESS::policy agent_id] eq "badpwd"} {table incr $user}if {[ACCESS::policy agent_id] eq "goodpwd"} {table delete $user
}
Branch Rule #1:
Name: | Expression: |
Lockout | expr { [mcget {session.custom.badpwdcount}] >= [mcget {session.custom.lockout}]} |
Add an additional Terminal called “Lockout.” Change the ending of the branch we created to “Lockout.” This way, we can differentiate this from all the other endings.
Macro: Captcha Auth with AD Auth
After the Captcha Logon Page, add a new iRule Event with ID “set session vars” with Branch Rule # 1 and Branch Rule # 2. Repeat the above process to add the lockout ending.
Branch Rule #1:
Name: | Expression: |
Lockout | expr { [mcget {session.custom.badpwdcount}] >= [mcget {session.custom.lockout}]} |
Branch Rule #2:
Name: | Expression: |
Allow user to continue | expr { [mcget {session.custom.badpwdcount}] < [mcget {session.custom.lockout}]} |
The reason we’re not using session lookups as the event ID is because that in session lookups, we want to “touch” the user - reset the timeout back to the max value defined as timeout. With our new set session vars event ID, we will add an optional flag (-notouch) that tells the table not to reset the user’s timer. Without the -notouch flag for this event, even if our user is patiently waiting for the 15 minute (in our example) lockout timeout to expire, but tries to connect again before the timer expires, the timer gets reset and the user would have to wait for ANOTHER 15 minutes.
when ACCESS_POLICY_AGENT_EVENT {# Maxtries is a variable that sets how many times you want the regular logon page
# to show before showing the captcha. Timeout is a variable that sets how long a
# user entry will persist in the session table. Lockout is a variable that sets
# how many tries a user gets before getting locked out. This is the only place
# you need to change these variables.
set maxtries 2
set timeout 900
set lockout 5
set user [ACCESS::session data get session.logon.last.username]ACCESS::session data set session.custom.maxtries $maxtriesACCESS::session data set session.custom.lockout $lockoutif {[ACCESS::policy agent_id] eq "set session vars"} {ACCESS::session data set session.custom.badpwdcount [table lookup -notouch $user]}…}
Macro: Verify with Captcha Page
Add the lockout ending and attach it to the Lockout branch of AD Auth and iRule.
Macro: iRule Lookups and Auth
Add an iRule Event with ID, set session vars, right after “In” with Branch Rule # 1 and Branch Rule # 2, Lockout and Allow user to continue. Don’t forget to update the endings to lockout.
Branch Rule #1:
Name: | Expression: |
Lockout | expr { [mcget {session.custom.badpwdcount}] >= [mcget {session.custom.lockout}]} |
Branch Rule #2:
Name: | Expression: |
Allow user to continue | expr { [mcget {session.custom.badpwdcount}] < [mcget {session.custom.lockout}]} |
Access Policy: CaptchaProj_4
The last thing you need to do is edit/customize the default Deny ending (or add your own) to let the users know that they’ve been locked out and attach the ending to the branches labeled lockout.
An example of this page:
The Final iRule
when ACCESS_POLICY_AGENT_EVENT {# Maxtries is a variable that sets how many times you want
# the regular logon page to show before showing the captcha.
# Timeout is a variable that sets how long a user entry will
# persist in the session table. Lockout is a variable that sets
# how many tries a user gets before getting locked out. This is
# the only place you need to change these variables.
set maxtries 2
set timeout 900
set lockout 5
set user [ACCESS::session data get session.logon.last.username]ACCESS::session data set session.custom.maxtries $maxtriesACCESS::session data set session.custom.lockout $lockoutif {[ACCESS::policy agent_id] eq "set session vars"} {ACCESS::session data set session.custom.badpwdcount [table lookup -notouch $user]}if {[ACCESS::policy agent_id] eq "session lookups"} {set badpwd [table lookup $user]if {$badpwd == {}} {
set badpwd [table add $user 0 $timeout]}ACCESS::session data set session.custom.badpwdcount $badpwd# log local0. "$user has this number of incorrect logons(lookups): $badpwd"
}if {[ACCESS::policy agent_id] eq "badpwd"} {table incr $user}if {[ACCESS::policy agent_id] eq "goodpwd"} {table delete $user
}}
Final Notes
This solution builds on everything we learned in Parts 1-3. Exposing applications or services to the Internet opens inherent security risks. APM can help by providing advanced authentication, authorization, and endpoint security checks. With a bit of customization, iRules, and creative access policies, you can provide additional security layers beyond those built as standard features in APM. This was our final solution in this series. I hope you’ve enjoyed playing with BIG-IP Access Policy Manager and now feel comfortable creating your own new and innovative solutions. Best of luck!