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 } } } }
4 Comments
- Sonne_133164
Nimbostratus
Greetings, could you advise how the blocked IP can be removed later from the blacklist? Thank you. - Songseajoon_222
Nimbostratus
I 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_Sherman
Nimbostratus
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_189601
Nimbostratus
@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...