iRule for Brute Force Password Guessing Attacks

Problem this snippet solves:

Here I'm introducing an iRule for use as Brute Force Password Guessing Protection.


If you are running on 12.1.x you could solve this kind of attacks using ASM

https://support.f5.com/csp/article/K54335130


But this release it's also affected for one bug that could lead in some unwanted behavior.


The idea beneath this iRule is to workaround this bug and give a solution for those users that still run an ASM module on 12.1.x.


How to use this snippet:

This iRule requires a Data-Group to reference all the login pages in your infrastructure.


ltm data-group internal LOGIN_URI_DG {
    records {
        /app1/login { }
        /app2/login { }
    }
    type string
}


In the STREAM expression you should identify how a login failure attempt is represented in your HTTP response. In my case, all login failures are represented by one JSON structure with the word "ACCESS_FAIL" in some of their fields.


STREAM::expression {=ACCESS_FAIL=}


You should also customize these global variables.


set static::maxtry 5     ; # Maximum failed login attempts
set static::time 900     ; # Time window for failed login attempts evaluation [seconds]
set static::bantime 3600 ; # How long the user is banned [seconds]


Code :

when RULE_INIT {
    # Variable Definition
	set static::maxtry 5          ; # Maximum failed login attempts
	set static::time 900         ; # Time window for failed login attempts evaluation [seconds]
	set static::bantime 3600 ; # How long the user is banned [seconds]
}

when CLIENT_ACCEPTED priority 100 {
    # Get client IP
	set source_ip [IP::remote_addr]
	# Reject user if exists in blacklist 
	if { [table lookup -subtable "blacklist" $source_ip] != "" } {
		reject
	}
}

when HTTP_REQUEST priority 100 {
    event HTTP_RESPONSE enable
	event STREAM_MATCHED enable
	STREAM::disable
	set login_access 0
	# Enable login access flag
	if { [class match [string tolower [HTTP::uri] ] contains LOGIN_URI_DG ] } {
		set login_access 1
	}
}

when HTTP_RESPONSE priority 100 {
    if { $login_access } {
        # Configure stream expression when response is JSON
    	if {[HTTP::header value Content-Type] starts_with "application/json"} {
    		STREAM::expression {=ACCESS_FAIL=}
    		STREAM::enable
    	}
		event HTTP_RESPONSE disable
    }
}

when STREAM_MATCHED priority 100 {
    if { $login_access } {
        # Increase variable of login attempts
    	set key "attempts:$source_ip"
    	table add $key 0 indefinite $static::time
    	set count [table incr $key]
        # If maximum login attempts value is exceeded, then include user in blacklist
    	if { $count >= $static::maxtry } {
    		table add -subtable "blacklist" $source_ip "blocked" indefinite $static::bantime
    		table delete $key
    		log local1. "User Rejected: $source_ip"
    	}
		event STREAM_MATCHED disable
    }
}

Tested this on version:

12.1
Published Feb 07, 2020
Version 1.0