#include "client_app_ssh.h"
+#include <cstring>
+#include <string>
+
#include "app_info_table.h"
#include "application_ids.h"
#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
{
struct ClientSSHData
{
- SSHClientState state;
SSH2HeaderState hstate;
SSH1HeaderState oldhstate;
unsigned len;
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)
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;
{
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;
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <danmcgar@cisco.com>
+
+#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;
+}
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <danmcgar@cisco.com>
+
+#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 <string>
+#include <unordered_map>
+
+#include "application_ids.h"
+
+typedef std::unordered_map<std::string, AppId> 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
#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"
return 0;
}
+
+static int detector_add_ssh_client_pattern(lua_State* L)
+{
+ auto& ud = *UserData<LuaObject>::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
{ "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 },