From 2c975495d3cf50b5d3c1bf69219bbbb501ec8d12 Mon Sep 17 00:00:00 2001 From: Don Appleman Date: Mon, 7 Mar 2011 14:46:23 -0600 Subject: [PATCH] Initial drop of updated ssl.bro --- {policy.old => policy}/ssl.bro | 267 +++++++++++++++++---------------- 1 file changed, 137 insertions(+), 130 deletions(-) rename {policy.old => policy}/ssl.bro (65%) diff --git a/policy.old/ssl.bro b/policy/ssl.bro similarity index 65% rename from policy.old/ssl.bro rename to policy/ssl.bro index 6a347a14cc..a27a7e3086 100644 --- a/policy.old/ssl.bro +++ b/policy/ssl.bro @@ -5,54 +5,84 @@ @load weird @load ssl-ciphers @load ssl-errors +@load functions + +module SSL; + +export { + redef enum Log::ID += { SSL_SERVER }; + type ServerLog: record { + ts: time; # timestamp + client_address: addr; # client address + client_port: port; # client port + cert_subject: X509; # certificate subject + not_valid_before: time; # certificate valid time constraint + not_valid_after: time; # certificate valid time constraint + ssl_tls_version: string; # version number + weak_client_ciphers_offered: bool; # true if client offered insecure ciphers + weak_server_ciphers_offered: bool; # true if server offered insecure ciphers + weak_cipher_agreed: bool; # true if insecure cipher agreed upon for use + }; + + + # If true, Bro stores the client and server cipher specs and performs + # additional tests. This costs an extra amount of memory (normally + # only for a short time) but enables detecting of non-intersecting + # cipher sets, for example. + const ssl_compare_cipherspecs = T &redef; + + # Whether to analyze certificates seen in SSL connections. + const ssl_analyze_certificates = T &redef; + + # If we analyze SSL certificates, we can choose to store them. + const ssl_store_certificates = T &redef; + + # Path where we dump the certificates into. If it's empty, + # use the current directory. + const ssl_store_cert_path = "certs" &redef; + + # If we analyze SSL certificates, we can choose to verify them. + const ssl_verify_certificates = T &redef; + + # This is the path where OpenSSL looks after the trusted certificates. + # If empty, the default path will be used. + const x509_trusted_cert_path = "" &redef; + + # Whether to store key-material exchanged in the handshaking phase. + const ssl_store_key_material = F &redef; + + # Report weak/unknown ciphers in CLIENT_HELLO, SSLv2 SERVER_HELLO. + const ssl_report_client_weak = F &redef; + const ssl_report_client_unknown = F &redef; + const ssl_report_server_weak = F &redef; + + # Log all ciphers. +# TODO: dga 3/11 Discarded for now; could be re-added, perhaps as a separate stream for the curious +# const ssl_log_ciphers = T &redef; + + global ssl_ports = { + 443/tcp, 563/tcp, 585/tcp, 614/tcp, 636/tcp, + 989/tcp, 990/tcp, 992/tcp, 993/tcp, 995/tcp, + } &redef; +} + +event bro_init() + { + Log::create_stream( "SSL_SERVER", "SSL::ServerLog" ); + Log::add_default_filter( "SSL_SERVER" ); + } -global ssl_log = open_log_file("ssl") &redef; redef enum Notice += { SSL_X509Violation, # blanket X509 error SSL_SessConIncon, # session data not consistent with connection }; - const SSLv2 = 0x0002; const SSLv3 = 0x0300; const TLSv10 = 0x0301; const TLSv11 = 0x0302; -# If true, Bro stores the client and server cipher specs and performs -# additional tests. This costs an extra amount of memory (normally -# only for a short time) but enables detecting of non-intersecting -# cipher sets, for example. -const ssl_compare_cipherspecs = T &redef; - -# Whether to analyze certificates seen in SSL connections. -const ssl_analyze_certificates = T &redef; - -# If we analyze SSL certificates, we can choose to store them. -const ssl_store_certificates = T &redef; - -# Path where we dump the certificates into. If it's empty, -# use the current directory. -const ssl_store_cert_path = "certs" &redef; - -# If we analyze SSL certificates, we can choose to verify them. -const ssl_verify_certificates = T &redef; - -# This is the path where OpenSSL looks after the trusted certificates. -# If empty, the default path will be used. -const x509_trusted_cert_path = "" &redef; - -# Whether to store key-material exchanged in the handshaking phase. -const ssl_store_key_material = F &redef; - -# Report weak/unknown ciphers in CLIENT_HELLO, SSLv2 SERVER_HELLO. -const ssl_report_client_weak = F &redef; -const ssl_report_client_unknown = F &redef; -const ssl_report_server_weak = F &redef; - -# Log all ciphers. -const ssl_log_ciphers = T &redef; - # NOTE: this is a 'local' port format for your site # --- well-known ports for ssl --------- redef capture_filters += { @@ -69,11 +99,6 @@ redef capture_filters += { ["pop3s"] = "tcp port 995" }; -global ssl_ports = { - 443/tcp, 563/tcp, 585/tcp, 614/tcp, 636/tcp, - 989/tcp, 990/tcp, 992/tcp, 993/tcp, 995/tcp, -} &redef; - redef dpd_config += { [[ANALYZER_SSL, ANALYZER_SSL_BINPAC]] = [$ports = ssl_ports] }; @@ -130,29 +155,33 @@ redef Weird::weird_action += { "SSLv3_data_without_full_handshake"]] = Weird::WEIRD_IGNORE }; - global SSL_cipherCount: table[count] of count &default = 0; +# track weak ciphers offered by client and/or server until it can be logged +global ssl_weak_client_ciphers: table[conn_id] of bool &read_expire = 2 hrs; +global ssl_weak_server_ciphers: table[conn_id] of bool &read_expire = 2 hrs; + + type ssl_connection_info: record { - id: count; # the log identifier number - connection_id: conn_id; # IP connection information - version: string; # version assosciated with connection - client_cert: X509; - server_cert: X509; - id_index: string; # index for associated SSL_sessionID - handshake_cipher: string; # agreed-upon cipher for session/conn. + id: count; # the log identifier number + connection_id: conn_id; # IP connection information + version: string; # version associated with connection + client_cert: X509; + server_cert: X509; + id_index: string; # index for associated SSL_sessionID + handshake_cipher: string; # agreed-upon cipher for session/conn. }; -# SSL_sessionID index - used to track version assosciated with a session id. +# SSL_sessionID index - used to track version associated with a session id. type SSL_sessionID_record: record { - num_reuse: count; - id: SSL_sessionID; # literal session ID + num_reuse: count; + id: SSL_sessionID; # literal session ID # everything below is an example of session vs connection monitoring. - version: string; # version assosciated with session id - client_cert: X509; - server_cert: X509; - handshake_cipher: string; + version: string; # version assosciated with session id + client_cert: X509; + server_cert: X509; + handshake_cipher: string; }; global ssl_connections: table[conn_id] of ssl_connection_info; @@ -181,10 +210,7 @@ function new_ssl_connection(c: connection) info$connection_id = conn; ssl_connections[conn] = info; - append_addl(c, fmt("#%d", new_id)); - - print ssl_log, fmt("%.6f #%d %s start", - network_time(), new_id, id_string(conn)); + append_addl( c, fmt( "#%d", new_id ) ); } function new_sessionID_record(session: SSL_sessionID) @@ -231,28 +257,19 @@ function ssl_con2str(c: connection): string function lookup_ssl_conn(c: connection, func: string, log_if_new: bool) { if ( c$id !in ssl_connections ) - { new_ssl_connection(c); - - if ( log_if_new ) - print ssl_log, - fmt("%.6f #%d creating new SSL connection in %s", - network_time(), ssl_connections[c$id]$id, func); - } } event ssl_conn_weak(name: string, c: connection) { lookup_ssl_conn(c, "ssl_conn_weak", T); - print ssl_log, fmt("%.6f #%d %s", - network_time(), ssl_connections[c$id]$id, name); } # --- SSL events ------------------- event ssl_certificate_seen(c: connection, is_server: bool) { - # Called whenever there's an certificate to analyze. + # Called whenever there's a certificate to analyze. # we could do something here, like... # if ( c$id$orig_h in hostsToIgnore ) @@ -288,12 +305,6 @@ event ssl_certificate(c: connection, cert: X509, is_server: bool) ssl_sessionIDs[conn$id_index]$server_cert$subject = cert$subject; } - - print ssl_log, fmt("%.6f #%d X.509 %s issuer %s", - network_time(), conn$id, direction, cert$issuer); - - print ssl_log, fmt("%.6f #%d X.509 %s subject %s", - network_time(), conn$id, direction, cert$subject); } event ssl_conn_attempt(c: connection, version: count, @@ -303,30 +314,28 @@ event ssl_conn_attempt(c: connection, version: count, local conn = ssl_connections[c$id]; local version_string = ssl_get_version_string(version); - print ssl_log, fmt("%.6f #%d SSL connection attempt %s", - network_time(), conn$id, version_string); - conn$version = version_string; + local has_weak_ciphers = F; for ( cs in ciphers ) { # display a list of the cipher suites # Demo: report clients who support weak ciphers. - if ( ssl_report_client_weak && cs in myWeakCiphers ) + if ( cs in myWeakCiphers ) + { + has_weak_ciphers = T; event ssl_conn_weak( fmt("SSL client supports weak cipher: %s (0x%x)", ssl_get_cipher_name(cs), cs), c); + } # Demo: report unknown ciphers. if ( ssl_report_client_unknown && cs !in ssl_cipher_desc ) event ssl_conn_weak( fmt("SSL: unknown cipher-spec: %s (0x%x)", ssl_get_cipher_name(cs), cs), c); - - if ( ssl_log_ciphers ) - print ssl_log, fmt("%.6f #%d client cipher %s (0x%x)", - network_time(), conn$id, - ssl_get_cipher_name(cs), cs); } + + ssl_weak_client_ciphers[ c$id ] = has_weak_ciphers; } event ssl_conn_server_reply(c: connection, version: count, @@ -337,25 +346,26 @@ event ssl_conn_server_reply(c: connection, version: count, local conn = ssl_connections[c$id]; local version_string = ssl_get_version_string(version); - print ssl_log, fmt("%.6f #%d SSL connection server reply, %s", - network_time(), conn$id, version_string); +# print ssl_log, fmt("%.6f #%d SSL connection server reply, %s", +# network_time(), conn$id, version_string); conn$version = version_string; + local has_weak_ciphers = F; for ( cs in ciphers ) { # Demo: report servers who support weak ciphers. if ( ssl_report_server_weak && version == SSLv2 && cs in myWeakCiphers ) + { + has_weak_ciphers = T; event ssl_conn_weak( fmt("SSLv2 server supports weak cipher: %s (0x%x)", ssl_get_cipher_name(cs), cs), c); - - if ( ssl_log_ciphers ) - print ssl_log, fmt("%.6f #%d server cipher %s (0x%x)", - network_time(), conn$id, - ssl_get_cipher_name(cs), cs); + } } + + ssl_weak_server_ciphers[ c$id ] = has_weak_ciphers; } event ssl_conn_established(c: connection, version: count, cipher_suite: count) @@ -365,25 +375,34 @@ event ssl_conn_established(c: connection, version: count, cipher_suite: count) local conn = ssl_connections[c$id]; local version_string = ssl_get_version_string(version); - print ssl_log, - fmt("%.6f #%d handshake finished, %s", - network_time(), conn$id, version_string); - + local has_weak_ciphers = F; if ( cipher_suite in myWeakCiphers ) + { + has_weak_ciphers = T; event ssl_conn_weak(fmt("%.6f #%d weak cipher: %s (0x%x)", network_time(), conn$id, ssl_get_cipher_name(cipher_suite), cipher_suite), c); - - if ( ssl_log_ciphers ) - print ssl_log, fmt("%.6f #%d connection cipher %s (0x%x)", - network_time(), conn$id, - ssl_get_cipher_name(cipher_suite), cipher_suite); + } ++SSL_cipherCount[cipher_suite]; # This should be the version identified with the session, unless # there is some renegotiation. That will be caught later. conn$version = version_string; + + # log the connection + Log::write( "SSL_SERVER", [ $ts = network_time(), + $client_address = c$id$orig_h, + $client_port = c$id$orig_p, + $cert_subject = conn$client_cert$subject, + # TODO: dga 3/11 The following are not yet picked up +# $not_valid_before = ???, +# $not_valid_after = ???, +# $ssl_tls_version = ???, + $weak_client_ciphers_offered = ssl_weak_client_ciphers[ c$id ], + $weak_server_ciphers_offered = ssl_weak_server_ciphers[ c$id ], + $weak_cipher_agreed = has_weak_ciphers + ] ); } event process_X509_extensions(c: connection, ex: X509_extension) @@ -394,8 +413,6 @@ event process_X509_extensions(c: connection, ex: X509_extension) local msg = fmt("%.6f #%d X.509 extensions: ", network_time(), conn$id); for ( i in ex ) msg = fmt("%s, %s", msg, ex[i]); - - print ssl_log, msg; } event ssl_session_insertion(c: connection, id: SSL_sessionID) @@ -406,10 +423,6 @@ event ssl_session_insertion(c: connection, id: SSL_sessionID) { new_ssl_connection(c); - print ssl_log, - fmt("%.6f #%d creating new SSL connection in ssl_session_insertion", - network_time(), ssl_connections[c$id]$id); - # None of the conn$object values will exist, so we leave this # to prevent needless crashing. return; @@ -447,9 +460,6 @@ event ssl_conn_reused(c: connection, session_id: SSL_sessionID) local conn = ssl_connections[c$id]; local id_index = md5_hash(session_id); - print ssl_log, fmt("%.6f #%d reusing former SSL session: %s", - network_time(), conn$id, id_index); - # We cannot track sessions with SSLv2. if ( conn$version == ssl_get_version_string(SSLv2) ) return; @@ -496,10 +506,6 @@ event ssl_X509_error(c: connection, err: int, err_string: string) ++c$hot; severity = "error"; } - - print ssl_log, - fmt("%.6f #%d X.509 %s %s (%s)", - network_time(), conn$id, severity, error, err_string); } event connection_state_remove(c: connection) @@ -517,20 +523,21 @@ event bro_init() event bro_done() { - print ssl_log, "Cipher suite statistics: "; - for ( i in SSL_cipherCount ) - print ssl_log, fmt("%s (0x%x): %d", ssl_get_cipher_name(i), i, - SSL_cipherCount[i]); - - print ssl_log, ("count session ID"); - print ssl_log, ("----- ---------------------------------"); - for ( j in ssl_sessionIDs ) - if ( ssl_sessionIDs[j]$server_cert$subject != NONE ) - { - print ssl_log, - fmt("(%s) %s %s", - ssl_sessionIDs[j]$num_reuse, - ssl_sessionIDs[j]$server_cert$subject, - j); - } + # TODO: Do we want this end-of-run logging back? +# print ssl_log, "Cipher suite statistics: "; +# for ( i in SSL_cipherCount ) +# print ssl_log, fmt("%s (0x%x): %d", ssl_get_cipher_name(i), i, +# SSL_cipherCount[i]); +# +# print ssl_log, ("count session ID"); +# print ssl_log, ("----- ---------------------------------"); +# for ( j in ssl_sessionIDs ) +# if ( ssl_sessionIDs[j]$server_cert$subject != NONE ) +# { +# print ssl_log, +# fmt("(%s) %s %s", +# ssl_sessionIDs[j]$num_reuse, +# ssl_sessionIDs[j]$server_cert$subject, +# j); +# } }