diff --git a/CHANGES b/CHANGES index fc4b7a8ea8..01cb336793 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,24 @@ +2.2-377 | 2014-04-24 16:57:54 -0700 + + * A larger set of SSL improvements and extensions. Addresses + BIT-1178. (Bernhard Amann) + + - Fixes TLS protocol version detection. It also should + bail-out correctly on non-tls-connections now + + - Adds support for a few TLS extensions, including + server_name, alpn, and ec-curves. + + - Adds support for the heartbeat events. + + - Add Heartbleed detector script. + + - Adds basic support for OCSP stapling. + + * Fix parsing of DNS TXT RRs w/ multiple character-strings. + Addresses BIT-1156. (Jon Siwek) + 2.2-353 | 2014-04-24 16:12:30 -0700 * Adapt HTTP partial content to cache file analysis IDs. (Jon Siwek) diff --git a/NEWS b/NEWS index 9da01925d1..9de61a2dc4 100644 --- a/NEWS +++ b/NEWS @@ -40,6 +40,11 @@ New Functionality magic matches and their corresponding strength against a given chunk of data. +- The SSL analyzer now has support heartbeats as well as for a few + extensions, including server_name, alpn, and ec-curves. + +- The SSL analyzer comes with Heartbleed detector script in + protocols/ssl/heartbleed.bro. Changed Functionality --------------------- diff --git a/VERSION b/VERSION index 4d60030d10..1bd7cbc5f9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.2-353 +2.2-377 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/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 5b974222a1..e3c3320f74 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 ssl_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 @@ -194,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 new file mode 100644 index 0000000000..c4842d6a0a --- /dev/null +++ b/scripts/policy/protocols/ssl/heartbleed.bro @@ -0,0 +1,121 @@ +##! Detect the TLS heartbleed attack. See http://heartbleed.com for more. + +@load base/protocols/ssl +@load base/frameworks/notice + +module Heartbleed; + +export { + redef enum Notice::Type += { + ## Indicates that a host performing a heartbleed attack. + SSL_Heartbeat_Attack, + ## Indicates that a host performing a heartbleed attack was probably successful. + SSL_Heartbeat_Attack_Success, + ## Indicates we saw heartbeat requests with odd length. Probably an attack. + SSL_Heartbeat_Odd_Length, + ## Indicates we saw many heartbeat requests without an reply. Might be an attack. + SSL_Heartbeat_Many_Requests + }; +} + +# 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; + last_responder_heartbeat_request_size: count &optional; + originator_heartbeats: count &default=0; + responder_heartbeats: count &default=0; + + heartbleed_detected: bool &default=F; + }; + +event ssl_heartbeat(c: connection, is_orig: bool, length: count, heartbeat_type: count, payload_length: count, payload: string) + { + if ( heartbeat_type == 1 ) + { + local checklength: count = (length<(3+16)) ? length : (length - 3 - 16); + + if ( payload_length > checklength ) + { + c$ssl$heartbleed_detected = T; + NOTICE([$note=SSL_Heartbeat_Attack, + $msg=fmt("An TLS heartbleed attack was detected! Record length %d, payload length %d", length, payload_length), + $conn=c, + $identifier=cat(c$uid, length, payload_length) + ]); + } + } + + if ( heartbeat_type == 2 && c$ssl$heartbleed_detected ) + { + NOTICE([$note=SSL_Heartbeat_Attack_Success, + $msg=fmt("An TLS heartbleed attack detected before was probably exploited. Transmitted payload length in first packet: %d", payload_length), + $conn=c, + $identifier=c$uid + ]); + } + } + +event ssl_encrypted_heartbeat(c: connection, is_orig: bool, length: count) + { + if ( is_orig ) + ++c$ssl$originator_heartbeats; + else + ++c$ssl$responder_heartbeats; + + if ( c$ssl$originator_heartbeats > c$ssl$responder_heartbeats + 3 ) + NOTICE([$note=SSL_Heartbeat_Many_Requests, + $msg=fmt("Seeing more than 3 heartbeat requests without replies from server. Possible attack. Client count: %d, server count: %d", c$ssl$originator_heartbeats, c$ssl$responder_heartbeats), + $conn=c, + $n=(c$ssl$originator_heartbeats-c$ssl$responder_heartbeats), + $identifier=fmt("%s%d", c$uid, c$ssl$responder_heartbeats/1000) # re-throw every 1000 heartbeats + ]); + + if ( c$ssl$responder_heartbeats > c$ssl$originator_heartbeats + 3 ) + NOTICE([$note=SSL_Heartbeat_Many_Requests, + $msg=fmt("Server is sending more heartbleed responsed than requests were seen. Possible attack. Client count: %d, server count: %d", c$ssl$originator_heartbeats, c$ssl$responder_heartbeats), + $conn=c, + $n=(c$ssl$originator_heartbeats-c$ssl$responder_heartbeats), + $identifier=fmt("%s%d", c$uid, c$ssl$responder_heartbeats/1000) # re-throw every 1000 heartbeats + ]); + + if ( is_orig && length < 19 ) + NOTICE([$note=SSL_Heartbeat_Odd_Length, + $msg=fmt("Heartbeat message smaller than minimum required length. Probable attack. Message length: %d", length), + $conn=c, + $n=length, + $identifier=cat(c$uid, length) + ]); + + if ( is_orig ) + { + if ( c$ssl?$last_responder_heartbeat_request_size ) + { + # server originated heartbeat. Ignore & continue + delete c$ssl$last_responder_heartbeat_request_size; + } + + else + c$ssl$last_originator_heartbeat_request_size = length; + } + else + { + if ( c$ssl?$last_originator_heartbeat_request_size && c$ssl$last_originator_heartbeat_request_size < length ) + { + NOTICE([$note=SSL_Heartbeat_Attack_Success, + $msg=fmt("An Encrypted TLS heartbleed attack was probably detected! First packet client record length %d, first packet server record length %d", + c$ssl?$last_originator_heartbeat_request_size, c$ssl$last_originator_heartbeat_request_size), + $conn=c, + $identifier=c$uid # only throw once per connection + ]); + } + + else if ( ! c$ssl?$last_originator_heartbeat_request_size ) + c$ssl$last_responder_heartbeat_request_size = length; + + if ( c$ssl?$last_originator_heartbeat_request_size ) + delete c$ssl$last_originator_heartbeat_request_size; + } + } diff --git a/scripts/site/local.bro b/scripts/site/local.bro index e1a3574424..afe1d9d4f2 100644 --- a/scripts/site/local.bro +++ b/scripts/site/local.bro @@ -81,3 +81,6 @@ # Detect SHA1 sums in Team Cymru's Malware Hash Registry. @load frameworks/files/detect-MHR +# 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/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/src/analyzer/protocol/ssl/events.bif b/src/analyzer/protocol/ssl/events.bif index 054d9c672f..555168e82f 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 more specialized events for a few extensions. +## ## c: The connection. ## ## is_orig: True if event is raised for originator side of the connection. @@ -77,9 +79,81 @@ 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%); +## Generated for an SSL/TLS Elliptic Curves extension. 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%); + +## Generated for an SSL/TLS Supported Point Formats extension. 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%); + +## Generated for an SSL/TLS Application-Layer Protocol Negotiation extension. +## 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%); + +## Generated for an SSL/TLS Server Name extension. 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 ## as it can. This event signals the time when an SSL/TLS has finished the @@ -138,3 +212,52 @@ 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%); + +## 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. +## +## 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 +## +## 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. +## +## 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 5f9d092440..62300557da 100644 --- a/src/analyzer/protocol/ssl/ssl-analyzer.pac +++ b/src/analyzer/protocol/ssl/ssl-analyzer.pac @@ -86,46 +86,9 @@ 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; - #%} - %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(), @@ -217,12 +180,106 @@ 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_ssl_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_ssl_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_ssl_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 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 ssl connection"); + } + } + + BifEvent::generate_ssl_extension_server_name(bro_analyzer(), bro_analyzer()->Conn(), + ${rec.is_orig}, servers); + return true; %} @@ -263,9 +320,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(), @@ -278,11 +335,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()); @@ -296,17 +348,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", @@ -317,25 +358,48 @@ 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()); } + if ( ${rec.content_type} == HEARTBEAT ) + BifEvent::generate_ssl_encrypted_heartbeat(bro_analyzer(), + bro_analyzer()->Conn(), ${rec.is_orig}, ${rec.length}); + return true; %} -}; -refine typeattr ChangeCipherSpec += &let { - proc : bool = $context.connection.proc_change_cipher_spec(rec) - &requires(state_changed); + function proc_heartbeat(rec : SSLRecord, type: uint8, payload_length: uint16, data: bytestring) : bool + %{ + BifEvent::generate_ssl_heartbeat(bro_analyzer(), + bro_analyzer()->Conn(), ${rec.is_orig}, ${rec.length}, type, payload_length, + new StringVal(data.length(), (const char*) data.data())); + return true; + %} + + 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; + %} + + 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 { @@ -346,57 +410,49 @@ 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); }; 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); + + 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); }; 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 { - 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 { 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); } @@ -410,5 +466,25 @@ 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); +}; + +refine typeattr CertificateStatus += &let { + proc : bool = $context.connection.proc_certificate_status(rec, status_type, response); }; diff --git a/src/analyzer/protocol/ssl/ssl-defs.pac b/src/analyzer/protocol/ssl/ssl-defs.pac index c35fc56e85..24827d3621 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, @@ -19,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, @@ -27,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 9368122eaa..a3729826c4 100644 --- a/src/analyzer/protocol/ssl/ssl-protocol.pac +++ b/src/analyzer/protocol/ssl/ssl-protocol.pac @@ -34,26 +34,23 @@ 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); + $context.connection.determine_ssl_record_layer(head0, head1, head2, head3, head4); content_type : int = case version of { - UNKNOWN_VERSION -> 0; SSLv20 -> head2+300; default -> head0; }; length : int = case version of { - UNKNOWN_VERSION -> 0; SSLv20 -> (((head0 & 0x7f) << 8) | head1) - 3; default -> (head3 << 8) | head4; }; }; -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); @@ -63,6 +60,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); @@ -71,75 +69,98 @@ 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 { - 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 +197,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 +216,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); }; @@ -226,17 +233,21 @@ type ApplicationData(rec: SSLRecord) = record { }; ###################################################################### -# Handshake Protocol (7.4.) +# V3 Heartbeat ###################################################################### +type Heartbeat(rec: SSLRecord) = record { + type : uint8; + payload_length : uint16; + data : bytestring &restofdata; +}; + ###################################################################### # V3 Hello Request (7.4.1.1.) ###################################################################### # Hello Request is empty -type HelloRequest(rec: SSLRecord) = empty &let { - hr: bool = $context.connection.set_hello_requested(true); -}; +type HelloRequest(rec: SSLRecord) = empty; ###################################################################### @@ -257,16 +268,8 @@ 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(); }; - ###################################################################### # V2 Client Hello (SSLv2 2.5.) ###################################################################### @@ -279,13 +282,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 +302,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 +320,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; }; @@ -351,20 +334,18 @@ 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); -} &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(); -}; + 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 + length : uint24; + response: bytestring &restofdata; +}; ###################################################################### # V3 Server Key Exchange Message (7.4.3.) @@ -373,11 +354,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 +364,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 +372,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 +391,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,12 +406,10 @@ 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); + # encryption starts for both sides after this message. + state_changed_client : bool = $context.connection.startEncryption(true); + state_changed_server : bool = $context.connection.startEncryption(false); }; @@ -465,11 +420,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 +431,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 +442,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 { @@ -522,7 +463,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); }; @@ -532,33 +473,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,67 +498,88 @@ type SSLPDU(is_orig: bool) = record { refine connection SSL_Conn += { %member{ - int state_; - int old_state_; - bool hello_requested_; + int client_state_; + int server_state_; + int record_layer_version_; %} %init{ - state_ = STATE_INITIAL; - old_state_ = STATE_INITIAL; - hello_requested_ = false; + 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 + else + return SSLv20; + } + + 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 state() : int %{ return state_; %} - function old_state() : int %{ return old_state_; %} + function client_state() : int %{ return client_state_; %} - function transition(olds : AnalyzerState, news : AnalyzerState, - current_record_is_orig : bool, is_orig : bool) : bool + function server_state() : int %{ return client_state_; %} + + function state(is_orig: bool) : int %{ - if ( (olds != STATE_ANY && olds != state_) || - current_record_is_orig != is_orig ) - return false; + if ( is_orig ) + return client_state_; + else + return server_state_; + %} - old_state_ = state_; - state_ = news; - - //printf("transitioning from %s to %s\n", state_label(old_state()).c_str(), state_label(state()).c_str()); + function startEncryption(is_orig: bool) : bool + %{ + 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; - %} }; 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/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 0000000000..e02d35a5f1 Binary files /dev/null and b/testing/btest/Traces/tls/chrome-34-google.trace differ diff --git a/testing/btest/Traces/tls/heartbleed-encrypted-success.pcap b/testing/btest/Traces/tls/heartbleed-encrypted-success.pcap new file mode 100644 index 0000000000..36584582d0 Binary files /dev/null and b/testing/btest/Traces/tls/heartbleed-encrypted-success.pcap differ diff --git a/testing/btest/Traces/tls/heartbleed-success.pcap b/testing/btest/Traces/tls/heartbleed-success.pcap new file mode 100644 index 0000000000..47a2bac1a3 Binary files /dev/null and b/testing/btest/Traces/tls/heartbleed-success.pcap differ diff --git a/testing/btest/Traces/tls/heartbleed.pcap b/testing/btest/Traces/tls/heartbleed.pcap new file mode 100644 index 0000000000..46e7935d18 Binary files /dev/null and b/testing/btest/Traces/tls/heartbleed.pcap differ diff --git a/testing/btest/Traces/tls/ocsp-stapling.trace b/testing/btest/Traces/tls/ocsp-stapling.trace new file mode 100644 index 0000000000..8b66f7288d Binary files /dev/null and b/testing/btest/Traces/tls/ocsp-stapling.trace differ 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 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|; + } diff --git a/testing/btest/scripts/base/protocols/ssl/tls-extension-events.test b/testing/btest/scripts/base/protocols/ssl/tls-extension-events.test new file mode 100644 index 0000000000..a2db2afe95 --- /dev/null +++ b/testing/btest/scripts/base/protocols/ssl/tls-extension-events.test @@ -0,0 +1,26 @@ +# @TEST-EXEC: bro -C -r $TRACES/tls/chrome-34-google.trace %INPUT +# @TEST-EXEC: btest-diff .stdout + +event ssl_extension_elliptic_curves(c: connection, is_orig: bool, curves: index_vec) + { + print "Curves", c$id$orig_h, c$id$resp_h; + for ( i in curves ) + print SSL::ec_curves[curves[i]]; + } + +event ssl_extension_ec_point_formats(c: connection, is_orig: bool, point_formats: index_vec) + { + print "Point formats", c$id$orig_h, c$id$resp_h, is_orig; + for ( i in point_formats ) + print SSL::ec_point_formats[point_formats[i]]; + } + +event ssl_extension_application_layer_protocol_negotiation(c: connection, is_orig: bool, protocols: string_vec) + { + print "ALPN", c$id$orig_h, c$id$resp_h, protocols; + } + +event ssl_extension_server_name(c: connection, is_orig: bool, names: string_vec) + { + print "server_name", c$id$orig_h, c$id$resp_h, names; + } diff --git a/testing/btest/scripts/policy/protocols/ssl/heartbleed.bro b/testing/btest/scripts/policy/protocols/ssl/heartbleed.bro new file mode 100644 index 0000000000..4a980bb895 --- /dev/null +++ b/testing/btest/scripts/policy/protocols/ssl/heartbleed.bro @@ -0,0 +1,13 @@ +# TEST-EXEC: bro -C -r $TRACES/tls/heartbleed.pcap %INPUT +# TEST-EXEC: mv notice.log notice-heartbleed.log +# TEST-EXEC: btest-diff notice-heartbleed.log + +# @TEST-EXEC: bro -C -r $TRACES/tls/heartbleed-success.pcap %INPUT +# @TEST-EXEC: mv notice.log notice-heartbleed-success.log +# @TEST-EXEC: btest-diff notice-heartbleed-success.log + +# @TEST-EXEC: bro -C -r $TRACES/tls/heartbleed-encrypted-success.pcap %INPUT +# @TEST-EXEC: mv notice.log notice-encrypted.log +# @TEST-EXEC: btest-diff notice-encrypted.log + +@load protocols/ssl/heartbleed