cancel
Showing results for 
Search instead for 
Did you mean: 

ASM/WAF rate limit and block clients by source ip (or device_id fingerprint if needed) if there are too many violations by them

You can see my code about how to block users by source ip address that test if the servers and are vunrable to web attacks:

 

 

 

https://devcentral.f5.com/s/articles/ASM-WAF-rate-limit-and-block-clients-by-source-ip-if-there-are-...

 

 

 

when RULE_INIT {

    # The max requests served within the timing interval per the static::timeout variable

    set static::maxReqs 3

    # Timer Interval in seconds within which only static::maxReqs Requests are allowed.

    # (i.e: 10 req per 2 sec == 5 req per sec)

    # If this timer expires, it means that the limit was not reached for this interval and

    # the request counting starts over. Making this timeout large increases memory usage.

    # Making it too small negatively affects performance.

    set static::timeout 60

}

 

 

 

 

#when CLIENT_ACCEPTED {

 

#set cIP_addr [IP::client_addr]

 

#   set getcount [table lookup -notouch $cIP_addr]

 

#   if { ! ( [class match $cIP_addr equals ip_whitelist] ) } {

       # The following expects the IP addresses in multiple X-forwarded-for headers.

       # It picks the first one. If XFF isn’t defined it can grab the true source IP.

 

#           if { $getcount > $static::maxReqs } {

#               log local0. "Request Count for $cIP_addr is $getcount"

#              drop

 

#       }

#   }

 

#}

 

 

when HTTP_REQUEST {

   # Allows throttling for only specific URIs. List the URIs_to_throttle in a data group.

   # Note: a URI is everything after the hostname: e.g. /path1/login.aspx?name=user

   if { [HTTP::header exists X-forwarded-for] } {

           set cIP_addr [getfield [lindex [HTTP::header values X-Forwarded-For] 0] "," 1]

       } else {

           set cIP_addr [IP::client_addr]

       }

 

   set getcount [table lookup -notouch $cIP_addr]

 

   if { ! ( [class match $cIP_addr equals ip_whitelist] ) } {

       # The following expects the IP addresses in multiple X-forwarded-for headers.

       # It picks the first one. If XFF isn’t defined it can grab the true source IP.

 

           if { $getcount > $static::maxReqs } {

               log local0. "Request Count for $cIP_addr is $getcount"

       #       drop

               HTTP::respond 403 content {

               <html>

              <head><title>HTTP Request denied</title></head>

               <body>Your HTTP requests are being blocked because of too many violations.</body>

               </html>

               }

           }

   }

}

 

 

 

 

#when ASM_REQUEST_BLOCKING {

 

when ASM_REQUEST_DONE {

 

   #log local0.debug "\[ASM::status\] = [ASM::status]"

 

 

 

 if { [ASM::status] equals "blocked" } {

 

 

 

 

   # Allows throttling for only specific URIs. List the URIs_to_throttle in a data group.

   # Note: a URI is everything after the hostname: e.g. /path1/login.aspx?name=user1

 #  if { [class match [HTTP::uri] equals URIs_to_throttle] } {

       # The following expects the IP addresses in multiple X-forwarded-for headers.

       # It picks the first one. If XFF isn’t defined it can grab the true source IP.

 #      set getcount [table lookup -notouch $cIP_addr]

       if { $getcount equals "" } {

           table set $cIP_addr "1" $static::timeout $static::timeout

           # Record of this session does not exist, starting new record

           # Request is allowed.

       } else {

               log local0. "Request Count for $cIP_addr is $getcount"

               table incr -notouch $cIP_addr

              # record of this session exists but request is allowed.

         }

 #   }

  }

}

 

 

 

 

 

EDIT:

 

 

 

