From ffd4711a41ba0e9ea0f8cfd3097aadbe68912eb4 Mon Sep 17 00:00:00 2001 From: Bernhard Amann Date: Tue, 8 Apr 2014 07:41:08 -0700 Subject: [PATCH 01/17] Throw new event for heartbeat messages. Not tested. --- src/analyzer/protocol/ssl/events.bif | 2 ++ src/analyzer/protocol/ssl/ssl-analyzer.pac | 17 +++++++++++++++++ src/analyzer/protocol/ssl/ssl-defs.pac | 1 + src/analyzer/protocol/ssl/ssl-protocol.pac | 11 +++++++++++ 4 files changed, 31 insertions(+) diff --git a/src/analyzer/protocol/ssl/events.bif b/src/analyzer/protocol/ssl/events.bif index 054d9c672f..c85e911ee8 100644 --- a/src/analyzer/protocol/ssl/events.bif +++ b/src/analyzer/protocol/ssl/events.bif @@ -138,3 +138,5 @@ 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_heartbeat%(c: connection, length: count%); diff --git a/src/analyzer/protocol/ssl/ssl-analyzer.pac b/src/analyzer/protocol/ssl/ssl-analyzer.pac index 49104fa549..e6ea1628a1 100644 --- a/src/analyzer/protocol/ssl/ssl-analyzer.pac +++ b/src/analyzer/protocol/ssl/ssl-analyzer.pac @@ -306,6 +306,10 @@ refine connection SSL_Conn += { function proc_ciphertext_record(rec : SSLRecord) : bool %{ + if ( ${rec.content_type} == HEARTBEAT ) + BifEvent::generate_ssl_heartbeat(bro_analyzer(), + bro_analyzer()->Conn(), ${rec.length}); + if ( state_ == STATE_TRACK_LOST ) bro_analyzer()->ProtocolViolation(fmt("unexpected ciphertext record from %s in state %s", orig_label(${rec.is_orig}).c_str(), @@ -320,6 +324,15 @@ refine connection SSL_Conn += { return true; %} + + function proc_heartbeat(rec : SSLRecord) : bool + %{ + BifEvent::generate_ssl_heartbeat(bro_analyzer(), + bro_analyzer()->Conn(), ${rec.length}); + + return true; + %} + }; refine typeattr ChangeCipherSpec += &let { @@ -339,6 +352,10 @@ refine typeattr ApplicationData += &let { proc : bool = $context.connection.proc_application_data(rec); }; +refine typeattr Heartbeat += &let { + proc : bool = $context.connection.proc_heartbeat(rec); +}; + 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 9368122eaa..c12130abf8 100644 --- a/src/analyzer/protocol/ssl/ssl-protocol.pac +++ b/src/analyzer/protocol/ssl/ssl-protocol.pac @@ -63,6 +63,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); @@ -225,6 +226,16 @@ type ApplicationData(rec: SSLRecord) = record { data : bytestring &restofdata &transient; }; +###################################################################### +# V3 Heartbeat +###################################################################### + +# Heartbeats should basically always be encrypted, so we should not +# reach this point. +type Heartbeat(rec: SSLRecord) = record { + data : bytestring &restofdata &transient; +}; + ###################################################################### # Handshake Protocol (7.4.) ###################################################################### From 902d52e261069addbf6647537b1a5db9a171f79d Mon Sep 17 00:00:00 2001 From: Bernhard Amann Date: Tue, 8 Apr 2014 08:43:38 -0700 Subject: [PATCH 02/17] add is_orig to heartbeat event --- src/analyzer/protocol/ssl/events.bif | 2 +- src/analyzer/protocol/ssl/ssl-analyzer.pac | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/analyzer/protocol/ssl/events.bif b/src/analyzer/protocol/ssl/events.bif index c85e911ee8..3f780bdd60 100644 --- a/src/analyzer/protocol/ssl/events.bif +++ b/src/analyzer/protocol/ssl/events.bif @@ -139,4 +139,4 @@ event ssl_alert%(c: connection, is_orig: bool, level: count, desc: count%); ## ssl_alert event ssl_session_ticket_handshake%(c: connection, ticket_lifetime_hint: count, ticket: string%); -event ssl_heartbeat%(c: connection, length: count%); +event ssl_heartbeat%(c: connection, is_orig: bool, length: count%); diff --git a/src/analyzer/protocol/ssl/ssl-analyzer.pac b/src/analyzer/protocol/ssl/ssl-analyzer.pac index e6ea1628a1..1730ce8ce5 100644 --- a/src/analyzer/protocol/ssl/ssl-analyzer.pac +++ b/src/analyzer/protocol/ssl/ssl-analyzer.pac @@ -308,7 +308,7 @@ refine connection SSL_Conn += { %{ if ( ${rec.content_type} == HEARTBEAT ) BifEvent::generate_ssl_heartbeat(bro_analyzer(), - bro_analyzer()->Conn(), ${rec.length}); + bro_analyzer()->Conn(), ${rec.is_orig}, ${rec.length}); if ( state_ == STATE_TRACK_LOST ) bro_analyzer()->ProtocolViolation(fmt("unexpected ciphertext record from %s in state %s", @@ -328,7 +328,7 @@ refine connection SSL_Conn += { function proc_heartbeat(rec : SSLRecord) : bool %{ BifEvent::generate_ssl_heartbeat(bro_analyzer(), - bro_analyzer()->Conn(), ${rec.length}); + bro_analyzer()->Conn(), ${rec.is_orig}, ${rec.length}); return true; %} From 018735a5744d8a82bbd7cee48fd828b4018d257f Mon Sep 17 00:00:00 2001 From: Bernhard Amann Date: Tue, 8 Apr 2014 09:49:00 -0700 Subject: [PATCH 03/17] default to TLS when not being able to determine version --- src/analyzer/protocol/ssl/ssl-protocol.pac | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/analyzer/protocol/ssl/ssl-protocol.pac b/src/analyzer/protocol/ssl/ssl-protocol.pac index c12130abf8..944f081060 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; }; From 335a30b08f10ecc6243182d71f786ac104f31897 Mon Sep 17 00:00:00 2001 From: Bernhard Amann Date: Tue, 8 Apr 2014 11:03:12 -0700 Subject: [PATCH 04/17] detect and alert on simple case of heartbleed --- scripts/policy/protocols/ssl/heartbleed.bro | 44 +++++++++++++++++++++ src/analyzer/protocol/ssl/events.bif | 4 +- src/analyzer/protocol/ssl/ssl-analyzer.pac | 8 ++-- src/analyzer/protocol/ssl/ssl-protocol.pac | 4 +- 4 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 scripts/policy/protocols/ssl/heartbleed.bro diff --git a/scripts/policy/protocols/ssl/heartbleed.bro b/scripts/policy/protocols/ssl/heartbleed.bro new file mode 100644 index 0000000000..b3f81034b0 --- /dev/null +++ b/scripts/policy/protocols/ssl/heartbleed.bro @@ -0,0 +1,44 @@ +module Heartbleed; + +redef record SSL::Info += { +# last_originator_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, + }; +} + +event ssl_heartbeat(c: connection, is_orig: bool, length: count, heartbeat_type: count, payload_length: count) + { + 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="An TLS heartbleed attack was detected!", + $conn=c + ]); + } + } + + if ( heartbeat_type == 2 && c$ssl$heartbleed_detected ) + { + NOTICE([$note=SSL_Heartbeat_Attack_Success, + $msg="An TLS heartbleed attack was detected and probably exploited", + $conn=c + ]); + } + } diff --git a/src/analyzer/protocol/ssl/events.bif b/src/analyzer/protocol/ssl/events.bif index 3f780bdd60..e720089f45 100644 --- a/src/analyzer/protocol/ssl/events.bif +++ b/src/analyzer/protocol/ssl/events.bif @@ -139,4 +139,6 @@ event ssl_alert%(c: connection, is_orig: bool, level: count, desc: count%); ## ssl_alert event ssl_session_ticket_handshake%(c: connection, ticket_lifetime_hint: count, ticket: string%); -event ssl_heartbeat%(c: connection, is_orig: bool, length: count%); +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%); diff --git a/src/analyzer/protocol/ssl/ssl-analyzer.pac b/src/analyzer/protocol/ssl/ssl-analyzer.pac index 1730ce8ce5..d0ac88a5a1 100644 --- a/src/analyzer/protocol/ssl/ssl-analyzer.pac +++ b/src/analyzer/protocol/ssl/ssl-analyzer.pac @@ -307,7 +307,7 @@ refine connection SSL_Conn += { function proc_ciphertext_record(rec : SSLRecord) : bool %{ if ( ${rec.content_type} == HEARTBEAT ) - BifEvent::generate_ssl_heartbeat(bro_analyzer(), + BifEvent::generate_ssl_encrypted_heartbeat(bro_analyzer(), bro_analyzer()->Conn(), ${rec.is_orig}, ${rec.length}); if ( state_ == STATE_TRACK_LOST ) @@ -325,10 +325,10 @@ refine connection SSL_Conn += { return true; %} - function proc_heartbeat(rec : SSLRecord) : bool + function proc_heartbeat(rec : SSLRecord, type: uint8, payload_length: uint16) : bool %{ BifEvent::generate_ssl_heartbeat(bro_analyzer(), - bro_analyzer()->Conn(), ${rec.is_orig}, ${rec.length}); + bro_analyzer()->Conn(), ${rec.is_orig}, ${rec.length}, type, payload_length); return true; %} @@ -353,7 +353,7 @@ refine typeattr ApplicationData += &let { }; refine typeattr Heartbeat += &let { - proc : bool = $context.connection.proc_heartbeat(rec); + proc : bool = $context.connection.proc_heartbeat(rec, type, payload_length); }; refine typeattr ClientHello += &let { diff --git a/src/analyzer/protocol/ssl/ssl-protocol.pac b/src/analyzer/protocol/ssl/ssl-protocol.pac index 944f081060..acded2dbf9 100644 --- a/src/analyzer/protocol/ssl/ssl-protocol.pac +++ b/src/analyzer/protocol/ssl/ssl-protocol.pac @@ -230,9 +230,9 @@ type ApplicationData(rec: SSLRecord) = record { # V3 Heartbeat ###################################################################### -# Heartbeats should basically always be encrypted, so we should not -# reach this point. type Heartbeat(rec: SSLRecord) = record { + type : uint8; + payload_length : uint16; data : bytestring &restofdata &transient; }; From c41810a33780b1263f2e8a1b6939d398a267fa0f Mon Sep 17 00:00:00 2001 From: Bernhard Amann Date: Tue, 8 Apr 2014 11:19:30 -0700 Subject: [PATCH 05/17] polish script and probably detect encrypted attacks too. --- scripts/policy/protocols/ssl/heartbleed.bro | 65 +++++++++++++++++++-- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/scripts/policy/protocols/ssl/heartbleed.bro b/scripts/policy/protocols/ssl/heartbleed.bro index b3f81034b0..c9a9622e2c 100644 --- a/scripts/policy/protocols/ssl/heartbleed.bro +++ b/scripts/policy/protocols/ssl/heartbleed.bro @@ -1,9 +1,11 @@ module Heartbleed; redef record SSL::Info += { -# last_originator_heartbeat_request_size: count &optional; -# originator_heartbeats: count &default=0; -# responder_heartbeats: count &default=0; + 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; }; @@ -11,8 +13,14 @@ 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. + ## Indicates that a host performing a heartbleed attack was successful. SSL_Heartbeat_Attack_Success, + ## Indivcates that a host performing a heartbleed attack after encryption was started was probably successful + SSL_Heartbeat_Encrypted_Attack_Success, + ## Indicates we saw heartbeet 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 }; } @@ -20,7 +28,6 @@ event ssl_heartbeat(c: connection, is_orig: bool, length: count, heartbeat_type: { if ( heartbeat_type == 1 ) { - local checklength: count = (length<(3+16)) ? length : (length - 3 - 16); @@ -42,3 +49,51 @@ event ssl_heartbeat(c: connection, is_orig: bool, length: count, heartbeat_type: ]); } } + +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="Seeing more than 3 heartbeat requests without replies from server. Possible attack?", + $conn=c + ]); + + if ( is_orig && length < 19 ) + NOTICE([$note=SSL_Heartbeat_Odd_Length, + $msg="Heartbeat message smaller than minimum length. Probable attack.", + $conn=c + ]); + + 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_Encrypted_Attack_Success, + $msg="An Encrypted TLS heartbleed attack was probably detected!", + $conn=c + ]); + } + 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; + } + } From 4d33bdbb1ebe531a238803ef612395d8a39fc6e8 Mon Sep 17 00:00:00 2001 From: Bernhard Amann Date: Tue, 8 Apr 2014 11:28:13 -0700 Subject: [PATCH 06/17] fix tabs. --- scripts/policy/protocols/ssl/heartbleed.bro | 146 ++++++++++---------- 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/scripts/policy/protocols/ssl/heartbleed.bro b/scripts/policy/protocols/ssl/heartbleed.bro index c9a9622e2c..7089758e93 100644 --- a/scripts/policy/protocols/ssl/heartbleed.bro +++ b/scripts/policy/protocols/ssl/heartbleed.bro @@ -6,94 +6,94 @@ redef record SSL::Info += { originator_heartbeats: count &default=0; responder_heartbeats: count &default=0; - heartbleed_detected: bool &default=F; + 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 successful. - SSL_Heartbeat_Attack_Success, - ## Indivcates that a host performing a heartbleed attack after encryption was started was probably successful - SSL_Heartbeat_Encrypted_Attack_Success, - ## Indicates we saw heartbeet 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 - }; + ## Indicates that a host performing a heartbleed attack was successful. + SSL_Heartbeat_Attack_Success, + ## Indivcates that a host performing a heartbleed attack after encryption was started was probably successful + SSL_Heartbeat_Encrypted_Attack_Success, + ## Indicates we saw heartbeet 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) - { - if ( heartbeat_type == 1 ) - { - local checklength: count = (length<(3+16)) ? length : (length - 3 - 16); + { + 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="An TLS heartbleed attack was detected!", - $conn=c - ]); - } - } + if ( payload_length > checklength ) + { + c$ssl$heartbleed_detected = T; + NOTICE([$note=SSL_Heartbeat_Attack, + $msg="An TLS heartbleed attack was detected!", + $conn=c + ]); + } + } - if ( heartbeat_type == 2 && c$ssl$heartbleed_detected ) - { - NOTICE([$note=SSL_Heartbeat_Attack_Success, - $msg="An TLS heartbleed attack was detected and probably exploited", - $conn=c - ]); - } - } + if ( heartbeat_type == 2 && c$ssl$heartbleed_detected ) + { + NOTICE([$note=SSL_Heartbeat_Attack_Success, + $msg="An TLS heartbleed attack was detected and probably exploited", + $conn=c + ]); + } + } event ssl_encrypted_heartbeat(c: connection, is_orig: bool, length: count) - { - if ( is_orig ) - ++c$ssl$originator_heartbeats; - else - ++c$ssl$responder_heartbeats; + { + 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="Seeing more than 3 heartbeat requests without replies from server. Possible attack?", - $conn=c - ]); + if ( c$ssl$originator_heartbeats > c$ssl$responder_heartbeats + 3 ) + NOTICE([$note=SSL_Heartbeat_Many_Requests, + $msg="Seeing more than 3 heartbeat requests without replies from server. Possible attack?", + $conn=c + ]); - if ( is_orig && length < 19 ) - NOTICE([$note=SSL_Heartbeat_Odd_Length, - $msg="Heartbeat message smaller than minimum length. Probable attack.", - $conn=c - ]); + if ( is_orig && length < 19 ) + NOTICE([$note=SSL_Heartbeat_Odd_Length, + $msg="Heartbeat message smaller than minimum length. Probable attack.", + $conn=c + ]); - 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_Encrypted_Attack_Success, - $msg="An Encrypted TLS heartbleed attack was probably detected!", - $conn=c - ]); - } - else if ( ! c$ssl?$last_originator_heartbeat_request_size ) - { - c$ssl$last_responder_heartbeat_request_size = 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_Encrypted_Attack_Success, + $msg="An Encrypted TLS heartbleed attack was probably detected!", + $conn=c + ]); + } + 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; - } - } + if ( c$ssl?$last_originator_heartbeat_request_size ) + delete c$ssl$last_originator_heartbeat_request_size; + } + } From cb87f834f9360aae450f778b0a9b49cee4102a4e Mon Sep 17 00:00:00 2001 From: Bernhard Amann Date: Tue, 8 Apr 2014 11:40:48 -0700 Subject: [PATCH 07/17] make tls heartbeat messages a bit better. --- scripts/policy/protocols/ssl/heartbleed.bro | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/policy/protocols/ssl/heartbleed.bro b/scripts/policy/protocols/ssl/heartbleed.bro index 7089758e93..0e5abc7ab3 100644 --- a/scripts/policy/protocols/ssl/heartbleed.bro +++ b/scripts/policy/protocols/ssl/heartbleed.bro @@ -30,12 +30,11 @@ event ssl_heartbeat(c: connection, is_orig: bool, length: count, heartbeat_type: { 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="An TLS heartbleed attack was detected!", + $msg=fmt("An TLS heartbleed attack was detected! Record length %d, payload length %d", length, payload_length), $conn=c ]); } @@ -60,13 +59,15 @@ event ssl_encrypted_heartbeat(c: connection, is_orig: bool, length: count) if ( c$ssl$originator_heartbeats > c$ssl$responder_heartbeats + 3 ) NOTICE([$note=SSL_Heartbeat_Many_Requests, $msg="Seeing more than 3 heartbeat requests without replies from server. Possible attack?", - $conn=c + $conn=c, + $n=(c$ssl$originator_heartbeats-c$ssl$responder_heartbeats) ]); if ( is_orig && length < 19 ) NOTICE([$note=SSL_Heartbeat_Odd_Length, $msg="Heartbeat message smaller than minimum length. Probable attack.", - $conn=c + $conn=c, + $n=length ]); if ( is_orig ) From f2c2da92c6505a2a979051e553ff4043f1317a0e Mon Sep 17 00:00:00 2001 From: Bernhard Amann Date: Tue, 8 Apr 2014 11:53:01 -0700 Subject: [PATCH 08/17] add to local.bro, add disclaimer --- scripts/policy/protocols/ssl/heartbleed.bro | 2 ++ scripts/site/local.bro | 2 ++ 2 files changed, 4 insertions(+) diff --git a/scripts/policy/protocols/ssl/heartbleed.bro b/scripts/policy/protocols/ssl/heartbleed.bro index 0e5abc7ab3..d66ff4df2a 100644 --- a/scripts/policy/protocols/ssl/heartbleed.bro +++ b/scripts/policy/protocols/ssl/heartbleed.bro @@ -1,5 +1,7 @@ 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; 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 From 2942a26280866c5ecad13ce8d7a63a706dc9e1af Mon Sep 17 00:00:00 2001 From: Bernhard Amann Date: Tue, 8 Apr 2014 12:44:51 -0700 Subject: [PATCH 09/17] also extract payload data in ssl_heartbeat --- scripts/policy/protocols/ssl/heartbleed.bro | 2 +- src/analyzer/protocol/ssl/events.bif | 2 +- src/analyzer/protocol/ssl/ssl-analyzer.pac | 8 ++++---- src/analyzer/protocol/ssl/ssl-protocol.pac | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/policy/protocols/ssl/heartbleed.bro b/scripts/policy/protocols/ssl/heartbleed.bro index d66ff4df2a..19728b1d0c 100644 --- a/scripts/policy/protocols/ssl/heartbleed.bro +++ b/scripts/policy/protocols/ssl/heartbleed.bro @@ -26,7 +26,7 @@ export { }; } -event ssl_heartbeat(c: connection, is_orig: bool, length: count, heartbeat_type: count, payload_length: count) +event ssl_heartbeat(c: connection, is_orig: bool, length: count, heartbeat_type: count, payload_length: count, payload: string) { if ( heartbeat_type == 1 ) { diff --git a/src/analyzer/protocol/ssl/events.bif b/src/analyzer/protocol/ssl/events.bif index e720089f45..a11e7bcc68 100644 --- a/src/analyzer/protocol/ssl/events.bif +++ b/src/analyzer/protocol/ssl/events.bif @@ -141,4 +141,4 @@ event ssl_session_ticket_handshake%(c: connection, ticket_lifetime_hint: count, 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%); +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 d0ac88a5a1..29240161d1 100644 --- a/src/analyzer/protocol/ssl/ssl-analyzer.pac +++ b/src/analyzer/protocol/ssl/ssl-analyzer.pac @@ -325,11 +325,11 @@ refine connection SSL_Conn += { return true; %} - function proc_heartbeat(rec : SSLRecord, type: uint8, payload_length: uint16) : bool + 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); - + bro_analyzer()->Conn(), ${rec.is_orig}, ${rec.length}, type, payload_length, + new StringVal(data.length(), (const char*) data.data())); return true; %} @@ -353,7 +353,7 @@ refine typeattr ApplicationData += &let { }; refine typeattr Heartbeat += &let { - proc : bool = $context.connection.proc_heartbeat(rec, type, payload_length); + proc : bool = $context.connection.proc_heartbeat(rec, type, payload_length, data); }; refine typeattr ClientHello += &let { diff --git a/src/analyzer/protocol/ssl/ssl-protocol.pac b/src/analyzer/protocol/ssl/ssl-protocol.pac index acded2dbf9..f8645410dc 100644 --- a/src/analyzer/protocol/ssl/ssl-protocol.pac +++ b/src/analyzer/protocol/ssl/ssl-protocol.pac @@ -233,7 +233,7 @@ type ApplicationData(rec: SSLRecord) = record { type Heartbeat(rec: SSLRecord) = record { type : uint8; payload_length : uint16; - data : bytestring &restofdata &transient; + data : bytestring &restofdata; }; ###################################################################### From 2414aaf4bb9e1a9ba9aafbf748f2905311f8cd8e Mon Sep 17 00:00:00 2001 From: Bernhard Amann Date: Tue, 8 Apr 2014 21:57:37 -0700 Subject: [PATCH 10/17] enable detection of encrypted heartbleeds. --- scripts/policy/protocols/ssl/heartbleed.bro | 2 +- src/analyzer/protocol/ssl/ssl-protocol.pac | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/policy/protocols/ssl/heartbleed.bro b/scripts/policy/protocols/ssl/heartbleed.bro index 19728b1d0c..0049c4c51f 100644 --- a/scripts/policy/protocols/ssl/heartbleed.bro +++ b/scripts/policy/protocols/ssl/heartbleed.bro @@ -84,7 +84,7 @@ event ssl_encrypted_heartbeat(c: connection, is_orig: bool, length: count) } else { - if ( c$ssl?$last_originator_heartbeat_request_size && c$ssl$last_originator_heartbeat_request_size > length ) + if ( c$ssl?$last_originator_heartbeat_request_size && c$ssl$last_originator_heartbeat_request_size < length ) { NOTICE([$note=SSL_Heartbeat_Encrypted_Attack_Success, $msg="An Encrypted TLS heartbleed attack was probably detected!", diff --git a/src/analyzer/protocol/ssl/ssl-protocol.pac b/src/analyzer/protocol/ssl/ssl-protocol.pac index f8645410dc..b8e5c9f624 100644 --- a/src/analyzer/protocol/ssl/ssl-protocol.pac +++ b/src/analyzer/protocol/ssl/ssl-protocol.pac @@ -52,7 +52,7 @@ type SSLRecord(is_orig: bool) = record { }; type RecordText(rec: SSLRecord) = case $context.connection.state() of { - STATE_ABBREV_SERVER_ENCRYPTED, STATE_CLIENT_ENCRYPTED, + STATE_ABBREV_SERVER_ENCRYPTED, STATE_CLIENT_ENCRYPTED, STATE_CLIENT_FINISHED, STATE_COMM_ENCRYPTED, STATE_CONN_ESTABLISHED -> ciphertext : CiphertextRecord(rec); default From cc838c6b2e1d20bb8ae07bec91840eca13285dc6 Mon Sep 17 00:00:00 2001 From: Bernhard Amann Date: Thu, 10 Apr 2014 15:11:43 -0700 Subject: [PATCH 11/17] rip out state handline from ssl analyzer. still seems to work, but basically untested. --- src/analyzer/protocol/ssl/ssl-analyzer.pac | 79 ++----- src/analyzer/protocol/ssl/ssl-protocol.pac | 249 ++++----------------- 2 files changed, 55 insertions(+), 273 deletions(-) diff --git a/src/analyzer/protocol/ssl/ssl-analyzer.pac b/src/analyzer/protocol/ssl/ssl-analyzer.pac index 49104fa549..39388c448e 100644 --- a/src/analyzer/protocol/ssl/ssl-analyzer.pac +++ b/src/analyzer/protocol/ssl/ssl-analyzer.pac @@ -107,25 +107,6 @@ refine connection SSL_Conn += { %cleanup{ %} - function proc_change_cipher_spec(rec: SSLRecord) : bool - %{ - if ( state_ == STATE_TRACK_LOST ) - bro_analyzer()->ProtocolViolation(fmt("unexpected ChangeCipherSpec from %s at state %s", - orig_label(${rec.is_orig}).c_str(), - state_label(old_state_).c_str())); - return true; - %} - - function proc_application_data(rec: SSLRecord) : bool - %{ - if ( state_ != STATE_CONN_ESTABLISHED && - (state_ != STATE_CLIENT_FINISHED && ! ${rec.is_orig}) ) - bro_analyzer()->ProtocolViolation(fmt("unexpected ApplicationData from %s at state %s", - orig_label(${rec.is_orig}).c_str(), - state_label(old_state_).c_str())); - return true; - %} - function proc_alert(rec: SSLRecord, level : int, desc : int) : bool %{ BifEvent::generate_ssl_alert(bro_analyzer(), bro_analyzer()->Conn(), @@ -267,11 +248,6 @@ refine connection SSL_Conn += { function proc_v2_client_master_key(rec: SSLRecord, cipher_kind: int) : bool %{ - if ( state_ == STATE_TRACK_LOST ) - bro_analyzer()->ProtocolViolation(fmt("unexpected v2 client master key message from %s in state %s", - orig_label(${rec.is_orig}).c_str(), - state_label(old_state_).c_str())); - BifEvent::generate_ssl_established(bro_analyzer(), bro_analyzer()->Conn()); @@ -285,17 +261,6 @@ refine connection SSL_Conn += { return true; %} - function proc_handshake(hs: Handshake, is_orig: bool) : bool - %{ - if ( state_ == STATE_TRACK_LOST ) - bro_analyzer()->ProtocolViolation(fmt("unexpected Handshake message %s from %s in state %s", - handshake_type_label(${hs.msg_type}).c_str(), - orig_label(is_orig).c_str(), - state_label(old_state_).c_str())); - - return true; - %} - function proc_unknown_record(rec: SSLRecord) : bool %{ bro_analyzer()->ProtocolViolation(fmt("unknown SSL record type (%d) from %s", @@ -306,13 +271,8 @@ refine connection SSL_Conn += { function proc_ciphertext_record(rec : SSLRecord) : bool %{ - if ( state_ == STATE_TRACK_LOST ) - bro_analyzer()->ProtocolViolation(fmt("unexpected ciphertext record from %s in state %s", - orig_label(${rec.is_orig}).c_str(), - state_label(old_state_).c_str())); - - else if ( state_ == STATE_CONN_ESTABLISHED && - old_state_ == STATE_COMM_ENCRYPTED ) + if ( client_state_ == STATE_ENCRYPTED && + server_state_ == STATE_ENCRYPTED ) { BifEvent::generate_ssl_established(bro_analyzer(), bro_analyzer()->Conn()); @@ -322,10 +282,10 @@ refine connection SSL_Conn += { %} }; -refine typeattr ChangeCipherSpec += &let { - proc : bool = $context.connection.proc_change_cipher_spec(rec) - &requires(state_changed); -}; +#refine typeattr ChangeCipherSpec += &let { +# proc : bool = $context.connection.proc_change_cipher_spec(rec) +# &requires(state_changed); +#}; refine typeattr Alert += &let { proc : bool = $context.connection.proc_alert(rec, level, description); @@ -335,42 +295,37 @@ refine typeattr V2Error += &let { proc : bool = $context.connection.proc_alert(rec, -1, error_code); }; -refine typeattr ApplicationData += &let { - proc : bool = $context.connection.proc_application_data(rec); -}; +#refine typeattr ApplicationData += &let { +# proc : bool = $context.connection.proc_application_data(rec); +#}; refine typeattr ClientHello += &let { proc : bool = $context.connection.proc_client_hello(rec, client_version, gmt_unix_time, random_bytes, - session_id, csuits, 0) - &requires(state_changed); + session_id, csuits, 0); }; refine typeattr V2ClientHello += &let { proc : bool = $context.connection.proc_client_hello(rec, client_version, 0, - challenge, session_id, 0, ciphers) - &requires(state_changed); + challenge, session_id, 0, ciphers); }; refine typeattr ServerHello += &let { proc : bool = $context.connection.proc_server_hello(rec, server_version, gmt_unix_time, random_bytes, session_id, cipher_suite, 0, - compression_method) - &requires(state_changed); + compression_method); }; refine typeattr V2ServerHello += &let { proc : bool = $context.connection.proc_server_hello(rec, server_version, 0, - conn_id_data, 0, 0, ciphers, 0) - &requires(state_changed); + conn_id_data, 0, 0, ciphers, 0); cert : bool = $context.connection.proc_v2_certificate(rec, cert_data) &requires(proc); }; refine typeattr Certificate += &let { - proc : bool = $context.connection.proc_v3_certificate(rec, certificates) - &requires(state_changed); + proc : bool = $context.connection.proc_v3_certificate(rec, certificates); }; refine typeattr V2ClientMasterKey += &let { @@ -382,9 +337,9 @@ refine typeattr UnknownHandshake += &let { proc : bool = $context.connection.proc_unknown_handshake(hs, is_orig); }; -refine typeattr Handshake += &let { - proc : bool = $context.connection.proc_handshake(this, rec.is_orig); -}; +#refine typeattr Handshake += &let { +# proc : bool = $context.connection.proc_handshake(this, rec.is_orig); +#}; refine typeattr SessionTicketHandshake += &let { proc : bool = $context.connection.proc_session_ticket_handshake(this, rec.is_orig); diff --git a/src/analyzer/protocol/ssl/ssl-protocol.pac b/src/analyzer/protocol/ssl/ssl-protocol.pac index 9368122eaa..7ee71df352 100644 --- a/src/analyzer/protocol/ssl/ssl-protocol.pac +++ b/src/analyzer/protocol/ssl/ssl-protocol.pac @@ -34,7 +34,7 @@ type SSLRecord(is_orig: bool) = record { head4 : uint8; rec : RecordText(this)[] &length=length, &requires(content_type); } &length = length+5, &byteorder=bigendian, - &let { + &let { version : int = $context.connection.determine_ssl_version(head0, head1, head2); @@ -51,9 +51,8 @@ type SSLRecord(is_orig: bool) = record { }; }; -type RecordText(rec: SSLRecord) = case $context.connection.state() of { - STATE_ABBREV_SERVER_ENCRYPTED, STATE_CLIENT_ENCRYPTED, - STATE_COMM_ENCRYPTED, STATE_CONN_ESTABLISHED +type RecordText(rec: SSLRecord) = case $context.connection.state(rec.is_orig) of { + STATE_ENCRYPTED -> ciphertext : CiphertextRecord(rec); default -> plaintext : PlaintextRecord(rec); @@ -82,64 +81,18 @@ type SSLExtension(rec: SSLRecord) = record { ###################################################################### enum AnalyzerState { - STATE_INITIAL, - STATE_CLIENT_HELLO_RCVD, - STATE_IN_SERVER_HELLO, - STATE_SERVER_HELLO_DONE, - STATE_CLIENT_CERT, - STATE_CLIENT_KEY_WITH_CERT, - STATE_CLIENT_KEY_NO_CERT, - STATE_CLIENT_CERT_VERIFIED, - STATE_CLIENT_ENCRYPTED, - STATE_CLIENT_FINISHED, - STATE_ABBREV_SERVER_ENCRYPTED, - STATE_ABBREV_SERVER_FINISHED, - STATE_COMM_ENCRYPTED, - STATE_CONN_ESTABLISHED, - STATE_V2_CL_MASTER_KEY_EXPECTED, - - STATE_TRACK_LOST, - STATE_ANY + STATE_CLEAR, + STATE_ENCRYPTED }; %code{ string state_label(int state_nr) { switch ( state_nr ) { - case STATE_INITIAL: - return string("INITIAL"); - case STATE_CLIENT_HELLO_RCVD: - return string("CLIENT_HELLO_RCVD"); - case STATE_IN_SERVER_HELLO: - return string("IN_SERVER_HELLO"); - case STATE_SERVER_HELLO_DONE: - return string("SERVER_HELLO_DONE"); - case STATE_CLIENT_CERT: - return string("CLIENT_CERT"); - case STATE_CLIENT_KEY_WITH_CERT: - return string("CLIENT_KEY_WITH_CERT"); - case STATE_CLIENT_KEY_NO_CERT: - return string("CLIENT_KEY_NO_CERT"); - case STATE_CLIENT_CERT_VERIFIED: - return string("CLIENT_CERT_VERIFIED"); - case STATE_CLIENT_ENCRYPTED: - return string("CLIENT_ENCRYPTED"); - case STATE_CLIENT_FINISHED: - return string("CLIENT_FINISHED"); - case STATE_ABBREV_SERVER_ENCRYPTED: - return string("ABBREV_SERVER_ENCRYPTED"); - case STATE_ABBREV_SERVER_FINISHED: - return string("ABBREV_SERVER_FINISHED"); - case STATE_COMM_ENCRYPTED: - return string("COMM_ENCRYPTED"); - case STATE_CONN_ESTABLISHED: - return string("CONN_ESTABLISHED"); - case STATE_V2_CL_MASTER_KEY_EXPECTED: - return string("STATE_V2_CL_MASTER_KEY_EXPECTED"); - case STATE_TRACK_LOST: - return string("TRACK_LOST"); - case STATE_ANY: - return string("ANY"); + case STATE_CLEAR: + return string("CLEAR"); + case STATE_ENCRYPTED: + return string("ENCRYPTED"); default: return string(fmt("UNKNOWN (%d)", state_nr)); @@ -176,21 +129,7 @@ type ChangeCipherSpec(rec: SSLRecord) = record { type : uint8; } &length = 1, &let { state_changed : bool = - $context.connection.transition(STATE_CLIENT_FINISHED, - STATE_COMM_ENCRYPTED, rec.is_orig, false) || - $context.connection.transition(STATE_IN_SERVER_HELLO, - STATE_ABBREV_SERVER_ENCRYPTED, rec.is_orig, false) || - $context.connection.transition(STATE_CLIENT_KEY_NO_CERT, - STATE_CLIENT_ENCRYPTED, rec.is_orig, true) || - $context.connection.transition(STATE_CLIENT_CERT_VERIFIED, - STATE_CLIENT_ENCRYPTED, rec.is_orig, true) || - $context.connection.transition(STATE_CLIENT_CERT, - STATE_CLIENT_ENCRYPTED, rec.is_orig, true) || - $context.connection.transition(STATE_CLIENT_KEY_WITH_CERT, - STATE_CLIENT_ENCRYPTED, rec.is_orig, true) || - $context.connection.transition(STATE_ABBREV_SERVER_FINISHED, - STATE_COMM_ENCRYPTED, rec.is_orig, true) || - $context.connection.lost_track(); + $context.connection.startEncryption(rec.is_orig); }; @@ -209,7 +148,7 @@ type Alert(rec: SSLRecord) = record { ###################################################################### type V2Error(rec: SSLRecord) = record { - data: bytestring &restofdata &transient; + data : bytestring &restofdata &transient; } &let { error_code : uint16 = ((rec.head3 << 8) | rec.head4); }; @@ -234,9 +173,7 @@ type ApplicationData(rec: SSLRecord) = record { ###################################################################### # Hello Request is empty -type HelloRequest(rec: SSLRecord) = empty &let { - hr: bool = $context.connection.set_hello_requested(true); -}; +type HelloRequest(rec: SSLRecord) = empty; ###################################################################### @@ -257,13 +194,6 @@ type ClientHello(rec: SSLRecord) = record { # of the following fields. ext_len: uint16[] &until($element == 0 || $element != 0); extensions : SSLExtension(rec)[] &until($input.length() == 0); -} &let { - state_changed : bool = - $context.connection.transition(STATE_INITIAL, - STATE_CLIENT_HELLO_RCVD, rec.is_orig, true) || - ($context.connection.hello_requested() && - $context.connection.transition(STATE_ANY, STATE_CLIENT_HELLO_RCVD, rec.is_orig, true)) || - $context.connection.lost_track(); }; @@ -279,13 +209,6 @@ type V2ClientHello(rec: SSLRecord) = record { session_id : uint8[session_len]; challenge : bytestring &length = chal_len; } &length = 6 + csuit_len + session_len + chal_len, &let { - state_changed : bool = - $context.connection.transition(STATE_INITIAL, - STATE_CLIENT_HELLO_RCVD, rec.is_orig, true) || - ($context.connection.hello_requested() && - $context.connection.transition(STATE_ANY, STATE_CLIENT_HELLO_RCVD, rec.is_orig, true)) || - $context.connection.lost_track(); - client_version : int = rec.version; }; @@ -306,11 +229,6 @@ type ServerHello(rec: SSLRecord) = record { # of the following fields. ext_len: uint16[] &until($element == 0 || $element != 0); extensions : SSLExtension(rec)[] &until($input.length() == 0); -} &let { - state_changed : bool = - $context.connection.transition(STATE_CLIENT_HELLO_RCVD, - STATE_IN_SERVER_HELLO, rec.is_orig, false) || - $context.connection.lost_track(); }; @@ -329,14 +247,6 @@ type V2ServerHello(rec: SSLRecord) = record { ciphers : uint24[ciph_len/3]; conn_id_data : bytestring &length = conn_id_len; } &let { - state_changed : bool = - (session_id_hit > 0 ? - $context.connection.transition(STATE_CLIENT_HELLO_RCVD, - STATE_CONN_ESTABLISHED, rec.is_orig, false) : - $context.connection.transition(STATE_CLIENT_HELLO_RCVD, - STATE_V2_CL_MASTER_KEY_EXPECTED, rec.is_orig, false)) || - $context.connection.lost_track(); - session_id_hit : uint8 = rec.head3; cert_type : uint8 = rec.head4; }; @@ -357,12 +267,10 @@ type Certificate(rec: SSLRecord) = record { length : uint24; certificates : CertificateList &length = to_int()(length); } &let { - state_changed : bool = - $context.connection.transition(STATE_IN_SERVER_HELLO, - STATE_IN_SERVER_HELLO, rec.is_orig, false) || - $context.connection.transition(STATE_SERVER_HELLO_DONE, - STATE_CLIENT_CERT, rec.is_orig, true) || - $context.connection.lost_track(); + state_changed_client : bool = + $context.connection.startEncryption(true); + state_changed_server : bool = + $context.connection.startEncryption(false); }; @@ -373,11 +281,6 @@ type Certificate(rec: SSLRecord) = record { # For now ignore details; just eat up complete message type ServerKeyExchange(rec: SSLRecord) = record { key : bytestring &restofdata &transient; -} &let { - state_changed : bool = - $context.connection.transition(STATE_IN_SERVER_HELLO, - STATE_IN_SERVER_HELLO, rec.is_orig, false) || - $context.connection.lost_track(); }; @@ -388,11 +291,6 @@ type ServerKeyExchange(rec: SSLRecord) = record { # For now, ignore Certificate Request Details; just eat up message. type CertificateRequest(rec: SSLRecord) = record { cont : bytestring &restofdata &transient; -} &let { - state_changed : bool = - $context.connection.transition(STATE_IN_SERVER_HELLO, - STATE_IN_SERVER_HELLO, rec.is_orig, false) || - $context.connection.lost_track(); }; @@ -401,12 +299,7 @@ type CertificateRequest(rec: SSLRecord) = record { ###################################################################### # Server Hello Done is empty -type ServerHelloDone(rec: SSLRecord) = empty &let { - state_changed : bool = - $context.connection.transition(STATE_IN_SERVER_HELLO, - STATE_SERVER_HELLO_DONE, rec.is_orig, false) || - $context.connection.lost_track(); -}; +type ServerHelloDone(rec: SSLRecord) = empty; ###################################################################### @@ -425,15 +318,6 @@ type ServerHelloDone(rec: SSLRecord) = empty &let { # encrypted anyway); just eat up message. type ClientKeyExchange(rec: SSLRecord) = record { key : bytestring &restofdata &transient; -} &let { - state_changed : bool = - $context.connection.transition(STATE_SERVER_HELLO_DONE, - STATE_CLIENT_KEY_NO_CERT, rec.is_orig, true) || - $context.connection.transition(STATE_CLIENT_CERT, - STATE_CLIENT_KEY_WITH_CERT, rec.is_orig, true) || - $context.connection.transition(STATE_CLIENT_CERT, - STATE_CLIENT_KEY_WITH_CERT, rec.is_orig, true) || - $context.connection.lost_track(); }; ###################################################################### @@ -449,11 +333,6 @@ type V2ClientMasterKey(rec: SSLRecord) = record { en_key_data : bytestring &length = en_key_len &transient; key_arg_data : bytestring &length = key_arg_len &transient; } &length = 7 + cl_key_len + en_key_len + key_arg_len, &let { - state_changed : bool = - $context.connection.transition(STATE_V2_CL_MASTER_KEY_EXPECTED, - STATE_CONN_ESTABLISHED, rec.is_orig, true) || - $context.connection.lost_track(); - cipher_kind : int = (((rec.head3 << 16) | (rec.head4 << 8)) | cipher_kind_8); }; @@ -465,11 +344,6 @@ type V2ClientMasterKey(rec: SSLRecord) = record { # For now, ignore Certificate Verify; just eat up the message. type CertificateVerify(rec: SSLRecord) = record { cont : bytestring &restofdata &transient; -} &let { - state_changed : bool = - $context.connection.transition(STATE_CLIENT_KEY_WITH_CERT, - STATE_CLIENT_CERT_VERIFIED, rec.is_orig, true) || - $context.connection.lost_track(); }; @@ -481,13 +355,6 @@ type CertificateVerify(rec: SSLRecord) = record { # so we will not be able to read those messages. type Finished(rec: SSLRecord) = record { cont : bytestring &restofdata &transient; -} &let { - state_changed : bool = - $context.connection.transition(STATE_SERVER_HELLO_DONE, - STATE_COMM_ENCRYPTED, rec.is_orig, true) || - $context.connection.transition(STATE_CLIENT_FINISHED, - STATE_COMM_ENCRYPTED, rec.is_orig, false) || - $context.connection.lost_track(); }; type SessionTicketHandshake(rec: SSLRecord) = record { @@ -499,10 +366,8 @@ type SessionTicketHandshake(rec: SSLRecord) = record { # V3 Handshake Protocol (7.) ###################################################################### -type UnknownHandshake(hs: Handshake, is_orig: bool) = record { +type UnknownHandshake(hs: Handshake, is_orig: bool) = record { data : bytestring &restofdata &transient; -} &let { - state_changed : bool = $context.connection.lost_track(); }; type Handshake(rec: SSLRecord) = record { @@ -532,33 +397,12 @@ type Handshake(rec: SSLRecord) = record { # Fragmentation (6.2.1.) ###################################################################### -type UnknownRecord(rec: SSLRecord) = record { +type UnknownRecord(rec: SSLRecord) = record { cont : bytestring &restofdata &transient; -} &let { - state_changed : bool = $context.connection.lost_track(); }; type CiphertextRecord(rec: SSLRecord) = record { cont : bytestring &restofdata &transient; -} &let { - state_changed : bool = - $context.connection.transition(STATE_CLIENT_FINISHED, - STATE_CLIENT_FINISHED, rec.is_orig, false) || - $context.connection.transition(STATE_CLIENT_FINISHED, - STATE_CLIENT_FINISHED, rec.is_orig, true) || - $context.connection.transition(STATE_ABBREV_SERVER_ENCRYPTED, - STATE_ABBREV_SERVER_FINISHED, rec.is_orig, false) || - $context.connection.transition(STATE_CLIENT_ENCRYPTED, - STATE_CLIENT_FINISHED, rec.is_orig, true) || - $context.connection.transition(STATE_COMM_ENCRYPTED, - STATE_CONN_ESTABLISHED, rec.is_orig, false) || - $context.connection.transition(STATE_COMM_ENCRYPTED, - STATE_CONN_ESTABLISHED, rec.is_orig, true) || - $context.connection.transition(STATE_CONN_ESTABLISHED, - STATE_CONN_ESTABLISHED, rec.is_orig, false) || - $context.connection.transition(STATE_CONN_ESTABLISHED, - STATE_CONN_ESTABLISHED, rec.is_orig, true) || - $context.connection.lost_track(); }; @@ -578,22 +422,22 @@ type SSLPDU(is_orig: bool) = record { refine connection SSL_Conn += { %member{ - int state_; + int client_state_; + int server_state_; int old_state_; bool hello_requested_; %} %init{ - state_ = STATE_INITIAL; - old_state_ = STATE_INITIAL; - hello_requested_ = false; + server_state_ = STATE_CLEAR; + client_state_ = STATE_CLEAR; %} function determine_ssl_version(head0 : uint8, head1 : uint8, head2 : uint8) : int %{ if ( head0 >= 20 && head0 <= 23 && - head1 == 0x03 && head2 <= 0x03 ) + head1 == 0x03 && head2 <= 0x03 ) // This is most probably SSL version 3. return (head1 << 8) | head2; @@ -606,39 +450,22 @@ refine connection SSL_Conn += { return UNKNOWN_VERSION; %} - function state() : int %{ return state_; %} - function old_state() : int %{ return old_state_; %} + function client_state() : int %{ return client_state_; %} + function server_state() : int %{ return client_state_; %} + function state(is_orig: bool) : int + %{ + if ( is_orig ) + return client_state_; + else + return server_state_; + %} - function transition(olds : AnalyzerState, news : AnalyzerState, - current_record_is_orig : bool, is_orig : bool) : bool + function startEncryption(is_orig: bool) : bool %{ - if ( (olds != STATE_ANY && olds != state_) || - current_record_is_orig != is_orig ) - return false; - - old_state_ = state_; - state_ = news; - - //printf("transitioning from %s to %s\n", state_label(old_state()).c_str(), state_label(state()).c_str()); + if ( is_orig ) + client_state_ = STATE_ENCRYPTED; + else + server_state_ = STATE_ENCRYPTED; return true; %} - - function lost_track() : bool - %{ - state_ = STATE_TRACK_LOST; - return false; - %} - - function hello_requested() : bool - %{ - bool ret = hello_requested_; - hello_requested_ = false; - return ret; - %} - - function set_hello_requested(val : bool) : bool - %{ - hello_requested_ = val; - return val; - %} }; From ef41cc7189cf199d8d229aa47acd58b37e6d0eb4 Mon Sep 17 00:00:00 2001 From: Bernhard Amann Date: Wed, 16 Apr 2014 10:48:22 -0700 Subject: [PATCH 12/17] Nicer notices for heartbleed. Duplicates are now excluded and the notice texts contain a bit more useful information. --- scripts/policy/protocols/ssl/heartbleed.bro | 40 +++++++++++++-------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/scripts/policy/protocols/ssl/heartbleed.bro b/scripts/policy/protocols/ssl/heartbleed.bro index 0049c4c51f..dc38c66f73 100644 --- a/scripts/policy/protocols/ssl/heartbleed.bro +++ b/scripts/policy/protocols/ssl/heartbleed.bro @@ -15,11 +15,9 @@ 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 successful. + ## Indicates that a host performing a heartbleed attack was probably successful. SSL_Heartbeat_Attack_Success, - ## Indivcates that a host performing a heartbleed attack after encryption was started was probably successful - SSL_Heartbeat_Encrypted_Attack_Success, - ## Indicates we saw heartbeet requests with odd length. Probably an attack. + ## 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 @@ -37,7 +35,8 @@ event ssl_heartbeat(c: connection, is_orig: bool, length: count, heartbeat_type: 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 + $conn=c, + $identifier=cat(c$uid, length, payload_length) ]); } } @@ -45,8 +44,9 @@ event ssl_heartbeat(c: connection, is_orig: bool, length: count, heartbeat_type: if ( heartbeat_type == 2 && c$ssl$heartbleed_detected ) { NOTICE([$note=SSL_Heartbeat_Attack_Success, - $msg="An TLS heartbleed attack was detected and probably exploited", - $conn=c + $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 ]); } } @@ -60,16 +60,26 @@ event ssl_encrypted_heartbeat(c: connection, is_orig: bool, length: count) if ( c$ssl$originator_heartbeats > c$ssl$responder_heartbeats + 3 ) NOTICE([$note=SSL_Heartbeat_Many_Requests, - $msg="Seeing more than 3 heartbeat requests without replies from server. Possible attack?", + $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) + $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="Heartbeat message smaller than minimum length. Probable attack.", + $msg=fmt("Heartbeat message smaller than minimum required length. Probable attack. Message length: %d", length), $conn=c, - $n=length + $n=length, + $identifier=cat(c$uid, length) ]); if ( is_orig ) @@ -86,9 +96,11 @@ 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_Encrypted_Attack_Success, - $msg="An Encrypted TLS heartbleed attack was probably detected!", - $conn=c + 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 ) From 594975c93d88d3a176fd5ab967cf2d35a883747f Mon Sep 17 00:00:00 2001 From: Bernhard Amann Date: Mon, 21 Apr 2014 11:23:12 -0700 Subject: [PATCH 13/17] Make SSL/TLS version detection less brittle. This still cannot deal with v2 hellos that use the long length. On the other hand - OpenSSL also cannot deal with these and we should not see many sslv2 connections in any case - so... they probably would not work in practice in any case. --- src/analyzer/protocol/ssl/ssl-analyzer.pac | 13 +++- src/analyzer/protocol/ssl/ssl-protocol.pac | 84 +++++++++++++++------- 2 files changed, 68 insertions(+), 29 deletions(-) diff --git a/src/analyzer/protocol/ssl/ssl-analyzer.pac b/src/analyzer/protocol/ssl/ssl-analyzer.pac index 997faf0436..b6422584e3 100644 --- a/src/analyzer/protocol/ssl/ssl-analyzer.pac +++ b/src/analyzer/protocol/ssl/ssl-analyzer.pac @@ -293,6 +293,14 @@ refine connection SSL_Conn += { return true; %} + function proc_check_v2_server_hello_version(version: uint16) : bool + %{ + if ( version != SSLv20 ) + bro_analyzer()->ProtocolViolation(fmt("Invalid version in SSL server hello. Version: %d", version)); + + return true; + %} + }; #refine typeattr ChangeCipherSpec += &let { @@ -337,6 +345,8 @@ refine typeattr V2ServerHello += &let { proc : bool = $context.connection.proc_server_hello(rec, server_version, 0, conn_id_data, 0, 0, ciphers, 0); + check_v2 : bool = $context.connection.proc_check_v2_server_hello_version(server_version); + cert : bool = $context.connection.proc_v2_certificate(rec, cert_data) &requires(proc); }; @@ -346,8 +356,7 @@ refine typeattr Certificate += &let { }; refine typeattr V2ClientMasterKey += &let { - proc : bool = $context.connection.proc_v2_client_master_key(rec, cipher_kind) - &requires(state_changed); + proc : bool = $context.connection.proc_v2_client_master_key(rec, cipher_kind); }; refine typeattr UnknownHandshake += &let { diff --git a/src/analyzer/protocol/ssl/ssl-protocol.pac b/src/analyzer/protocol/ssl/ssl-protocol.pac index f72f34e9cc..d32d926f65 100644 --- a/src/analyzer/protocol/ssl/ssl-protocol.pac +++ b/src/analyzer/protocol/ssl/ssl-protocol.pac @@ -36,16 +36,14 @@ type SSLRecord(is_orig: bool) = record { } &length = length+5, &byteorder=bigendian, &let { version : int = - $context.connection.determine_ssl_version(head0, head1, head2); + $context.connection.determine_ssl_record_layer(head0, head1, head2, head3, head4); content_type : int = case version of { - # UNKNOWN_VERSION -> 0; assume tls on unknown version SSLv20 -> head2+300; default -> head0; }; length : int = case version of { - # UNKNOWN_VERSION -> 0; assume tls on unknown version SSLv20 -> (((head0 & 0x7f) << 8) | head1) - 3; default -> (head3 << 8) | head4; }; @@ -277,11 +275,6 @@ type CertificateList = X509Certificate[] &until($input.length() == 0); type Certificate(rec: SSLRecord) = record { length : uint24; certificates : CertificateList &length = to_int()(length); -} &let { - state_changed_client : bool = - $context.connection.startEncryption(true); - state_changed_server : bool = - $context.connection.startEncryption(false); }; @@ -345,6 +338,9 @@ type V2ClientMasterKey(rec: SSLRecord) = record { key_arg_data : bytestring &length = key_arg_len &transient; } &length = 7 + cl_key_len + en_key_len + key_arg_len, &let { cipher_kind : int = (((rec.head3 << 16) | (rec.head4 << 8)) | cipher_kind_8); + # encryption starts for both sides after this message. + state_changed_client : bool = $context.connection.startEncryption(true); + state_changed_server : bool = $context.connection.startEncryption(false); }; @@ -435,41 +431,75 @@ refine connection SSL_Conn += { %member{ int client_state_; int server_state_; - int old_state_; - bool hello_requested_; + int record_layer_version_; %} %init{ server_state_ = STATE_CLEAR; client_state_ = STATE_CLEAR; + record_layer_version_ = UNKNOWN_VERSION; %} - function determine_ssl_version(head0 : uint8, head1 : uint8, - head2 : uint8) : int + function determine_ssl_record_layer(head0 : uint8, head1 : uint8, + head2 : uint8, head3: uint8, head4: uint8) : int %{ - if ( head0 >= 20 && head0 <= 23 && - head1 == 0x03 && head2 <= 0x03 ) - // This is most probably SSL version 3. - return (head1 << 8) | head2; + if ( record_layer_version_ != UNKNOWN_VERSION ) + return record_layer_version_; - else if ( head0 >= 128 && head2 < 5 && head2 != 3 ) - // Not very strong evidence, but we suspect - // this to be SSLv2. - return SSLv20; + if ( head0 & 0x80 ) + { + if ( head2 == 0x01 ) // SSLv2 client hello. + { + uint16 version = (head3<<8) | head4; + if ( version != SSLv20 && version != SSLv30 && version != TLSv10 + && version != TLSv11 && version != TLSv12 ) + { + bro_analyzer()->ProtocolViolation(fmt("Invalid version in SSL client hello. Version: %d", version)); + return UNKNOWN_VERSION; + } + else + return SSLv20; + } - else + else if ( head2 == 0x04 ) // SSLv2 server hello. This connection will continue using SSLv2. + { + record_layer_version_ = SSLv20; + return SSLv20; + } + else // this is not SSL or TLS. + { + bro_analyzer()->ProtocolViolation(fmt("Invalid headers in SSL connection. Head1: %d, head2: %d, head3: %d", head1, head2, head3)); + return UNKNOWN_VERSION; + } + } + + uint16 version = (head1<<8) | head2; + if ( version != SSLv30 && version != TLSv10 + && version != TLSv11 && version != TLSv12 ) + { + bro_analyzer()->ProtocolViolation(fmt("Invalid version in TLS connection. Version: %d", version)); return UNKNOWN_VERSION; + } + + if ( head0 >=20 && head0 <= 30 ) + { // ok, set record layer version, this never can be downgraded to v2 + record_layer_version_ = version; + return version; + } + + bro_analyzer()->ProtocolViolation(fmt("Invalid type in TLS connection. Version: %d, Type: %d", version, head0)); + return UNKNOWN_VERSION; %} function client_state() : int %{ return client_state_; %} function server_state() : int %{ return client_state_; %} function state(is_orig: bool) : int - %{ - if ( is_orig ) - return client_state_; - else - return server_state_; - %} + %{ + if ( is_orig ) + return client_state_; + else + return server_state_; + %} function startEncryption(is_orig: bool) : bool %{ From 4ae52d9e1c0153282a012ec38d910a106bc77117 Mon Sep 17 00:00:00 2001 From: Bernhard Amann Date: Wed, 23 Apr 2014 14:34:06 -0700 Subject: [PATCH 14/17] Support parsing of several TLS extensions. At the moment, we have support for: elliptic_curves: client supported elliptic curves ec_point_formats: list of client supported EC point formats application_layer_protocol_negotiation: list of supported application layer protocols (used for spdy/http2 negotiation) server_name: server name sent by client. This was supported before, but... a bit brittle. --- scripts/base/frameworks/notice/weird.bro | 1 + scripts/base/protocols/ssl/main.bro | 10 +- src/analyzer/protocol/ssl/events.bif | 8 + src/analyzer/protocol/ssl/ssl-analyzer.pac | 161 ++++++++++++++++----- src/analyzer/protocol/ssl/ssl-defs.pac | 32 ++++ src/analyzer/protocol/ssl/ssl-protocol.pac | 90 ++++++++++-- 6 files changed, 252 insertions(+), 50 deletions(-) diff --git a/scripts/base/frameworks/notice/weird.bro b/scripts/base/frameworks/notice/weird.bro index e7faf38df4..474f021cef 100644 --- a/scripts/base/frameworks/notice/weird.bro +++ b/scripts/base/frameworks/notice/weird.bro @@ -185,6 +185,7 @@ export { ["RPC_underflow"] = ACTION_LOG, ["RST_storm"] = ACTION_LOG, ["RST_with_data"] = ACTION_LOG, + ["SSL_many_server_names"] = ACTION_LOG, ["simultaneous_open"] = ACTION_LOG_PER_CONN, ["spontaneous_FIN"] = ACTION_IGNORE, ["spontaneous_RST"] = ACTION_IGNORE, diff --git a/scripts/base/protocols/ssl/main.bro b/scripts/base/protocols/ssl/main.bro index 5b974222a1..eb128483a4 100644 --- a/scripts/base/protocols/ssl/main.bro +++ b/scripts/base/protocols/ssl/main.bro @@ -159,12 +159,16 @@ event ssl_server_hello(c: connection, version: count, possible_ts: time, server_ c$ssl$cipher = cipher_desc[cipher]; } -event ssl_extension(c: connection, is_orig: bool, code: count, val: string) &priority=5 +event tls_extension_server_name(c: connection, is_orig: bool, names: string_vec) &priority=5 { set_session(c); - if ( is_orig && extensions[code] == "server_name" ) - c$ssl$server_name = sub_bytes(val, 6, |val|); + if ( is_orig && |names| > 0 ) + { + c$ssl$server_name = names[0]; + if ( |names| > 1 ) + event conn_weird("SSL_many_server_names", c, cat(names)); + } } event ssl_alert(c: connection, is_orig: bool, level: count, desc: count) &priority=5 diff --git a/src/analyzer/protocol/ssl/events.bif b/src/analyzer/protocol/ssl/events.bif index a11e7bcc68..457d18ea39 100644 --- a/src/analyzer/protocol/ssl/events.bif +++ b/src/analyzer/protocol/ssl/events.bif @@ -80,6 +80,14 @@ event ssl_server_hello%(c: connection, version: count, possible_ts: time, server ## ssl_session_ticket_handshake event ssl_extension%(c: connection, is_orig: bool, code: count, val: string%); +event tls_extension_ec_point_formats%(c: connection, is_orig: bool, point_formats: index_vec%); + +event tls_extension_elliptic_curves%(c: connection, is_orig: bool, curves: index_vec%); + +event tls_extension_application_layer_protocol_negotiation%(c: connection, is_orig: bool, protocols: string_vec%); + +event tls_extension_server_name%(c: connection, is_orig: bool, names: string_vec%); + ## Generated at the end of an SSL/TLS handshake. SSL/TLS sessions start with ## an unencrypted handshake, and Bro extracts as much information out of that ## as it can. This event signals the time when an SSL/TLS has finished the diff --git a/src/analyzer/protocol/ssl/ssl-analyzer.pac b/src/analyzer/protocol/ssl/ssl-analyzer.pac index b6422584e3..043b2271ec 100644 --- a/src/analyzer/protocol/ssl/ssl-analyzer.pac +++ b/src/analyzer/protocol/ssl/ssl-analyzer.pac @@ -86,23 +86,19 @@ function version_ok(vers : uint16) : bool refine connection SSL_Conn += { - %member{ - int eof; - %} - - %init{ - eof=0; - %} - - #%eof{ - # if ( ! eof && - # state_ != STATE_CONN_ESTABLISHED && - # state_ != STATE_TRACK_LOST && - # state_ != STATE_INITIAL ) - # bro_analyzer()->ProtocolViolation(fmt("unexpected end of connection in state %s", - # state_label(state_).c_str())); - # ++eof; - #%} +# %member{ +# int eof; +# %} +# +# %init{ +# eof=0; +# %} +# +# %eof{ +# if ( ! eof ) +# bro_analyzer()->ProtocolViolation(fmt("unexpected end of connection")); +# ++eof; +# %} %cleanup{ %} @@ -198,12 +194,104 @@ refine connection SSL_Conn += { return true; %} - function proc_ssl_extension(rec: SSLRecord, type: int, data: bytestring) : bool + function proc_ssl_extension(rec: SSLRecord, type: int, sourcedata: const_bytestring) : bool %{ + // we cheat a little bit here. We want to throw this event for every extension we encounter, + // even those that are handled by more specialized events later. + // To access the parsed data, we use sourcedata, which contains the whole data blob of + // the extension, including headers. We skip over those (4 bytes). + size_t length = sourcedata.length(); + if ( length < 4 ) + { + // this should be impossible due to the binpac parser and protocol description + bro_analyzer()->ProtocolViolation(fmt("Impossible extension length: %lu", length)); + return true; + } + length -= 4; + const unsigned char* data = sourcedata.begin() + 4; + if ( ssl_extension ) BifEvent::generate_ssl_extension(bro_analyzer(), bro_analyzer()->Conn(), ${rec.is_orig}, type, - new StringVal(data.length(), (const char*) data.data())); + new StringVal(length, reinterpret_cast(data))); + return true; + %} + + function proc_ec_point_formats(rec: SSLRecord, point_format_list: uint8[]) : bool + %{ + VectorVal* points = new VectorVal(internal_type("index_vec")->AsVectorType()); + if ( point_format_list ) + { + for ( unsigned int i = 0; i < point_format_list->size(); ++i ) + { + points->Assign(i, new Val((*point_format_list)[i], TYPE_COUNT)); + } + } + + BifEvent::generate_tls_extension_ec_point_formats(bro_analyzer(), bro_analyzer()->Conn(), + ${rec.is_orig}, points); + + return true; + %} + + function proc_elliptic_curves(rec: SSLRecord, list: uint16[]) : bool + %{ + VectorVal* curves = new VectorVal(internal_type("index_vec")->AsVectorType()); + if ( list ) + { + for ( unsigned int i = 0; i < list->size(); ++i ) + { + curves->Assign(i, new Val((*list)[i], TYPE_COUNT)); + } + } + + BifEvent::generate_tls_extension_elliptic_curves(bro_analyzer(), bro_analyzer()->Conn(), + ${rec.is_orig}, curves); + + return true; + %} + + function proc_apnl(rec: SSLRecord, protocols: ProtocolName[]) : bool + %{ + VectorVal* plist = new VectorVal(internal_type("string_vec")->AsVectorType()); + if ( protocols ) + { + for ( unsigned int i = 0; i < protocols->size(); ++i ) + { + plist->Assign(i, new StringVal((*protocols)[i]->name().length(), (const char*) (*protocols)[i]->name().data())); + } + } + + BifEvent::generate_tls_extension_application_layer_protocol_negotiation(bro_analyzer(), bro_analyzer()->Conn(), + ${rec.is_orig}, plist); + + return true; + %} + + function proc_server_name(rec: SSLRecord, list: ServerName[]) : bool + %{ + VectorVal* servers = new VectorVal(internal_type("string_vec")->AsVectorType()); + if ( list ) + { + for ( unsigned int i = 0, j=0; i < list->size(); ++i ) + { + ServerName* servername = (*list)[i]; + if ( servername->name_type() != 0 ) + { + bro_analyzer()->Weird(fmt("Encountered unknown type in server name tls extension: %d", servername->name_type())); + continue; + } + + if ( servername->host_name() ) + servers->Assign(j++, new StringVal(servername->host_name()->host_name().length(), (const char*) servername->host_name()->host_name().data())); + else + bro_analyzer()->Weird("Empty server_name extension in tls connection"); + } + } + + BifEvent::generate_tls_extension_server_name(bro_analyzer(), bro_analyzer()->Conn(), + ${rec.is_orig}, servers); + return true; %} @@ -233,9 +321,9 @@ refine connection SSL_Conn += { return ret; %} - function proc_v3_certificate(rec: SSLRecord, cl : CertificateList) : bool + function proc_v3_certificate(rec: SSLRecord, cl : X509Certificate[]) : bool %{ - vector* certs = cl->val(); + vector* certs = cl; vector* cert_list = new vector(); std::transform(certs->begin(), certs->end(), @@ -303,11 +391,6 @@ refine connection SSL_Conn += { }; -#refine typeattr ChangeCipherSpec += &let { -# proc : bool = $context.connection.proc_change_cipher_spec(rec) -# &requires(state_changed); -#}; - refine typeattr Alert += &let { proc : bool = $context.connection.proc_alert(rec, level, description); }; @@ -316,10 +399,6 @@ refine typeattr V2Error += &let { proc : bool = $context.connection.proc_alert(rec, -1, error_code); }; -#refine typeattr ApplicationData += &let { -# proc : bool = $context.connection.proc_application_data(rec); -#}; - refine typeattr Heartbeat += &let { proc : bool = $context.connection.proc_heartbeat(rec, type, payload_length, data); }; @@ -363,10 +442,6 @@ refine typeattr UnknownHandshake += &let { proc : bool = $context.connection.proc_unknown_handshake(hs, is_orig); }; -#refine typeattr Handshake += &let { -# proc : bool = $context.connection.proc_handshake(this, rec.is_orig); -#}; - refine typeattr SessionTicketHandshake += &let { proc : bool = $context.connection.proc_session_ticket_handshake(this, rec.is_orig); } @@ -380,5 +455,21 @@ refine typeattr CiphertextRecord += &let { } refine typeattr SSLExtension += &let { - proc : bool = $context.connection.proc_ssl_extension(rec, type, data); + proc : bool = $context.connection.proc_ssl_extension(rec, type, sourcedata); +}; + +refine typeattr EcPointFormats += &let { + proc : bool = $context.connection.proc_ec_point_formats(rec, point_format_list); +}; + +refine typeattr EllipticCurves += &let { + proc : bool = $context.connection.proc_elliptic_curves(rec, elliptic_curve_list); +}; + +refine typeattr ApplicationLayerProtocolNegotiationExtension += &let { + proc : bool = $context.connection.proc_apnl(rec, protocol_name_list); +}; + +refine typeattr ServerNameExt += &let { + proc : bool = $context.connection.proc_server_name(rec, server_names); }; diff --git a/src/analyzer/protocol/ssl/ssl-defs.pac b/src/analyzer/protocol/ssl/ssl-defs.pac index 23fa7abce5..24827d3621 100644 --- a/src/analyzer/protocol/ssl/ssl-defs.pac +++ b/src/analyzer/protocol/ssl/ssl-defs.pac @@ -20,6 +20,7 @@ enum ContentType { UNKNOWN_OR_V2_ENCRYPTED = 400 }; +# If you add a new TLS version here, do not forget to also adjust the DPD signature. enum SSLVersions { UNKNOWN_VERSION = 0x0000, SSLv20 = 0x0002, @@ -28,3 +29,34 @@ enum SSLVersions { TLSv11 = 0x0302, TLSv12 = 0x0303 }; + +enum SSLExtensions { + EXT_SERVER_NAME = 0, + EXT_MAX_FRAGMENT_LENGTH = 1, + EXT_CLIENT_CERTIFICATE_URL = 2, + EXT_TRUSTED_CA_KEYS = 3, + EXT_TRUNCATED_HMAC = 4, + EXT_STATUS_REQUEST = 5, + EXT_USER_MAPPING = 6, + EXT_CLIENT_AUTHZ = 7, + EXT_SERVER_AUTHZ = 8, + EXT_CERT_TYPE = 9, + EXT_ELLIPTIC_CURVES = 10, + EXT_EC_POINT_FORMATS = 11, + EXT_SRP = 12, + EXT_SIGNATURE_ALGORITHMS = 13, + EXT_USE_SRTP = 14, + EXT_HEARTBEAT = 15, + EXT_APPLICATION_LAYER_PROTOCOL_NEGOTIATION = 16, + EXT_STATUS_REQUEST_V2 = 17, + EXT_SIGNED_CERTIFICATE_TIMESTAMP = 18, + EXT_SESSIONTICKET_TLS = 35, + EXT_EXTENDED_RANDOM = 40, + EXT_NEXT_PROTOCOL_NEGOTIATION = 13172, + EXT_ORIGIN_BOUND_CERTIFICATES = 13175, + EXT_ENCRYPTED_CLIENT_CERTIFICATES = 13180, + EXT_CHANNEL_ID = 30031, + EXT_CHANNEL_ID_NEW = 30032, + EXT_PADDING = 35655, + EXT_RENEGOTIATION_INFO = 65281 +}; diff --git a/src/analyzer/protocol/ssl/ssl-protocol.pac b/src/analyzer/protocol/ssl/ssl-protocol.pac index d32d926f65..857bd01f26 100644 --- a/src/analyzer/protocol/ssl/ssl-protocol.pac +++ b/src/analyzer/protocol/ssl/ssl-protocol.pac @@ -69,14 +69,81 @@ type PlaintextRecord(rec: SSLRecord) = case rec.content_type of { default -> unknown_record : UnknownRecord(rec); }; +###################################################################### +# TLS Extensions +###################################################################### + type SSLExtension(rec: SSLRecord) = record { type: uint16; data_len: uint16; - data: bytestring &length=data_len; + + # Pretty code ahead. Deal with the fact that perhaps extensions are not really present and we do not want to fail because of that. + ext: case type of { + EXT_APPLICATION_LAYER_PROTOCOL_NEGOTIATION -> apnl: ApplicationLayerProtocolNegotiationExtension(rec)[] &until($element == 0 || $element != 0); + EXT_ELLIPTIC_CURVES -> elliptic_curves: EllipticCurves(rec)[] &until($element == 0 || $element != 0); + EXT_EC_POINT_FORMATS -> ec_point_formats: EcPointFormats(rec)[] &until($element == 0 || $element != 0); +# EXT_STATUS_REQUEST -> status_request: StatusRequest(rec)[] &until($element == 0 || $element != 0); + EXT_SERVER_NAME -> server_name: ServerNameExt(rec)[] &until($element == 0 || $element != 0); + default -> data: bytestring &restofdata; + }; +} &length=data_len+4 &exportsourcedata; + +type ServerNameHostName() = record { + length: uint16; + host_name: bytestring &length=length; }; +type ServerName() = record { + name_type: uint8; # has to be 0 for host-name + name: case name_type of { + 0 -> host_name: ServerNameHostName; + default -> data : bytestring &restofdata; # unknown name + }; +}; + +type ServerNameExt(rec: SSLRecord) = record { + length: uint16; + server_names: ServerName[] &until($input.length() == 0); +} &length=length+2; + +# Do not parse for now. Structure is correct, but only contains asn.1 data that we would not use further. +#type OcspStatusRequest(rec: SSLRecord) = record { +# responder_id_list_length: uint16; +# responder_id_list: bytestring &length=responder_id_list_length; +# request_extensions_length: uint16; +# request_extensions: bytestring &length=request_extensions_length; +#}; +# +#type StatusRequest(rec: SSLRecord) = record { +# status_type: uint8; # 1 -> ocsp +# req: case status_type of { +# 1 -> ocsp_status_request: OcspStatusRequest(rec); +# default -> data : bytestring &restofdata; # unknown +# }; +#}; + +type EcPointFormats(rec: SSLRecord) = record { + length: uint8; + point_format_list: uint8[length]; +}; + +type EllipticCurves(rec: SSLRecord) = record { + length: uint16; + elliptic_curve_list: uint16[length/2]; +}; + +type ProtocolName() = record { + length: uint8; + name: bytestring &length=length; +}; + +type ApplicationLayerProtocolNegotiationExtension(rec: SSLRecord) = record { + length: uint16; + protocol_name_list: ProtocolName[] &until($input.length() == 0); +} &length=length+2; + ###################################################################### -# state management according to Section 7.3. in spec +# Encryption Tracking ###################################################################### enum AnalyzerState { @@ -173,10 +240,6 @@ type Heartbeat(rec: SSLRecord) = record { data : bytestring &restofdata; }; -###################################################################### -# Handshake Protocol (7.4.) -###################################################################### - ###################################################################### # V3 Hello Request (7.4.1.1.) ###################################################################### @@ -205,7 +268,6 @@ type ClientHello(rec: SSLRecord) = record { extensions : SSLExtension(rec)[] &until($input.length() == 0); }; - ###################################################################### # V2 Client Hello (SSLv2 2.5.) ###################################################################### @@ -270,13 +332,17 @@ type X509Certificate = record { certificate : bytestring &length = to_int()(length); }; -type CertificateList = X509Certificate[] &until($input.length() == 0); - type Certificate(rec: SSLRecord) = record { length : uint24; - certificates : CertificateList &length = to_int()(length); -}; + certificates : X509Certificate[] &until($input.length() == 0); +} &length = to_int()(length)+3; +# OCSP Stapling + +type CertificateStatus(rec: SSLRecord) = record { + status_type: uint8; # 1 = ocsp, everything else is undefined + response: bytestring &restofdata; +}; ###################################################################### # V3 Server Key Exchange Message (7.4.3.) @@ -394,7 +460,7 @@ type Handshake(rec: SSLRecord) = record { CLIENT_KEY_EXCHANGE -> client_key_exchange : ClientKeyExchange(rec); FINISHED -> finished : Finished(rec); CERTIFICATE_URL -> certificate_url : bytestring &restofdata &transient; - CERTIFICATE_STATUS -> certificate_status : bytestring &restofdata &transient; + CERTIFICATE_STATUS -> certificate_status : CertificateStatus(rec); default -> unknown_handshake : UnknownHandshake(this, rec.is_orig); } &length = to_int()(length); }; From 9b7eb293f1aaee78a10b21dfd5b581ec268b9e80 Mon Sep 17 00:00:00 2001 From: Bernhard Amann Date: Thu, 24 Apr 2014 12:05:21 -0700 Subject: [PATCH 15/17] Add documentation, consts and tests for the new events. This also fixes the heartbleed detector to work for encrypted attacks in this branch again. It stopped working, because the SSL analyzer now successfully detects established connections, and the scripts usually disable analyzing after that. (The heartbeat branch should not have been affected) --- scripts/base/protocols/ssl/consts.bro | 97 ++++++++-------- scripts/base/protocols/ssl/main.bro | 4 +- scripts/policy/protocols/ssl/heartbleed.bro | 5 +- src/analyzer/protocol/ssl/events.bif | 104 +++++++++++++++++- src/analyzer/protocol/ssl/ssl-analyzer.pac | 12 +- .../.stdout | 13 +++ .../notice-encrypted.log | 10 ++ .../notice-heartbleed-success.log | 11 ++ .../notice-heartbleed.log | 10 ++ .../btest/Traces/tls/chrome-34-google.trace | Bin 0 -> 11582 bytes .../tls/heartbleed-encrypted-success.pcap | Bin 0 -> 64480 bytes .../btest/Traces/tls/heartbleed-success.pcap | Bin 0 -> 39524 bytes testing/btest/Traces/tls/heartbleed.pcap | Bin 0 -> 22223 bytes .../protocols/ssl/tls-extension-events.test | 26 +++++ .../policy/protocols/ssl/heartbleed.bro | 13 +++ 15 files changed, 244 insertions(+), 61 deletions(-) create mode 100644 testing/btest/Baseline/scripts.base.protocols.ssl.tls-extension-events/.stdout create mode 100644 testing/btest/Baseline/scripts.policy.protocols.ssl.heartbleed/notice-encrypted.log create mode 100644 testing/btest/Baseline/scripts.policy.protocols.ssl.heartbleed/notice-heartbleed-success.log create mode 100644 testing/btest/Baseline/scripts.policy.protocols.ssl.heartbleed/notice-heartbleed.log create mode 100644 testing/btest/Traces/tls/chrome-34-google.trace create mode 100644 testing/btest/Traces/tls/heartbleed-encrypted-success.pcap create mode 100644 testing/btest/Traces/tls/heartbleed-success.pcap create mode 100644 testing/btest/Traces/tls/heartbleed.pcap create mode 100644 testing/btest/scripts/base/protocols/ssl/tls-extension-events.test create mode 100644 testing/btest/scripts/policy/protocols/ssl/heartbleed.bro diff --git a/scripts/base/protocols/ssl/consts.bro b/scripts/base/protocols/ssl/consts.bro index 1ccace102c..e60363e14c 100644 --- a/scripts/base/protocols/ssl/consts.bro +++ b/scripts/base/protocols/ssl/consts.bro @@ -14,15 +14,15 @@ export { [TLSv11] = "TLSv11", [TLSv12] = "TLSv12", } &default=function(i: count):string { return fmt("unknown-%d", i); }; - - ## Mapping between numeric codes and human readable strings for alert + + ## Mapping between numeric codes and human readable strings for alert ## levels. const alert_levels: table[count] of string = { [1] = "warning", [2] = "fatal", } &default=function(i: count):string { return fmt("unknown-%d", i); }; - - ## Mapping between numeric codes and human readable strings for alert + + ## Mapping between numeric codes and human readable strings for alert ## descriptions. const alert_descriptions: table[count] of string = { [0] = "close_notify", @@ -58,7 +58,7 @@ export { [115] = "unknown_psk_identity", [120] = "no_application_protocol", } &default=function(i: count):string { return fmt("unknown-%d", i); }; - + ## Mapping between numeric codes and human readable strings for SSL/TLS ## extensions. # More information can be found here: @@ -93,7 +93,50 @@ export { [35655] = "padding", [65281] = "renegotiation_info" } &default=function(i: count):string { return fmt("unknown-%d", i); }; - + + ## Mapping between numeric codes and human readable string for SSL/TLS elliptic curves. + # See http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8 + const ec_curves: table[count] of string = { + [1] = "sect163k1", + [2] = "sect163r1", + [3] = "sect163r2", + [4] = "sect193r1", + [5] = "sect193r2", + [6] = "sect233k1", + [7] = "sect233r1", + [8] = "sect239k1", + [9] = "sect283k1", + [10] = "sect283r1", + [11] = "sect409k1", + [12] = "sect409r1", + [13] = "sect571k1", + [14] = "sect571r1", + [15] = "secp160k1", + [16] = "secp160r1", + [17] = "secp160r2", + [18] = "secp192k1", + [19] = "secp192r1", + [20] = "secp224k1", + [21] = "secp224r1", + [22] = "secp256k1", + [23] = "secp256r1", + [24] = "secp384r1", + [25] = "secp521r1", + [26] = "brainpoolP256r1", + [27] = "brainpoolP384r1", + [28] = "brainpoolP512r1", + [0xFF01] = "arbitrary_explicit_prime_curves", + [0xFF02] = "arbitrary_explicit_char2_curves" + } &default=function(i: count):string { return fmt("unknown-%d", i); }; + + ## Mapping between numeric codes and human readable string for SSL/TLC EC point formats. + # See http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-9 + const ec_point_formats: table[count] of string = { + [0] = "uncompressed", + [1] = "ansiX962_compressed_prime", + [2] = "ansiX962_compressed_char2" + } &default=function(i: count):string { return fmt("unknown-%d", i); }; + # SSLv2 const SSLv20_CK_RC4_128_WITH_MD5 = 0x010080; const SSLv20_CK_RC4_128_EXPORT40_WITH_MD5 = 0x020080; @@ -458,8 +501,8 @@ export { const SSL_RSA_WITH_DES_CBC_MD5 = 0xFF82; const SSL_RSA_WITH_3DES_EDE_CBC_MD5 = 0xFF83; const TLS_EMPTY_RENEGOTIATION_INFO_SCSV = 0x00FF; - - ## This is a table of all known cipher specs. It can be used for + + ## This is a table of all known cipher specs. It can be used for ## detecting unknown ciphers and for converting the cipher spec ## constants into a human readable format. const cipher_desc: table[count] of string = { @@ -820,43 +863,5 @@ export { [SSL_RSA_WITH_3DES_EDE_CBC_MD5] = "SSL_RSA_WITH_3DES_EDE_CBC_MD5", [TLS_EMPTY_RENEGOTIATION_INFO_SCSV] = "TLS_EMPTY_RENEGOTIATION_INFO_SCSV", } &default=function(i: count):string { return fmt("unknown-%d", i); }; - - ## Mapping between the constants and string values for SSL/TLS errors. - const x509_errors: table[count] of string = { - [0] = "ok", - [1] = "unable to get issuer cert", - [2] = "unable to get crl", - [3] = "unable to decrypt cert signature", - [4] = "unable to decrypt crl signature", - [5] = "unable to decode issuer public key", - [6] = "cert signature failure", - [7] = "crl signature failure", - [8] = "cert not yet valid", - [9] = "cert has expired", - [10] = "crl not yet valid", - [11] = "crl has expired", - [12] = "error in cert not before field", - [13] = "error in cert not after field", - [14] = "error in crl last update field", - [15] = "error in crl next update field", - [16] = "out of mem", - [17] = "depth zero self signed cert", - [18] = "self signed cert in chain", - [19] = "unable to get issuer cert locally", - [20] = "unable to verify leaf signature", - [21] = "cert chain too long", - [22] = "cert revoked", - [23] = "invalid ca", - [24] = "path length exceeded", - [25] = "invalid purpose", - [26] = "cert untrusted", - [27] = "cert rejected", - [28] = "subject issuer mismatch", - [29] = "akid skid mismatch", - [30] = "akid issuer serial mismatch", - [31] = "keyusage no certsign", - [32] = "unable to get crl issuer", - [33] = "unhandled critical extension", - } &default=function(i: count):string { return fmt("unknown-%d", i); }; } diff --git a/scripts/base/protocols/ssl/main.bro b/scripts/base/protocols/ssl/main.bro index eb128483a4..e3c3320f74 100644 --- a/scripts/base/protocols/ssl/main.bro +++ b/scripts/base/protocols/ssl/main.bro @@ -159,7 +159,7 @@ event ssl_server_hello(c: connection, version: count, possible_ts: time, server_ c$ssl$cipher = cipher_desc[cipher]; } -event tls_extension_server_name(c: connection, is_orig: bool, names: string_vec) &priority=5 +event ssl_extension_server_name(c: connection, is_orig: bool, names: string_vec) &priority=5 { set_session(c); @@ -198,7 +198,7 @@ event connection_state_remove(c: connection) &priority=-5 event protocol_confirmation(c: connection, atype: Analyzer::Tag, aid: count) &priority=5 { - if ( atype == Analyzer::ANALYZER_SSL ) + if ( atype == Analyzer::ANALYZER_SSL ) { set_session(c); c$ssl$analyzer_id = aid; diff --git a/scripts/policy/protocols/ssl/heartbleed.bro b/scripts/policy/protocols/ssl/heartbleed.bro index dc38c66f73..dbf27ec63e 100644 --- a/scripts/policy/protocols/ssl/heartbleed.bro +++ b/scripts/policy/protocols/ssl/heartbleed.bro @@ -1,6 +1,9 @@ module Heartbleed; -# Please note - this is not well tested. Use at your own risk. +# Detect the TLS heartbleed attack. See http://heartbleed.com/ + +# Do not disable analyzers after detection - otherwhise we will not notice encrypted attacks +redef SSL::disable_analyzer_after_detection=F; redef record SSL::Info += { last_originator_heartbeat_request_size: count &optional; diff --git a/src/analyzer/protocol/ssl/events.bif b/src/analyzer/protocol/ssl/events.bif index 457d18ea39..5106552740 100644 --- a/src/analyzer/protocol/ssl/events.bif +++ b/src/analyzer/protocol/ssl/events.bif @@ -66,6 +66,8 @@ event ssl_server_hello%(c: connection, version: count, possible_ts: time, server ## information out of that as it can. This event provides access to any ## extensions either side sends as part of an extended *hello* message. ## +## Note that Bro offers a few more specialized events for a few extensions. +## ## c: The connection. ## ## is_orig: True if event is raised for originator side of the connection. @@ -77,16 +79,75 @@ event ssl_server_hello%(c: connection, version: count, possible_ts: time, server ## val: The raw extension value that was sent in the message. ## ## .. bro:see:: ssl_alert ssl_client_hello ssl_established ssl_server_hello -## ssl_session_ticket_handshake +## ssl_session_ticket_handshake ssl_extension_ec_point_formats +## ssl_extension_elliptic_curves ssl_extension_application_layer_protocol_negotiation +## ssl_extension_server_name event ssl_extension%(c: connection, is_orig: bool, code: count, val: string%); -event tls_extension_ec_point_formats%(c: connection, is_orig: bool, point_formats: index_vec%); +## This TLS extension is defined in :rfc:`4492` and sent by the client in the initial +## handshake. It gives the list of elliptic curves supported by the client. +## +## c: The connection. +## +## is_orig: True if event is raised for originator side of the connection. +## +## curves: List of supported elliptic curves. +## +## .. bro:see:: ssl_alert ssl_client_hello ssl_established ssl_server_hello +## ssl_session_ticket_handshake ssl_extension +## ssl_extension_ec_point_formats ssl_extension_application_layer_protocol_negotiation +## ssl_extension_server_name +event ssl_extension_elliptic_curves%(c: connection, is_orig: bool, curves: index_vec%); -event tls_extension_elliptic_curves%(c: connection, is_orig: bool, curves: index_vec%); +## This TLS extension is defined in :rfc:`4492` and sent by the client and/or server in the initial +## handshake. It gives the list of elliptic curve point formats supported by the client. +## +## c: The connection. +## +## is_orig: True if event is raised for originator side of the connection. +## +## point_formats: List of supported point formats. +## +## .. bro:see:: ssl_alert ssl_client_hello ssl_established ssl_server_hello +## ssl_session_ticket_handshake ssl_extension +## ssl_extension_elliptic_curves ssl_extension_application_layer_protocol_negotiation +## ssl_extension_server_name +event ssl_extension_ec_point_formats%(c: connection, is_orig: bool, point_formats: index_vec%); -event tls_extension_application_layer_protocol_negotiation%(c: connection, is_orig: bool, protocols: string_vec%); +## This TLS extension is defined in draft-ietf-tls-applayerprotoneg and sent +## in the initial handshake. It contains the list of client supported application protocols +## by the client or the server, respectovely. +## +## At the moment it is mostly used to negotiate the use of SPDY / HTTP2-drafts. +## +## c: The connection. +## +## is_orig: True if event is raised for originator side of the connection. +## +## protocols: List of supported application layer protocols. +## +## .. bro:see:: ssl_alert ssl_client_hello ssl_established ssl_server_hello +## ssl_session_ticket_handshake ssl_extension +## ssl_extension_elliptic_curves ssl_extension_ec_point_formats +## ssl_extension_server_name +event ssl_extension_application_layer_protocol_negotiation%(c: connection, is_orig: bool, protocols: string_vec%); -event tls_extension_server_name%(c: connection, is_orig: bool, names: string_vec%); +## This SSL/TLS extension is defined in :rfc:`3546` and sent by the client +## in the initial handshake. It contains the name of the server it is contacting. +## This information can be used by the server to choose the correct certificate for +## the host the client wants to contact. +## +## c: The connection. +## +## is_orig: True if event is raised for originator side of the connection. +## +## protocols: List of supported application layer protocols. +## +## .. bro:see:: ssl_alert ssl_client_hello ssl_established ssl_server_hello +## ssl_session_ticket_handshake ssl_extension +## ssl_extension_elliptic_curves ssl_extension_ec_point_formats +## ssl_extension_application_layer_protocol_negotiation +event ssl_extension_server_name%(c: connection, is_orig: bool, names: string_vec%); ## Generated at the end of an SSL/TLS handshake. SSL/TLS sessions start with ## an unencrypted handshake, and Bro extracts as much information out of that @@ -147,6 +208,37 @@ event ssl_alert%(c: connection, is_orig: bool, level: count, desc: count%); ## ssl_alert event ssl_session_ticket_handshake%(c: connection, ticket_lifetime_hint: count, ticket: string%); +## Generated for SSL/TLS heartbeat messages that are sent before session encryption +## starts. Generally heartbeat messages should rarely be seen in normal TLS traffic. +## Heartbeats are described in :rfc:`6520`. +## +## c: The connection. +## +## length: length of the entire heartbeat message. +## +## heartbeat_type: type of the heartbeat message. Per RFC, 1 = request, 2 = response +## +## payload_length: length of the payload of the heartbeat message, according to packet field +## +## payload: payload contained in the heartbeat message. Size can differ from payload_length, +## if payload_length and actual packet length disagree. +## +## .. bro:see:: ssl_client_hello ssl_established ssl_extension ssl_server_hello +## ssl_alert ssl_encrypted_heartbeat +event ssl_heartbeat%(c: connection, is_orig: bool, length: count, heartbeat_type: count, payload_length: count, payload: string%); + +## Generated for SSL/TLS heartbeat messages that are sent after session encryption +## started. Generally heartbeat messages should rarely be seen in normal TLS traffic. +## Heartbeats are described in :rfc:`6520`. +## +## Note that :bro:id:`SSL::disable_analyzer_after_detection` has to be set to false. +## Otherwhise this event will never be thrown. +## +## c: The connection. +## +## length: length of the entire heartbeat message. +## +## .. bro:see:: ssl_client_hello ssl_established ssl_extension ssl_server_hello +## ssl_alert ssl_heartbeat 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 043b2271ec..66224bf45a 100644 --- a/src/analyzer/protocol/ssl/ssl-analyzer.pac +++ b/src/analyzer/protocol/ssl/ssl-analyzer.pac @@ -228,7 +228,7 @@ refine connection SSL_Conn += { } } - BifEvent::generate_tls_extension_ec_point_formats(bro_analyzer(), bro_analyzer()->Conn(), + BifEvent::generate_ssl_extension_ec_point_formats(bro_analyzer(), bro_analyzer()->Conn(), ${rec.is_orig}, points); return true; @@ -245,7 +245,7 @@ refine connection SSL_Conn += { } } - BifEvent::generate_tls_extension_elliptic_curves(bro_analyzer(), bro_analyzer()->Conn(), + BifEvent::generate_ssl_extension_elliptic_curves(bro_analyzer(), bro_analyzer()->Conn(), ${rec.is_orig}, curves); return true; @@ -262,7 +262,7 @@ refine connection SSL_Conn += { } } - BifEvent::generate_tls_extension_application_layer_protocol_negotiation(bro_analyzer(), bro_analyzer()->Conn(), + BifEvent::generate_ssl_extension_application_layer_protocol_negotiation(bro_analyzer(), bro_analyzer()->Conn(), ${rec.is_orig}, plist); return true; @@ -278,18 +278,18 @@ refine connection SSL_Conn += { ServerName* servername = (*list)[i]; if ( servername->name_type() != 0 ) { - bro_analyzer()->Weird(fmt("Encountered unknown type in server name tls extension: %d", servername->name_type())); + bro_analyzer()->Weird(fmt("Encountered unknown type in server name ssl extension: %d", servername->name_type())); continue; } if ( servername->host_name() ) servers->Assign(j++, new StringVal(servername->host_name()->host_name().length(), (const char*) servername->host_name()->host_name().data())); else - bro_analyzer()->Weird("Empty server_name extension in tls connection"); + bro_analyzer()->Weird("Empty server_name extension in ssl connection"); } } - BifEvent::generate_tls_extension_server_name(bro_analyzer(), bro_analyzer()->Conn(), + BifEvent::generate_ssl_extension_server_name(bro_analyzer(), bro_analyzer()->Conn(), ${rec.is_orig}, servers); return true; diff --git a/testing/btest/Baseline/scripts.base.protocols.ssl.tls-extension-events/.stdout b/testing/btest/Baseline/scripts.base.protocols.ssl.tls-extension-events/.stdout new file mode 100644 index 0000000000..8305175edb --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.ssl.tls-extension-events/.stdout @@ -0,0 +1,13 @@ +server_name, 192.168.4.149, 74.125.239.152, [google.de] +Curves, 192.168.4.149, 74.125.239.152 +secp256r1 +secp384r1 +secp521r1 +Point formats, 192.168.4.149, 74.125.239.152, T +uncompressed +ALPN, 192.168.4.149, 74.125.239.152, [spdy/3, spdy/3.1, http/1.1] +Point formats, 192.168.4.149, 74.125.239.152, F +uncompressed +ansiX962_compressed_prime +ansiX962_compressed_char2 +ALPN, 192.168.4.149, 74.125.239.152, [spdy/3.1] 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 new file mode 100644 index 0000000000..863d8dd9c0 --- /dev/null +++ b/testing/btest/Baseline/scripts.policy.protocols.ssl.heartbleed/notice-encrypted.log @@ -0,0 +1,10 @@ +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path notice +#open 2014-04-24-19-05-00 +#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 1, first packet server record length 32 - 192.168.4.149 107.170.241.107 443 - bro Notice::ACTION_LOG 3600.000000 F - - - - - +#close 2014-04-24-19-05-00 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 new file mode 100644 index 0000000000..9722e20655 --- /dev/null +++ b/testing/btest/Baseline/scripts.policy.protocols.ssl.heartbleed/notice-heartbleed-success.log @@ -0,0 +1,11 @@ +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path notice +#open 2014-04-24-18-30-54 +#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 diff --git a/testing/btest/Baseline/scripts.policy.protocols.ssl.heartbleed/notice-heartbleed.log b/testing/btest/Baseline/scripts.policy.protocols.ssl.heartbleed/notice-heartbleed.log new file mode 100644 index 0000000000..da376c79a0 --- /dev/null +++ b/testing/btest/Baseline/scripts.policy.protocols.ssl.heartbleed/notice-heartbleed.log @@ -0,0 +1,10 @@ +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path notice +#open 2014-04-24-18-29-46 +#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 +1396973486.753913 CXWv6p3arKYeMETxOg 173.203.79.216 46592 162.219.2.166 443 - - - tcp Heartbleed::SSL_Heartbeat_Attack An TLS heartbleed attack was detected! Record length 16368, payload length 16365 - 173.203.79.216 162.219.2.166 443 - bro Notice::ACTION_LOG 3600.000000 F - - - - - +#close 2014-04-24-18-29-46 diff --git a/testing/btest/Traces/tls/chrome-34-google.trace b/testing/btest/Traces/tls/chrome-34-google.trace new file mode 100644 index 0000000000000000000000000000000000000000..e02d35a5f1f37a6f2525fe7b7465dc9c4f4cf36c GIT binary patch literal 11582 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

fLIN#~ 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 literal 0 HcmV?d00001 diff --git a/testing/btest/Traces/tls/heartbleed-success.pcap b/testing/btest/Traces/tls/heartbleed-success.pcap new file mode 100644 index 0000000000000000000000000000000000000000..47a2bac1a3e69860dcff61ba86d805f26e5e9562 GIT binary patch literal 39524 zcmeI52~-nF8po@1K?30xK@fwWC@6F^C

_D5$6df`}*+AV>rfOoGTbDj>+Aj<++8 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 literal 0 HcmV?d00001 diff --git a/testing/btest/Traces/tls/heartbleed.pcap b/testing/btest/Traces/tls/heartbleed.pcap new file mode 100644 index 0000000000000000000000000000000000000000..46e7935d18109d31e681d0037c8b0cb1a93fb57b GIT binary patch literal 22223 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 Date: Thu, 24 Apr 2014 12:36:05 -0700 Subject: [PATCH 16/17] Add very basic ocsp stapling support. This only allows access to the ocsp stapling response data. No verification or anything else at the moment. --- scripts/site/local.bro | 5 +++-- src/analyzer/protocol/ssl/events.bif | 14 ++++++++++++++ src/analyzer/protocol/ssl/ssl-analyzer.pac | 15 +++++++++++++++ src/analyzer/protocol/ssl/ssl-protocol.pac | 1 + .../.stdout | 1 + testing/btest/Traces/tls/ocsp-stapling.trace | Bin 0 -> 9427 bytes .../base/protocols/ssl/ocsp-stapling.test | 7 +++++++ 7 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 testing/btest/Baseline/scripts.base.protocols.ssl.ocsp-stapling/.stdout create mode 100644 testing/btest/Traces/tls/ocsp-stapling.trace create mode 100644 testing/btest/scripts/base/protocols/ssl/ocsp-stapling.test diff --git a/scripts/site/local.bro b/scripts/site/local.bro index bb2cc73a53..afe1d9d4f2 100644 --- a/scripts/site/local.bro +++ b/scripts/site/local.bro @@ -81,5 +81,6 @@ # 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 +# Uncomment the following line to enable detection of the heartbleed attack. Enabling +# this might impact performance a bit. +# @load policy/protocols/ssl/heartbleed diff --git a/src/analyzer/protocol/ssl/events.bif b/src/analyzer/protocol/ssl/events.bif index 5106552740..f32649403b 100644 --- a/src/analyzer/protocol/ssl/events.bif +++ b/src/analyzer/protocol/ssl/events.bif @@ -214,6 +214,8 @@ event ssl_session_ticket_handshake%(c: connection, ticket_lifetime_hint: count, ## ## c: The connection. ## +## is_orig: True if event is raised for originator side of the connection. +## ## length: length of the entire heartbeat message. ## ## heartbeat_type: type of the heartbeat message. Per RFC, 1 = request, 2 = response @@ -236,9 +238,21 @@ event ssl_heartbeat%(c: connection, is_orig: bool, length: count, heartbeat_type ## ## c: The connection. ## +## is_orig: True if event is raised for originator side of the connection. +## ## length: length of the entire heartbeat message. ## ## .. bro:see:: ssl_client_hello ssl_established ssl_extension ssl_server_hello ## ssl_alert ssl_heartbeat event ssl_encrypted_heartbeat%(c: connection, is_orig: bool, length: count%); +## This event contains the OCSP response contained in a Certificate Status Request +## message, when the client requested OCSP stapling and the server supports it. See +## description in :rfc:`6066` +## +## c: The connection. +## +## is_orig: True if event is raised for originator side of the connection. +## +## response: OCSP data. +event ssl_stapled_ocsp%(c: connection, is_orig: bool, response: string%); diff --git a/src/analyzer/protocol/ssl/ssl-analyzer.pac b/src/analyzer/protocol/ssl/ssl-analyzer.pac index 66224bf45a..e9d29675c3 100644 --- a/src/analyzer/protocol/ssl/ssl-analyzer.pac +++ b/src/analyzer/protocol/ssl/ssl-analyzer.pac @@ -389,6 +389,17 @@ refine connection SSL_Conn += { return true; %} + function proc_certificate_status(rec : SSLRecord, status_type: uint8, response: bytestring) : bool + %{ + if ( status_type == 1 ) // ocsp + { + BifEvent::generate_ssl_stapled_ocsp(bro_analyzer(), + bro_analyzer()->Conn(), ${rec.is_orig}, + new StringVal(response.length(), (const char*) response.data())); + } + + return true; + %} }; refine typeattr Alert += &let { @@ -473,3 +484,7 @@ refine typeattr ApplicationLayerProtocolNegotiationExtension += &let { refine typeattr ServerNameExt += &let { proc : bool = $context.connection.proc_server_name(rec, server_names); }; + +refine typeattr CertificateStatus += &let { + proc : bool = $context.connection.proc_certificate_status(rec, status_type, response); +}; diff --git a/src/analyzer/protocol/ssl/ssl-protocol.pac b/src/analyzer/protocol/ssl/ssl-protocol.pac index 857bd01f26..a8b9975eb5 100644 --- a/src/analyzer/protocol/ssl/ssl-protocol.pac +++ b/src/analyzer/protocol/ssl/ssl-protocol.pac @@ -341,6 +341,7 @@ type Certificate(rec: SSLRecord) = record { type CertificateStatus(rec: SSLRecord) = record { status_type: uint8; # 1 = ocsp, everything else is undefined + length : uint24; response: bytestring &restofdata; }; diff --git a/testing/btest/Baseline/scripts.base.protocols.ssl.ocsp-stapling/.stdout b/testing/btest/Baseline/scripts.base.protocols.ssl.ocsp-stapling/.stdout new file mode 100644 index 0000000000..a8735f6d41 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.ssl.ocsp-stapling/.stdout @@ -0,0 +1 @@ +F, 1995 diff --git a/testing/btest/Traces/tls/ocsp-stapling.trace b/testing/btest/Traces/tls/ocsp-stapling.trace new file mode 100644 index 0000000000000000000000000000000000000000..8b66f7288d11316bcea20d3ca91066cf167391c4 GIT binary patch literal 9427 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 literal 0 HcmV?d00001 diff --git a/testing/btest/scripts/base/protocols/ssl/ocsp-stapling.test b/testing/btest/scripts/base/protocols/ssl/ocsp-stapling.test new file mode 100644 index 0000000000..b50f04a92e --- /dev/null +++ b/testing/btest/scripts/base/protocols/ssl/ocsp-stapling.test @@ -0,0 +1,7 @@ +# @TEST-EXEC: bro -C -r $TRACES/tls/ocsp-stapling.trace %INPUT +# @TEST-EXEC: btest-diff .stdout + +event ssl_stapled_ocsp(c: connection, is_orig: bool, response: string) + { + print is_orig, |response|; + } From 3d22692b6e897c1935cb60a8890de8bb76ef2b47 Mon Sep 17 00:00:00 2001 From: Bernhard Amann Date: Thu, 24 Apr 2014 14:45:06 -0700 Subject: [PATCH 17/17] Fix a few failing tests --- scripts/policy/protocols/ssl/heartbleed.bro | 7 +++++-- scripts/test-all-policy.bro | 1 + testing/btest/core/leaks/http-connect.bro | 2 +- testing/btest/core/leaks/x509_verify.bro | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/scripts/policy/protocols/ssl/heartbleed.bro b/scripts/policy/protocols/ssl/heartbleed.bro index dbf27ec63e..1e0fdcd98b 100644 --- a/scripts/policy/protocols/ssl/heartbleed.bro +++ b/scripts/policy/protocols/ssl/heartbleed.bro @@ -1,6 +1,9 @@ -module Heartbleed; +# Detect the TLS heartbleed attack. See http://heartbleed.com -# Detect the TLS heartbleed attack. See http://heartbleed.com/ +@load base/protocols/ssl +@load base/frameworks/notice + +module Heartbleed; # Do not disable analyzers after detection - otherwhise we will not notice encrypted attacks redef SSL::disable_analyzer_after_detection=F; diff --git a/scripts/test-all-policy.bro b/scripts/test-all-policy.bro index 895a9a8901..5c6ed286fb 100644 --- a/scripts/test-all-policy.bro +++ b/scripts/test-all-policy.bro @@ -85,6 +85,7 @@ @load protocols/ssh/software.bro @load protocols/ssl/expiring-certs.bro @load protocols/ssl/extract-certs-pem.bro +@load protocols/ssl/heartbleed.bro @load protocols/ssl/known-certs.bro @load protocols/ssl/log-hostcerts-only.bro #@load protocols/ssl/notary.bro diff --git a/testing/btest/core/leaks/http-connect.bro b/testing/btest/core/leaks/http-connect.bro index e9a47d00a2..fe42f3ec0a 100644 --- a/testing/btest/core/leaks/http-connect.bro +++ b/testing/btest/core/leaks/http-connect.bro @@ -5,7 +5,7 @@ # @TEST-REQUIRES: bro --help 2>&1 | grep -q mem-leaks # # @TEST-EXEC: HEAP_CHECK_DUMP_DIRECTORY=. HEAPCHECK=local btest-bg-run bro bro -b -m -r $TRACES/http/connect-with-smtp.trace %INPUT -# @TEST-EXEC: btest-bg-wait 15 +# @TEST-EXEC: btest-bg-wait 30 @load base/protocols/conn @load base/protocols/http diff --git a/testing/btest/core/leaks/x509_verify.bro b/testing/btest/core/leaks/x509_verify.bro index 426a95d2c2..f4a5ddc7d1 100644 --- a/testing/btest/core/leaks/x509_verify.bro +++ b/testing/btest/core/leaks/x509_verify.bro @@ -5,7 +5,7 @@ # @TEST-REQUIRES: bro --help 2>&1 | grep -q mem-leaks # # @TEST-EXEC: HEAP_CHECK_DUMP_DIRECTORY=. HEAPCHECK=local btest-bg-run bro bro -b -m -r $TRACES/tls/tls-expired-cert.trace %INPUT -# @TEST-EXEC: btest-bg-wait 15 +# @TEST-EXEC: btest-bg-wait 30 @load base/protocols/ssl