diff --git a/CHANGES b/CHANGES index 34ae3dbef7..267ff043fb 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,18 @@ +3.3.0-dev.541 | 2020-11-16 11:22:00 -0800 + + * Simplify ssh/main.zeek by using "ssh_server_host_key" for fingerprinting (Jon Siwek, Corelight) + + * Deprecate "ssh1_server_host_key" parameters *e* and *p* (Jon Siwek, Corelight) + + They are named such that *e* is actually the modulus, not the exponent. + The replacement parameters are named *exponent* and *modulus* for + clarity. + + * GH-1264: Implement "ssh_server_host_key" event (Jon Siwek, Corelight) + + This event provides host key fingerprints for both SSH1 and SSH2. + 3.3.0-dev.537 | 2020-11-16 11:03:05 +0000 * [SSH] Handle SSH version 1.99 diff --git a/NEWS b/NEWS index dd24cc43fd..849664440e 100644 --- a/NEWS +++ b/NEWS @@ -155,6 +155,10 @@ Deprecated Functionality - ``Type::GetAliases()`` and ``Type::AddAlias()`` are deprecated, use ``Type::Aliases()`` and ``Type::RegisterAlias()``. +- The ``ssh1_server_host_key`` event's modulus and exponent parameters, + *e* and *p*, were named in misleading way (*e* is the modulus) + and now deprecated in favor of the new *modulus* and *exponent* parameters. + Zeek 3.2.0 ========== diff --git a/VERSION b/VERSION index 9ad01af2e3..33f7935bf3 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.3.0-dev.537 +3.3.0-dev.541 diff --git a/scripts/base/frameworks/intel/main.zeek b/scripts/base/frameworks/intel/main.zeek index 0d1f9aa374..8c6a85fedb 100644 --- a/scripts/base/frameworks/intel/main.zeek +++ b/scripts/base/frameworks/intel/main.zeek @@ -30,7 +30,8 @@ export { USER_NAME, ## Certificate SHA-1 hash. CERT_HASH, - ## Public key MD5 hash. (SSH server host keys are a good example.) + ## Public key MD5 hash, formatted as hexadecimal digits delimited by colons. + ## (SSH server host keys are a good example.) PUBKEY_HASH, }; diff --git a/scripts/base/protocols/ssh/main.zeek b/scripts/base/protocols/ssh/main.zeek index 4a6d281c56..f2dbbac82b 100644 --- a/scripts/base/protocols/ssh/main.zeek +++ b/scripts/base/protocols/ssh/main.zeek @@ -111,17 +111,6 @@ export { ## ssh_server_host_key ssh_encrypted_packet ssh2_dh_server_params ## ssh2_gss_error ssh2_ecc_key global ssh_auth_result: event(c: connection, result: bool, auth_attempts: count); - - ## Event that can be handled when the analyzer sees an SSH server host - ## key. This abstracts :zeek:id:`ssh1_server_host_key` and - ## :zeek:id:`ssh2_server_host_key`. - ## - ## .. zeek:see:: ssh_server_version ssh_client_version - ## ssh_auth_successful ssh_auth_failed ssh_auth_result - ## ssh_auth_attempted ssh_capabilities ssh2_server_host_key - ## ssh1_server_host_key ssh_encrypted_packet ssh2_dh_server_params - ## ssh2_gss_error ssh2_ecc_key - global ssh_server_host_key: event(c: connection, hash: string); } module SSH; @@ -337,24 +326,12 @@ event ssh_auth_failed(c: connection) &priority=-5 event ssh_auth_result(c, F, c$ssh$auth_attempts); } - -function generate_fingerprint(c: connection, key: string) +event ssh_server_host_key(c: connection, hash: string) &priority=5 { - if ( !c?$ssh ) + if ( ! c?$ssh ) return; - local lx = str_split_indices(md5_hash(key), vector(2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30)); - c$ssh$host_key = join_string_vec(lx, ":"); - } - -event ssh1_server_host_key(c: connection, p: string, e: string) &priority=5 - { - generate_fingerprint(c, e + p); - } - -event ssh2_server_host_key(c: connection, key: string) &priority=5 - { - generate_fingerprint(c, key); + c$ssh$host_key = hash; } event protocol_confirmation(c: connection, atype: Analyzer::Tag, aid: count) &priority=20 diff --git a/src/analyzer/protocol/ssh/events.bif b/src/analyzer/protocol/ssh/events.bif index 6ff62e501d..fd1cd776d9 100644 --- a/src/analyzer/protocol/ssh/events.bif +++ b/src/analyzer/protocol/ssh/events.bif @@ -127,16 +127,54 @@ event ssh2_server_host_key%(c: connection, key: string%); ## c: The connection over which the :abbr:`SSH (Secure Shell)` ## connection took place. ## -## p: The prime for the server's public host key. +## p: The exponent for the server's public host key (note this parameter +## is truly the exponent even though named *p* and the *exponent* parameter +## will eventually replace it). ## -## e: The exponent for the serer's public host key. +## e: The prime modulus for the server's public host key (note this parameter +## is truly the modulus even though named *e* and the *modulus* parameter +## will eventually replace it). +## +## modulus: The prime modulus of the server's public host key. +## +## exponent: The exponent of the server's public host key. ## ## .. zeek:see:: ssh_server_version ssh_client_version ## ssh_auth_successful ssh_auth_failed ssh_auth_result ## ssh_auth_attempted ssh_capabilities ssh2_server_host_key ## ssh_server_host_key ssh_encrypted_packet ssh2_dh_server_params ## ssh2_gss_error ssh2_ecc_key -event ssh1_server_host_key%(c: connection, p: string, e: string%); +event ssh1_server_host_key%(c: connection, p: string &deprecated="Remove in v4.1", e: string &deprecated="Remove in v4.1", modulus: string, exponent: string%); +event ssh1_server_host_key%(c: connection, modulus: string, exponent: string%); +event ssh1_server_host_key%(c: connection, p: string, e: string%) &deprecated="Remove in v4.1. The 'p' and 'e' parameters are misleadingly named don't use them."; + +## During the :abbr:`SSH (Secure Shell)` key exchange, the server +## supplies its public host key. This event is generated when the +## appropriate key exchange message is seen for SSH1 or SSH2 and provides +## a fingerprint of the server's host key. +## +## c: The connection over which the :abbr:`SSH (Secure Shell)` +## connection took place. +## +## hash: an MD5 hash fingerprint associated with the server's host key. +## For SSH2, this is the hash of the "server public host key" string as +## seen on the wire in the Diffie-Hellman key exchange reply message +## (the string itself, excluding the 4-byte length associated with it), +## which is also the *key* parameter of :zeek:see:`ssh2_server_host_key` +## For SSH1, this is the hash of the combined multiprecision integer +## strings representing the RSA1 key's prime modulus and public exponent +## (concatenated in that order) as seen on the wire, +## which are also the parameters of :zeek:see:`ssh1_server_host_key`. +## In either case, the hash is the same "fingerprint" string as presented +## by other traditional tools, ``ssh``, ``ssh-keygen``, etc, and is the +## hexadecimal representation of all 16 MD5 hash bytes delimited by colons. +## +## .. zeek:see:: ssh_server_version ssh_client_version +## ssh_auth_successful ssh_auth_failed ssh_auth_result +## ssh_auth_attempted ssh_capabilities ssh2_server_host_key +## ssh1_server_host_key ssh_encrypted_packet ssh2_dh_server_params +## ssh2_gss_error ssh2_ecc_key +event ssh_server_host_key%(c: connection, hash: string%); ## This event is generated when an :abbr:`SSH (Secure Shell)` ## encrypted packet is seen. This event is not handled by default, but diff --git a/src/analyzer/protocol/ssh/ssh-analyzer.pac b/src/analyzer/protocol/ssh/ssh-analyzer.pac index 59a6864083..901f541ddf 100644 --- a/src/analyzer/protocol/ssh/ssh-analyzer.pac +++ b/src/analyzer/protocol/ssh/ssh-analyzer.pac @@ -2,10 +2,12 @@ #include #include #include +#include "zeek/digest.h" %} %header{ zeek::VectorValPtr name_list_to_vector(const bytestring& nl); +const char* fingerprint_md5(const unsigned char* d); %} %code{ @@ -45,6 +47,14 @@ zeek::VectorValPtr name_list_to_vector(const bytestring& nl) } return vv; } + +const char* fingerprint_md5(const unsigned char* d) + { + return zeek::util::fmt("%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:" + "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", + d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], + d[8], d[9], d[10], d[11], d[12], d[13], d[14], d[15]); + } %} refine flow SSH_Flow += { @@ -147,24 +157,52 @@ refine flow SSH_Flow += { function proc_ssh2_server_host_key(key: bytestring): bool %{ + if ( ssh_server_host_key ) + { + unsigned char digest[MD5_DIGEST_LENGTH]; + zeek::detail::internal_md5(${key}.data(), ${key}.length(), digest); + + zeek::BifEvent::enqueue_ssh_server_host_key(connection()->zeek_analyzer(), + connection()->zeek_analyzer()->Conn(), + zeek::make_intrusive(fingerprint_md5(digest))); + } + if ( ssh2_server_host_key ) { zeek::BifEvent::enqueue_ssh2_server_host_key(connection()->zeek_analyzer(), connection()->zeek_analyzer()->Conn(), to_stringval(${key})); } + return true; %} - function proc_ssh1_server_host_key(p: bytestring, e: bytestring): bool + function proc_ssh1_server_host_key(exp: bytestring, mod: bytestring): bool %{ + if ( ssh_server_host_key ) + { + unsigned char digest[MD5_DIGEST_LENGTH]; + auto ctx = zeek::detail::hash_init(zeek::detail::Hash_MD5); + // Fingerprint is calculated over concatenation of modulus + exponent. + zeek::detail::hash_update(ctx, ${mod}.data(), ${mod}.length()); + zeek::detail::hash_update(ctx, ${exp}.data(), ${exp}.length()); + zeek::detail::hash_final(ctx, digest); + + zeek::BifEvent::enqueue_ssh_server_host_key(connection()->zeek_analyzer(), + connection()->zeek_analyzer()->Conn(), + zeek::make_intrusive(fingerprint_md5(digest))); + } + if ( ssh1_server_host_key ) { zeek::BifEvent::enqueue_ssh1_server_host_key(connection()->zeek_analyzer(), connection()->zeek_analyzer()->Conn(), - to_stringval(${p}), - to_stringval(${e})); + to_stringval(${exp}), + to_stringval(${mod}), + to_stringval(${mod}), + to_stringval(${exp})); } + return true; %} @@ -229,5 +267,5 @@ refine typeattr SSH2_ECC_INIT += &let { }; refine typeattr SSH1_PUBLIC_KEY += &let { - proc: bool = $context.flow.proc_ssh1_server_host_key(host_key_p.val, host_key_e.val); + proc: bool = $context.flow.proc_ssh1_server_host_key(host_key_exp.val, host_key_mod.val); }; diff --git a/src/analyzer/protocol/ssh/ssh-protocol.pac b/src/analyzer/protocol/ssh/ssh-protocol.pac index 52a6db4b4b..de38afde13 100644 --- a/src/analyzer/protocol/ssh/ssh-protocol.pac +++ b/src/analyzer/protocol/ssh/ssh-protocol.pac @@ -60,11 +60,11 @@ type SSH1_Message(is_orig: bool, msg_type: uint8, length: uint32) = case msg_typ type SSH1_PUBLIC_KEY(length: uint32) = record { cookie : bytestring &length=8; server_key : uint32; - server_key_p : ssh1_mp_int; - server_key_e : ssh1_mp_int; + server_key_exp : ssh1_mp_int; + server_key_mod : ssh1_mp_int; host_key : uint32; - host_key_p : ssh1_mp_int; - host_key_e : ssh1_mp_int; + host_key_exp : ssh1_mp_int; + host_key_mod : ssh1_mp_int; flags : uint32; supported_ciphers : uint32; supported_auths : uint32; diff --git a/testing/btest/Baseline/scripts.base.frameworks.intel.remove-non-existing/output b/testing/btest/Baseline/scripts.base.frameworks.intel.remove-non-existing/output index d8517f51c6..f9158f8618 100644 --- a/testing/btest/Baseline/scripts.base.frameworks.intel.remove-non-existing/output +++ b/testing/btest/Baseline/scripts.base.frameworks.intel.remove-non-existing/output @@ -1,11 +1,12 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. #separator \x09 #set_separator , #empty_field (empty) #unset_field - #path reporter -#open 2020-09-24-00-19-13 +#open XXXX-XX-XX-XX-XX-XX #fields ts level message location #types time enum string string -1600906753.185591 Reporter::INFO Tried to remove non-existing item '192.168.1.1' (Intel::ADDR). /home/christian/devel/zeek/zeek/scripts/base/frameworks/intel/./main.zeek, lines 565-566 -1600906753.185591 Reporter::INFO received termination signal (empty) -#close 2020-09-24-00-19-13 +XXXXXXXXXX.XXXXXX Reporter::INFO Tried to remove non-existing item '192.168.1.1' (Intel::ADDR). <...>/main.zeek, lines 566-567 +XXXXXXXXXX.XXXXXX Reporter::INFO received termination signal (empty) +#close XXXX-XX-XX-XX-XX-XX diff --git a/testing/btest/Baseline/scripts.base.protocols.ssh.fingerprints/out b/testing/btest/Baseline/scripts.base.protocols.ssh.fingerprints/out new file mode 100644 index 0000000000..2dac5bca77 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.ssh.fingerprints/out @@ -0,0 +1,5 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +ssh server host key fingerprint, c7:eb:77:5d:d1:64:31:d6:1b:e8:99:5f:a7:09:a1:d7 +ssh2 server host key fingerprint, c7eb775dd16431d61be8995fa709a1d7 +ssh server host key fingerprint, 55:17:eb:fa:2e:7f:b3:7b:33:42:7c:9d:44:85:56:da +ssh1 server host key fingerprint, 5517ebfa2e7fb37b33427c9d448556da diff --git a/testing/btest/Traces/ssh/ssh1-ssh2-fingerprints.pcap b/testing/btest/Traces/ssh/ssh1-ssh2-fingerprints.pcap new file mode 100644 index 0000000000..730240e963 Binary files /dev/null and b/testing/btest/Traces/ssh/ssh1-ssh2-fingerprints.pcap differ diff --git a/testing/btest/scripts/base/protocols/ssh/fingerprints.zeek b/testing/btest/scripts/base/protocols/ssh/fingerprints.zeek new file mode 100644 index 0000000000..f0ef8fad21 --- /dev/null +++ b/testing/btest/scripts/base/protocols/ssh/fingerprints.zeek @@ -0,0 +1,19 @@ +# @TEST-EXEC: zeek -r $TRACES/ssh/ssh1-ssh2-fingerprints.pcap %INPUT >out +# @TEST-EXEC: btest-diff out + +@load base/protocols/ssh + +event ssh2_server_host_key(c: connection, key: string) + { + print "ssh2 server host key fingerprint", md5_hash(key); + } + +event ssh1_server_host_key(c: connection, modulus: string, exponent: string) + { + print "ssh1 server host key fingerprint", md5_hash(modulus + exponent); + } + +event ssh_server_host_key(c: connection, hash: string) + { + print "ssh server host key fingerprint", hash; + }