diff --git a/scripts/policy/protocols/ssl/heartbleed.bro b/scripts/policy/protocols/ssl/heartbleed.bro index 5c5333a7a2..63fc2e72c9 100644 --- a/scripts/policy/protocols/ssl/heartbleed.bro +++ b/scripts/policy/protocols/ssl/heartbleed.bro @@ -7,11 +7,11 @@ module Heartbleed; export { redef enum Notice::Type += { - ## Indicates that a host performing a heartbleed attack. + ## Indicates that a host performing a heartbleed attack or scan. SSL_Heartbeat_Attack, ## Indicates that a host performing a heartbleed attack was probably successful. SSL_Heartbeat_Attack_Success, - ## Indicates we saw heartbeat requests with odd length. Probably an attack. + ## Indicates we saw heartbeat requests with odd length. Probably an attack or scan. SSL_Heartbeat_Odd_Length, ## Indicates we saw many heartbeat requests without an reply. Might be an attack. SSL_Heartbeat_Many_Requests @@ -25,14 +25,76 @@ redef SSL::disable_analyzer_after_detection=F; redef record SSL::Info += { last_originator_heartbeat_request_size: count &optional; last_responder_heartbeat_request_size: count &optional; + originator_heartbeats: count &default=0; responder_heartbeats: count &default=0; + # Unencrypted connections - was an exploit attempt detected yet. heartbleed_detected: bool &default=F; - }; + + # Count number of appdata packages and bytes exchanged so far. + enc_appdata_packages: count &default=0; + enc_appdata_bytes: count &default=0; +}; + +# TLS content types: +const CHANGE_CIPHER_SPEC = 20; +const ALERT = 21; +const HANDSHAKE = 22; +const APPLICATION_DATA = 23; +const HEARTBEAT = 24; +const V2_ERROR = 300; +const V2_CLIENT_HELLO = 301; +const V2_CLIENT_MASTER_KEY = 302; +const V2_SERVER_HELLO = 304; + +type min_length: record { + cipher: pattern; + min_length: count; +}; + +global min_lengths: vector of min_length = vector(); +global min_lengths_tls11: vector of min_length = vector(); + +event bro_init() + { + # Minimum length a heartbeat packet must have for different cipher suites. + # Note - tls 1.1f and 1.0 have different lengths :( + # This should be all cipher suites usually supported by vulnerable servers. + min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_AES_256_GCM_SHA384$/, $min_length=43]; + min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_AES_128_GCM_SHA256$/, $min_length=43]; + min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_256_CBC_SHA384$/, $min_length=96]; + min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_256_CBC_SHA256$/, $min_length=80]; + min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_256_CBC_SHA$/, $min_length=64]; + min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_128_CBC_SHA256$/, $min_length=80]; + min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_128_CBC_SHA$/, $min_length=64]; + min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_3DES_EDE_CBC_SHA$/, $min_length=48]; + min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_SEED_CBC_SHA$/, $min_length=64]; + min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_IDEA_CBC_SHA$/, $min_length=48]; + min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_DES_CBC_SHA$/, $min_length=48]; + min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_DES40_CBC_SHA$/, $min_length=48]; + min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_RC4_128_SHA$/, $min_length=39]; + min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_RC4_128_MD5$/, $min_length=35]; + min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_RC4_40_MD5$/, $min_length=35]; + min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_RC2_CBC_40_MD5$/, $min_length=48]; + min_lengths[|min_lengths|] = [$cipher=/_256_CBC_SHA$/, $min_length=48]; + min_lengths[|min_lengths|] = [$cipher=/_128_CBC_SHA$/, $min_length=48]; + min_lengths[|min_lengths|] = [$cipher=/_3DES_EDE_CBC_SHA$/, $min_length=40]; + min_lengths[|min_lengths|] = [$cipher=/_SEED_CBC_SHA$/, $min_length=48]; + min_lengths[|min_lengths|] = [$cipher=/_IDEA_CBC_SHA$/, $min_length=40]; + min_lengths[|min_lengths|] = [$cipher=/_DES_CBC_SHA$/, $min_length=40]; + min_lengths[|min_lengths|] = [$cipher=/_DES40_CBC_SHA$/, $min_length=40]; + min_lengths[|min_lengths|] = [$cipher=/_RC4_128_SHA$/, $min_length=39]; + min_lengths[|min_lengths|] = [$cipher=/_RC4_128_MD5$/, $min_length=35]; + min_lengths[|min_lengths|] = [$cipher=/_RC4_40_MD5$/, $min_length=35]; + min_lengths[|min_lengths|] = [$cipher=/_RC2_CBC_40_MD5$/, $min_length=40]; + } event ssl_heartbeat(c: connection, is_orig: bool, length: count, heartbeat_type: count, payload_length: count, payload: string) { + if ( ! c?$ssl ) + return; + if ( heartbeat_type == 1 ) { local checklength: count = (length<(3+16)) ? length : (length - 3 - 16); @@ -40,18 +102,27 @@ event ssl_heartbeat(c: connection, is_orig: bool, length: count, heartbeat_type: if ( payload_length > checklength ) { c$ssl$heartbleed_detected = T; - NOTICE([$note=SSL_Heartbeat_Attack, - $msg=fmt("An TLS heartbleed attack was detected! Record length %d, payload length %d", length, payload_length), + NOTICE([$note=Heartbleed::SSL_Heartbeat_Attack, + $msg=fmt("An TLS heartbleed attack was detected! Record length %d. Payload length %d", length, payload_length), $conn=c, $identifier=cat(c$uid, length, payload_length) ]); } + else if ( is_orig ) + { + NOTICE([$note=Heartbleed::SSL_Heartbeat_Attack, + $msg=fmt("Heartbeat request before encryption. Probable Scan without exploit attempt. Message length: %d. Payload length: %d", length, payload_length), + $conn=c, + $n=length, + $identifier=cat(c$uid, length) + ]); + } } if ( heartbeat_type == 2 && c$ssl$heartbleed_detected ) { - NOTICE([$note=SSL_Heartbeat_Attack_Success, - $msg=fmt("An TLS heartbleed attack detected before was probably exploited. Transmitted payload length in first packet: %d", payload_length), + NOTICE([$note=Heartbleed::SSL_Heartbeat_Attack_Success, + $msg=fmt("An TLS heartbleed attack detected before was probably exploited. Message length: %d. Payload length: %d", length, payload_length), $conn=c, $identifier=c$uid ]); @@ -65,9 +136,26 @@ event ssl_encrypted_heartbeat(c: connection, is_orig: bool, length: count) else ++c$ssl$responder_heartbeats; + local duration = network_time() - c$start_time; + + if ( c$ssl$enc_appdata_packages == 0 ) + NOTICE([$note=SSL_Heartbeat_Attack, + $msg=fmt("Heartbeat before ciphertext. Probable attack or scan. Length: %d, is_orig: %d", length, is_orig), + $conn=c, + $n=length, + $identifier=fmt("%s%s", c$uid, "early") + ]); + else if ( duration < 1min ) + NOTICE([$note=SSL_Heartbeat_Attack, + $msg=fmt("Heartbeat within first minute. Possible attack or scan. Length: %d, is_orig: %d, time: %d", length, is_orig, duration), + $conn=c, + $n=length, + $identifier=fmt("%s%s", c$uid, "early") + ]); + if ( c$ssl$originator_heartbeats > c$ssl$responder_heartbeats + 3 ) NOTICE([$note=SSL_Heartbeat_Many_Requests, - $msg=fmt("Seeing more than 3 heartbeat requests without replies from server. Possible attack. Client count: %d, server count: %d", c$ssl$originator_heartbeats, c$ssl$responder_heartbeats), + $msg=fmt("More than 3 heartbeat requests without replies from server. Possible attack. Client count: %d, server count: %d", c$ssl$originator_heartbeats, c$ssl$responder_heartbeats), $conn=c, $n=(c$ssl$originator_heartbeats-c$ssl$responder_heartbeats), $identifier=fmt("%s%d", c$uid, c$ssl$responder_heartbeats/1000) # re-throw every 1000 heartbeats @@ -75,7 +163,7 @@ event ssl_encrypted_heartbeat(c: connection, is_orig: bool, length: count) if ( c$ssl$responder_heartbeats > c$ssl$originator_heartbeats + 3 ) NOTICE([$note=SSL_Heartbeat_Many_Requests, - $msg=fmt("Server is sending more heartbleed responsed than requests were seen. Possible attack. Client count: %d, server count: %d", c$ssl$originator_heartbeats, c$ssl$responder_heartbeats), + $msg=fmt("Server sending more heartbeat responses than requests seen. Possible attack. Client count: %d, server count: %d", c$ssl$originator_heartbeats, c$ssl$responder_heartbeats), $conn=c, $n=(c$ssl$originator_heartbeats-c$ssl$responder_heartbeats), $identifier=fmt("%s%d", c$uid, c$ssl$responder_heartbeats/1000) # re-throw every 1000 heartbeats @@ -83,12 +171,38 @@ event ssl_encrypted_heartbeat(c: connection, is_orig: bool, length: count) if ( is_orig && length < 19 ) NOTICE([$note=SSL_Heartbeat_Odd_Length, - $msg=fmt("Heartbeat message smaller than minimum required length. Probable attack. Message length: %d", length), + $msg=fmt("Heartbeat message smaller than minimum required length. Probable attack or scan. Message length: %d. Cipher: %s. Time: %f", length, c$ssl$cipher, duration), $conn=c, $n=length, - $identifier=cat(c$uid, length) + $identifier=fmt("%s-weak-%d", c$uid, length) ]); + # Examine request lengths based on used cipher... + local min_length_choice: vector of min_length; + if ( (c$ssl$version == "TLSv11") || (c$ssl$version == "TLSv12") ) # tls 1.1+ have different lengths for CBC + min_length_choice = min_lengths_tls11; + else + min_length_choice = min_lengths; + + for ( i in min_length_choice ) + { + if ( min_length_choice[i]$cipher in c$ssl$cipher ) + { + if ( length < min_length_choice[i]$min_length ) + { + NOTICE([$note=SSL_Heartbeat_Odd_Length, + $msg=fmt("Heartbeat message smaller than minimum required length. Probable attack. Message length: %d. Required length: %d. Cipher: %s. Cipher match: %s", length, min_length_choice[i]$min_length, c$ssl$cipher, min_length_choice[i]$cipher), + $conn=c, + $n=length, + $identifier=fmt("%s-weak-%d", c$uid, length) + ]); + } + + break; + } + + } + if ( is_orig ) { if ( c$ssl?$last_responder_heartbeat_request_size ) @@ -105,8 +219,8 @@ event ssl_encrypted_heartbeat(c: connection, is_orig: bool, length: count) if ( c$ssl?$last_originator_heartbeat_request_size && c$ssl$last_originator_heartbeat_request_size < length ) { NOTICE([$note=SSL_Heartbeat_Attack_Success, - $msg=fmt("An Encrypted TLS heartbleed attack was probably detected! First packet client record length %d, first packet server record length %d", - c$ssl$last_originator_heartbeat_request_size, length), + $msg=fmt("An encrypted TLS heartbleed attack was probably detected! First packet client record length %d, first packet server record length %d. Time: %f", + c$ssl$last_originator_heartbeat_request_size, length, duration), $conn=c, $identifier=c$uid # only throw once per connection ]); @@ -119,3 +233,14 @@ event ssl_encrypted_heartbeat(c: connection, is_orig: bool, length: count) delete c$ssl$last_originator_heartbeat_request_size; } } + +event ssl_encrypted_data(c: connection, is_orig: bool, content_type: count, length: count) + { + if ( content_type == HEARTBEAT ) + event ssl_encrypted_heartbeat(c, is_orig, length); + else if ( (content_type == APPLICATION_DATA) && (length > 0) ) + { + ++c$ssl$enc_appdata_packages; + c$ssl$enc_appdata_bytes += length; + } + } diff --git a/src/analyzer/protocol/ssl/ssl-analyzer.pac b/src/analyzer/protocol/ssl/ssl-analyzer.pac index 8d62245f6b..2c242eb4cb 100644 --- a/src/analyzer/protocol/ssl/ssl-analyzer.pac +++ b/src/analyzer/protocol/ssl/ssl-analyzer.pac @@ -366,7 +366,7 @@ refine connection SSL_Conn += { } BifEvent::generate_ssl_encrypted_data(bro_analyzer(), - bro_analyzer()->Conn(), ${rec.content_type}, ${rec.is_orig}, ${rec.length}); + bro_analyzer()->Conn(), ${rec.is_orig}, ${rec.content_type}, ${rec.length}); return true; %} diff --git a/testing/btest/Baseline/scripts.policy.protocols.ssl.heartbleed/notice-encrypted-short.log b/testing/btest/Baseline/scripts.policy.protocols.ssl.heartbleed/notice-encrypted-short.log new file mode 100644 index 0000000000..a3812210d1 --- /dev/null +++ b/testing/btest/Baseline/scripts.policy.protocols.ssl.heartbleed/notice-encrypted-short.log @@ -0,0 +1,12 @@ +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path notice +#open 2014-05-14-22-40-47 +#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 +#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 +1398954957.074664 CXWv6p3arKYeMETxOg 192.168.4.149 54233 162.219.2.166 4443 - - - tcp Heartbleed::SSL_Heartbeat_Attack Heartbeat before ciphertext. Probable attack or scan. Length: 32, is_orig: 1 - 192.168.4.149 162.219.2.166 4443 32 bro Notice::ACTION_LOG 3600.000000 F - - - - - +1398954957.074664 CXWv6p3arKYeMETxOg 192.168.4.149 54233 162.219.2.166 4443 - - - tcp Heartbleed::SSL_Heartbeat_Odd_Length Heartbeat message smaller than minimum required length. Probable attack. Message length: 32. Required length: 48. Cipher: TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA. Cipher match: /^?(_256_CBC_SHA$)$?/ - 192.168.4.149 162.219.2.166 4443 32 bro Notice::ACTION_LOG 3600.000000 F - - - - - +1398954957.145535 CXWv6p3arKYeMETxOg 192.168.4.149 54233 162.219.2.166 4443 - - - tcp Heartbleed::SSL_Heartbeat_Attack_Success An encrypted TLS heartbleed attack was probably detected! First packet client record length 32, first packet server record length 48. Time: 0.351035 - 192.168.4.149 162.219.2.166 4443 - bro Notice::ACTION_LOG 3600.000000 F - - - - - +#close 2014-05-14-22-40-47 diff --git a/testing/btest/Baseline/scripts.policy.protocols.ssl.heartbleed/notice-encrypted-success.log b/testing/btest/Baseline/scripts.policy.protocols.ssl.heartbleed/notice-encrypted-success.log new file mode 100644 index 0000000000..95960e7e5c --- /dev/null +++ b/testing/btest/Baseline/scripts.policy.protocols.ssl.heartbleed/notice-encrypted-success.log @@ -0,0 +1,12 @@ +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path notice +#open 2014-05-14-22-40-36 +#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 +#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 +1397169549.882425 CXWv6p3arKYeMETxOg 192.168.4.149 59676 107.170.241.107 443 - - - tcp Heartbleed::SSL_Heartbeat_Attack Heartbeat before ciphertext. Probable attack or scan. Length: 32, is_orig: 1 - 192.168.4.149 107.170.241.107 443 32 bro Notice::ACTION_LOG 3600.000000 F - - - - - +1397169549.882425 CXWv6p3arKYeMETxOg 192.168.4.149 59676 107.170.241.107 443 - - - tcp Heartbleed::SSL_Heartbeat_Odd_Length Heartbeat message smaller than minimum required length. Probable attack. Message length: 32. Required length: 48. Cipher: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA. Cipher match: /^?(_256_CBC_SHA$)$?/ - 192.168.4.149 107.170.241.107 443 32 bro Notice::ACTION_LOG 3600.000000 F - - - - - +1397169549.895057 CXWv6p3arKYeMETxOg 192.168.4.149 59676 107.170.241.107 443 - - - tcp Heartbleed::SSL_Heartbeat_Attack_Success An encrypted TLS heartbleed attack was probably detected! First packet client record length 32, first packet server record length 16416. Time: 0.035413 - 192.168.4.149 107.170.241.107 443 - bro Notice::ACTION_LOG 3600.000000 F - - - - - +#close 2014-05-14-22-40-37 diff --git a/testing/btest/Baseline/scripts.policy.protocols.ssl.heartbleed/notice-encrypted.log b/testing/btest/Baseline/scripts.policy.protocols.ssl.heartbleed/notice-encrypted.log index dfe9dcec74..db96ffeeaf 100644 --- a/testing/btest/Baseline/scripts.policy.protocols.ssl.heartbleed/notice-encrypted.log +++ b/testing/btest/Baseline/scripts.policy.protocols.ssl.heartbleed/notice-encrypted.log @@ -3,8 +3,8 @@ #empty_field (empty) #unset_field - #path notice -#open 2014-04-30-19-34-39 +#open 2014-05-14-22-40-26 #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 #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 -1397169549.895057 CXWv6p3arKYeMETxOg 192.168.4.149 59676 107.170.241.107 443 - - - tcp Heartbleed::SSL_Heartbeat_Attack_Success An Encrypted TLS heartbleed attack was probably detected! First packet client record length 32, first packet server record length 16416 - 192.168.4.149 107.170.241.107 443 - bro Notice::ACTION_LOG 3600.000000 F - - - - - -#close 2014-04-30-19-34-39 +1400106542.810248 CXWv6p3arKYeMETxOg 54.221.166.250 56323 162.219.2.166 443 - - - tcp Heartbleed::SSL_Heartbeat_Attack Heartbeat before ciphertext. Probable attack or scan. Length: 86, is_orig: 1 - 54.221.166.250 162.219.2.166 443 86 bro Notice::ACTION_LOG 3600.000000 F - - - - - +#close 2014-05-14-22-40-27 diff --git a/testing/btest/Baseline/scripts.policy.protocols.ssl.heartbleed/notice-heartbleed-success.log b/testing/btest/Baseline/scripts.policy.protocols.ssl.heartbleed/notice-heartbleed-success.log index 9722e20655..d96ddd42e1 100644 --- a/testing/btest/Baseline/scripts.policy.protocols.ssl.heartbleed/notice-heartbleed-success.log +++ b/testing/btest/Baseline/scripts.policy.protocols.ssl.heartbleed/notice-heartbleed-success.log @@ -3,9 +3,9 @@ #empty_field (empty) #unset_field - #path notice -#open 2014-04-24-18-30-54 +#open 2014-05-14-22-40-19 #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 #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 -1396976220.863714 CXWv6p3arKYeMETxOg 173.203.79.216 41459 107.170.241.107 443 - - - tcp Heartbleed::SSL_Heartbeat_Attack An TLS heartbleed attack was detected! Record length 16368, payload length 16365 - 173.203.79.216 107.170.241.107 443 - bro Notice::ACTION_LOG 3600.000000 F - - - - - -1396976220.918017 CXWv6p3arKYeMETxOg 173.203.79.216 41459 107.170.241.107 443 - - - tcp Heartbleed::SSL_Heartbeat_Attack_Success An TLS heartbleed attack detected before was probably exploited. Transmitted payload length in first packet: 16365 - 173.203.79.216 107.170.241.107 443 - bro Notice::ACTION_LOG 3600.000000 F - - - - - -#close 2014-04-24-18-30-54 +1396976220.863714 CXWv6p3arKYeMETxOg 173.203.79.216 41459 107.170.241.107 443 - - - tcp Heartbleed::SSL_Heartbeat_Attack An TLS heartbleed attack was detected! Record length 16368. Payload length 16365 - 173.203.79.216 107.170.241.107 443 - bro Notice::ACTION_LOG 3600.000000 F - - - - - +1396976220.918017 CXWv6p3arKYeMETxOg 173.203.79.216 41459 107.170.241.107 443 - - - tcp Heartbleed::SSL_Heartbeat_Attack_Success An TLS heartbleed attack detected before was probably exploited. Message length: 16384. Payload length: 16365 - 173.203.79.216 107.170.241.107 443 - bro Notice::ACTION_LOG 3600.000000 F - - - - - +#close 2014-05-14-22-40-19 diff --git a/testing/btest/Traces/tls/heartbleed-encrypted-short.pcap b/testing/btest/Traces/tls/heartbleed-encrypted-short.pcap new file mode 100644 index 0000000000..91942d52bb Binary files /dev/null and b/testing/btest/Traces/tls/heartbleed-encrypted-short.pcap differ diff --git a/testing/btest/Traces/tls/heartbleed-encrypted.pcap b/testing/btest/Traces/tls/heartbleed-encrypted.pcap new file mode 100644 index 0000000000..dc32d689a2 Binary files /dev/null and b/testing/btest/Traces/tls/heartbleed-encrypted.pcap differ diff --git a/testing/btest/scripts/policy/protocols/ssl/heartbleed.bro b/testing/btest/scripts/policy/protocols/ssl/heartbleed.bro index 4a980bb895..52137adbd0 100644 --- a/testing/btest/scripts/policy/protocols/ssl/heartbleed.bro +++ b/testing/btest/scripts/policy/protocols/ssl/heartbleed.bro @@ -6,8 +6,16 @@ # @TEST-EXEC: mv notice.log notice-heartbleed-success.log # @TEST-EXEC: btest-diff notice-heartbleed-success.log -# @TEST-EXEC: bro -C -r $TRACES/tls/heartbleed-encrypted-success.pcap %INPUT +# @TEST-EXEC: bro -C -r $TRACES/tls/heartbleed-encrypted.pcap %INPUT # @TEST-EXEC: mv notice.log notice-encrypted.log # @TEST-EXEC: btest-diff notice-encrypted.log +# @TEST-EXEC: bro -C -r $TRACES/tls/heartbleed-encrypted-success.pcap %INPUT +# @TEST-EXEC: mv notice.log notice-encrypted-success.log +# @TEST-EXEC: btest-diff notice-encrypted-success.log + +# @TEST-EXEC: bro -C -r $TRACES/tls/heartbleed-encrypted-short.pcap %INPUT +# @TEST-EXEC: mv notice.log notice-encrypted-short.log +# @TEST-EXEC: btest-diff notice-encrypted-short.log + @load protocols/ssl/heartbleed