diff --git a/scripts/policy/protocols/ssl/heartbleed.bro b/scripts/policy/protocols/ssl/heartbleed.bro new file mode 100644 index 0000000000..dc38c66f73 --- /dev/null +++ b/scripts/policy/protocols/ssl/heartbleed.bro @@ -0,0 +1,114 @@ +module Heartbleed; + +# Please note - this is not well tested. Use at your own risk. + +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; + + heartbleed_detected: bool &default=F; + }; + +export { + redef enum Notice::Type += { + ## Indicates that a host performing a heartbleed attack. + 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. + SSL_Heartbeat_Odd_Length, + ## Indicates we saw many heartbeat requests without an reply. Might be an attack. + SSL_Heartbeat_Many_Requests + }; +} + +event ssl_heartbeat(c: connection, is_orig: bool, length: count, heartbeat_type: count, payload_length: count, payload: string) + { + if ( heartbeat_type == 1 ) + { + local checklength: count = (length<(3+16)) ? length : (length - 3 - 16); + + 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), + $conn=c, + $identifier=cat(c$uid, length, payload_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), + $conn=c, + $identifier=c$uid + ]); + } + } + +event ssl_encrypted_heartbeat(c: connection, is_orig: bool, length: count) + { + if ( is_orig ) + ++c$ssl$originator_heartbeats; + else + ++c$ssl$responder_heartbeats; + + 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), + $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 + ]); + + 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), + $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 + ]); + + 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), + $conn=c, + $n=length, + $identifier=cat(c$uid, length) + ]); + + if ( is_orig ) + { + if ( c$ssl?$last_responder_heartbeat_request_size ) + { + # server originated heartbeat. Ignore & continue + delete c$ssl$last_responder_heartbeat_request_size; + } + else + c$ssl$last_originator_heartbeat_request_size = length; + } + else + { + 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, c$ssl$last_originator_heartbeat_request_size), + $conn=c, + $identifier=c$uid # only throw once per connection + ]); + } + else if ( ! c$ssl?$last_originator_heartbeat_request_size ) + { + c$ssl$last_responder_heartbeat_request_size = length; + } + + if ( c$ssl?$last_originator_heartbeat_request_size ) + delete c$ssl$last_originator_heartbeat_request_size; + } + } diff --git a/scripts/site/local.bro b/scripts/site/local.bro index e1a3574424..bb2cc73a53 100644 --- a/scripts/site/local.bro +++ b/scripts/site/local.bro @@ -81,3 +81,5 @@ # Detect SHA1 sums in Team Cymru's Malware Hash Registry. @load frameworks/files/detect-MHR +# Load heartbleed detection. Only superficially tested, might contain bugs. +@load policy/protocols/ssl/heartbleed diff --git a/src/analyzer/protocol/ssl/events.bif b/src/analyzer/protocol/ssl/events.bif index 054d9c672f..a11e7bcc68 100644 --- a/src/analyzer/protocol/ssl/events.bif +++ b/src/analyzer/protocol/ssl/events.bif @@ -138,3 +138,7 @@ event ssl_alert%(c: connection, is_orig: bool, level: count, desc: count%); ## .. bro:see:: ssl_client_hello ssl_established ssl_extension ssl_server_hello ## ssl_alert event ssl_session_ticket_handshake%(c: connection, ticket_lifetime_hint: count, ticket: string%); + +event ssl_encrypted_heartbeat%(c: connection, is_orig: bool, length: count%); + +event ssl_heartbeat%(c: connection, is_orig: bool, length: count, heartbeat_type: count, payload_length: count, payload: string%); diff --git a/src/analyzer/protocol/ssl/ssl-analyzer.pac b/src/analyzer/protocol/ssl/ssl-analyzer.pac index 39388c448e..997faf0436 100644 --- a/src/analyzer/protocol/ssl/ssl-analyzer.pac +++ b/src/analyzer/protocol/ssl/ssl-analyzer.pac @@ -278,8 +278,21 @@ refine connection SSL_Conn += { bro_analyzer()->Conn()); } + if ( ${rec.content_type} == HEARTBEAT ) + BifEvent::generate_ssl_encrypted_heartbeat(bro_analyzer(), + bro_analyzer()->Conn(), ${rec.is_orig}, ${rec.length}); + return true; %} + + function proc_heartbeat(rec : SSLRecord, type: uint8, payload_length: uint16, data: bytestring) : bool + %{ + BifEvent::generate_ssl_heartbeat(bro_analyzer(), + bro_analyzer()->Conn(), ${rec.is_orig}, ${rec.length}, type, payload_length, + new StringVal(data.length(), (const char*) data.data())); + return true; + %} + }; #refine typeattr ChangeCipherSpec += &let { @@ -299,6 +312,10 @@ refine typeattr V2Error += &let { # proc : bool = $context.connection.proc_application_data(rec); #}; +refine typeattr Heartbeat += &let { + proc : bool = $context.connection.proc_heartbeat(rec, type, payload_length, data); +}; + refine typeattr ClientHello += &let { proc : bool = $context.connection.proc_client_hello(rec, client_version, gmt_unix_time, random_bytes, diff --git a/src/analyzer/protocol/ssl/ssl-defs.pac b/src/analyzer/protocol/ssl/ssl-defs.pac index c35fc56e85..23fa7abce5 100644 --- a/src/analyzer/protocol/ssl/ssl-defs.pac +++ b/src/analyzer/protocol/ssl/ssl-defs.pac @@ -12,6 +12,7 @@ enum ContentType { ALERT = 21, HANDSHAKE = 22, APPLICATION_DATA = 23, + HEARTBEAT = 24, V2_ERROR = 300, V2_CLIENT_HELLO = 301, V2_CLIENT_MASTER_KEY = 302, diff --git a/src/analyzer/protocol/ssl/ssl-protocol.pac b/src/analyzer/protocol/ssl/ssl-protocol.pac index 7ee71df352..f72f34e9cc 100644 --- a/src/analyzer/protocol/ssl/ssl-protocol.pac +++ b/src/analyzer/protocol/ssl/ssl-protocol.pac @@ -39,13 +39,13 @@ type SSLRecord(is_orig: bool) = record { $context.connection.determine_ssl_version(head0, head1, head2); content_type : int = case version of { - UNKNOWN_VERSION -> 0; + # UNKNOWN_VERSION -> 0; assume tls on unknown version SSLv20 -> head2+300; default -> head0; }; length : int = case version of { - UNKNOWN_VERSION -> 0; + # UNKNOWN_VERSION -> 0; assume tls on unknown version SSLv20 -> (((head0 & 0x7f) << 8) | head1) - 3; default -> (head3 << 8) | head4; }; @@ -62,6 +62,7 @@ type PlaintextRecord(rec: SSLRecord) = case rec.content_type of { CHANGE_CIPHER_SPEC -> ch_cipher : ChangeCipherSpec(rec); ALERT -> alert : Alert(rec); HANDSHAKE -> handshake : Handshake(rec); + HEARTBEAT -> heartbeat: Heartbeat(rec); APPLICATION_DATA -> app_data : ApplicationData(rec); V2_ERROR -> v2_error : V2Error(rec); V2_CLIENT_HELLO -> v2_client_hello : V2ClientHello(rec); @@ -164,6 +165,16 @@ type ApplicationData(rec: SSLRecord) = record { data : bytestring &restofdata &transient; }; +###################################################################### +# V3 Heartbeat +###################################################################### + +type Heartbeat(rec: SSLRecord) = record { + type : uint8; + payload_length : uint16; + data : bytestring &restofdata; +}; + ###################################################################### # Handshake Protocol (7.4.) ######################################################################