HashDoS Defender - Advanced Protection against hash collision attacks (for ASP.NET, PHP and Java/JSP)¶
Contributed by: Simon Kowallik - sk @ simonkowallik.com¶
Description¶
If you read HashDos – The Post of Doom
Explained
and/or VU#903934 – Post of
Doom
and wonder how you can protect your vulnerable web applications you
should continue reading.
This iRule protects against Hash collision “HashDoS” Attacks through
HTTP POST Parameters. If you can’t limit your HTTP POST size and/or
Parameter count for your vulnerable Web Application, this iRule is for
you!
The following Hash functions are protected:
1. djb x33a used by PHP5
2. djb x33x used by ASP.NET and PHP4
3. “x31s” (similar to x33a) used by Java/JSP like Tomcat, Geronimo
or Oracle Glassfish
This iRule will inspect two random POST Parameter and check them for
hash collision against the different algorithms.
If a collision is detected (or another violation occurs), the
connection will be closed with a HTTP 500 Internal Server Error. The
requesting client (or attacker) will receive a “Internal Error
$DropID” message.
The $DropID will be a random number from 1000000 to 9999999 and
will also be logged.
Configuration¶
You can control the behavior by some configuration variables in the
RULE_INIT block.
Those configuration variables are:
static::MaxLengthCollect
Defaults to: 1048576
Defines the maximum Content-Length which will be collected by this
iRule. Only POST Parameters inside this “Content Size” can be
inspected for collisions.
static::MaxLengthAllowed
Defaults to: 10485760
Defines the maximum allowed Content-Length. If a POST request is
greater it will be treated as a violation (rejected or dropped).
static::MaxPostParameter
Defaults to: 50000
Defines the maximum count of HTTP POST Request Parameter allowed. If a
POST request contains more Parameter it will be treated as a violation
(rejected or dropped).
static::PostInspectStart
Defaults to: 1000
Defines the minimum count of HTTP POST Request Parameter to start
inspecting the Parameter for collisions. If POST Parameter count is
below this value, they will not be inspected for collisions.
Within the iRule you can modify the behavior when a collision or other
violation occurs:
You can either drop the connection or, as described above, send a
HTTP 500 Internal Server Error with a obscured DropID.
Words of Warning¶
While this iRule CAN protect your web applications, there are several
attack vectors which this iRule does not cover.
And this iRule is not perfect, too. While we handle some evasion
techniques like sending POST Parameters in Uppercase, lowercase and
Hex encoded, we do not inspect the whole data by default
(::MaxLengthCollect).
While you can set ::MaxLengthCollect to the same value as
::MaxLengthAllowed, it is still possible for an attacker to submit
different collisions! To detect collisions we only inspect two
Parameters with this iRule, while an attacker can sent 50k by default.
If the attacker sents 2x25k collisions which differ, we might fail to
detect collisions at all.
Counter measures?
1. Set ::MaxLengthCollect to the same value as
::MaxLengthAllowed (Performance Impact)
2. Inspect multiple POST Parameters, i.e. Parameter 1 each 1000 POST
Parameters supplied (not implemented in this Version)
3. Best approach: Patch your Application Servers with non-vulnerable
“Web Application Framework” version
Compatibility (Tested with)¶
- BIG-IP Version 10.1.0 3341.1084
- BIG-IP Version 10.2.2 969.0
- BIG-IP Version 10.2.3 123.0
Applies to¶
CERT: VU#903934 - http://www.kb.cert.org/vuls/id/903934
Feedback¶
If you (don’t) like/use this iRule, miss some features or have any
other question/suggestion I’m happy
aboutFeedback!
iRule Source¶
# iRule: HashDoS_Defender
# Author: Simon Kowallik <sk @simonkowallik.com>
# Version: 1.1
#
# Changes:
# @2012-02-05: Version 1.0: First release
# @2012-02-06: Version 1.1: Formating changes, variable name changes
# HTTP 500 Error on collision detection with "DropID" to client
#
# Compatibility (Tested with):
# - BIG-IP Version 10.1.0 3341.1084
# - BIG-IP Version 10.2.2 969.0
# - BIG-IP Version 10.2.3 123.0
#
# Applies to:
# CERT: VU#903934 - http://www.kb.cert.org/vuls/id/903934
# CVEs: CVE-2011-3414, CVE-2011-4885, CVE-2011-4858, CVE-2011-5034
#
# Description:
# This iRule protects against Hash collision "HashDoS" Attacks through HTTP POST Parameters.
# If you can't limit your HTTP POST size and/or Parameter count for your vulnerable
# Web Application, this iRule is for you!
#
# The following Hash functions are protected:
# - djb x33a used by PHP5
# - djb x33x used by ASP.NET and PHP4
# - "x31s" (similar to x33a) used by Java/JSP like Tomcat, Geronimo or Oracle Glassfish
#
# You can control the behavior by some configuration variables in the RULE_INIT block.
#
# Words of Warning:
# While this iRule CAN protect your web applications, there are several attack vectors which this
# iRule does not cover.
#
# And this iRule is not perfect, too.
# While we handle some evasion techniques like sending POST Parameters in Uppercase,
# lowercase and Hex encoded, we do not inspect the whole data by default (::MaxLengthCollect).
#
# While you can set ::MaxLengthCollect to the same value as ::MaxLengthAllowed,
# it is still possible for an attacker to submit different collisions!
# To detect collisions we only inspect two Parameters with this iRule, while an attacker can sent
# 50k by default. If the attacker sents 2x25k collisions which differ, we might fail to detect
# collisions at all.
#
# Counter measures? Yes.
# 1. Set ::MaxLengthCollect to the same value as ::MaxLengthAllowed
# 2. Inspect multiple POST Parameters, i.e. Parameter 1 each 1000 POST Parameters supplied
# (not implemented in this Version)
# 3. Best: Patch your Application Servers with non-vulnerable "Web Application Framework" version
#
when RULE_INIT {
# Max Content-Length which will be collected by this iRule
set static::MaxLengthCollect 1048576
# Max Content-length allowed.
# Connection will be dropped if size sent is greater specified bytes
set static::MaxLengthAllowed 10485760
# Max HTTP POST Parameters allowed.
# Connection will be dropped if more Parameters are sent then specified
set static::MaxPostParameter 50000
# Post Parameters will be inspected for collisions starting from this count
set static::PostInspectStart 1000
}
when HTTP_REQUEST {
# Check for HTTP POST && Content-Length Header
if {[HTTP::method] eq "POST" && [HTTP::header exists Content-Length]} {
# if Content-Length > ::MaxLengthAllowed -> Drop
if {[HTTP::header Content-Length] > $static::MaxLengthAllowed} {
# You can choose between a simple TCP Connection Reset or a HTTP 500 Response with
# an individual "Internal Error nnnnnnn" message
# Please pay attention to the "DropID: $DropID" entry in the log command!
set DropID [expr { int(8999999 * rand() + 1000000) }]
HTTP::respond 500 content "Internal Error $DropID" noserver Connection close
#drop
log local0. "HashDoS Defender: HTTP connection dropped: \
Client: [IP::client_addr]:[TCP::client_port] \
DropID: $DropID \
Reason: POST size limit reached"
# if Content-Length > ::MaxLengthCollect just collect first MByte, else collect exact value
} elseif {[HTTP::header Content-Length] > $static::MaxLengthCollect} {
HTTP::collect $static::MaxLengthCollect
log local0. "HashDoS Defender: Warning: Collecting HTTP connection from \
Client: [IP::client_addr]:[TCP::client_port] \
Reason: Collecting only first $static::MaxLengthCollect Bytes"
} elseif {[HTTP::header Content-Length] > 0} {
HTTP::collect [HTTP::header Content-Length]
}
}
}
when HTTP_REQUEST_DATA {
# sanitize and split the HTTP Payload to 'ParameterName=Value' elements
set POST_array [split [regsub -all {&=|&$} [HTTP::payload] {}] "&"]
# calculate the length
set POST_count [llength $POST_array]
# Check if we have more elements then defined in ::MaxPostParameter, then drop.
if {$POST_count > $static::MaxPostParameter} {
# You can choose between a simple TCP Connection Reset or a HTTP 500 Response with
# an individual "Internal Error nnnnnnn" message
# Please pay attention to the "DropID: $DropID" entry in the log command!
set DropID [expr { int(8999999 * rand() + 1000000) }]
HTTP::respond 500 content "Internal Error $DropID" noserver Connection close
#drop
log local0. "HashDoS Defender: HTTP connection dropped: \
Client: [IP::client_addr]:[TCP::client_port] \
DropID: $DropID \
Reason: POST parameter limit reached"
# If we have more/equal amount of elements then defined in ::PostInspectStart,
# we will inspect them for collisions.
} elseif {$POST_count >= $static::PostInspectStart} {
# Post Parameter Inspection starts here..
# Set hash IVs
set hAx33a 5381
set hAx33a_UC 5381
set hAx33a_lc 5381
set hAx33x 5381
set hAx33x_UC 5381
set hAx33x_lc 5381
set hAx31s 0
set hAx31s_UC 0
set hAx31s_lc 0
set hBx33a 5381
set hBx33x 5381
set hBx31s 0
set hBx33a_UC 5381
set hBx33x_UC 5381
set hBx31s_UC 0
set hBx33a_lc 5381
set hBx33x_lc 5381
set hBx31s_lc 0
#[split[URI::decode[lindex[split[lindex $POST_array [expr {int($POST_count/3*rand())}]]=]0]] {}]
# explanation:
# NUMBER -> [expr {int($POST_count / 3 * rand())}] Number in 1. third of $POST_count
# PARAMETER=VALUE -> [lindex $POST_array NUMBER] Extract Element # NUMBER from POST_array
# {PARAMETER VALUE} -> [split PARAMETER=VALUE =] Split to PARAMETER VALUE list
# PARAMETER -> [lindex {PARAMETER VALUE} 0] Extract Element 0 which is PARAMETER
# PARAMETER -> [URI::decode PARAMETER] URI decode PARAMETER, turn %xx to ascii char
# {P A R A M ..} -> [split PARAMETER {}] iterate (foreach) every single character
# Inspect the first Parameter
foreach chr [split [URI::decode [lindex [split [lindex $POST_array [expr { int($POST_count / 3 * rand()) }]] =] 0]] {}] {
# Calculate hash values
binary scan $chr c chrInt
binary scan [string toupper $chr] c chrInt_UC
binary scan [string tolower $chr] c chrInt_lc
set hAx33a [expr { int(($hAx33a << 5)) + int($hAx33a) + $chrInt }]
set hAx33x [expr {(int(($hAx33x << 5)) + int($hAx33x)) ^ $chrInt}]
set hAx31s [expr { int(($hAx31s << 5)) - int($hAx31s) + $chrInt }]
set hAx33a_UC [expr { int(($hAx33a_UC << 5)) + int($hAx33a_UC) + $chrInt_UC }]
set hAx33x_UC [expr {(int(($hAx33x_UC << 5)) + int($hAx33x_UC)) ^ $chrInt_UC}]
set hAx31s_UC [expr { int(($hAx31s_UC << 5)) - int($hAx31s_UC) + $chrInt_UC }]
set hAx33a_lc [expr { int(($hAx33a_lc << 5)) + int($hAx33a_lc) + $chrInt_lc }]
set hAx33x_lc [expr {(int(($hAx33x_lc << 5)) + int($hAx33x_lc)) ^ $chrInt_lc}]
set hAx31s_lc [expr { int(($hAx31s_lc << 5)) - int($hAx31s_lc) + $chrInt_lc }]
}
# Inspect the second Parameter
foreach chr [split [URI::decode [lindex [split [lindex $POST_array [expr { int($POST_count / 2 * rand() + $POST_count / 3 + 1) }]] =] 0]] {}] {
# Calculate hash values
binary scan $chr c chrInt
binary scan [string toupper $chr] c chrInt_UC
binary scan [string tolower $chr] c chrInt_lc
set hBx33a [expr { int(($hBx33a << 5)) + int($hBx33a) + $chrInt }]
set hBx33x [expr {(int(($hBx33x << 5)) + int($hBx33x)) ^ $chrInt}]
set hBx31s [expr { int(($hBx31s << 5)) - int($hBx31s) + $chrInt }]
set hBx33a_UC [expr { int(($hBx33a_UC << 5)) + int($hBx33a_UC) + $chrInt_UC }]
set hBx33x_UC [expr {(int(($hBx33x_UC << 5)) + int($hBx33x_UC)) ^ $chrInt_UC}]
set hBx31s_UC [expr { int(($hBx31s_UC << 5)) - int($hBx31s_UC) + $chrInt_UC }]
set hBx33a_lc [expr { int(($hBx33a_lc << 5)) + int($hBx33a_lc) + $chrInt_lc }]
set hBx33x_lc [expr {(int(($hBx33x_lc << 5)) + int($hBx33x_lc)) ^ $chrInt_lc}]
set hBx31s_lc [expr { int(($hBx31s_lc << 5)) - int($hBx31s_lc) + $chrInt_lc }]
}
#log local0. "HashDoS Defender: DEBUG: djbX33A - PHP5 Protection: ParamA -> \
# orig($hAx33a) UC($hAx33a_UC) lc($hAx33a_lc)"
#log local0. "HashDoS Defender: DEBUG: djbX33A - PHP5 Protection: ParamB -> \
# orig($hBx33a) UC($hBx33a_UC) lc($hBx33a_lc)"
#log local0. "HashDoS Defender: DEBUG: djbX33X - PHP4+ASP.NET Protection: ParamA -> \
# orig($hAx33x) UC($hAx33x_UC) lc($hAx33x_lc)"
#log local0. "HashDoS Defender: DEBUG: djbX33X - PHP4+ASP.NET Protection: ParamB -> \
# orig($hBx33x) UC($hBx33x_UC) lc($hBx33x_lc)"
#log local0. "HashDoS Defender: DEBUG: X31S - JSP/Tomcat Protection: ParamA -> \
# orig($hAx31s) UC($hAx31s_UC) lc($hAx31s_lc)"
#log local0. "HashDoS Defender: DEBUG: X31S - JSP/Tomcat Protection: ParamB -> \
# orig($hBx31s) UC($hBx31s_UC) lc($hBx31s_lc)"
# Check for collisions and take action
if {$hAx33a == $hBx33a || $hAx33a_UC == $hBx33a_UC || $hAx33a_lc == $hBx33a_lc} {
# x33a hash collision detected
#
# You can choose between a simple TCP Connection Reset or a HTTP 500 Response with
# an individual "Internal Error nnnnnnn" message
# Please pay attention to the "DropID: $DropID" entry in the log command!
set DropID [expr { int(8999999 * rand() + 1000000) }]
HTTP::respond 500 content "Internal Error $DropID" noserver Connection close
#drop
log local0. "HashDoS Defender: HTTP connection dropped: \
Client: [IP::client_addr]:[TCP::client_port] \
DropID: $DropID \
Reason: X33A (PHP5) hash collision detected"
} elseif {$hAx33x == $hBx33x || $hAx33x_UC == $hBx33x_UC || $hAx33x_lc == $hBx33x_lc} {
# x33x hash collision detected
#
# You can choose between a simple TCP Connection Reset or a HTTP 500 Response with
# an individual "Internal Error nnnnnnn" message
# Please pay attention to the "DropID: $DropID" entry in the log command!
set DropID [expr { int(8999999 * rand() + 1000000) }]
HTTP::respond 500 content "Internal Error $DropID" noserver Connection close
#drop
log local0. "HashDoS Defender: HTTP connection dropped: \
Client: [IP::client_addr]:[TCP::client_port] \
DropID: $DropID \
Reason: X33X (ASP.NET/PHP4) hash collision detected"
} elseif {$hAx31s == $hBx31s || $hAx31s_UC == $hBx31s_UC || $hAx31s_lc == $hBx31s_lc} {
# x31s hash collision detected
#
# You can choose between a simple TCP Connection Reset or a HTTP 500 Response with
# an individual "Internal Error nnnnnnn" message
# Please pay attention to the "DropID: $DropID" entry in the log command!
set DropID [expr { int(8999999 * rand() + 1000000) }]
HTTP::respond 500 content "Internal Error $DropID" noserver Connection close
#drop
log local0. "HashDoS Defender: HTTP connection dropped: \
Client: [IP::client_addr]:[TCP::client_port] \
DropID: $DropID \
Reason: X31S (Java/JSP) hash collision detected"
}
}
}
Implementation Details¶
This iRule requires LTM v10. or higher.
The BIG-IP API Reference documentation contains community-contributed content. F5 does not monitor or control community code contributions. We make no guarantees or warranties regarding the available code, and it may contain errors, defects, bugs, inaccuracies, or security vulnerabilities. Your access to and use of any code available in the BIG-IP API reference guides is solely at your own risk.