]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Merge pull request #2958 in SNORT/snort3 from ~DANMCGAR/snort3:ssh-client-patterns...
authorShravan Rangarajuvenkata (shrarang) <shrarang@cisco.com>
Tue, 6 Jul 2021 18:01:09 +0000 (18:01 +0000)
committerShravan Rangarajuvenkata (shrarang) <shrarang@cisco.com>
Tue, 6 Jul 2021 18:01:09 +0000 (18:01 +0000)
Squashed commit of the following:

commit cb11ffef012d75d00e3635a62e652dd5c570d8cb
Author: Daniel McGarvey <danmcgar@cisco.com>
Date:   Mon Jun 21 11:57:48 2021 -0400

    appid: support SSH client detection through lua detector

src/network_inspectors/appid/CMakeLists.txt
src/network_inspectors/appid/appid_config.cc
src/network_inspectors/appid/appid_config.h
src/network_inspectors/appid/client_plugins/client_app_ssh.cc
src/network_inspectors/appid/detector_plugins/ssh_patterns.cc [new file with mode: 0644]
src/network_inspectors/appid/detector_plugins/ssh_patterns.h [new file with mode: 0644]
src/network_inspectors/appid/lua_detector_api.cc

index 095521b2f80ed8b384d20f9e3c39a35954799e30..d940998b480aad25a546456edb836395e5bacdaa 100644 (file)
@@ -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
 )
index 707ea6b78ac14392d696fdc2e64bce14cd5adc0f..4dde961aa5dcfd7254c7f372719632b7e041f2d2 100644 (file)
@@ -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()
index a1b6582f64a3391e94ee0e57b319e9054bdcbe6b..c5f823e267a1d5d0628bb059e22365c991b6e01c 100644 (file)
@@ -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;
 
index 235a442c8ad0f84f927b87fb061e255028837f02..7282373dda335dccef85ac6df580c7e11fe3d039 100644 (file)
@@ -25,6 +25,9 @@
 
 #include "client_app_ssh.h"
 
+#include <cstring>
+#include <string>
+
 #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 (file)
index 0000000..bcb37e5
--- /dev/null
@@ -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 <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;
+}
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 (file)
index 0000000..b10e338
--- /dev/null
@@ -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 <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
index ad2fe8cfd2af2f5a6a6f56344b2a7809a8f4c3e4..5bd5966e6376ab7f56f38d3d7df6c3c1604fdffc 100644 (file)
@@ -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<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
@@ -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 },