From: Pranav Bhalerao (prbhaler) Date: Wed, 14 Oct 2020 03:28:37 +0000 (+0000) Subject: Merge pull request #2505 in SNORT/snort3 from ~PRBHALER/snort3:CSCvv22127 to master X-Git-Tag: 3.0.3-3~24 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4c95090718960c339e9fb927fe40542cdffcadf6;p=thirdparty%2Fsnort3.git Merge pull request #2505 in SNORT/snort3 from ~PRBHALER/snort3:CSCvv22127 to master Squashed commit of the following: commit af592ee2c72291609f0d8cb27589fd8c9b438d20 Author: Pranav Bhalerao Date: Mon Sep 28 12:47:38 2020 -0400 ssh: ssh splitter implementation --- diff --git a/src/service_inspectors/ssh/CMakeLists.txt b/src/service_inspectors/ssh/CMakeLists.txt index ff633b1d0..b73bf25bf 100644 --- a/src/service_inspectors/ssh/CMakeLists.txt +++ b/src/service_inspectors/ssh/CMakeLists.txt @@ -5,6 +5,8 @@ set( FILE_LIST ssh_config.h ssh_module.cc ssh_module.h + ssh_splitter.cc + ssh_splitter.h ) if (STATIC_INSPECTORS) diff --git a/src/service_inspectors/ssh/ssh.cc b/src/service_inspectors/ssh/ssh.cc index bb9ddb019..89b8cdb68 100644 --- a/src/service_inspectors/ssh/ssh.cc +++ b/src/service_inspectors/ssh/ssh.cc @@ -37,6 +37,7 @@ #include "stream/stream.h" #include "ssh_module.h" +#include "ssh_splitter.h" using namespace snort; @@ -67,14 +68,14 @@ SshFlowData::~SshFlowData() sshstats.concurrent_sessions--; } -static SSHData* SetNewSSHData(Packet* p) +SSHData* SetNewSSHData(Packet* p) { SshFlowData* fd = new SshFlowData; p->flow->set_flow_data(fd); return &fd->session; } -static SSHData* get_session_data(Flow* flow) +SSHData* get_session_data(const Flow* flow) { SshFlowData* fd = (SshFlowData*)flow->get_flow_data(SshFlowData::inspector_id); return fd ? &fd->session : nullptr; @@ -127,19 +128,6 @@ static void snort_ssh(SSH_PROTO_CONF* config, Packet* p) // Attempt to get a previously allocated SSH block. SSHData* sessp = get_session_data(p->flow); - if (sessp == nullptr) - { - /* Check the stream session. If it does not currently - * have our SSH data-block attached, create one. - */ - sessp = SetNewSSHData(p); - - if ( !sessp ) - // Could not get/create the session data for this packet. - return; - - } - // Don't process if we've missed packets if (sessp->state_flags & SSH_FLG_MISSED_PACKETS) return; @@ -668,7 +656,11 @@ static unsigned int ProcessSSHKeyExchange(SSHData* sessionp, Packet* p, */ if ( direction == SSH_DIR_FROM_CLIENT ) { - sessionp->state_flags |= SSH_FLG_NEWKEYS_SEEN; + sessionp->state_flags |= SSH_FLG_CLIENT_NEWKEYS_SEEN; + } + else + { + sessionp->state_flags |= SSH_FLG_SERVER_NEWKEYS_SEEN; } break; default: @@ -727,6 +719,8 @@ public: void show(const SnortConfig*) const override; void eval(Packet*) override; + class StreamSplitter* get_splitter(bool to_server) override + { return new SshSplitter(to_server); } private: SSH_PROTO_CONF* config; diff --git a/src/service_inspectors/ssh/ssh.h b/src/service_inspectors/ssh/ssh.h index 09347e6ed..41c608586 100644 --- a/src/service_inspectors/ssh/ssh.h +++ b/src/service_inspectors/ssh/ssh.h @@ -31,6 +31,7 @@ // packets appear malformed/spoofed. #include "flow/flow.h" +#include "protocols/packet.h" // FIXIT-L move these to ssh.cc // Session state flags for SSHData::state_flags @@ -47,13 +48,14 @@ #define SSH_FLG_GEX_GRP_SEEN (0x200) #define SSH_FLG_GEX_INIT_SEEN (0x400) #define SSH_FLG_GEX_REPLY_SEEN (0x800) -#define SSH_FLG_NEWKEYS_SEEN (0x1000) +#define SSH_FLG_CLIENT_NEWKEYS_SEEN (0x1000) #define SSH_FLG_SESS_ENCRYPTED (0x2000) #define SSH_FLG_RESPOVERFLOW_ALERTED (0x4000) #define SSH_FLG_CRC32_ALERTED (0x8000) #define SSH_FLG_MISSED_PACKETS (0x10000) #define SSH_FLG_REASSEMBLY_SET (0x20000) #define SSH_FLG_AUTODETECTED (0x40000) +#define SSH_FLG_SERVER_NEWKEYS_SEEN (0x8000) // Some convenient combinations of state flags. #define SSH_FLG_BOTH_IDSTRING_SEEN \ @@ -71,14 +73,14 @@ #define SSH_FLG_V2_DHOLD_DONE \ (SSH_FLG_KEXDH_INIT_SEEN | \ SSH_FLG_KEXDH_REPLY_SEEN | \ - SSH_FLG_NEWKEYS_SEEN ) + SSH_FLG_CLIENT_NEWKEYS_SEEN ) #define SSH_FLG_V2_DHNEW_DONE \ (SSH_FLG_GEX_REQ_SEEN | \ SSH_FLG_GEX_GRP_SEEN | \ SSH_FLG_GEX_INIT_SEEN | \ SSH_FLG_GEX_REPLY_SEEN | \ - SSH_FLG_NEWKEYS_SEEN ) + SSH_FLG_CLIENT_NEWKEYS_SEEN ) // SSH version values for SSHData::version #define SSH_VERSION_UNKNOWN (0x0) @@ -114,6 +116,10 @@ public: // Length of SSH2 header, in bytes. #define SSH2_HEADERLEN (5) +// Length of SSH2 Padding, in bytes. +#define SSH2_PADDING_LEN (1) +// Length of SSH2 packet, in bytes. +#define SSH2_PACKET_LEN (SSH2_HEADERLEN - SSH2_PADDING_LEN) #define SSH2_PACKET_MAX_SIZE (256 * 1024) struct SSH2Packet @@ -142,4 +148,6 @@ struct SSH2Packet #define SSH_DIR_FROM_SERVER (0x1) #define SSH_DIR_FROM_CLIENT (0x2) +SSHData* get_session_data(const snort::Flow* flow); +SSHData* SetNewSSHData(snort::Packet* p); #endif diff --git a/src/service_inspectors/ssh/ssh_splitter.cc b/src/service_inspectors/ssh/ssh_splitter.cc new file mode 100644 index 000000000..2ead5bf90 --- /dev/null +++ b/src/service_inspectors/ssh/ssh_splitter.cc @@ -0,0 +1,169 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2020 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// ssh_splitter.cc author Pranav Bhalerao + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ssh.h" +#include "ssh_splitter.h" + +using namespace snort; + +SshSplitter::SshSplitter(bool c2s) : StreamSplitter(c2s) +{ + client_remain_bytes = 0; + server_remain_bytes = 0; + state = 0; +} + +StreamSplitter::Status SshSplitter::ssh2_key_exchange_scan( + const uint8_t* data, uint32_t len, uint32_t* fp, + uint32_t &remain_bytes) +{ + if (remain_bytes < len) + { + uint32_t offset = remain_bytes; + while (offset < len) + { + const SSH2Packet *sshp = (const SSH2Packet*)(data + offset); + uint32_t ssh_len = ntohl(sshp->packet_length); + if (ssh_len > (len - offset)) + { + remain_bytes = ssh_len - (len - SSH2_PACKET_LEN); + return StreamSplitter::SEARCH; + } + + switch (data[offset + SSH2_HEADERLEN]) + { + case SSH_MSG_KEXDH_GEX_INIT: + case SSH_MSG_KEXDH_GEX_GRP: + case SSH_MSG_KEXDH_GEX_REQ: + case SSH_MSG_KEXDH_REPLY: + case SSH_MSG_KEXDH_INIT: + case SSH_MSG_KEXINIT: + offset += (ssh_len + SSH2_PACKET_LEN); + break; + case SSH_MSG_NEWKEYS: + offset += (ssh_len + SSH2_PACKET_LEN); + // fallthrough + default: + goto exit_loop; + + } + } +exit_loop: + *fp = offset; + return StreamSplitter::FLUSH; + } + else + { + remain_bytes = remain_bytes - len; + if (!remain_bytes) + { + *fp = len; + return StreamSplitter::FLUSH; + } + else + { + return StreamSplitter::SEARCH; + } + } +} + +StreamSplitter::Status SshSplitter::ssh2_scan( SSHData* sessp, + const uint8_t* data, uint32_t len, uint32_t flags, uint32_t* fp) +{ + if (flags & PKT_FROM_SERVER) + { + // Do not scan if sever new keys message seen. + if (sessp->state_flags & SSH_FLG_SERVER_NEWKEYS_SEEN) + { + return SEARCH; + } + + return ssh2_key_exchange_scan(data, len, fp, server_remain_bytes); + } + else + { + return ssh2_key_exchange_scan(data, len, fp, client_remain_bytes); + } +} + +StreamSplitter::Status SshSplitter::scan( + Packet* p, const uint8_t* data, uint32_t len, + uint32_t flags, uint32_t* fp) +{ + Flow* flow = p->flow; + SSHData* sessp = get_session_data(flow); + + if (nullptr == sessp) + { + sessp = SetNewSSHData(p); + if (nullptr == sessp) + return ABORT; + } + + if ((sessp->state_flags & SSH_FLG_SERV_IDSTRING_SEEN) + && (sessp->state_flags & SSH_FLG_CLIENT_IDSTRING_SEEN)) + { + state = SSH_PAF_KEY_EXCHANGE; + } + + if (sessp->state_flags & SSH_FLG_SESS_ENCRYPTED) + { + state = SSH_PAF_ENCRYPTED; + } + + switch(state) + { + case SSH_PAF_VER_EXCHANGE: + { + uint32_t n = len; + const uint8_t* lf = nullptr, *tmp = data; + + while ((tmp = (const uint8_t*)memchr(tmp, '\n', n))) + { + lf = tmp++; + n = len - (tmp - data); + } + if (!lf) + return SEARCH; + + *fp = lf - data + 1; + return FLUSH; + } + case SSH_PAF_KEY_EXCHANGE: + { + if (sessp->version == SSH_VERSION_2) + { + return ssh2_scan(sessp, data, len, flags, fp); + } + } + // fallthrough + default: + { + // there will not be multiple SSH payloads in single TCP PDU. + // for SSH1 or Encrypted PDUs flush it at data boundary. + *fp = len; + return FLUSH; + } + } +} diff --git a/src/service_inspectors/ssh/ssh_splitter.h b/src/service_inspectors/ssh/ssh_splitter.h new file mode 100644 index 000000000..ffc281359 --- /dev/null +++ b/src/service_inspectors/ssh/ssh_splitter.h @@ -0,0 +1,57 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2020 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// ssh_splitter.h author Pranav Bhalerao + +#ifndef SSH_SPLITTER_H +#define SSH_SPLITTER_H + +#include "protocols/packet.h" +#include "stream/stream_splitter.h" + +enum SshPafState +{ + SSH_PAF_VER_EXCHANGE, + SSH_PAF_KEY_EXCHANGE, + SSH_PAF_ENCRYPTED +}; + +class SshSplitter : public snort::StreamSplitter +{ +public: + SshSplitter(bool c2s); + + Status scan(snort::Packet*, const uint8_t* data, uint32_t len, + uint32_t flags, uint32_t* fp) override; + + bool is_paf() override + { + return true; + } + +private: + Status ssh2_key_exchange_scan(const uint8_t* data, uint32_t len, + uint32_t* fp, uint32_t& remain_bytes); + Status ssh2_scan(SSHData* sessp, const uint8_t* data, uint32_t len, + uint32_t flags, uint32_t* fp); + + uint32_t state; + uint32_t client_remain_bytes = 0; + uint32_t server_remain_bytes = 0; +}; +#endif