cancel
Showing results for 
Search instead for 
Did you mean: 
lnxgeek
Cirrostratus
Cirrostratus

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

 

Version history
Last update:
‎18-Mar-2015 14:11
Updated by:
Contributors