diff --git a/scripts/base/protocols/ssh/main.bro b/scripts/base/protocols/ssh/main.bro index c3f90ec332..b9768b6dbb 100644 --- a/scripts/base/protocols/ssh/main.bro +++ b/scripts/base/protocols/ssh/main.bro @@ -18,6 +18,8 @@ export { client: string &log &optional; ## The server's version string server: string &log &optional; + ## The server's key fingerprint + host_key: string &log &optional; ## Auth result result: string &log &optional; ## Auth method @@ -42,7 +44,7 @@ event bro_init() &priority=5 } -event ssh_version(c: connection, is_orig: bool, version: string) +event ssh_server_version(c: connection, version: string) { if ( !c?$ssh ) { @@ -52,16 +54,25 @@ event ssh_version(c: connection, is_orig: bool, version: string) s$id = c$id; c$ssh = s; } - if ( is_orig ) - c$ssh$client = version; - else - c$ssh$server = version; -# print c$ssh; + c$ssh$server = version; + } + +event ssh_client_version(c: connection, version: string) + { + if ( !c?$ssh ) + { + local s: SSH::Info; + s$ts = network_time(); + s$uid = c$uid; + s$id = c$id; + c$ssh = s; + } + c$ssh$client = version; } event ssh_auth_successful(c: connection, method: string) { - if ( !c?$ssh ) + if ( !c?$ssh || ( c$ssh?$result && c$ssh$result == "success" ) ) return; c$ssh$result = "success"; c$ssh$method = method; @@ -70,7 +81,7 @@ event ssh_auth_successful(c: connection, method: string) event ssh_auth_failed(c: connection, method: string) { - if ( !c?$ssh ) + if ( !c?$ssh || ( c$ssh?$result && c$ssh$result == "success" ) ) return; c$ssh$result = "failure"; c$ssh$method = method; @@ -85,4 +96,13 @@ event connection_closed(c: connection) c$ssh$method = "unknown"; Log::write(SSH::LOG, c$ssh); } + } + +event ssh_server_host_key(c: connection, key: string) + { + if ( !c?$ssh ) + return; + local lx = str_split(md5_hash(key), vector(2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30)); + lx[0] = ""; + c$ssh$host_key = sub(join_string_vec(lx, ":"), /:/, ""); } \ No newline at end of file diff --git a/src/analyzer/protocol/ssh/SSH.cc b/src/analyzer/protocol/ssh/SSH.cc index caeb5c4ca7..c89a77b9e7 100644 --- a/src/analyzer/protocol/ssh/SSH.cc +++ b/src/analyzer/protocol/ssh/SSH.cc @@ -18,6 +18,8 @@ SSH_Analyzer::SSH_Analyzer(Connection* c) interp = new binpac::SSH::SSH_Conn(this); had_gap = false; num_encrypted_packets_seen = 0; + initial_client_packet_size = 0; + initial_server_packet_size = 0; } SSH_Analyzer::~SSH_Analyzer() @@ -54,7 +56,7 @@ void SSH_Analyzer::DeliverStream(int len, const u_char* data, bool orig) // deliver data to the other side if the script layer can handle this. return; - if ( num_encrypted_packets_seen || interp->get_state(orig) == binpac::SSH::ENCRYPTED ) + if ( interp->get_state(orig) == binpac::SSH::ENCRYPTED ) { ProcessEncrypted(len, orig); return; @@ -80,23 +82,33 @@ void SSH_Analyzer::Undelivered(int seq, int len, bool orig) void SSH_Analyzer::ProcessEncrypted(int len, bool orig) { - if (!num_encrypted_packets_seen) - { - initial_encrypted_packet_size = len; - } - // printf("Encrypted packet of size %d from %s.\n", len, orig?"client":"server"); - int relative_len = len - initial_encrypted_packet_size; - if ( num_encrypted_packets_seen >= 2 ) + if (orig && !initial_client_packet_size) + initial_client_packet_size = len; + if (!orig && !initial_server_packet_size) + initial_server_packet_size = len; + + int relative_len; + if (orig) + relative_len = len - initial_client_packet_size; + else + relative_len = len - initial_server_packet_size; + // printf("Encrypted packet of length %d from %s.\n", len, orig?"client":"server"); + if ( num_encrypted_packets_seen >= 4 ) { int auth_result = AuthResult(relative_len, orig); if ( auth_result > 0 ) { + num_encrypted_packets_seen = 1; + //printf("Have auth\n"); StringVal* method = new StringVal(AuthMethod(relative_len, orig)); if ( auth_result == 1 ) BifEvent::generate_ssh_auth_successful(interp->bro_analyzer(), interp->bro_analyzer()->Conn(), method); if ( auth_result == 2 ) BifEvent::generate_ssh_auth_failed(interp->bro_analyzer(), interp->bro_analyzer()->Conn(), method); } + } + if ( num_encrypted_packets_seen >= 2 ) + { packet_n_2_is_orig = packet_n_1_is_orig; packet_n_2_size = packet_n_1_size; } @@ -108,7 +120,7 @@ void SSH_Analyzer::ProcessEncrypted(int len, bool orig) int SSH_Analyzer::AuthResult(int len, bool orig) { - if ( orig && !packet_n_1_is_orig && packet_n_2_is_orig ) + if ( !orig && packet_n_1_is_orig && !packet_n_2_is_orig ) { if ( len == -16 ) return 1; @@ -123,13 +135,13 @@ int SSH_Analyzer::AuthResult(int len, bool orig) const char* SSH_Analyzer::AuthMethod(int len, bool orig) { if ( packet_n_1_size == 96 ) // Password auth - return "keyboard-interactive"; - if ( packet_n_1_size == 32 ) // Challenge-response auth - return "challenge-response"; + return fmt("password (L=%d, L-1=%d, L-2=%d)", len, packet_n_1_size, packet_n_2_size); + if ( packet_n_1_size == 32 && ( packet_n_2_size == 0 || packet_n_2_size == 48 ) ) // Challenge-response auth + return fmt("challenge-response (L=%d, L-1=%d, L-2=%d)", len, packet_n_1_size, packet_n_2_size); if ( packet_n_2_size >= 112 && packet_n_2_size <= 432 ) // Public key auth - return "pubkey"; + return fmt("pubkey (L=%d, L-1=%d, L-2=%d)", len, packet_n_1_size, packet_n_2_size); if ( packet_n_2_size == 16 ) // Host-based auth - return "host-based"; - return fmt("unknown auth method: n-1=%d n-2=%d", packet_n_1_size, packet_n_2_size); + return fmt("host-based (L=%d, L-1=%d, L-2=%d)", len, packet_n_1_size, packet_n_2_size); + return fmt("unknown (L=%d, L-1=%d, L-2=%d)", len, packet_n_1_size, packet_n_2_size); } diff --git a/src/analyzer/protocol/ssh/SSH.h b/src/analyzer/protocol/ssh/SSH.h index 7d391e8d66..b0d8aade57 100644 --- a/src/analyzer/protocol/ssh/SSH.h +++ b/src/analyzer/protocol/ssh/SSH.h @@ -34,9 +34,9 @@ public: static bool Available() { - // TODO: After you define your events, || them together here. - // See events.bif for more information - return ( ssh_event ); + return ( ssh_server_version || ssh_client_version || + ssh_auth_successful || ssh_auth_failed || + ssh_server_capabilities || ssh_server_host_key ); } protected: @@ -49,7 +49,8 @@ protected: bool had_gap; // Packet analysis stuff - int initial_encrypted_packet_size; + int initial_client_packet_size; + int initial_server_packet_size; int num_encrypted_packets_seen; bool packet_n_1_is_orig; diff --git a/src/analyzer/protocol/ssh/events.bif b/src/analyzer/protocol/ssh/events.bif index f1fa16919d..cefb591a6e 100644 --- a/src/analyzer/protocol/ssh/events.bif +++ b/src/analyzer/protocol/ssh/events.bif @@ -1,17 +1,11 @@ -# Generated by binpac_quickstart +event ssh_server_version%(c: connection, version: string%); -# In this file, you'll define the events that your analyzer will generate. A sample event is included. - -## Generated for SSH connections -## -## See `Google `__ for more information about SSH -## -## c: The connection -##3 -event ssh_event%(c: connection%); - -event ssh_version%(c: connection, is_orig: bool, version: string%); +event ssh_client_version%(c: connection, version: string%); event ssh_auth_successful%(c: connection, method: string%); -event ssh_auth_failed%(c: connection, method: string%); \ No newline at end of file +event ssh_auth_failed%(c: connection, method: string%); + +event ssh_server_capabilities%(c: connection, kex_algorithms: string, server_host_key_algorithms: string, encryption_algorithms_client_to_server: string, encryption_algorithms_server_to_client: string, mac_algorithms_client_to_server: string, mac_algorithms_server_to_client: string, compression_algorithms_client_to_server: string, compression_algorithms_server_to_client: string, languages_client_to_server: string, languages_server_to_client: string%); + +event ssh_server_host_key%(c: connection, key: string%); \ No newline at end of file diff --git a/src/analyzer/protocol/ssh/ssh-analyzer.pac b/src/analyzer/protocol/ssh/ssh-analyzer.pac index 5cde754521..05cf20d4b4 100644 --- a/src/analyzer/protocol/ssh/ssh-analyzer.pac +++ b/src/analyzer/protocol/ssh/ssh-analyzer.pac @@ -3,8 +3,34 @@ refine flow SSH_Flow += { function proc_ssh_version(msg: SSH_Version): bool %{ - BifEvent::generate_ssh_version(connection()->bro_analyzer(), connection()->bro_analyzer()->Conn(), ${msg.is_orig}, - bytestring_to_val(${msg.version})); + if ( ssh_client_version && ${msg.is_orig } ) + BifEvent::generate_ssh_client_version(connection()->bro_analyzer(), connection()->bro_analyzer()->Conn(), bytestring_to_val(${msg.version})); + else if ( ssh_server_version ) + BifEvent::generate_ssh_server_version(connection()->bro_analyzer(), connection()->bro_analyzer()->Conn(), bytestring_to_val(${msg.version})); + return true; + %} + + function proc_ssh_kexinit(msg: SSH_KEXINIT): bool + %{ + if ( ssh_server_capabilities ) + BifEvent::generate_ssh_server_capabilities(connection()->bro_analyzer(), connection()->bro_analyzer()->Conn(), + bytestring_to_val(${msg.kex_algorithms}), bytestring_to_val(${msg.server_host_key_algorithms}), + bytestring_to_val(${msg.encryption_algorithms_client_to_server}), + bytestring_to_val(${msg.encryption_algorithms_server_to_client}), + bytestring_to_val(${msg.mac_algorithms_client_to_server}), + bytestring_to_val(${msg.mac_algorithms_server_to_client}), + bytestring_to_val(${msg.compression_algorithms_client_to_server}), + bytestring_to_val(${msg.compression_algorithms_server_to_client}), + bytestring_to_val(${msg.languages_client_to_server}), + bytestring_to_val(${msg.languages_server_to_client})); + return true; + %} + + function proc_ssh_server_host_key(key: bytestring): bool + %{ + if ( ssh_server_host_key ) + BifEvent::generate_ssh_server_host_key(connection()->bro_analyzer(), connection()->bro_analyzer()->Conn(), + bytestring_to_val(${key})); return true; %} @@ -14,12 +40,30 @@ refine flow SSH_Flow += { return true; %} + function debug(loc: uint8): bool + %{ + printf("DEBUG: %d", loc); + return true; + %} + }; refine typeattr SSH_Version += &let { proc: bool = $context.flow.proc_ssh_version(this); }; +refine typeattr SSH_KEXINIT += &let { + proc: bool = $context.flow.proc_ssh_kexinit(this); +}; + +refine typeattr SSH_DH_GEX_REPLY += &let { + proc: bool = $context.flow.proc_ssh_server_host_key(k_s.val); +}; + +refine typeattr SSH_DH_GEX_GROUP += &let { + proc: bool = $context.flow.proc_ssh_server_host_key(p.val); +}; + refine typeattr SSH_Message += &let { proc_newkeys: bool = $context.flow.proc_newkeys() &if(msg_type == SSH_MSG_NEWKEYS); -}; +}; \ No newline at end of file diff --git a/src/analyzer/protocol/ssh/ssh-protocol.pac b/src/analyzer/protocol/ssh/ssh-protocol.pac index 84b1bc1f6a..aea112ff10 100644 --- a/src/analyzer/protocol/ssh/ssh-protocol.pac +++ b/src/analyzer/protocol/ssh/ssh-protocol.pac @@ -41,7 +41,7 @@ enum message_id { type SSH_PDU(is_orig: bool) = case $context.connection.get_state(is_orig) of { VERSION_EXCHANGE -> version: SSH_Version(is_orig); KEY_EXCHANGE_CLEARTEXT -> kex: SSH_Key_Exchange(is_orig); - ENCRYPTED -> unk: bytestring &length=100; + ENCRYPTED -> ciphertext: bytestring &length=1 &transient; } &byteorder=bigendian; type SSH_Version(is_orig: bool) = record { @@ -71,12 +71,13 @@ type SSH_Payload(is_orig: bool, packet_length: uint32) = record { }; type SSH_Message(is_orig: bool, msg_type: uint8, packet_length: uint32) = case msg_type of { - SSH_MSG_KEXINIT -> kexinit: SSH_KEXINIT(is_orig, packet_length); - SSH_MSG_KEX_DH_GEX_REQUEST -> dh_gex_request: SSH_DH_GEX_REQUEST(is_orig, packet_length); - SSH_MSG_KEX_DH_GEX_GROUP -> dh_gex_group: SSH_DH_GEX_GROUP(is_orig, packet_length); - SSH_MSG_KEX_DH_GEX_INIT -> dh_gex_init: SSH_DH_GEX_INIT(is_orig, packet_length); - SSH_MSG_KEX_DH_GEX_REPLY -> dh_gex_reply: SSH_DH_GEX_REPLY(is_orig, packet_length); - default -> unknown: bytestring &length=packet_length; + SSH_MSG_KEXINIT -> kexinit: SSH_KEXINIT(is_orig, packet_length); + SSH_MSG_KEX_DH_GEX_REQUEST -> dh_gex_request: SSH_DH_GEX_REQUEST(is_orig, packet_length); + SSH_MSG_KEX_DH_GEX_REQUEST_OLD -> dh_gex_request_old: SSH_DH_GEX_REQUEST_OLD(is_orig, packet_length); + SSH_MSG_KEX_DH_GEX_GROUP -> dh_gex_group: SSH_DH_GEX_GROUP(is_orig, packet_length); + SSH_MSG_KEX_DH_GEX_INIT -> dh_gex_init: SSH_DH_GEX_INIT(is_orig, packet_length); + SSH_MSG_KEX_DH_GEX_REPLY -> dh_gex_reply: SSH_DH_GEX_REPLY(is_orig, packet_length); + SSH_MSG_NEWKEYS -> new_keys: bytestring &length=packet_length; } &let { detach: bool = $context.connection.update_state(ENCRYPTED, is_orig) &if(msg_type == SSH_MSG_NEWKEYS); }; @@ -113,6 +114,10 @@ type SSH_DH_GEX_REQUEST(is_orig: bool, length: uint32) = record { max: uint32; } &length=12; +type SSH_DH_GEX_REQUEST_OLD(is_orig: bool, length: uint32) = record { + payload: bytestring &length=length; +} &length=length; + type SSH_DH_GEX_GROUP(is_orig: bool, length: uint32) = record { p: mpint; g: mpint;