Please carefully read documentation for the code as the ASM session tracking and session awerness (https://support.f5.com/csp/article/K02212345 ) are better futures as  mentioned that can do simillar things but the idea for this irule to be less CPU intensive by blocking bad actors before the ASM processing.

1 REPLY 1

Here is an example with failover to using the client ip address if ASM fingerprint if it is equal to "0" (no device ID fingerprint available). As I mentioned better use the main irule for blocking by source IP address or for device fingerprint the session tracking and awareness (https://support.f5.com/csp/article/K02212345).

 

I just played with this as the ASM fingerprint is not aways generated for a web request and you need to first enable a feature like web scraping or in the newer versions the unified bot defense (see https://devcentral.f5.com/s/question/0D51T00006kGZft/asm-fingerprinting-with-irule and the general articles https://support.f5.com/csp/article/K19556739 ), so this irule can be helpfull if the default F5 features that use the fingerprint don't failover to client source IP address if there is no available fingerprint.

 

 

 

I also created 2 subtables for the ip addresses and fingerprints for optimizing the lookups.

 

 

 

Please read the full description of the code in:

 

 

https://devcentral.f5.com/s/articles/ASM-WAF-rate-limit-and-block-clients-by-source-ip-if-there-are-too-many-violations?page=1

 

 

 

 

 

when RULE_INIT {

 

# The max requests served within the timing interval per the static::timeout variable

 

set static::maxReqs 2

 

# Timer Interval in seconds within which only static::maxReqs Requests are allowed.

 

# (i.e: 10 req per 2 sec == 5 req per sec)

 

# If this timer expires, it means that the limit was not reached for this interval and

 

# the request counting starts over. Making this timeout large increases memory usage.

 

# Making it too small negatively affects performance.

 

set static::timeout 20

 

}

 

 

 

 

#when CLIENT_ACCEPTED {

 

#set cIP_addr [IP::client_addr]

 

#   set getcount [table lookup -subtable "bad_ip" -notouch $cIP_addr]

 

#   if { ! ( [class match $cIP_addr equals ip_whitelist] ) } {

       # The following expects the IP addresses in multiple X-forwarded-for headers.

       # It picks the first one. If XFF isn’t defined it can grab the true source IP.

 

#           if { $getcount > $static::maxReqs } {

#               log local0. "Request Count for $cIP_addr is $getcount"

#              drop

 

#       }

#   }

 

#}

 

 

 

 

when HTTP_REQUEST {

   # Allows throttling for only specific URIs. List the URIs_to_throttle in a data group.

   # Note: a URI is everything after the hostname: e.g. /path1/login.aspx?name=user

   if { [HTTP::header exists X-forwarded-for] } {

           set cIP_addr [getfield [lindex [HTTP::header values X-Forwarded-For] 0] "," 1]

       } else {

           set cIP_addr [IP::client_addr]

       }

 

   set getcount1 [table lookup -subtable "bad_ip" -notouch $cIP_addr]

 

   if { ! ( [class match $cIP_addr equals ip_whitelist] ) } {

       # The following expects the IP addresses in multiple X-forwarded-for headers.

       # It picks the first one. If XFF isn’t defined it can grab the true source IP.

 

           if { $getcount1 >= $static::maxReqs } {

               log local0. "Request Count for $cIP_addr is $getcount1"

       #       drop

               HTTP::respond 403 content {

               <html>

              <head><title>HTTP Request denied</title></head>

               <body>Your HTTP requests are being blocked because of too many violations.</body>

               </html>

               }

           }

   }

}

 

 

 

 

 

when ASM_REQUEST_DONE {

 

 

 

log local0.debug "\[ASM::status\] = [ASM::status]  and ASM_Device_id = [ASM::fingerprint] "

 

 

 

 

 

 

 

if { [ASM::status] equals "blocked" } {

 

 

 

# Allows throttling for only specific URIs. List the URIs_to_throttle in a data group.

 

# Note: a URI is everything after the hostname: e.g. /path1/login.aspx?name=user1

 

if { not ( [class match [IP::client_addr] equals whitelist] ) } {

 

# The following expects the IP addresses in multiple X-forwarded-for headers.

 

# It picks the first one. If XFF isn’t defined it can grab the true source IP.

 

set cIP_finger [ASM::fingerprint]

 

 

 

 

 if { $cIP_finger == 0 } {

 

 

 

       if { $getcount1 equals "" } {

           table set -subtable "bad_ip" $cIP_addr "1" $static::timeout $static::timeout

           # Record of this session does not exist, starting new record

           # Request is allowed.

       } else {

               log local0. "Request Count for $cIP_addr is $getcount1"

               table incr -subtable "bad_ip" -notouch $cIP_addr

              # record of this session exists but request is allowed.

         }

 

 

 

 } else {

 

 

 

 

 

set getcount2 [table lookup -subtable "bad_id" -notouch $cIP_finger]

 

 

if { $getcount2 equals "" } {

 

table set -subtable "bad_id" $cIP_finger "1" $static::timeout $static::timeout

 

# Record of this session does not exist, starting new record

 

# Request is allowed.

 

} else {

 

if { $getcount2 >= $static::maxReqs } {

 

log local0. "Request Count for $cIP_finger is $getcount"

 

table incr -subtable "bad_id" -notouch $cIP_finger

 

# record of this session exists but request is allowed.

 

} else {

 

#drop

 

HTTP::respond 403 content {

 

<html>

 

<head><title>HTTP Request denied</title></head>

 

<body>Your HTTP requests are being throttled.</body>

 

</html>

 

                 }

 

           } 

 

         }

 

       }

 

     }

 

   }

 

 }