From d9b4693240309a496e5e75e6c028605fc76c39ba Mon Sep 17 00:00:00 2001 From: Vlad Grigorescu Date: Mon, 9 Mar 2015 16:04:35 -0400 Subject: [PATCH] Some cleanup and refactoring on SSH main.bro. Specifically, an overhaul of how the algorithm negotiation is calculated, to simplify a lot of the code. --- scripts/base/init-bare.bro | 46 +++--- scripts/base/protocols/ssh/main.bro | 171 +++++++++------------ src/analyzer/protocol/ssh/ssh-analyzer.pac | 40 +++-- src/analyzer/protocol/ssh/types.bif | 1 + 4 files changed, 125 insertions(+), 133 deletions(-) diff --git a/scripts/base/init-bare.bro b/scripts/base/init-bare.bro index e8e35c2e3b..ac73ba980d 100644 --- a/scripts/base/init-bare.bro +++ b/scripts/base/init-bare.bro @@ -2218,37 +2218,31 @@ export { module SSH; export { + ## The client and server each have some preferences for the algorithms used + ## in each direction. + type Algorithm_Prefs: record { + ## The algorithm preferences for client to server communication + client_to_server: vector of string &optional; + ## The algorithm preferences for server to client communication + server_to_client: vector of string &optional; + }; + ## SSH Capability record type Capabilities: record { ## Key exchange algorithms - kex_algorithms : string_vec; + kex_algorithms: string_vec; ## The algorithms supported for the server host key - server_host_key_algorithms : string_vec; - ## Acceptable symmetric encryption algorithms for c->s, - ## in order of preference - encryption_algorithms_client_to_server : string_vec; - ## Acceptable symmetric encryption algorithms for s->c, - ## in order of preference - encryption_algorithms_server_to_client : string_vec; - ## Acceptable MAC algorithms for c->s, - ## in order of preference - mac_algorithms_client_to_server : string_vec; - - ## Acceptable MAC algorithms for s->c, - ## in order of preference - mac_algorithms_server_to_client : string_vec; - ## Acceptable compression algorithms for c->s, - ## in order of preference - compression_algorithms_client_to_server : string_vec; - ## Acceptable compression algorithms for c->s, - ## in order of preference - compression_algorithms_server_to_client : string_vec; - ## Language tags in order of preference for c->s - languages_client_to_server : string_vec &optional; - ## Language tags in order of preference for s->c - languages_server_to_client : string_vec &optional; + server_host_key_algorithms: string_vec; + ## Symmetric encryption algorithm preferences + encryption_algorithms: Algorithm_Prefs; + ## Symmetric MAC algorithm preferences + mac_algorithms: Algorithm_Prefs; + ## Compression algorithm preferences + compression_algorithms: Algorithm_Prefs; + ## Language preferences + languages: Algorithm_Prefs &optional; ## Are these the capabilities of the server? - is_server : bool; + is_server: bool; }; } diff --git a/scripts/base/protocols/ssh/main.bro b/scripts/base/protocols/ssh/main.bro index 8db2e4d023..706b687131 100644 --- a/scripts/base/protocols/ssh/main.bro +++ b/scripts/base/protocols/ssh/main.bro @@ -1,56 +1,51 @@ ##! Implements base functionality for SSH analysis. Generates the ssh.log file. +@load base/utils/directions-and-hosts + module SSH; export { + ## The SSH protocol logging stream identifier. redef enum Log::ID += { LOG }; type Info: record { - ## Timestamp for when the event happened. - ts: time &log; + ## Time when the SSH connection began. + ts: time &log; ## Unique ID for the connection. - uid: string &log; + uid: string &log; ## The connection's 4-tuple of endpoint addresses/ports. - id: conn_id &log; + id: conn_id &log; ## SSH major version (1 or 2) - version: count &log; - ## Auth result - auth_success: bool &log &optional; - - ## Auth details - auth_details: string &log &optional; + version: count &log; + ## Authentication result (T=success, F=failure, unset=unknown) + auth_success: bool &log &optional; ## Direction of the connection. If the client was a local host ## logging into an external host, this would be OUTBOUND. INBOUND ## would be set for the opposite situation. - ## TODO: handle local-local and remote-remote better. - direction: Direction &log &optional; - ## The encryption algorithm in use - cipher_alg: string &log &optional; - ## The signing (MAC) algorithm in use - mac_alg: string &log &optional; - ## The compression algorithm in use - compression_alg: string &log &optional; - ## The key exchange algorithm in use - kex_alg: string &log &optional; - - ## The server host key's algorithm - host_key_alg: string &log &optional; - ## The server's key fingerprint - host_key: string &log &optional; + # TODO - handle local-local and remote-remote better. + direction: Direction &log &optional; ## The client's version string - client: string &log &optional; + client: string &log &optional; ## The server's version string - server: string &log &optional; - - ## This connection has been logged (internal use) - logged: bool &default=F; - ## Number of failures seen (internal use) - num_failures: count &default=0; - ## Store capabilities from the first host for - ## comparison with the second (internal use) - capabilities: Capabilities &optional; + server: string &log &optional; + ## The encryption algorithm in use + cipher_alg: string &log &optional; + ## The signing (MAC) algorithm in use + mac_alg: string &log &optional; + ## The compression algorithm in use + compression_alg: string &log &optional; + ## The key exchange algorithm in use + kex_alg: string &log &optional; + ## The server host key's algorithm + host_key_alg: string &log &optional; + ## The server's key fingerprint + host_key: string &log &optional; }; + ## The set of compression algorithms. We can't accurately determine + ## authentication success or failure when compression is enabled. + const compression_algorithms = set("zlib", "zlib@openssh.com") &redef; + ## If true, we tell the event engine to not look at further data ## packets after the initial SSH handshake. Helps with performance ## (especially with large file transfers) but precludes some @@ -62,6 +57,16 @@ export { global log_ssh: event(rec: Info); } +redef record Info += { + ## This connection has been logged (internal use) + logged: bool &default=F; + ## Number of failures seen (internal use) + num_failures: count &default=0; + ## Store capabilities from the first host for + ## comparison with the second (internal use) + capabilities: Capabilities &optional; +}; + redef record connection += { ssh: Info &optional; }; @@ -72,52 +77,47 @@ event bro_init() &priority=5 } -function init_record(c: connection) +function set_session(c: connection) { - local s: SSH::Info; - s$ts = network_time(); - s$uid = c$uid; - s$id = c$id; - c$ssh = s; + if ( ! c?$ssh ) + { + local info: SSH::Info; + info$ts = network_time(); + info$uid = c$uid; + info$id = c$id; + c$ssh = info; + } } - event ssh_server_version(c: connection, version: string) { - if ( !c?$ssh ) - init_record(c); - + set_session(c); c$ssh$server = version; } event ssh_client_version(c: connection, version: string) { - if ( !c?$ssh ) - init_record(c); - + set_session(c); c$ssh$client = version; - if ( version[4] == "1" ) + if ( ( |version| > 3 ) && ( version[4] == "1" ) ) c$ssh$version = 1; - if ( version[4] == "2" ) + if ( ( |version| > 3 ) && ( version[4] == "2" ) ) c$ssh$version = 2; } event ssh_auth_successful(c: connection, auth_method_none: bool) { + # TODO - what to do here? if ( !c?$ssh || ( c$ssh?$auth_success && c$ssh$auth_success ) ) return; # We can't accurately tell for compressed streams - if ( c$ssh?$compression_alg && ( c$ssh$compression_alg == "zlib@openssh.com" || - c$ssh$compression_alg == "zlib" ) ) + if ( c$ssh?$compression_alg && ( c$ssh$compression_alg in compression_algorithms ) ) return; c$ssh$auth_success = T; - if ( auth_method_none ) - c$ssh$auth_details = "method: none"; - if ( skip_processing_after_detection) { skip_further_processing(c$id); @@ -140,39 +140,30 @@ event ssh_auth_failed(c: connection) return; # We can't accurately tell for compressed streams - if ( c$ssh?$compression_alg && ( c$ssh$compression_alg == "zlib@openssh.com" || - c$ssh$compression_alg == "zlib" ) ) + if ( c$ssh?$compression_alg && ( c$ssh$compression_alg == "zlib@openssh.com" || c$ssh$compression_alg == "zlib" ) ) return; c$ssh$auth_success = F; c$ssh$num_failures += 1; } -function array_to_vec(s: string_array): vector of string - { - local r: vector of string; - - for (i in s) - r[i] = s[i]; - return r; - } - -function find_client_preferred_algorithm(client_algorithms: vector of string, server_algorithms: vector of string): string +# Determine the negotiated algorithm +function find_alg(client_algorithms: vector of string, server_algorithms: vector of string): string { for ( i in client_algorithms ) for ( j in server_algorithms ) if ( client_algorithms[i] == server_algorithms[j] ) return client_algorithms[i]; } - -function find_client_preferred_algorithm_bidirectional(client_algorithms_c_to_s: vector of string, - server_algorithms_c_to_s: vector of string, - client_algorithms_s_to_c: vector of string, - server_algorithms_s_to_c: vector of string): string - { - local c_to_s = find_client_preferred_algorithm(client_algorithms_c_to_s, server_algorithms_c_to_s); - local s_to_c = find_client_preferred_algorithm(client_algorithms_s_to_c, server_algorithms_s_to_c); +# This is a simple wrapper around find_alg for cases where client to server and server to client +# negotiate different algorithms. This is rare, but provided for completeness. +function find_bidirectional_alg(client_prefs: Algorithm_Prefs, server_prefs: Algorithm_Prefs): string + { + local c_to_s = find_alg(client_prefs$client_to_server, server_prefs$client_to_server); + local s_to_c = find_alg(client_prefs$server_to_client, server_prefs$server_to_client); + + # Usually these are the same, but if they're not, return the details return c_to_s == s_to_c ? c_to_s : fmt("To server: %s, to client: %s", c_to_s, s_to_c); } @@ -190,33 +181,21 @@ event ssh_capabilities(c: connection, cookie: string, capabilities: Capabilities local client_caps = capabilities$is_server ? c$ssh$capabilities : capabilities; local server_caps = capabilities$is_server ? capabilities : c$ssh$capabilities; - c$ssh$cipher_alg = find_client_preferred_algorithm_bidirectional(client_caps$encryption_algorithms_client_to_server, - server_caps$encryption_algorithms_client_to_server, - client_caps$encryption_algorithms_server_to_client, - server_caps$encryption_algorithms_server_to_client); - - c$ssh$mac_alg = find_client_preferred_algorithm_bidirectional(client_caps$mac_algorithms_client_to_server, - server_caps$mac_algorithms_client_to_server, - client_caps$mac_algorithms_server_to_client, - server_caps$mac_algorithms_server_to_client); - - c$ssh$compression_alg = find_client_preferred_algorithm_bidirectional(client_caps$compression_algorithms_client_to_server, - server_caps$compression_algorithms_client_to_server, - client_caps$compression_algorithms_server_to_client, - server_caps$compression_algorithms_server_to_client); - - c$ssh$kex_alg = find_client_preferred_algorithm(client_caps$kex_algorithms, server_caps$kex_algorithms); - c$ssh$host_key_alg = find_client_preferred_algorithm(client_caps$server_host_key_algorithms, - server_caps$server_host_key_algorithms); + c$ssh$cipher_alg = find_bidirectional_alg(client_caps$encryption_algorithms, + server_caps$encryption_algorithms); + c$ssh$mac_alg = find_bidirectional_alg(client_caps$mac_algorithms, + server_caps$mac_algorithms); + c$ssh$compression_alg = find_bidirectional_alg(client_caps$compression_algorithms, + server_caps$compression_algorithms); + c$ssh$kex_alg = find_alg(client_caps$kex_algorithms, server_caps$kex_algorithms); + c$ssh$host_key_alg = find_alg(client_caps$server_host_key_algorithms, + server_caps$server_host_key_algorithms); } event connection_state_remove(c: connection) &priority=-5 { if ( c?$ssh && !c$ssh$logged && c$ssh?$client && c$ssh?$server ) { - if ( c$ssh?$auth_success && !c$ssh$auth_success ) - c$ssh$auth_details = fmt("%d failure%s", c$ssh$num_failures, c$ssh$num_failures == 1 ? "" : "s"); - c$ssh$logged = T; Log::write(SSH::LOG, c$ssh); } diff --git a/src/analyzer/protocol/ssh/ssh-analyzer.pac b/src/analyzer/protocol/ssh/ssh-analyzer.pac index d448bdae60..71609e7316 100644 --- a/src/analyzer/protocol/ssh/ssh-analyzer.pac +++ b/src/analyzer/protocol/ssh/ssh-analyzer.pac @@ -73,17 +73,35 @@ refine flow SSH_Flow += { RecordVal* result = new RecordVal(BifType::Record::SSH::Capabilities); result->Assign(0, name_list_to_vector(${msg.kex_algorithms.val})); result->Assign(1, name_list_to_vector(${msg.server_host_key_algorithms.val})); - result->Assign(2, name_list_to_vector(${msg.encryption_algorithms_client_to_server.val})); - result->Assign(3, name_list_to_vector(${msg.encryption_algorithms_server_to_client.val})); - result->Assign(4, name_list_to_vector(${msg.mac_algorithms_client_to_server.val})); - result->Assign(5, name_list_to_vector(${msg.mac_algorithms_server_to_client.val})); - result->Assign(6, name_list_to_vector(${msg.compression_algorithms_client_to_server.val})); - result->Assign(7, name_list_to_vector(${msg.compression_algorithms_server_to_client.val})); - if ( ${msg.languages_client_to_server.len} ) - result->Assign(8, name_list_to_vector(${msg.languages_client_to_server.val})); - if ( ${msg.languages_server_to_client.len} ) - result->Assign(9, name_list_to_vector(${msg.languages_server_to_client.val})); - result->Assign(10, new Val(${msg.is_orig}, TYPE_BOOL)); + + RecordVal* encryption_algs = new RecordVal(BifType::Record::SSH::Algorithm_Prefs); + encryption_algs->Assign(0, name_list_to_vector(${msg.encryption_algorithms_client_to_server.val})); + encryption_algs->Assign(1, name_list_to_vector(${msg.encryption_algorithms_server_to_client.val})); + result->Assign(2, encryption_algs); + + RecordVal* mac_algs = new RecordVal(BifType::Record::SSH::Algorithm_Prefs); + mac_algs->Assign(0, name_list_to_vector(${msg.mac_algorithms_client_to_server.val})); + mac_algs->Assign(1, name_list_to_vector(${msg.mac_algorithms_server_to_client.val})); + result->Assign(3, mac_algs); + + RecordVal* compression_algs = new RecordVal(BifType::Record::SSH::Algorithm_Prefs); + compression_algs->Assign(0, name_list_to_vector(${msg.compression_algorithms_client_to_server.val})); + compression_algs->Assign(1, name_list_to_vector(${msg.compression_algorithms_server_to_client.val})); + result->Assign(4, compression_algs); + + if ( ${msg.languages_client_to_server.len} || ${msg.languages_server_to_client.len} ) + { + RecordVal* languages = new RecordVal(BifType::Record::SSH::Algorithm_Prefs); + if ( ${msg.languages_client_to_server.len} ) + languages->Assign(0, name_list_to_vector(${msg.languages_client_to_server.val})); + if ( ${msg.languages_server_to_client.len} ) + languages->Assign(1, name_list_to_vector(${msg.languages_server_to_client.val})); + + result->Assign(5, languages); + } + + + result->Assign(6, new Val(${msg.is_orig}, TYPE_BOOL)); BifEvent::generate_ssh_capabilities(connection()->bro_analyzer(), connection()->bro_analyzer()->Conn(), bytestring_to_val(${msg.cookie}), diff --git a/src/analyzer/protocol/ssh/types.bif b/src/analyzer/protocol/ssh/types.bif index 38e51600f3..0b2b861723 100644 --- a/src/analyzer/protocol/ssh/types.bif +++ b/src/analyzer/protocol/ssh/types.bif @@ -1,5 +1,6 @@ module SSH; +type Algorithm_Prefs: record; type Capabilities: record; module GLOBAL; \ No newline at end of file