mirror of
https://github.com/zeek/zeek.git
synced 2025-10-08 01:28:20 +00:00

I replaced a few strcmps with either calls to std::str.compare or with the == operator of BroString. Also changed two of the input framework tests that did not pass anymore after the merge. The new SSH analyzer no longer loads the scripts that let network time run, hence those tests failed because updates were not propagated from the threads (that took a while to find.) * origin/topic/vladg/ssh: (25 commits) SSH: Register analyzer for 22/tcp. SSH: Add 22/tcp to likely_server_ports SSH: Ignore encrypted packets by default. SSH: Fix some edge-cases which created BinPAC exceptions SSH: Add memleak btest SSH: Update baselines SSH: Added some more events for SSH2 SSH: Intel framework integration (PUBKEY_HASH) Update baselines for new SSH analyzer. Update SSH policy scripts with new events. SSH: Add documentation Refactoring ssh-protocol.pac: SSH: Use the compression_algorithms const in another place. Some cleanup and refactoring on SSH main.bro. SSH: A bit of code cleanup. Move SSH constants to consts.pac SSH: Cleanup code style. SSH: Fix some memleaks. Refactored the SSH analyzer. Added supported for algorithm detection and more key exchange message types. Add host key support for SSH1. Add support for SSH1 Move SSH analyzer to new plugin architecture. ... Conflicts: scripts/base/protocols/ssh/main.bro testing/btest/Baseline/core.print-bpf-filters/output2 testing/btest/Baseline/plugins.hooks/output BIT-1344: #merged
430 lines
12 KiB
JavaScript
430 lines
12 KiB
JavaScript
%include consts.pac
|
|
|
|
# Common constructs across SSH1 and SSH2
|
|
########################################
|
|
|
|
# We have 3 basic types of messages:
|
|
#
|
|
# - SSH_Version messages just have a string with the banner string of the client or server
|
|
# - Encrypted messages have no usable data, so we'll just ignore them as best we can.
|
|
# - Finally, key exchange messages have a common format.
|
|
|
|
type SSH_PDU(is_orig: bool) = case $context.connection.get_state(is_orig) of {
|
|
VERSION_EXCHANGE -> version : SSH_Version(is_orig);
|
|
ENCRYPTED -> encrypted : bytestring &length=1 &transient;
|
|
default -> kex : SSH_Key_Exchange(is_orig);
|
|
} &byteorder=bigendian;
|
|
|
|
type SSH_Version(is_orig: bool) = record {
|
|
version : bytestring &oneline;
|
|
} &let {
|
|
update_state : bool = $context.connection.update_state(KEX_INIT, is_orig);
|
|
update_version : bool = $context.connection.update_version(version, is_orig);
|
|
};
|
|
|
|
type SSH_Key_Exchange(is_orig: bool) = case $context.connection.get_version() of {
|
|
SSH1 -> ssh1_msg : SSH1_Key_Exchange(is_orig);
|
|
SSH2 -> ssh2_msg : SSH2_Key_Exchange(is_orig);
|
|
};
|
|
|
|
# SSH1 constructs
|
|
#################
|
|
|
|
type SSH1_Key_Exchange(is_orig: bool) = record {
|
|
packet_length : uint32;
|
|
pad_fill : bytestring &length = 8 - (packet_length % 8);
|
|
msg_type : uint8;
|
|
message : SSH1_Message(is_orig, msg_type, packet_length - 5);
|
|
crc : uint32;
|
|
} &length = packet_length + 4 + 8 - (packet_length % 8);
|
|
|
|
type SSH1_Message(is_orig: bool, msg_type: uint8, length: uint32) = case msg_type of {
|
|
SSH_SMSG_PUBLIC_KEY -> public_key : SSH1_PUBLIC_KEY(length);
|
|
SSH_CMSG_SESSION_KEY -> session_key : SSH1_SESSION_KEY(length);
|
|
} &let {
|
|
detach : bool=$context.connection.update_state(ENCRYPTED, is_orig);
|
|
};
|
|
|
|
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;
|
|
host_key : uint32;
|
|
host_key_p : ssh1_mp_int;
|
|
host_key_e : ssh1_mp_int;
|
|
flags : uint32;
|
|
supported_ciphers : uint32;
|
|
supported_auths : uint32;
|
|
} &length=length;
|
|
|
|
type SSH1_SESSION_KEY(length: uint32) = record {
|
|
cipher : uint8;
|
|
cookie : bytestring &length=8;
|
|
session_key : ssh1_mp_int;
|
|
flags : uint32;
|
|
} &length=length;
|
|
|
|
type ssh1_mp_int = record {
|
|
len : uint16;
|
|
val : bytestring &length=(len+7)/8;
|
|
};
|
|
|
|
|
|
## SSH2
|
|
|
|
type SSH2_Header(is_orig: bool) = record {
|
|
packet_length : uint32;
|
|
padding_length : uint8;
|
|
msg_type : uint8;
|
|
} &let {
|
|
payload_length : uint32 = packet_length - padding_length - 2;
|
|
detach : bool = $context.connection.update_state(ENCRYPTED, is_orig) &if(msg_type == MSG_NEWKEYS);
|
|
};
|
|
|
|
type SSH2_Key_Exchange(is_orig: bool) = record {
|
|
header : SSH2_Header(is_orig);
|
|
payload : SSH2_Message(is_orig, header.msg_type, header.payload_length);
|
|
pad : bytestring &length=header.padding_length;
|
|
} &length=header.packet_length + 4;
|
|
|
|
type SSH2_Message(is_orig: bool, msg_type: uint8, length: uint32) = case $context.connection.get_state(is_orig) of {
|
|
KEX_INIT -> kex : SSH2_KEXINIT(length, is_orig);
|
|
KEX_DH_GEX -> kex_dh_gex : SSH2_Key_Exchange_DH_GEX_Message(is_orig, msg_type, length);
|
|
KEX_DH -> kex_dh : SSH2_Key_Exchange_DH_Message(is_orig, msg_type, length);
|
|
KEX_ECC -> kex_ecc : SSH2_Key_Exchange_ECC_Message(is_orig, msg_type, length);
|
|
KEX_GSS -> kex_gss : SSH2_Key_Exchange_GSS_Message(is_orig, msg_type, length);
|
|
KEX_RSA -> kex_rsa : SSH2_Key_Exchange_RSA_Message(is_orig, msg_type, length);
|
|
default -> unknown : bytestring &length=length;
|
|
};
|
|
|
|
type SSH2_KEXINIT(length: uint32, is_orig: bool) = record {
|
|
cookie : bytestring &length=16;
|
|
kex_algorithms : ssh_string;
|
|
server_host_key_algorithms : ssh_string;
|
|
encryption_algorithms_client_to_server : ssh_string;
|
|
encryption_algorithms_server_to_client : ssh_string;
|
|
mac_algorithms_client_to_server : ssh_string;
|
|
mac_algorithms_server_to_client : ssh_string;
|
|
compression_algorithms_client_to_server : ssh_string;
|
|
compression_algorithms_server_to_client : ssh_string;
|
|
languages_client_to_server : ssh_string;
|
|
languages_server_to_client : ssh_string;
|
|
first_kex_packet_follows : uint8;
|
|
reserved : uint32;
|
|
} &let {
|
|
proc_kex : bool= $context.connection.update_kex(kex_algorithms.val, is_orig);
|
|
} &length=length;
|
|
|
|
# KEX_DH exchanges
|
|
|
|
type SSH2_Key_Exchange_DH_Message(is_orig: bool, msg_type: uint8, length: uint32) = case msg_type of {
|
|
SSH_MSG_KEXDH_INIT -> init : SSH2_DH_GEX_INIT(length);
|
|
SSH_MSG_KEXDH_REPLY -> reply : SSH2_DH_GEX_REPLY(length);
|
|
default -> unknown: bytestring &length=length &transient;
|
|
};
|
|
|
|
# KEX_DH_GEX exchanges
|
|
|
|
type SSH2_Key_Exchange_DH_GEX_Message(is_orig: bool, msg_type: uint8, length: uint32) = case msg_type of {
|
|
SSH_MSG_KEX_DH_GEX_REQUEST_OLD -> request_old : SSH2_DH_GEX_REQUEST_OLD;
|
|
SSH_MSG_KEX_DH_GEX_REQUEST -> request : SSH2_DH_GEX_REQUEST;
|
|
SSH_MSG_KEX_DH_GEX_GROUP -> group : SSH2_DH_GEX_GROUP(length);
|
|
SSH_MSG_KEX_DH_GEX_INIT -> init : SSH2_DH_GEX_INIT(length);
|
|
SSH_MSG_KEX_DH_GEX_REPLY -> reply : SSH2_DH_GEX_REPLY(length);
|
|
default -> unknown : bytestring &length=length &transient;
|
|
};
|
|
|
|
type SSH2_DH_GEX_REQUEST = record {
|
|
min : uint32;
|
|
n : uint32;
|
|
max : uint32;
|
|
} &length=12;
|
|
|
|
type SSH2_DH_GEX_REQUEST_OLD = record {
|
|
n : uint32;
|
|
} &length=4;
|
|
|
|
type SSH2_DH_GEX_GROUP(length: uint32) = record {
|
|
p : ssh_string;
|
|
g : ssh_string;
|
|
} &length=length;
|
|
|
|
type SSH2_DH_GEX_INIT(length: uint32) = record {
|
|
e : ssh_string;
|
|
} &length=length;
|
|
|
|
type SSH2_DH_GEX_REPLY(length: uint32) = record {
|
|
k_s : ssh_string;
|
|
f : ssh_string;
|
|
signature : ssh_string;
|
|
} &length=length;
|
|
|
|
# KEX_RSA exchanges
|
|
|
|
type SSH2_Key_Exchange_RSA_Message(is_orig: bool, msg_type: uint8, length: uint32) = case msg_type of {
|
|
SSH_MSG_KEXRSA_PUBKEY -> pubkey : SSH2_RSA_PUBKEY(length);
|
|
SSH_MSG_KEXRSA_SECRET -> secret : SSH2_RSA_SECRET(length);
|
|
SSH_MSG_KEXRSA_DONE -> done : SSH2_RSA_DONE(length);
|
|
};
|
|
|
|
type SSH2_RSA_PUBKEY(length: uint32) = record {
|
|
k_s : ssh_string;
|
|
k_t : ssh_string;
|
|
} &length=length;
|
|
|
|
type SSH2_RSA_SECRET(length: uint32) = record {
|
|
encrypted_payload : ssh_string;
|
|
} &length=length;
|
|
|
|
type SSH2_RSA_DONE(length: uint32) = record {
|
|
signature : ssh_string;
|
|
} &length=length;
|
|
|
|
# KEX_GSS exchanges
|
|
|
|
type SSH2_Key_Exchange_GSS_Message(is_orig: bool, msg_type: uint8, length: uint32) = case msg_type of {
|
|
SSH_MSG_KEXGSS_INIT -> init : SSH2_GSS_INIT(length);
|
|
SSH_MSG_KEXGSS_CONTINUE -> cont : SSH2_GSS_CONTINUE(length);
|
|
SSH_MSG_KEXGSS_COMPLETE -> complete : SSH2_GSS_COMPLETE(length);
|
|
SSH_MSG_KEXGSS_HOSTKEY -> hostkey : SSH2_GSS_HOSTKEY(length);
|
|
SSH_MSG_KEXGSS_ERROR -> error : SSH2_GSS_ERROR(length);
|
|
SSH_MSG_KEXGSS_GROUPREQ -> groupreq : SSH2_DH_GEX_REQUEST;
|
|
SSH_MSG_KEXGSS_GROUP -> group : SSH2_DH_GEX_GROUP(length);
|
|
};
|
|
|
|
type SSH2_GSS_INIT(length: uint32) = record {
|
|
output_token : ssh_string;
|
|
e : ssh_string;
|
|
} &length=length;
|
|
|
|
type SSH2_GSS_CONTINUE(length: uint32) = record {
|
|
output_token : ssh_string;
|
|
} &length=length;
|
|
|
|
type SSH2_GSS_COMPLETE(length: uint32) = record {
|
|
f : ssh_string;
|
|
per_msg_token : ssh_string;
|
|
have_token : uint8;
|
|
parse_token : case have_token of {
|
|
0 -> no_token : empty;
|
|
default -> token : ssh_string;
|
|
};
|
|
} &length=length;
|
|
|
|
type SSH2_GSS_HOSTKEY(length: uint32) = record {
|
|
k_s : ssh_string;
|
|
} &length=length;
|
|
|
|
type SSH2_GSS_ERROR(length: uint32) = record {
|
|
major_status : uint32;
|
|
minor_status : uint32;
|
|
message : ssh_string;
|
|
language : ssh_string;
|
|
} &length=length;
|
|
|
|
# KEX_ECDH and KEX_ECMQV exchanges
|
|
|
|
type SSH2_Key_Exchange_ECC_Message(is_orig: bool, msg_type: uint8, length: uint32) = case msg_type of {
|
|
SSH_MSG_KEX_ECDH_INIT -> init : SSH2_ECC_INIT(length);
|
|
SSH_MSG_KEX_ECDH_REPLY -> reply : SSH2_ECC_REPLY(length);
|
|
};
|
|
|
|
# This deviates from the RFC. SSH_MSG_KEX_ECDH_INIT and
|
|
# SSH_MSG_KEX_ECMQV_INIT can be parsed the same way.
|
|
type SSH2_ECC_INIT(length: uint32) = record {
|
|
q_c : ssh_string;
|
|
};
|
|
|
|
# This deviates from the RFC. SSH_MSG_KEX_ECDH_REPLY and
|
|
# SSH_MSG_KEX_ECMQV_REPLY can be parsed the same way.
|
|
type SSH2_ECC_REPLY(length: uint32) = record {
|
|
k_s : ssh_string;
|
|
q_s : ssh_string;
|
|
signature : ssh_string;
|
|
};
|
|
|
|
# Helper types
|
|
|
|
type ssh_string = record {
|
|
len : uint32;
|
|
val : bytestring &length=len;
|
|
};
|
|
|
|
type ssh_host_key = record {
|
|
len : uint32;
|
|
key_type : ssh_string;
|
|
key : ssh_string;
|
|
} &length=(len + 4);
|
|
|
|
## Done with types
|
|
|
|
refine connection SSH_Conn += {
|
|
%member{
|
|
int state_up_;
|
|
int state_down_;
|
|
int version_;
|
|
|
|
bool kex_orig_;
|
|
bool kex_seen_;
|
|
bytestring kex_algs_cache_;
|
|
bytestring kex_algorithm_;
|
|
%}
|
|
|
|
%init{
|
|
state_up_ = VERSION_EXCHANGE;
|
|
state_down_ = VERSION_EXCHANGE;
|
|
version_ = UNK;
|
|
|
|
kex_seen_ = false;
|
|
kex_orig_ = false;
|
|
kex_algs_cache_ = bytestring();
|
|
kex_algorithm_ = bytestring();
|
|
%}
|
|
|
|
%cleanup{
|
|
kex_algorithm_.free();
|
|
kex_algs_cache_.free();
|
|
%}
|
|
|
|
function get_state(is_orig: bool) : int
|
|
%{
|
|
if ( is_orig )
|
|
{
|
|
return state_up_;
|
|
}
|
|
else
|
|
{
|
|
return state_down_;
|
|
}
|
|
%}
|
|
|
|
function update_state(s: state, is_orig: bool) : bool
|
|
%{
|
|
if ( is_orig )
|
|
state_up_ = s;
|
|
else
|
|
state_down_ = s;
|
|
return true;
|
|
%}
|
|
|
|
function get_version() : int
|
|
%{
|
|
return version_;
|
|
%}
|
|
|
|
function update_version(v: bytestring, is_orig: bool) : bool
|
|
%{
|
|
if ( is_orig && ( v.length() >= 4 ) )
|
|
{
|
|
if ( v[4] == '2' )
|
|
version_ = SSH2;
|
|
if ( v[4] == '1' )
|
|
version_ = SSH1;
|
|
}
|
|
return true;
|
|
%}
|
|
|
|
function update_kex_state_if_equal(s: string, to_state: state) : bool
|
|
%{
|
|
if ( std_str(kex_algorithm_).compare(s) == 0 )
|
|
{
|
|
update_state(to_state, true);
|
|
update_state(to_state, false);
|
|
return true;
|
|
}
|
|
return false;
|
|
%}
|
|
|
|
function update_kex_state_if_startswith(s: string, to_state: state) : bool
|
|
%{
|
|
if ( (uint) kex_algorithm_.length() < s.length() )
|
|
return false;
|
|
|
|
if ( std_str(kex_algorithm_).substr(0, s.length()).compare(s) == 0 )
|
|
{
|
|
update_state(to_state, true);
|
|
update_state(to_state, false);
|
|
return true;
|
|
}
|
|
return false;
|
|
%}
|
|
|
|
function update_kex(algs: bytestring, orig: bool) : bool
|
|
%{
|
|
if ( !kex_seen_ )
|
|
{
|
|
kex_seen_ = true;
|
|
kex_orig_ = orig;
|
|
kex_algs_cache_.init(${algs}.data(), ${algs}.length());
|
|
|
|
return false;
|
|
}
|
|
else if ( kex_orig_ == orig )
|
|
return false;
|
|
|
|
VectorVal* client_list = name_list_to_vector(orig ? algs : kex_algs_cache_);
|
|
VectorVal* server_list = name_list_to_vector(orig ? kex_algs_cache_ : algs);
|
|
|
|
for ( unsigned int i = 0; i < client_list->Size(); ++i )
|
|
{
|
|
for ( unsigned int j = 0; j < server_list->Size(); ++j )
|
|
{
|
|
if ( *(client_list->Lookup(i)->AsStringVal()->AsString()) == *(server_list->Lookup(j)->AsStringVal()->AsString()) )
|
|
{
|
|
kex_algorithm_.init((const uint8 *) client_list->Lookup(i)->AsStringVal()->Bytes(),
|
|
client_list->Lookup(i)->AsStringVal()->Len());
|
|
|
|
Unref(client_list);
|
|
Unref(server_list);
|
|
|
|
// UNTESTED
|
|
if ( update_kex_state_if_equal("rsa1024-sha1", KEX_RSA) )
|
|
return true;
|
|
// UNTESTED
|
|
if ( update_kex_state_if_equal("rsa2048-sha256", KEX_RSA) )
|
|
return true;
|
|
|
|
// UNTESTED
|
|
if ( update_kex_state_if_equal("diffie-hellman-group1-sha1", KEX_DH) )
|
|
return true;
|
|
// UNTESTED
|
|
if ( update_kex_state_if_equal("diffie-hellman-group14-sha1", KEX_DH) )
|
|
return true;
|
|
|
|
if ( update_kex_state_if_equal("diffie-hellman-group-exchange-sha1", KEX_DH_GEX) )
|
|
return true;
|
|
if ( update_kex_state_if_equal("diffie-hellman-group-exchange-sha256", KEX_DH_GEX) )
|
|
return true;
|
|
|
|
if ( update_kex_state_if_startswith("gss-group1-sha1-", KEX_GSS) )
|
|
return true;
|
|
if ( update_kex_state_if_startswith("gss-group14-sha1-", KEX_GSS) )
|
|
return true;
|
|
if ( update_kex_state_if_startswith("gss-gex-sha1-", KEX_GSS) )
|
|
return true;
|
|
if ( update_kex_state_if_startswith("gss-", KEX_GSS) )
|
|
return true;
|
|
|
|
if ( update_kex_state_if_startswith("ecdh-sha2-", KEX_ECC) )
|
|
return true;
|
|
if ( update_kex_state_if_equal("ecmqv-sha2", KEX_ECC) )
|
|
return true;
|
|
if ( update_kex_state_if_equal("curve25519-sha256@libssh.org", KEX_ECC) )
|
|
return true;
|
|
|
|
|
|
bro_analyzer()->Weird(fmt("ssh_unknown_kex_algorithm=%s", c_str(kex_algorithm_)));
|
|
return true;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
Unref(client_list);
|
|
Unref(server_list);
|
|
|
|
return true;
|
|
%}
|
|
|
|
};
|