Protecting Login Pages against Brute Force Attack v1
Problem this snippet solves:
This is the first version of an iRule that can help protect a login site from brute force attacks.
When a request is made against the application the iRule looks for an error text in the server response which increment a counter. That counter has a threshold value and when that has been reached the client IP address is inserted into a blacklist table and completely blocked.
This is a simple solution that can be easily expanded to suit your needs.
Note: ">" should be changed to ">" in the iRule.
How to use this snippet:
Update: iRule using data groups
I just made an updated version of the iRule where the failure text is pulled from a data group instead. This way the iRule becomes more dynamic and "correct" maintenance wise.
Data group "failtext"
ltm data-group internal /Common/failtext { records { "not allowed" { } "unable to login with provided credentials" { } "you do not have permission to perform this action" { } non_field_errors { } } type string }
Code :
when RULE_INIT { set static::maxtry 3 set static::bantime 600 set static::failtext "unable to login with provided credentials" set static::debug 1 } when CLIENT_ACCEPTED { set srcip [IP::remote_addr] if { [table lookup -subtable "blacklist" $srcip] != "" } { if {$static::debug} {log "Blocking $srcip"} drop return } } when HTTP_REQUEST { set login_request 0 if {([HTTP::method] equals "POST") and ( [string tolower [HTTP::uri]] equals "/api2/auth-token/" )}{ set login_request 1 if {$static::debug} {log "Login attempt from $srcip"} } } when HTTP_RESPONSE { if {[HTTP::header "Content-Length"] ne "" && [HTTP::header "Content-Length"] <= 1048576}{ set content_length [HTTP::header "Content-Length"] } else { set content_length 1048576 } # Check if $content_length is not set to 0 if { $content_length > 0} { HTTP::collect $content_length } } when HTTP_RESPONSE_DATA { if {$login_request == 1}{ if {$static::debug} {log "Payload: [HTTP::payload]"} if { ([string tolower [HTTP::payload]] contains $static::failtext) }{ if {$static::debug} {log "Login attempt condition failed"} set key "count:$srcip" set count [table incr $key] if {$static::debug} {log "Failed attempt $count"} if { $count > $static::maxtry } { table add -subtable "blacklist" $srcip "blocked" indef $static::bantime table delete $key if {$static::debug} {log "Insert into blacklist table"} drop return } } } } # iRule version 1.1 with data group support when RULE_INIT { set static::maxtry 3 set static::bantime 600 #set static::failtext "unable to login with provided credentials" set static::debug 1 } when CLIENT_ACCEPTED { set srcip [IP::remote_addr] if { [table lookup -subtable "blacklist" $srcip] != "" } { if {$static::debug} {log "Blocking $srcip"} drop return } } when HTTP_REQUEST { set login_request 0 if {([HTTP::method] equals "POST") and ( [string tolower [HTTP::uri]] equals "/api2/auth-token/" )}{ set login_request 1 if {$static::debug} {log "Login attempt from $srcip"} } } when HTTP_RESPONSE { if {[HTTP::header "Content-Length"] ne "" && [HTTP::header "Content-Length"] <= 1048576}{ set content_length [HTTP::header "Content-Length"] } else { set content_length 1048576 } # Check if $content_length is not set to 0 if { $content_length > 0} { HTTP::collect $content_length } } when HTTP_RESPONSE_DATA { if {$login_request == 1}{ if {$static::debug} {log "Payload: [HTTP::payload]"} if { [class match [string tolower [HTTP::payload]] contains "failtext"]}{ if {$static::debug} {log "Login attempt condition failed"} set key "count:$srcip" set count [table incr $key] if {$static::debug} {log "Failed attempt $count"} if { $count > $static::maxtry } { table add -subtable "blacklist" $srcip "blocked" indef $static::bantime table delete $key log "Insert into blacklist table" drop return } } } }
- Sonne_133164NimbostratusGreetings, could you advise how the blocked IP can be removed later from the blacklist? Thank you.
- Songseajoon_222NimbostratusI looked to try this method, it was fine. However, too slow, regardless of the login failure / success or not. Login also slow, slow even fail to log in. I saw reduces the size of content-length, but works quickly and well Login successful when there was still experiencing a slow phenomenon. how can i do?
- Dmitry_ShermanNimbostratus
There is no time threshold window per IP, rather simple counter which counts tries without expiration. Please add time between tries or total time window for tries, for example 5 tries in 60 secs.
- kamols_189601Nimbostratus
@Interhost
 
There's a default time window which is set in the line 42
 
set count [table incr $key]
As per the documentation if you don't specify the timeout in the 'table' command it will take default 180s https://clouddocs.f5.com/api/irules/table.html
 
If you want to specify your own time window you can set the timeout to 'indefinite' and set a 'lifetime' instead in such way
 
table add $key indefinite set count [table incr -notouch $key]
When the lifetime is reached the counter get's deleted so it starts over...