DoS and NTLM Brute force protection for HTTP(s) traffic
Problem this snippet solves:
This snippet has been designed to mainly protect against NTLM's Denial of Service and brute force attacks against web application that use this authentication mecanism.
This irule help companies to fight against brute force attacks at the HTTP layer. You can combine this irule with another one on non-http traffic to provide a brute force protection across multiple layers.
For pure HTTP application, you should prefer the Brute force protection provided by the ASM module.
How to use this snippet:
This irule should be installed on each Virtual Server that require NTLM protection. In a Microsoft Skype for Business deployment, you may need to protect following services :
- Web Services
- Conf
- Autodiscover
- Exchange Web Services
TO BE DONE : Provide a way to unlock blocked users
External links
Github : https://github.com/e-XpertSolutions/f5
Related Articles
Credits
- This irule is based on NTLM logger
Code :
when RULE_INIT {
array set NTLMFlags {
unicode 0x00000001
oem 0x00000002
req_target 0x00000004
unknown1 0x00000008
sign 0x00000010
seal 0x00000020
datagram 0x00000040
lmkey 0x00000080
netware 0x00000100
ntlm 0x00000200
unknown2 0x00000400
unknown3 0x00000800
ntlm_domain 0x00001000
ntlm_server 0x00002000
ntlm_share 0x00004000
NTLM2 0x00008000
targetinfo 0x00800000
128bit 0x20000000
keyexch 0x40000000
56bit 0x80000000
}
set static::irule_name "irule-ntlm-bruteforce"
set static::email_domain "domain.com"
set static::user_domain "DOMAIN"
set static::log_server ""
set static::log_pri "local0."
set static::fail_tab "NTLMfails"
set static::blacklist_tab "NTLMblackhole"
set static::userfail_tab "NTLMUserfails"
set static::userblacklist_tab "NTLMUserblackhole"
set static::max_failures 5
set static::fail_memory 300
set static::block_duration 300
}
when CLIENT_ACCEPTED {
if {[table lookup -subtable $static::blacklist_tab [IP::client_addr]] == 1} {
log $static::log_pri "[virtual] - BLACKHOLED IPADDR [IP::client_addr]:[TCP::client_port] (Reputation=[IP::reputation [IP::client_addr]])"
reject
return
}
}
when HTTP_REQUEST {
if {[table lookup -subtable $static::blacklist_tab [IP::client_addr]] == 1} {
log $static::log_pri "[virtual] - BLACKHOLED IPADDR [IP::client_addr]:[TCP::client_port] (Reputation=[IP::reputation [IP::client_addr]])"
reject
return
}
if {[HTTP::header Authorization] starts_with "NTLM "} {
set ntlm_msg [ b64decode [split [lindex [HTTP::header Authorization] 1] ] ]
binary scan $ntlm_msg a7ci protocol zero type
if { $type eq 3 } {
binary scan $ntlm_msg @12ssissississississii \
lmlen lmlen2 lmoff \
ntlen ntlen2 ntoff \
dlen dlen2 doff \
ulen ulen2 uoff \
hlen hlen2 hoff \
slen slen2 soff \
flags
set ntlm_domain {}; binary scan $ntlm_msg @${doff}a${dlen} ntlm_domain
set ntlm_user {}; binary scan $ntlm_msg @${uoff}a${ulen} ntlm_user
set ntlm_host {}; binary scan $ntlm_msg @${hoff}a${hlen} ntlm_host
set unicode [expr {$flags & 0x00000001}]
if {$unicode} {
set ntlm_domain_convert ""
foreach i [ split $ntlm_domain ""] {
scan $i %c c
if {$c>1} {
append ntlm_domain_convert $i
} elseif {$c<128} {
set ntlm_domain_convert $ntlm_domain_convert
} else {
append ntlm_domain_convert \\u[format %04.4X $c]
}
}
set ntlm_domain $ntlm_domain_convert
set ntlm_user_convert ""
foreach i [ split $ntlm_user ""] {
scan $i %c c
if {$c>1} {
append ntlm_user_convert $i
} elseif {$c<128} {
set ntlm_user_convert $ntlm_user_convert
} else {
append ntlm_user_convert \\u[format %04.4X $c]
}
}
set ntlm_user $ntlm_user_convert
set ntlm_host_convert ""
foreach i [ split $ntlm_host ""] {
scan $i %c c
if {$c>1} {
append ntlm_host_convert $i
} elseif {$c<128} {
set ntlm_host_convert $ntlm_host_convert
} else {
append ntlm_host_convert \\u[format %04.4X $c]
}
}
set ntlm_host $ntlm_host_convert
}
binary scan $ntlm_msg @${ntoff}a${ntlen} ntdata
binary scan $ntlm_msg @${lmoff}a${lmlen} lmdata
binary scan $ntdata H* ntdata_h
binary scan $lmdata H* lmdata_h
set interesting 1
if { ($ntlm_domain equals $static::user_domain or [string tolower $ntlm_user] ends_with $static::email_domain) } {
set attack 1
if {[table lookup -subtable $static::userblacklist_tab $ntlm_user] == 1} {
# Block ntlm_user exceeding the number of failed logons in the timeout period
log $static::log_pri "[virtual] - BLACKHOLED $ntlm_domain\\$ntlm_user from $ntlm_host at [IP::client_addr]:[TCP::client_port] (Reputation=[IP::reputation [IP::client_addr]])"
reject
return
} else {
log $static::log_pri "[virtual] - Login attempt by $ntlm_domain\\$ntlm_user from $ntlm_host for [HTTP::uri]."
}
} else {
set attack 0
log $static::log_pri "[virtual] - Not a valid user - Login attempt by $ntlm_domain\\$ntlm_user from $ntlm_host for [HTTP::uri]."
}
return [list type $type flags [format 0x%08x $flags] \
ntlm_domain $ntlm_domain ntlm_host $ntlm_host ntlm_user $ntlm_user \
lmhash $lmdata nthash $ntdata]
}
}
}
when HTTP_RESPONSE {
if {[info exists interesting] && $interesting == 1} {
set client [IP::client_addr]:[TCP::client_port]
set node [IP::server_addr]:[TCP::server_port]
set nodeResp [HTTP::status]
if { $nodeResp == 401 and ([info exists attack] and $attack == 1)} {
log $static::log_pri "[virtual] - invalid credentials detected for $ntlm_user"
table set -subtable $static::fail_tab -notouch -excl [IP::client_addr] 0 indef $static::fail_memory
table incr -subtable $static::fail_tab [IP::client_addr]
if {[table lookup -subtable $static::fail_tab [IP::client_addr]] >= $static::max_failures} {
set now [clock seconds]
set now_date [split [clock format $now -format {%X %x}] " "]
set later [expr {$now + $static::block_duration}]
set later_date [split [clock format $later -format {%X %x}] " "]
log $static::log_pri "[virtual] - BLACKHOLING IPADDR - [IP::client_addr] (Reputation=[IP::reputation [IP::client_addr]]) at $now_date until $later_date"
table set -subtable $static::blacklist_tab -excl [IP::client_addr] 1 indef $static::block_duration
}
if {[info exists ntlm_user]} {
table set -subtable $static::userfail_tab -notouch -excl $ntlm_user 0 indef $static::fail_memory
table incr -subtable $static::userfail_tab $ntlm_user
if {[table lookup -subtable $static::userfail_tab $ntlm_user] >= $static::max_failures} {
set now [clock seconds]
set later [expr {$now + $static::block_duration}]
log $static::log_pri "[virtual] - BLACKHOLING USER - $ntlm_user at $now_date until $later_date"
table set -subtable $static::userblacklist_tab -excl $ntlm_user 1 indef $static::block_duration
}
}
}
}
}Tested this on version:
11.53 Comments
- AntonyMillingto
Nimbostratus
Hi Yann,
I have a customer with Skype for Business and they are worried about brute-force attacks on logins so this snippet is perfect. I have added the i-rule to the reverse proxy VS and when I log in with incorrect credentials I see a syslog 'invalid credentials detected for ' sent but I never see the IP blacklisted. Can you advise why this may not be working. I'm on 11.5.4.2.0.291. Thanks, Antony
Hi Antony,
It looks like you are not using NTLM authentication on your backend. The following line of code log an invalid authentication attempt for an ntlm user:
log $static::log_pri "[virtual] - invalid credentials detected for $ntlm_user"If you are using different kind of authentication than NTLM, you should change this code snippet to match your needs
Hope it helps
Yann
Another question, Do you get some other logs coming from this irule ?