]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Merge pull request #2505 in SNORT/snort3 from ~PRBHALER/snort3:CSCvv22127 to master
authorPranav Bhalerao (prbhaler) <prbhaler@cisco.com>
Wed, 14 Oct 2020 03:28:37 +0000 (03:28 +0000)
committerPranav Bhalerao (prbhaler) <prbhaler@cisco.com>
Wed, 14 Oct 2020 03:28:37 +0000 (03:28 +0000)
Squashed commit of the following:

commit af592ee2c72291609f0d8cb27589fd8c9b438d20
Author: Pranav Bhalerao <prbhaler@cisco.com>
Date:   Mon Sep 28 12:47:38 2020 -0400

    ssh: ssh splitter implementation

src/service_inspectors/ssh/CMakeLists.txt
src/service_inspectors/ssh/ssh.cc
src/service_inspectors/ssh/ssh.h
src/service_inspectors/ssh/ssh_splitter.cc [new file with mode: 0644]
src/service_inspectors/ssh/ssh_splitter.h [new file with mode: 0644]

index ff633b1d0604e425b28b979628496052a5141729..b73bf25bfb806e12784e5b6096d27cd491fc8821 100644 (file)
@@ -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)
index bb9ddb019624f40d7f1dd1c1fa846dafc754cac2..89b8cdb68f714c73d77a747f6888d9e7435817ad 100644 (file)
@@ -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;
index 09347e6ed0dae3eae85e0dcef9332ada55b77074..41c60858660c019686bb4ebe5a0384f9889d74ca 100644 (file)
@@ -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
 #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 \
 #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 (file)
index 0000000..2ead5bf
--- /dev/null
@@ -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 <prbhaler@cisco.com>
+
+#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 (file)
index 0000000..ffc2813
--- /dev/null
@@ -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 <prbhaler@cisco.com>
+
+#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