Forum Discussion
mattias_56723
Nov 29, 2010Nimbostratus
Mitigating Slow HTTP Post DDoS Attacks With iRules
I have tried to implement the "slow http post ddos.." iRule, but I got some errors. http://devcentral.f5.com/Tutorials/...Rules.aspx
when HTTP_REQUEST {
if { [HTTP::method] equal...
hooleylist
Dec 04, 2010Cirrostratus
Here's an updated version. I've fixed a couple of issues including the unset collect_length error and a maths rounding problem. Can you try testing this on a non-production virtual server first and reply back with the results?
For requests with a content length header value greater than $static::collect_length bytes, the iRule collects $static::collect_length bytes and gives the client up to the $static::timeout in seconds to send the data.
For requests with a content length header value greater than 0 and less than the default collect length (2048 bytes), the client is given the same ratio of bytes per second as the defaults ($static::collect_length / $static::timeout).
This method of collecting a small amount of data even for large requests is more efficient for LTM, but leaves open the possibility of a client sending the first part of the payload fast enough but then slowing the transmission down to keep the server busy. If you're concerned about security more than performance, you could increase the collection size. You'd probably want to increase the timeout as well to ensure slow clients aren't unintentionally blocked.
Thanks, Aaron
Based on 'Mitigating Slow HTTP Post DDoS Attacks With iRules' from George Watkins
Requires LTM v10.0+ for the after command
when RULE_INIT {
This iRule enforces a minimum length of time ($static::timeout) for a client to send POST request data.
The initial values are 2Kb / 2 sec = 1 Kb/s for the first 2Kb. These values should be tailored for the client base.
Default amount of request payload to collect (in bytes).
This is the maximum amount of content we will collect.
Clients will still be able to send unlimited payload sizes.
set static::collect_length 2048
Default timeout, for POST requests, to send $collect_length bytes (in milliseconds)
set static::timeout 2000
HTML response to send for blocked requests
set static::block_html {Your POST request is not being received quickly enough. Please retry.}
Log debug messages to /var/log/ltm? 1=yes, 0=no.
set static::post_debug 1
}
when HTTP_REQUEST {
Only check POST requests. If the application supports other request methods with payloads, add them in a switch statement here.
if { [HTTP::method] equals "POST"} {
if { $static::post_debug } { log local0. "[IP::client_addr]:[TCP::client_port]: POST to [HTTP::host][HTTP::uri]" }
Create a local variable copy of the collection amount
set collect_length $static::collect_length
Create a local variable copy of the static timeout
set timeout $static::timeout
Check for a non-existent Content-Length header
if {[HTTP::header Content-Length] eq ""}{
Use default collect length for POSTs without a Content-Length header
set collect_length $static::collect_length
if { $static::post_debug } { log local0. "[IP::client_addr]:[TCP::client_port]: No Content-Length value" }
} elseif {[HTTP::header Content-Length] <= 0}{
Don't try to collect a payload if there isn't one
unset collect_length
if { $static::post_debug } { log local0. "[IP::client_addr]:[TCP::client_port]: Content-Length: 0." }
} elseif {[HTTP::header Content-Length] > $static::collect_length}{
Use the default collect length
set collect_length $static::collect_length
if { $static::post_debug } { log local0. "[IP::client_addr]:[TCP::client_port]: Content-Length: [HTTP::header Content-Length], collecting $collect_length" }
} else {
Collect the actual payload length
set collect_length [HTTP::header Content-Length]
Calculate a custom timeout based on the same ratio we use for the default collect length and default timeout
set timeout [expr {[HTTP::header Content-Length] * $static::timeout / $static::collect_length }]
if { $static::post_debug } { log local0. "[IP::client_addr]:[TCP::client_port]: Content-Length: [HTTP::header Content-Length], collecting $collect_length bytes with timeout $timeout ms" }
}
If the POST Content-Length isn't 0, collect (a portion of) the payload
if {[info exists collect_length]}{
If the entire request hasn't been received within X seconds, send a 408, and close the connection
set id [after $timeout {
if { $static::post_debug } { log local0. "[IP::client_addr]:[TCP::client_port]: $timeout ms reached. Closing connection" }
HTTP::respond 408 content $static::block_html Connection Close
TCP::close
}]
Trigger collection of the request payload
HTTP::collect $collect_length
if { $static::post_debug } { log local0. "[IP::client_addr]:[TCP::client_port]: Collecting $collect_length" }
}
}
}
when HTTP_REQUEST_DATA {
if { $static::post_debug } { log local0. "[IP::client_addr]:[TCP::client_port]: Collected [HTTP::payload length] bytes." }
Check if the 'after' ID exists
if {[info exists id]} {
If all the POST data has been received, cancel the connection closure
if { $static::post_debug } { log local0. "[IP::client_addr]:[TCP::client_port]: Canceling \$id: $id" }
after cancel $id
}
}
One way to test this is to use netcat from the LTM command line. Type: 'nc . Then type the POST request line, a Content-Length header, two enters and wait.
$ nc 10.1.0.15 80
POST / HTTP/1.0
Content-Length: 5000
HTTP/1.0 408 Request Time-out
Server: BigIP
Connection: close
Content-Length: 69
Your POST request is not being received quickly enough. Please retry.
The /var/log/ltm will show the debug logging:
< HTTP_REQUEST>: 10.1.0.1:56236: POST to /
< HTTP_REQUEST>: 10.1.0.1:56236: Content-Length: 5000, collecting 2048
< HTTP_REQUEST>: 10.1.0.1:56236: Collecting 2048
< HTTP_REQUEST>: 10.1.0.1:56236: 2000 ms reached. Closing connection
Recent Discussions
Related Content
DevCentral Quicklinks
* Getting Started on DevCentral
* Community Guidelines
* Community Terms of Use / EULA
* Community Ranking Explained
* Community Resources
* Contact the DevCentral Team
* Update MFA on account.f5.com
Discover DevCentral Connects