├── LICENSE ├── README.md ├── bro-pkg.meta ├── scripts ├── __load__.bro └── main.bro └── tests ├── Baseline └── http-stalling.test1 │ └── notice.log ├── Makefile ├── Traces └── slowloris_scan.pcap ├── btest.cfg └── http-stalling └── test1.bro /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018, Corelight, Inc 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | HTTP Stalling Detector 2 | ====================== 3 | 4 | HTTP stalling DoS attacks take advantage of an inability for webservers 5 | to determine if a remote client is just connected over a slow link or 6 | if the remote client is deliberately sending data very slowly to avoid 7 | the webserver doing a timeout and shutting down the connection to preserve 8 | local system resources. The behavior that is typically seen by attackers 9 | is to open a lot of connections a slowly dribble out data to prevent the 10 | webserver from timing out the connection. If a webserver is using a 11 | smallish worker pool, this resource exhaustion can be easy to reach. 12 | 13 | Many modern webservers have become more defensive about these type of 14 | resource exhaustion attacks, but it's still prudent to watch for the attacks 15 | and it's very plausible that a number of vulnerable web servers are still 16 | present in production networks. It's also possible that a webserver might 17 | be resistent to one form of the attack and still vulnerable to another. For 18 | example, a webserver might be resistent to data being sent slowly in POST body 19 | but vulnerable to data being sent slowly in a single HTTP header or multiple 20 | HTTP headers. This script is intended to catch the overall notion of data 21 | being sent too slowly in any part of the client request and should catch any 22 | of these attack styles. 23 | 24 | Installation 25 | ------------ 26 | 27 | This is easiest to install through the Bro package manager:: 28 | 29 | bro-pkg refresh 30 | bro-pkg install bro/corelight/http-stalling-detector 31 | 32 | Usage 33 | ----- 34 | 35 | The output from this script is a pair of notices: 36 | 37 | **HTTPStalling::Attacker** - This indicates that attacker performing an 38 | HTTP stalling attack was detected. 39 | 40 | **HTTPStalling::Victim** - This indicates that a particular host was 41 | targetted by one or more attackers. 42 | 43 | The notices are split into two like this because there is some software 44 | to do these attacks that can use proxies to spread out the attackers 45 | across many originating IP addresses. The concern is that if a single 46 | request is all that ever comes from an IP address, the attack would never 47 | be detected. By splitting into attackers and victims, the victim shows 48 | up very clearly even in the presence of this type of attempt at obfuscation. 49 | 50 | 51 | About 52 | ----- 53 | 54 | Written by Seth Hall 55 | 56 | -------------------------------------------------------------------------------- /bro-pkg.meta: -------------------------------------------------------------------------------- 1 | [package] 2 | description = Detect HTTP stalling attacks like slowloris. 3 | tags = http, DoS, attack, notice 4 | script_dir = scripts 5 | -------------------------------------------------------------------------------- /scripts/__load__.bro: -------------------------------------------------------------------------------- 1 | @load ./main 2 | -------------------------------------------------------------------------------- /scripts/main.bro: -------------------------------------------------------------------------------- 1 | @load base/frameworks/sumstats 2 | 3 | module HTTPStalling; 4 | 5 | export { 6 | redef enum Notice::Type += { 7 | ## A stalling-type HTTP DoS attack against a webserver was detected. 8 | Victim, 9 | ## A stalling-type HTTP DoS attacker was detected. 10 | Attacker, 11 | }; 12 | 13 | ## Value representing how much time is considered too long to start and 14 | ## complete and HTTP request. 15 | const too_much_client_delay = 10secs &redef; 16 | 17 | ## Number of suspicious requests from an attacker or to a victim to be 18 | ## considered an attack. 19 | const requests_threshold: double = 40.0 &redef; 20 | } 21 | 22 | redef record HTTP::Info += { 23 | stalling_last_client_data: time &optional; 24 | stalling_client_done: bool &default=F; 25 | }; 26 | 27 | event bro_init() 28 | { 29 | local r1: SumStats::Reducer = [$stream="http.stalling.attacker", $apply=set(SumStats::SUM)]; 30 | 31 | SumStats::create([$name="detect-http-stalling-attackers", 32 | $epoch=10min, 33 | $reducers=set(r1), 34 | $threshold_val(key: SumStats::Key, result: SumStats::Result) = 35 | { 36 | return result["http.stalling.attacker"]$num + 0.0; 37 | }, 38 | $threshold=requests_threshold, 39 | $threshold_crossed(key: SumStats::Key, result: SumStats::Result) = 40 | { 41 | local r = result["http.stalling.attacker"]; 42 | NOTICE([$note=Attacker, 43 | $msg="An HTTP stalling attacker was discovered!", 44 | $src=key$host, 45 | $identifier=cat(key$host)]); 46 | }]); 47 | 48 | 49 | local r2: SumStats::Reducer = [$stream="http.stalling.victim", $apply=set(SumStats::SUM)]; 50 | SumStats::create([$name="detect-http-stalling-victims", 51 | $epoch=10min, 52 | $reducers=set(r2), 53 | $threshold_val(key: SumStats::Key, result: SumStats::Result) = 54 | { 55 | return result["http.stalling.victim"]$num + 0.0; 56 | }, 57 | $threshold=requests_threshold, 58 | $threshold_crossed(key: SumStats::Key, result: SumStats::Result) = 59 | { 60 | local r = result["http.stalling.victim"]; 61 | NOTICE([$note=Victim, 62 | $msg="An HTTP stalling victim was discovered!", 63 | $src=key$host, 64 | $identifier=cat(key$host)]); 65 | }]); 66 | } 67 | 68 | event watch_for_request_finishing(http: HTTP::Info) 69 | { 70 | if ( ! connection_exists(http$id) ) 71 | return; 72 | 73 | if ( http$stalling_client_done ) 74 | return; 75 | 76 | # If a client body is being sent, allow for the full too_much_client_delay 77 | # interval between chunks of body data. 78 | if ( http?$stalling_last_client_data && 79 | network_time() - http$stalling_last_client_data < too_much_client_delay ) 80 | schedule too_much_client_delay { watch_for_request_finishing(http) }; 81 | 82 | SumStats::observe("http.stalling.attacker", [$host=http$id$orig_h], [$num=1]); 83 | SumStats::observe("http.stalling.victim", [$host=http$id$resp_h], [$num=1]); 84 | } 85 | 86 | event http_request(c: connection, method: string, original_URI: string, 87 | unescaped_URI: string, version: string) 88 | { 89 | schedule too_much_client_delay { watch_for_request_finishing(c$http) }; 90 | } 91 | 92 | event http_reply(c: connection, version: string, code: count, reason: string) 93 | { 94 | # Ignore 1xx intermediate responses. 95 | if ( code < 100 || code >= 200 ) 96 | c$http$stalling_client_done=T; 97 | } 98 | 99 | event http_message_done(c: connection, is_orig: bool, stat: http_message_stat) 100 | { 101 | if ( is_orig && c?$http ) 102 | c$http$stalling_client_done=T; 103 | } 104 | 105 | event http_entity_data(c: connection, is_orig: bool, length: count, data: string) 106 | { 107 | c$http$stalling_last_client_data = network_time(); 108 | } 109 | -------------------------------------------------------------------------------- /tests/Baseline/http-stalling.test1/notice.log: -------------------------------------------------------------------------------- 1 | #separator \x09 2 | #set_separator , 3 | #empty_field (empty) 4 | #unset_field - 5 | #path notice 6 | #open 2018-03-01-13-08-46 7 | #fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p fuid file_mime_type file_desc proto note msg sub src dst p n peer_descr actions suppress_for dropped remote_location.country_code remote_location.region remote_location.city remote_location.latitude remote_location.longitude 8 | #types time string addr port addr port string string string enum enum string string addr addr port count string set[enum] interval bool string string string double double 9 | 1246370313.235291 - - - - - - - - - HTTPStalling::Attacker An HTTP stalling attacker was discovered! - 172.16.65.2 - - - bro Notice::ACTION_LOG 3600.000000 F - - - - - 10 | 1246370313.235291 - - - - - - - - - HTTPStalling::Victim An HTTP stalling victim was discovered! - 172.16.65.5 - - - bro Notice::ACTION_LOG 3600.000000 F - - - - - 11 | #close 2018-03-01-13-08-46 12 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @btest 3 | -------------------------------------------------------------------------------- /tests/Traces/slowloris_scan.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corelight/http-stalling-detector/f7f680dd6abbfd0db2f6f80585d2d3a334791060/tests/Traces/slowloris_scan.pcap -------------------------------------------------------------------------------- /tests/btest.cfg: -------------------------------------------------------------------------------- 1 | [btest] 2 | TestDirs = http-stalling 3 | TmpDir = %(testbase)s/.tmp 4 | BaselineDir = %(testbase)s/Baseline 5 | IgnoreDirs = .svn CVS .tmp 6 | IgnoreFiles = *.tmp *.swp #* *.trace .DS_Store 7 | 8 | [environment] 9 | TZ=UTC 10 | LC_ALL=C 11 | TRACES=%(testbase)s/Traces 12 | TMPDIR=%(testbase)s/.tmp 13 | BRO_SEED_FILE=`bro-config --bro_dist`/testing/btest/random.seed 14 | TEST_DIFF_CANONIFIER=`bro-config --bro_dist`/testing/scripts/diff-canonifier 15 | -------------------------------------------------------------------------------- /tests/http-stalling/test1.bro: -------------------------------------------------------------------------------- 1 | # @TEST-EXEC: bro -r $TRACES/slowloris_scan.pcap ../../../scripts %INPUT 2 | # @TEST-EXEC: btest-diff notice.log 3 | --------------------------------------------------------------------------------