From: Shravan Rangarajuvenkata (shrarang) Date: Tue, 6 Jul 2021 18:01:09 +0000 (+0000) Subject: Merge pull request #2958 in SNORT/snort3 from ~DANMCGAR/snort3:ssh-client-patterns... X-Git-Tag: 3.1.8.0~11 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6d6fbbcab9ff2666dadcb2037a671a0490d4566f;p=thirdparty%2Fsnort3.git Merge pull request #2958 in SNORT/snort3 from ~DANMCGAR/snort3:ssh-client-patterns to master Squashed commit of the following: commit cb11ffef012d75d00e3635a62e652dd5c570d8cb Author: Daniel McGarvey Date: Mon Jun 21 11:57:48 2021 -0400 appid: support SSH client detection through lua detector --- diff --git a/src/network_inspectors/appid/CMakeLists.txt b/src/network_inspectors/appid/CMakeLists.txt index 095521b2f..d940998b4 100644 --- a/src/network_inspectors/appid/CMakeLists.txt +++ b/src/network_inspectors/appid/CMakeLists.txt @@ -125,6 +125,8 @@ set ( DP_APPID_SOURCES detector_plugins/http_url_patterns.h detector_plugins/sip_patterns.cc detector_plugins/sip_patterns.h + detector_plugins/ssh_patterns.cc + detector_plugins/ssh_patterns.h detector_plugins/ssl_patterns.cc detector_plugins/ssl_patterns.h ) diff --git a/src/network_inspectors/appid/appid_config.cc b/src/network_inspectors/appid/appid_config.cc index 707ea6b78..4dde961aa 100644 --- a/src/network_inspectors/appid/appid_config.cc +++ b/src/network_inspectors/appid/appid_config.cc @@ -172,6 +172,7 @@ void OdpContext::initialize(AppIdInspector& inspector) sip_matchers.finalize_patterns(*this); ssl_matchers.finalize_patterns(); dns_matchers.finalize_patterns(); + ssh_matchers.finalize_patterns(); } void OdpContext::reload() diff --git a/src/network_inspectors/appid/appid_config.h b/src/network_inspectors/appid/appid_config.h index a1b6582f6..c5f823e26 100644 --- a/src/network_inspectors/appid/appid_config.h +++ b/src/network_inspectors/appid/appid_config.h @@ -39,6 +39,7 @@ #include "lua_detector_flow_api.h" #include "lua_detector_module.h" #include "service_plugins/service_discovery.h" +#include "detector_plugins/ssh_patterns.h" #include "tp_appid_module_api.h" #include "utils/sflsq.h" @@ -186,6 +187,11 @@ public: return ssl_matchers; } + SshPatternMatchers& get_ssh_matchers() + { + return ssh_matchers; + } + PatternClientDetector& get_client_pattern_detector() { return *client_pattern_detector; @@ -211,6 +217,7 @@ private: ServiceDiscovery service_disco_mgr; SipPatternMatchers sip_matchers; SslPatternMatchers ssl_matchers; + SshPatternMatchers ssh_matchers; PatternClientDetector* client_pattern_detector; PatternServiceDetector* service_pattern_detector; diff --git a/src/network_inspectors/appid/client_plugins/client_app_ssh.cc b/src/network_inspectors/appid/client_plugins/client_app_ssh.cc index 235a442c8..7282373dd 100644 --- a/src/network_inspectors/appid/client_plugins/client_app_ssh.cc +++ b/src/network_inspectors/appid/client_plugins/client_app_ssh.cc @@ -25,6 +25,9 @@ #include "client_app_ssh.h" +#include +#include + #include "app_info_table.h" #include "application_ids.h" @@ -51,20 +54,6 @@ static const char PUTTY_BANNER[] = "PuTTY"; #define SSH2 2 #define SSH1 1 -enum SSHClientState -{ - SSH_CLIENT_STATE_BANNER = 0, - SSH_CLIENT_STATE_ID_PROTO_VERSION, - SSH_CLIENT_STATE_LOOKING_FOR_DASH, - SSH_CLIENT_STATE_ID_CLIENT, - SSH_CLIENT_STATE_CHECK_OPENSSH, - SSH_CLIENT_STATE_CHECK_PUTTY, - SSH_CLIENT_STATE_CHECK_LSH, - SSH_CLIENT_STATE_CHECK_DROPBEAR, - SSH_CLIENT_STATE_ID_SOFTWARE_VERSION, - SSH_CLIENT_STATE_ID_REST_OF_LINE, - SSH_CLIENT_STATE_KEY -}; enum SSH2HeaderState { @@ -90,7 +79,6 @@ enum SSH1HeaderState struct ClientSSHData { - SSHClientState state; SSH2HeaderState hstate; SSH1HeaderState oldhstate; unsigned len; @@ -108,6 +96,9 @@ struct ClientSSHData uint8_t plen; uint8_t code; uint32_t client_id; + uint8_t proto_string[SSH_MAX_BANNER_LEN]; + uint8_t proto_strlen; + bool proto_string_done; }; #pragma pack(1) @@ -372,186 +363,79 @@ static inline int ssh_client_validate_pubkey(uint16_t offset, const uint8_t* dat return APPID_INPROCESS; } -static inline int ssh_client_sm(const uint8_t* data, uint16_t size, - ClientSSHData* fd) -{ - uint16_t offset = 0; +static inline int ssh_client_sm(AppIdDiscoveryArgs& args, ClientSSHData* fd) +{ + const char *pattern_begin; + const char *pattern_end; + uint8_t d; - while (offset < size) + if (strncmp((const char*)fd->proto_string, SSH_CLIENT_BANNER, SSH_CLIENT_BANNER_LEN) != 0) { - uint8_t d = data[offset]; - switch (fd->state) - { - case SSH_CLIENT_STATE_BANNER: - if (d != SSH_CLIENT_BANNER[fd->pos]) - return APPID_EINVALID; - if (fd->pos >= SSH_CLIENT_BANNER_MAXPOS) - fd->state = SSH_CLIENT_STATE_ID_PROTO_VERSION; - else - fd->pos++; - break; - - case SSH_CLIENT_STATE_ID_PROTO_VERSION: - if (d == '1') - fd->ssh_version = SSH1; - else if (d == '2') - fd->ssh_version = SSH2; - else - return APPID_EINVALID; - fd->state = SSH_CLIENT_STATE_LOOKING_FOR_DASH; - break; - - case SSH_CLIENT_STATE_LOOKING_FOR_DASH: - if (d == '-') - { - fd->state = SSH_CLIENT_STATE_ID_CLIENT; - break; - } - break; - - case SSH_CLIENT_STATE_ID_CLIENT: - switch (d) - { - case 'O': - fd->state = SSH_CLIENT_STATE_CHECK_OPENSSH; - break; - case 'P': - fd->state = SSH_CLIENT_STATE_CHECK_PUTTY; - break; - case 'l': - fd->state = SSH_CLIENT_STATE_CHECK_LSH; - break; - case 'd': - fd->state = SSH_CLIENT_STATE_CHECK_DROPBEAR; - break; - default: - fd->state = SSH_CLIENT_STATE_ID_REST_OF_LINE; - fd->client_id = APP_ID_SSH; - } - /*the next thing we want to see is the SECOND character... */ - fd->pos = 1; - break; - - case SSH_CLIENT_STATE_CHECK_OPENSSH: - if (d != OPENSSH_BANNER[fd->pos]) - { - fd->client_id = APP_ID_SSH; - fd->state = SSH_CLIENT_STATE_ID_REST_OF_LINE; - } - else if (fd->pos >= OPENSSH_BANNER_MAXPOS) - { - fd->client_id = APP_ID_OPENSSH; - fd->state = SSH_CLIENT_STATE_ID_SOFTWARE_VERSION; - fd->pos = 0; - } - else - fd->pos++; - break; - - case SSH_CLIENT_STATE_CHECK_PUTTY: - if (d != PUTTY_BANNER[fd->pos]) - { - fd->client_id = APP_ID_SSH; - fd->state = SSH_CLIENT_STATE_ID_REST_OF_LINE; - } - else if (fd->pos >= PUTTY_BANNER_MAXPOS) - { - fd->client_id = APP_ID_PUTTY; - fd->state = SSH_CLIENT_STATE_ID_SOFTWARE_VERSION; - fd->pos = 0; - } - else - fd->pos++; - break; - - case SSH_CLIENT_STATE_CHECK_LSH: - if (d != LSH_BANNER[fd->pos]) - { - fd->client_id = APP_ID_SSH; - fd->state = SSH_CLIENT_STATE_ID_REST_OF_LINE; - } - else if (fd->pos >= LSH_BANNER_MAXPOS) - { - fd->client_id = APP_ID_LSH; - fd->state = SSH_CLIENT_STATE_ID_SOFTWARE_VERSION; - fd->pos = 0; - } - else - fd->pos++; - break; - - case SSH_CLIENT_STATE_CHECK_DROPBEAR: - if (d != DROPBEAR_BANNER[fd->pos]) - { - fd->client_id = APP_ID_SSH; - fd->state = SSH_CLIENT_STATE_ID_REST_OF_LINE; - } - else if (fd->pos >= DROPBEAR_BANNER_MAXPOS) - { - fd->client_id = APP_ID_DROPBEAR; - fd->state = SSH_CLIENT_STATE_ID_SOFTWARE_VERSION; - fd->pos = 0; - } - else - fd->pos++; - break; + return APPID_EINVALID; + } - case SSH_CLIENT_STATE_ID_SOFTWARE_VERSION: - if (d == '\n') - { - fd->version[fd->pos] = 0; - fd->pos = 0; - fd->state = SSH_CLIENT_STATE_KEY; - break; - } - if (d == ' ') + d = fd->proto_string[SSH_CLIENT_BANNER_MAXPOS+1]; + if (d == '1') + fd->ssh_version = SSH1; + else if (d == '2') + fd->ssh_version = SSH2; + else + { + return APPID_EINVALID; + } + + + pattern_begin = strchr((const char*)(fd->proto_string + SSH_CLIENT_BANNER_MAXPOS + 1), '-'); + if (pattern_begin != nullptr) + { + pattern_begin++; + } + else + { + return APPID_EINVALID; + } + + pattern_end = strpbrk(pattern_begin, "_-"); + if (pattern_end != nullptr) + { + size_t pattern_len = (size_t)(pattern_end - pattern_begin); + string pattern(pattern_begin, pattern_len); + SshPatternMatchers& table = args.asd.get_odp_ctxt().get_ssh_matchers(); + + if (table.has_pattern(pattern)) + { + fd->client_id = table.get_appid(pattern); + } + else + { + fd->client_id = APP_ID_SSH; + } + while (pattern_end - (const char*)fd->proto_string < fd->proto_strlen) + { + d = *pattern_end; + if (d == ' ' || d == '\n') { - fd->version[fd->pos] = 0; - fd->state = SSH_CLIENT_STATE_ID_REST_OF_LINE; break; } - if (fd->pos < SSH_MAX_BANNER_LEN - 1 && d != '\r' && d != '-' && d != '_') + if (d != '\r' && d != '-' && d != '_') { fd->version[fd->pos++] = d; } - break; - - case SSH_CLIENT_STATE_ID_REST_OF_LINE: - if (d == '\n') - { - fd->pos = 0; - fd->state = SSH_CLIENT_STATE_KEY; - break; - } - break; - - case SSH_CLIENT_STATE_KEY: - switch (fd->ssh_version) - { - case SSH2: - return ssh_client_validate_keyx(offset, data, size, fd); - break; - case SSH1: - return ssh_client_validate_pubkey(offset, data, size, fd); - break; - default: - return APPID_EINVALID; - break; - } - break; - - default: - return APPID_EINVALID; + pattern_end++; } - offset++; } + else + { + fd->client_id = APP_ID_SSH; + } + fd->pos = 0; return APPID_INPROCESS; } int SshClientDetector::validate(AppIdDiscoveryArgs& args) { ClientSSHData* fd; - int sm_ret; + int sm_ret = APPID_INPROCESS; if (!args.size || args.dir != APP_ID_FROM_INITIATOR) return APPID_INPROCESS; @@ -561,14 +445,67 @@ int SshClientDetector::validate(AppIdDiscoveryArgs& args) { fd = (ClientSSHData*)snort_calloc(sizeof(ClientSSHData)); data_add(args.asd, fd, &snort_free); - fd->state = SSH_CLIENT_STATE_BANNER; fd->hstate = SSH2_HEADER_BEGIN; fd->oldhstate = SSH1_HEADER_BEGIN; + fd->proto_string_done = false; + } + + uint16_t offset = 0; + if (!(fd->proto_string_done)) + { + const uint8_t *line_end = (const uint8_t *)memchr(args.data, '\n', args.size); + size_t append_len; + if (line_end != nullptr) + { + append_len = (size_t)(line_end - args.data + 1); + fd->proto_string_done = true; + } + else + { + append_len = args.size; + } + + if (fd->proto_strlen + append_len < SSH_MAX_BANNER_LEN) + { + strncat((char*)fd->proto_string, (const char*)args.data, append_len); + fd->proto_strlen += append_len; + } + else + { + return APPID_EINVALID; + } + + if (line_end == nullptr) + { + return APPID_INPROCESS; + } + + fd->proto_string_done = true; + sm_ret = ssh_client_sm(args, fd); + if (sm_ret != APPID_INPROCESS) + { + return sm_ret; + } + offset = (uint16_t)(line_end - args.data + 1); + } + + switch (fd->ssh_version) + { + case SSH2: + sm_ret = ssh_client_validate_keyx(offset, args.data, args.size, fd); + break; + case SSH1: + sm_ret = ssh_client_validate_pubkey(offset, args.data, args.size, fd); + break; + default: + sm_ret = APPID_EINVALID; + break; } - sm_ret = ssh_client_sm(args.data, args.size, fd); if (sm_ret != APPID_SUCCESS) + { return sm_ret; + } add_app(args.asd, APP_ID_SSH, fd->client_id, (const char*)fd->version, args.change_bits); return APPID_SUCCESS; diff --git a/src/network_inspectors/appid/detector_plugins/ssh_patterns.cc b/src/network_inspectors/appid/detector_plugins/ssh_patterns.cc new file mode 100644 index 000000000..bcb37e54a --- /dev/null +++ b/src/network_inspectors/appid/detector_plugins/ssh_patterns.cc @@ -0,0 +1,53 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2021 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_patterns.cc author Daniel McGarvey + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ssh_patterns.h" + +void SshPatternMatchers::add_ssh_pattern(const std::string& pattern, AppId id) +{ + ssh_patterns[pattern] = id; +} + +bool SshPatternMatchers::has_pattern(const std::string& pattern) const +{ + return ssh_patterns.find(pattern) != ssh_patterns.end(); +} + +bool SshPatternMatchers::empty() const +{ + return ssh_patterns.empty(); +} + +AppId SshPatternMatchers::get_appid(const std::string& pattern) const +{ + return ssh_patterns.at(pattern); +} + +void SshPatternMatchers::finalize_patterns() +{ + ssh_patterns["dropbear"] = APP_ID_DROPBEAR; + ssh_patterns["OpenSSH"] = APP_ID_OPENSSH; + ssh_patterns["PuTTY"] = APP_ID_PUTTY; + ssh_patterns["lsh"] = APP_ID_LSH; +} diff --git a/src/network_inspectors/appid/detector_plugins/ssh_patterns.h b/src/network_inspectors/appid/detector_plugins/ssh_patterns.h new file mode 100644 index 000000000..b10e338ff --- /dev/null +++ b/src/network_inspectors/appid/detector_plugins/ssh_patterns.h @@ -0,0 +1,51 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2021 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_patterns.cc author Daniel McGarvey + +#ifndef SSH_PATTERNS_H +#define SSH_PATTERNS_H + +/* + * SshPatternMatchers is a wrapper around an unordered_map + * which maps strings to AppIds. SSH Client Patterns + * are registered through a lua API, and these mappings + * are used by client_app_ssh.cc to identify clients. + * An instance of the class is held by OdpContext. + */ + +#include +#include + +#include "application_ids.h" + +typedef std::unordered_map SshPatternTable; + +class SshPatternMatchers +{ +public: + void add_ssh_pattern(const std::string& pattern, AppId id); + bool has_pattern(const std::string& pattern) const; + bool empty() const; + AppId get_appid(const std::string& pattern) const; + void finalize_patterns(); +private: + SshPatternTable ssh_patterns; +}; + +#endif diff --git a/src/network_inspectors/appid/lua_detector_api.cc b/src/network_inspectors/appid/lua_detector_api.cc index ad2fe8cfd..5bd5966e6 100644 --- a/src/network_inspectors/appid/lua_detector_api.cc +++ b/src/network_inspectors/appid/lua_detector_api.cc @@ -43,6 +43,7 @@ #include "detector_plugins/detector_pattern.h" #include "detector_plugins/detector_sip.h" #include "detector_plugins/http_url_patterns.h" +#include "detector_plugins/ssh_patterns.h" #include "host_port_app_cache.h" #include "lua_detector_flow_api.h" #include "lua_detector_module.h" @@ -1239,6 +1240,30 @@ static int detector_add_content_type_pattern(lua_State* L) return 0; } + +static int detector_add_ssh_client_pattern(lua_State* L) +{ + auto& ud = *UserData::check(L, DETECTOR, 1); + ud->validate_lua_state(false); + if (!init(L)) return 0; + + size_t string_size = 0; + int index = 1; + + const char* tmp_string = lua_tolstring(L, ++index, &string_size); + if (!tmp_string || !string_size) + { + ErrorMessage("Invalid SSH Client string"); + return 0; + } + std::string pattern(tmp_string); + AppId app_id = lua_tointeger(L, ++index); + ud->get_odp_ctxt().get_ssh_matchers().add_ssh_pattern(pattern, app_id); + ud->get_odp_ctxt().get_app_info_mgr().set_app_info_active(app_id); + + return 0; +} + static int register_callback(lua_State* L, LuaObject& ud, AppInfoFlags flag) { // Verify detector user data and that we are NOT in packet context @@ -2614,6 +2639,7 @@ static const luaL_Reg detector_methods[] = { "addSipUserAgent", detector_add_sip_user_agent }, { "addSipServer", detector_add_sip_server }, { "addSSLCnamePattern", detector_add_ssl_cname_pattern }, + { "addSSHPattern", detector_add_ssh_client_pattern}, { "addHostPortApp", detector_add_host_port_application }, { "addHostPortAppDynamic", detector_add_host_port_dynamic }, { "addDNSHostPattern", detector_add_dns_host_pattern },