bool data_decrypted : 1; // indicate data in current flow is decrypted TLS application data
bool snort_proto_id_set_by_ha : 1;
bool efd_flow : 1; // Indicate that current flow is an elephant flow
+ bool svc_event_generated : 1; // Set if FLOW_NO_SERVICE_EVENT was generated for this flow
} flags;
FlowState flow_state;
#define FLOW_SERVICE_CHANGE_EVENT "flow.service_change_event"
// A flow has found the service inspector
#define SERVICE_INSPECTOR_CHANGE_EVENT "flow.service_inspector.changed"
+// No service has been found for the flow
+#define FLOW_NO_SERVICE_EVENT "flow.no_service_event"
// search of SSL is abandoned on this flow
#define SSL_SEARCH_ABANDONED "flow.ssl_search_abandoned"
flow->clear_clouseau();
- if ( !flow->gadget || !flow->is_stream() )
+ if ( !flow->gadget )
+ {
+ if ( !flow->flags.svc_event_generated )
+ {
+ DataBus::publish(FLOW_NO_SERVICE_EVENT, p);
+ flow->flags.svc_event_generated = true;
+ }
+
+ return;
+ }
+
+ if ( !flow->is_stream() )
return;
if ( flow->session )
client_plugins/client_app_msn.h
client_plugins/client_app_rtp.cc
client_plugins/client_app_rtp.h
- client_plugins/client_app_ssh.cc
- client_plugins/client_app_ssh.h
client_plugins/client_app_timbuktu.cc
client_plugins/client_app_timbuktu.h
client_plugins/client_app_tns.cc
service_plugins/service_rtmp.h
service_plugins/service_snmp.cc
service_plugins/service_snmp.h
- service_plugins/service_ssh.cc
- service_plugins/service_ssh.h
service_plugins/service_ssl.cc
service_plugins/service_ssl.h
service_plugins/service_telnet.cc
appid_opportunistic_tls_event_handler.h
appid_peg_counts.h
appid_peg_counts.cc
+ appid_service_event_handler.cc
+ appid_service_event_handler.h
appid_session.cc
appid_session.h
appid_session_api.cc
appid_module.cc
appid_module.h
appid_pegs.h
+ appid_ssh_event_handler.cc
+ appid_ssh_event_handler.h
appid_stats.cc
appid_stats.h
app_info_table.cc
sip_matchers.finalize_patterns(*this);
ssl_matchers.finalize_patterns();
dns_matchers.finalize_patterns();
- ssh_matchers.finalize_patterns();
}
void OdpContext::reload()
else if (appidDebug->is_active())
LogMessage("AppIdDbg %s New AppId session\n", appidDebug->get_debug_session());
}
+ else if (!asd->get_session_flags(APPID_SESSION_MID) and
+ (p->flow->get_session_flags() & SSNFLAG_MIDSTREAM))
+ {
+ asd->flags |= APPID_SESSION_MID;
+ if (appidDebug->is_active())
+ LogMessage("AppIdDbg %s AppId mid-stream session\n", appidDebug->get_debug_session());
+ }
+
if (!asd->get_session_flags(APPID_SESSION_DISCOVER_APP | APPID_SESSION_SPECIAL_MONITORED))
return false;
#include "appid_http2_req_body_event_handler.h"
#include "appid_opportunistic_tls_event_handler.h"
#include "appid_peg_counts.h"
+#include "appid_service_event_handler.h"
#include "appid_session.h"
+#include "appid_ssh_event_handler.h"
#include "appid_stats.h"
#include "client_plugins/client_discovery.h"
#include "detector_plugins/detector_pattern.h"
DataBus::subscribe_network(EFP_PROCESS_EVENT, new AppIdEfpProcessEventHandler());
+ DataBus::subscribe_network(SSH_EVENT, new SshEventHandler());
+
+ DataBus::subscribe_network(FLOW_NO_SERVICE_EVENT, new AppIdServiceEventHandler(*this));
+
return true;
}
--- /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.
+//--------------------------------------------------------------------------
+
+// appid_service_event_handler.cc author Shravan Rangaraju <shrarang@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "appid_service_event_handler.h"
+
+#include "detection/detection_engine.h"
+#include "protocols/packet.h"
+#include "appid_api.h"
+#include "appid_debug.h"
+#include "appid_inspector.h"
+#include "appid_session.h"
+
+using namespace snort;
+
+void AppIdServiceEventHandler::handle(DataEvent&, Flow* flow)
+{
+ if (!pkt_thread_odp_ctxt or !flow)
+ return;
+
+ Packet* p = DetectionEngine::get_current_packet();
+ assert(p);
+
+ // FIXIT-E: For now, wait for snort service inspection only for TCP. In the future, if AppId
+ // rolls any of its UDP detectors into service inspectors, below check needs to be removed.
+ if (!p->is_tcp())
+ return;
+
+ AppIdSession* asd = appid_api.get_appid_session(*flow);
+ AppidSessionDirection dir;
+
+ if (asd and asd->initiator_port)
+ dir = (asd->initiator_port == p->ptrs.sp) ? APP_ID_FROM_INITIATOR : APP_ID_FROM_RESPONDER;
+ else
+ dir = p->is_from_client() ? APP_ID_FROM_INITIATOR : APP_ID_FROM_RESPONDER;
+
+ if (!asd)
+ {
+ // Event is received before appid has seen any packet. Example, UDP in inline mode
+ asd = AppIdSession::allocate_session(p, p->get_ip_proto_next(), dir,
+ inspector, *pkt_thread_odp_ctxt);
+ if (appidDebug->is_enabled())
+ {
+ appidDebug->activate(flow, asd, inspector.get_ctxt().config.log_all_sessions);
+ if (appidDebug->is_active())
+ LogMessage("AppIdDbg %s New AppId session at service event\n",
+ appidDebug->get_debug_session());
+ }
+ }
+ else if (asd->get_odp_ctxt_version() != pkt_thread_odp_ctxt->get_version())
+ return; // Skip detection for sessions using old odp context after odp reload
+ if (!asd->get_session_flags(APPID_SESSION_DISCOVER_APP | APPID_SESSION_SPECIAL_MONITORED))
+ return;
+
+ asd->set_no_service_inspector();
+
+ if (!asd->has_no_service_candidate())
+ {
+ if (appidDebug->is_active())
+ LogMessage("AppIdDbg %s No service inspector\n",
+ appidDebug->get_debug_session());
+ return;
+ }
+
+ if (appidDebug->is_active())
+ LogMessage("AppIdDbg %s No service candidate and no inspector\n", appidDebug->get_debug_session());
+
+ const SfIp* service_ip;
+ uint16_t port;
+ int16_t group;
+ auto proto = asd->protocol;
+
+ if (asd->is_service_ip_set())
+ std::tie(service_ip, port, group) = asd->get_server_info();
+ else
+ {
+ if (dir == APP_ID_FROM_RESPONDER)
+ {
+ service_ip = p->ptrs.ip_api.get_src();
+ port = p->ptrs.sp;
+ group = p->get_ingress_group();
+ }
+ else
+ {
+ service_ip = p->ptrs.ip_api.get_dst();
+ port = p->ptrs.dp;
+ group = p->get_egress_group();
+ }
+ asd->set_server_info(*service_ip, port, group);
+ }
+
+ const SfIp* client_ip;
+ if (dir == APP_ID_FROM_RESPONDER)
+ client_ip = p->ptrs.ip_api.get_dst();
+ else
+ client_ip = p->ptrs.ip_api.get_src();
+
+ ServiceDiscoveryState* sds = AppIdServiceState::add(service_ip, proto, port, group, asd->asid,
+ asd->is_decrypted(), true);
+ asd->get_odp_ctxt().get_service_disco_mgr().fail_service(*asd, p, dir, nullptr, sds);
+ sds->set_service_id_failed(*asd, client_ip);
+}
//--------------------------------------------------------------------------
-// Copyright (C) 2014-2021 Cisco and/or its affiliates. All rights reserved.
-// Copyright (C) 2005-2013 Sourcefire, Inc.
+// 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
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//--------------------------------------------------------------------------
-// service_ssh.h author Sourcefire Inc.
+// appid_service_event_handler.h author Shravan Rangaraju <shrarang@cisco.com>
-#ifndef SERVICE_SSH_H
-#define SERVICE_SSH_H
+#ifndef APPID_SERVICE_EVENT_HANDLER_H
+#define APPID_SERVICE_EVENT_HANDLER_H
-#include "service_detector.h"
+#include "framework/data_bus.h"
-class ServiceDiscovery;
+#include "appid_module.h"
-class SshServiceDetector : public ServiceDetector
+namespace snort
+{
+class Flow;
+}
+
+class AppIdServiceEventHandler : public snort::DataHandler
{
public:
- SshServiceDetector(ServiceDiscovery*);
+ AppIdServiceEventHandler(AppIdInspector& inspector) :
+ DataHandler(MOD_NAME), inspector(inspector)
+ { }
+
+ void handle(snort::DataEvent&, snort::Flow* flow) override;
- int validate(AppIdDiscoveryArgs&) override;
+private:
+ AppIdInspector& inspector;
};
-#endif
+#endif
consumed_ha_data = val;
}
+ bool has_no_service_candidate() const
+ {
+ return no_service_candidate;
+ }
+
+ void set_no_service_candidate()
+ {
+ no_service_candidate = true;
+ }
+
+ bool has_no_service_inspector() const
+ {
+ return no_service_inspector;
+ }
+
+ void set_no_service_inspector()
+ {
+ no_service_inspector = true;
+ }
+
private:
uint16_t prev_http2_raw_packet = 0;
uint32_t odp_ctxt_version;
ThirdPartyAppIdContext* tp_appid_ctxt = nullptr;
bool consumed_ha_data = false;
+ bool no_service_candidate = false;
+ bool no_service_inspector = false;
};
#endif
--- /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.
+//--------------------------------------------------------------------------
+// appid_ssh_event_handler.cc author Daniel McGarvey <danmcgar@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "appid_ssh_event_handler.h"
+
+#include "detector_plugins/ssh_patterns.h"
+#include "service_inspectors/ssh/ssh.h"
+#include "appid_debug.h"
+#include "appid_detector.h"
+#include "appid_inspector.h"
+
+using namespace snort;
+using namespace std;
+
+static bool handle_protocol(SshEvent& event, SshAppIdInfo* fd)
+{
+ // FIXIT-L
+ // There isn't any real specification on what separates the vendor name from version number. It
+ // seems to usually be an underscore or dash, but there are some clients where this pattern
+ // isn't followed. For example, in the Bitvise SSH client, the version string has the version
+ // number first, separated by a space from the subsequent client identification. Parsing may
+ // need to be enhanced to handle unusual version strings for new client apps.
+ const string& protocol = event.get_version_str();
+
+ const char* vendor_begin = strchr(protocol.c_str() + sizeof(SSH_BANNER) - 1, '-');
+ if (vendor_begin != nullptr)
+ vendor_begin++;
+ else
+ return false;
+
+ const char* vendor_end = strpbrk(vendor_begin, "_- \r\n");
+ if (vendor_end == nullptr)
+ return false;
+
+ size_t vendor_len = (size_t)(vendor_end - vendor_begin);
+ fd->vendor.assign(vendor_begin, vendor_len);
+
+ const char* version_begin = vendor_end + 1;
+ const char* version_end = strpbrk(version_begin, " \r\n");
+ if (version_end == nullptr)
+ return false;
+ size_t version_len = (size_t)(version_end - version_begin);
+ fd->version.assign(version_begin, version_len);
+
+ if (appidDebug->is_active())
+ LogMessage("AppIdDbg %s SSH event handler read SSH version string with vendor %s and version %s\n",
+ appidDebug->get_debug_session(), fd->vendor.c_str(), fd->version.c_str());
+
+ return true;
+}
+
+
+static void handle_failure(AppIdSession& asd, SshEventFlowData& data)
+{
+ asd.set_service_id(APP_ID_UNKNOWN, asd.get_odp_ctxt());
+ asd.set_service_detected();
+
+ asd.set_client_id(APP_ID_UNKNOWN);
+ asd.set_client_detected();
+
+ data.failed = true;
+}
+
+static void client_success(const SshAppIdInfo& fd, AppIdSession& asd, AppidChangeBits& change_bits)
+{
+ SshPatternMatchers& table = asd.get_odp_ctxt().get_ssh_matchers();
+ AppId client_id;
+
+ if (table.has_pattern(fd.vendor))
+ {
+ client_id = table.get_appid(fd.vendor);
+ if (appidDebug->is_active())
+ LogMessage("AppIdDbg %s SSH event handler identified client with AppId %u\n",
+ appidDebug->get_debug_session(), client_id);
+ }
+ else
+ {
+ client_id = APP_ID_SSH;
+ if (appidDebug->is_active())
+ LogMessage("AppIdDbg %s SSH event handler client detected, but vendor not recognized\n",
+ appidDebug->get_debug_session());
+ }
+
+ asd.set_client_id(client_id);
+ asd.set_ss_application_ids(client_id, APP_ID_NONE, change_bits);
+ asd.set_client_version(fd.version.c_str(), change_bits);
+ asd.set_client_detected();
+ asd.client_inferred_service_id = APP_ID_SSH;
+}
+
+static void service_success(SshAppIdInfo& fd, const Packet& p, AppIdSession& asd,
+ AppidChangeBits& change_bits)
+{
+ int16_t group;
+ uint16_t port;
+ const SfIp* ip;
+
+ if (p.is_from_client())
+ {
+ ip = p.ptrs.ip_api.get_dst();
+ port = p.ptrs.dp;
+ group = p.get_egress_group();
+ }
+ else
+ {
+ ip = p.ptrs.ip_api.get_src();
+ port = p.ptrs.sp;
+ group = p.get_ingress_group();
+ }
+
+ asd.set_server_info(*ip, port, group);
+ asd.set_service_id(APP_ID_SSH, asd.get_odp_ctxt());
+ asd.set_application_ids_service(APP_ID_SSH, change_bits);
+ asd.set_service_vendor(fd.vendor.c_str(), change_bits);
+ asd.set_service_version(fd.version.c_str(), change_bits);
+ asd.set_service_detected();
+}
+
+static void handle_success(SshEventFlowData& data, SshEvent& event,
+ AppIdSession& asd, AppidChangeBits& change_bits)
+{
+ service_success(data.service_info, *event.get_packet(), asd, change_bits);
+ client_success(data.client_info, asd, change_bits);
+
+ if (appidDebug->is_active())
+ LogMessage("AppIdDbg %s SSH event handler service detected\n",
+ appidDebug->get_debug_session());
+}
+
+
+static void free_ssh_flow_data(void* data)
+{
+ delete (SshEventFlowData* )data;
+}
+
+unsigned int SshEventHandler::id;
+
+void SshEventHandler::handle(DataEvent& event, Flow* flow)
+{
+ if (!flow)
+ return;
+
+ AppIdSession* asd = appid_api.get_appid_session(*flow);
+ if (!asd)
+ return;
+
+ if (asd->get_odp_ctxt_version() != pkt_thread_odp_ctxt->get_version())
+ return; // Skip detection for sessions using old odp context after odp reload
+ if (!asd->get_session_flags(APPID_SESSION_DISCOVER_APP | APPID_SESSION_SPECIAL_MONITORED))
+ return;
+
+ SshEventFlowData* data = (SshEventFlowData* )asd->get_flow_data(id);
+
+ if (data and data->failed)
+ {
+ if (appidDebug->is_active())
+ {
+ LogMessage("AppIdDbg %s SSH detection failed, ignoring event\n",
+ appidDebug->get_debug_session());
+ }
+ return;
+ }
+
+ if (!data)
+ {
+ data = new SshEventFlowData;
+ asd->add_flow_data(data, id, &free_ssh_flow_data);
+ }
+
+ SshEvent& ssh_event = (SshEvent&)event;
+ SshAppIdInfo* fd;
+ if (ssh_event.get_direction() == PKT_FROM_SERVER)
+ fd = &data->service_info;
+ else
+ fd = &data->client_info;
+
+ if (fd->finished)
+ return;
+
+ switch(ssh_event.get_event_type())
+ {
+ case SSH_VERSION_STRING:
+ if (!handle_protocol(ssh_event, fd) and appidDebug->is_active())
+ LogMessage("AppIdDbg %s SSH event handler received unsupported protocol %s\n",
+ appidDebug->get_debug_session(), ssh_event.get_version_str().c_str());
+ break;
+
+ case SSH_VALIDATION:
+ AppidChangeBits change_bits;
+ switch (ssh_event.get_validation_result())
+ {
+ case SSH_VALID_KEXINIT:
+ if (appidDebug->is_active())
+ LogMessage("AppIdDbg %s SSH event handler received valid key exchange\n",
+ appidDebug->get_debug_session());
+ fd->finished = true;
+ break;
+
+ case SSH_INVALID_KEXINIT:
+ if (appidDebug->is_active())
+ LogMessage("AppIdDbg %s SSH event handler received invalid key exchange\n",
+ appidDebug->get_debug_session());
+ handle_failure(*asd, *data);
+ break;
+
+ case SSH_INVALID_VERSION:
+ if (appidDebug->is_active())
+ LogMessage("AppIdDbg %s SSH event handler received invalid version\n",
+ appidDebug->get_debug_session());
+ handle_failure(*asd, *data);
+ break;
+
+ default:
+ break;
+ }
+
+ if (data->service_info.finished and data->client_info.finished)
+ {
+ handle_success(*data, ssh_event, *asd, change_bits);
+ asd->publish_appid_event(change_bits, *ssh_event.get_packet());
+ }
+ // Don't generate an event in case of failure. We want to give third-party a chance
+
+ break;
+ }
+}
//--------------------------------------------------------------------------
-// Copyright (C) 2014-2021 Cisco and/or its affiliates. All rights reserved.
-// Copyright (C) 2005-2013 Sourcefire, Inc.
+// 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
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//--------------------------------------------------------------------------
+// appid_ssh_event_handler.h author Daniel McGarvey <danmcgar@cisco.com>
-// client_app_ssh.h author Sourcefire Inc.
+#ifndef APPID_SSH_EVENT_HANDLER_H
+#define APPID_SSH_EVENT_HANDLER_H
-#ifndef CLIENT_APP_SSH_H
-#define CLIENT_APP_SSH_H
+#include "pub_sub/ssh_events.h"
-#include "client_plugins/client_detector.h"
+#include "appid_module.h"
-class SshClientDetector : public ClientDetector
+class SshEventHandler : public snort::DataHandler
{
public:
- SshClientDetector(ClientDiscovery*);
+ SshEventHandler() : snort::DataHandler(MOD_NAME)
+ { id = snort::FlowData::create_flow_data_id(); }
- int validate(AppIdDiscoveryArgs&) override;
+ void handle(snort::DataEvent &, snort::Flow *) override;
+
+private:
+ static unsigned int id;
+};
+
+struct SshAppIdInfo
+{
+ std::string vendor;
+ std::string version;
+ bool finished = false;
};
-#endif
+struct SshEventFlowData
+{
+ SshAppIdInfo service_info;
+ SshAppIdInfo client_info;
+ bool failed = false;
+};
+
+#endif
+++ /dev/null
-//--------------------------------------------------------------------------
-// Copyright (C) 2014-2021 Cisco and/or its affiliates. All rights reserved.
-// Copyright (C) 2005-2013 Sourcefire, Inc.
-//
-// 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.
-//--------------------------------------------------------------------------
-
-// client_app_ssh.cc author Sourcefire Inc.
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include "client_app_ssh.h"
-
-#include <cstring>
-#include <string>
-
-#include "app_info_table.h"
-#include "application_ids.h"
-
-static const char SSH_CLIENT_BANNER[] = "SSH-";
-#define SSH_CLIENT_BANNER_LEN (sizeof(SSH_CLIENT_BANNER)-1)
-#define SSH_CLIENT_BANNER_MAXPOS (sizeof(SSH_CLIENT_BANNER)-2)
-
-static const char DROPBEAR_BANNER[] = "dropbear";
-#define DROPBEAR_BANNER_MAXPOS (sizeof(DROPBEAR_BANNER)-2)
-
-static const char LSH_BANNER[] = "lsh";
-#define LSH_BANNER_MAXPOS (sizeof(LSH_BANNER)-2)
-
-static const char OPENSSH_BANNER[] = "OpenSSH";
-#define OPENSSH_BANNER_MAXPOS (sizeof(OPENSSH_BANNER)-2)
-
-static const char PUTTY_BANNER[] = "PuTTY";
-#define PUTTY_BANNER_MAXPOS (sizeof(PUTTY_BANNER)-2)
-
-#define SSH_MSG_KEYXINIT 20
-#define SSH_MSG_IGNORE 2
-#define SSH_MSG_SESSION_KEY 3
-#define SSH_MAX_BANNER_LEN 255
-#define SSH2 2
-#define SSH1 1
-
-
-enum SSH2HeaderState
-{
- SSH2_HEADER_BEGIN,
- SSH2_HEADER_PLEN,
- SSH2_HEADER_CODE,
- SSH2_IGNORE,
- SSH2_PADDING,
- SSH2_KEYX_HEADER_FINISH,
- SSH2_FIELD_LEN_BEGIN,
- SSH2_FIELD_DATA_BEGIN,
- SSH2_PAYLOAD_BEGIN
-};
-
-enum SSH1HeaderState
-{
- SSH1_HEADER_BEGIN,
- SSH1_HEADER_PLEN,
- SSH1_HEADER_FIND_CODE,
- SSH1_HEADER_CODE,
- SSH1_SESSION_KEY
-};
-
-struct ClientSSHData
-{
- SSH2HeaderState hstate;
- SSH1HeaderState oldhstate;
- unsigned len;
- unsigned pos;
- unsigned field;
- unsigned field_len;
- unsigned read_data;
- union
- {
- uint32_t len;
- uint8_t raw_len[4];
- } l;
- unsigned ssh_version;
- uint8_t version[SSH_MAX_BANNER_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)
-
-struct ClientSSHKeyString
-{
- uint32_t len;
- uint8_t data;
-};
-
-struct ClientSSHMsg
-{
- uint32_t len;
- uint8_t plen;
- uint8_t code;
-};
-
-struct ClientSSH2KeyExchange
-{
- ClientSSHMsg msg;
- uint8_t cookie[16];
-};
-
-struct ClientSSH1KeyExchangeV1
-{
- uint32_t len;
- uint8_t code;
-};
-
-struct ClientSSHKeyExchangeFinal
-{
- uint8_t kex_pkt;
- uint32_t future;
-};
-
-#pragma pack()
-
-SshClientDetector::SshClientDetector(ClientDiscovery* cdm)
-{
- handler = cdm;
- name = "SSH";
- proto = IpProtocol::TCP;
- minimum_matches = 1;
- provides_user = true;
-
- tcp_patterns =
- {
- { (const uint8_t*)SSH_CLIENT_BANNER, sizeof(SSH_CLIENT_BANNER) - 1, 0, 0, APP_ID_SSH },
- { (const uint8_t*)OPENSSH_BANNER, sizeof(OPENSSH_BANNER) - 1, -1, 0,
- APP_ID_OPENSSH },
- { (const uint8_t*)PUTTY_BANNER, sizeof(PUTTY_BANNER) - 1, -1, 0, APP_ID_PUTTY },
- { (const uint8_t*)LSH_BANNER, sizeof(LSH_BANNER) - 1, 0, 0, APP_ID_LSH },
- { (const uint8_t*)DROPBEAR_BANNER, sizeof(DROPBEAR_BANNER) - 1, -1, 0,
- APP_ID_DROPBEAR },
- };
-
- appid_registry =
- {
- { APP_ID_DROPBEAR, APPINFO_FLAG_CLIENT_ADDITIONAL },
- { APP_ID_SSH, APPINFO_FLAG_CLIENT_ADDITIONAL },
- { APP_ID_LSH, APPINFO_FLAG_CLIENT_ADDITIONAL },
- { APP_ID_PUTTY, APPINFO_FLAG_CLIENT_ADDITIONAL },
- { APP_ID_OPENSSH, APPINFO_FLAG_CLIENT_ADDITIONAL }
- };
-
- handler->register_detector(name, this, proto);
-}
-
-
-static inline int ssh_client_validate_keyx(uint16_t offset, const uint8_t* data,
- uint16_t size, ClientSSHData* fd)
-{
- const ClientSSHMsg* ckx;
- const ClientSSHKeyString* cks;
- const ClientSSH2KeyExchange* ckex;
-
- while (offset < size)
- {
- switch (fd->hstate)
- {
- case SSH2_HEADER_BEGIN:
- fd->l.raw_len[fd->pos] = data[offset];
- fd->pos++;
- if (fd->pos == sizeof(ckx->len))
- {
- fd->len = ntohl(fd->l.len);
- fd->hstate = SSH2_HEADER_PLEN;
- }
- break;
- case SSH2_HEADER_PLEN:
- fd->plen = data[offset];
- fd->hstate = SSH2_HEADER_CODE;
- fd->pos++;
- break;
- case SSH2_HEADER_CODE:
- fd->code = data[offset];
- if (fd->code == SSH_MSG_KEYXINIT)
- {
- fd->pos = 0;
- fd->hstate = SSH2_KEYX_HEADER_FINISH;
- fd->read_data = fd->plen + sizeof(ckex->cookie) + sizeof(ckx->len);
- }
- else if (fd->code == SSH_MSG_IGNORE)
- {
- fd->pos = sizeof(ckx->len) + 2;
- fd->hstate = SSH2_IGNORE;
- }
- else
- return APPID_EINVALID;
- fd->len = ntohl(fd->l.len) + sizeof(ckx->len);
- if (fd->len > 35000)
- return APPID_EINVALID;
- break;
- case SSH2_IGNORE:
- fd->pos++;
- if (fd->pos >= fd->len)
- {
- fd->hstate = SSH2_HEADER_BEGIN;
- fd->pos = 0;
- }
- break;
- case SSH2_KEYX_HEADER_FINISH:
- fd->pos++;
- if (fd->pos >= sizeof(ckex->cookie))
- {
- fd->hstate = SSH2_FIELD_LEN_BEGIN;
- fd->pos = 0;
- }
- break;
- case SSH2_FIELD_LEN_BEGIN:
- fd->l.raw_len[fd->pos] = data[offset];
- fd->pos++;
- if (fd->pos >= sizeof(cks->len))
- {
- fd->pos = 0;
- fd->field_len = ntohl(fd->l.len);
- fd->read_data += fd->field_len + sizeof(cks->len);
- if (fd->read_data > fd->len)
- return APPID_EINVALID;
- if (fd->field_len)
- fd->hstate = SSH2_FIELD_DATA_BEGIN;
- else
- {
- fd->field++;
- if (fd->field >= 10)
- fd->hstate = SSH2_PAYLOAD_BEGIN;
- }
- }
- break;
- case SSH2_FIELD_DATA_BEGIN:
- fd->pos++;
- if (fd->pos >= fd->field_len)
- {
- fd->field++;
- if (fd->field >= 10)
- fd->hstate = SSH2_PAYLOAD_BEGIN;
- else
- fd->hstate = SSH2_FIELD_LEN_BEGIN;
- fd->pos = 0;
- }
- break;
- case SSH2_PAYLOAD_BEGIN:
- if (fd->pos >= offsetof(ClientSSHKeyExchangeFinal, future))
- {
- fd->l.raw_len[fd->pos - offsetof(ClientSSHKeyExchangeFinal, future)] =
- data[offset];
- }
- fd->pos++;
- if (fd->pos >= sizeof(ClientSSHKeyExchangeFinal))
- {
- if (fd->l.len != 0)
- return APPID_EINVALID;
- fd->hstate = SSH2_PADDING;
- fd->pos = 0;
- }
- break;
- case SSH2_PADDING:
- fd->pos++;
- if (fd->pos >= fd->plen)
- {
- offset++;
-
- // FIXIT-L if offset > size then there is probably a D-H
- // Key Exchange Init packet in this payload. For now parsing
- // the Key Exchange Init is good enough to declare valid
- // key exchange but for future enhance parsing to validate
- // the D-H Key Exchange Init.
-
- if (offset == size)
- return APPID_SUCCESS;
- else
- return APPID_SUCCESS;
- }
- break;
-
- default:
- assert(0); // All cases should be handled above.
- }
- offset++;
- }
- return APPID_INPROCESS;
-}
-
-static inline int ssh_client_validate_pubkey(uint16_t offset, const uint8_t* data,
- uint16_t size, ClientSSHData* fd)
-{
- const ClientSSHMsg* ckx;
-
- while (offset < size)
- {
- switch (fd->oldhstate)
- {
- case SSH1_HEADER_BEGIN:
- fd->l.raw_len[fd->pos] = data[offset];
- fd->pos++;
- if (fd->pos == sizeof(ckx->len))
- {
- fd->len = ntohl(fd->l.len);
- fd->oldhstate = SSH1_HEADER_PLEN;
- }
- break;
- case SSH1_HEADER_PLEN:
- if (size > (fd->len + sizeof(ckx->len)))
- fd->plen = size - (fd->len + sizeof(ckx->len));
- else
- fd->plen = 0;
- fd->oldhstate = SSH1_HEADER_FIND_CODE;
- // fallthrough
- case SSH1_HEADER_FIND_CODE:
- if (fd->pos == fd->plen + sizeof(ckx->len))
- {
- fd->oldhstate = SSH1_HEADER_CODE;
- fd->code = data[offset];
- }
- fd->pos++;
- break;
- case SSH1_HEADER_CODE:
- if (fd->code == SSH_MSG_SESSION_KEY)
- {
- fd->oldhstate = SSH1_SESSION_KEY;
- fd->pos++;
- }
- else
- return APPID_EINVALID;
- fd->len = fd->len + fd->plen + sizeof(ckx->len);
- if (fd->len > 35000)
- return APPID_EINVALID;
- break;
- case SSH1_SESSION_KEY:
- fd->pos++;
- if (fd->pos >= fd->len)
- {
- offset++;
- if (offset == size)
- return APPID_SUCCESS;
- return APPID_EINVALID;
- }
- break;
- }
- offset++;
- }
- return APPID_INPROCESS;
-}
-
-static inline int ssh_client_sm(AppIdDiscoveryArgs& args, ClientSSHData* fd)
-{
- const char *pattern_begin;
- const char *pattern_end;
- uint8_t d;
-
- if (strncmp((const char*)fd->proto_string, SSH_CLIENT_BANNER, SSH_CLIENT_BANNER_LEN) != 0)
- {
- return APPID_EINVALID;
- }
-
- 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')
- {
- break;
- }
- if (d != '\r' && d != '-' && d != '_')
- {
- fd->version[fd->pos++] = d;
- }
- pattern_end++;
- }
- }
- else
- {
- fd->client_id = APP_ID_SSH;
- }
- fd->pos = 0;
- return APPID_INPROCESS;
-}
-
-int SshClientDetector::validate(AppIdDiscoveryArgs& args)
-{
- ClientSSHData* fd;
- int sm_ret = APPID_INPROCESS;
-
- if (!args.size || args.dir != APP_ID_FROM_INITIATOR)
- return APPID_INPROCESS;
-
- fd = ( ClientSSHData*)data_get(args.asd);
- if (!fd)
- {
- fd = (ClientSSHData*)snort_calloc(sizeof(ClientSSHData));
- data_add(args.asd, fd, &snort_free);
- 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;
- }
-
- 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;
-}
-
#include "client_app_bit.h"
#include "client_app_msn.h"
#include "client_app_rtp.h"
-#include "client_app_ssh.h"
#include "client_app_timbuktu.h"
#include "client_app_tns.h"
#include "client_app_vnc.h"
new SipTcpClientDetector(this);
new SipUdpClientDetector(this);
new SmtpClientDetector(this);
- new SshClientDetector(this);
new TimbuktuClientDetector(this);
new TnsClientDetector(this);
new VncClientDetector(this);
{
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;
-}
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//--------------------------------------------------------------------------
-// ssh_patterns.cc author Daniel McGarvey <danmcgar@cisco.com>
+// ssh_patterns.h 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.
+ * are used by AppId to identify clients.
* An instance of the class is held by OdpContext.
*/
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;
};
#include "service_rsync.h"
#include "service_rtmp.h"
#include "service_snmp.h"
-#include "service_ssh.h"
#include "service_ssl.h"
#include "service_telnet.h"
#include "service_tftp.h"
new SipServiceDetector(this);
new SmtpServiceDetector(this);
new SnmpServiceDetector(this);
- new SshServiceDetector(this);
new SslServiceDetector(this);
new TelnetServiceDetector(this);
new TftpServiceDetector(this);
asd.service_detector = sds->get_service();
/* If we've gotten to brute force, give next detector a try. */
else if ( sds_state == ServiceState::SEARCHING_BRUTE_FORCE and
- asd.service_candidates.empty() )
+ asd.service_candidates.empty() )
{
asd.service_detector = sds->select_detector_by_brute_force(proto,
asd.get_odp_ctxt().get_service_disco_mgr());
/* If we tried everything and found nothing, then fail. */
if ( asd.service_candidates.empty() and ret != APPID_SUCCESS and
( asd.service_search_state == SESSION_SERVICE_SEARCH_STATE::PENDING ) )
- got_fail_service = true;
+ {
+ // FIXIT-E: For now, wait for snort service inspection only for TCP. In the future,
+ // if AppId rolls any of its UDP detectors into service inspectors, the UDP condition
+ // below needs to be removed.
+ if (asd.has_no_service_inspector() or (proto == IpProtocol::UDP))
+ got_fail_service = true;
+ else if (appidDebug->is_active() and !asd.service_detector and !asd.has_no_service_candidate())
+ LogMessage("AppIdDbg %s No service candidate, wait for snort service inspection\n",
+ appidDebug->get_debug_session());
+
+ asd.set_no_service_candidate();
+ }
}
/* Failed all candidates, or no detector identified after seeing bidirectional exchange */
// Don't log this if fail service is not due to empty list
if (appidDebug->is_active() and !(got_fail_service and asd.service_detector))
LogMessage("AppIdDbg %s No service %s\n", appidDebug->get_debug_session(),
- got_fail_service? "candidate" : "detector");
+ got_fail_service ? "candidate" : "detector");
got_fail_service = true;
fail_service(asd, p, dir, nullptr, sds);
ret = APPID_NOMATCH;
bool ServiceDiscovery::do_service_discovery(AppIdSession& asd, Packet* p,
AppidSessionDirection direction, AppidChangeBits& change_bits)
{
- bool isTpAppidDiscoveryDone = false;
+ bool is_discovery_done = false;
uint32_t prev_service_state = asd.service_disco_state;
AppId tp_app_id = asd.get_tp_app_id();
if (asd.service_disco_state == APPID_DISCO_STATE_STATEFUL)
{
identify_service(asd, p, direction, change_bits);
- isTpAppidDiscoveryDone = true;
+ is_discovery_done = true;
//to stop executing validator after service has been detected
if (asd.get_session_flags(APPID_SESSION_SERVICE_DETECTED |
APPID_SESSION_CONTINUE) == APPID_SESSION_SERVICE_DETECTED)
{
asd.stop_service_inspection(p, direction);
asd.set_service_id(APP_ID_UNKNOWN, asd.get_odp_ctxt());
- return isTpAppidDiscoveryDone;
+ return is_discovery_done;
}
AppIdDnsSession* dsession = asd.get_dns_session();
}
}
- return isTpAppidDiscoveryDone;
+ return is_discovery_done;
}
/**Called when service can not be identified on a flow but the checks failed on client request
+++ /dev/null
-//--------------------------------------------------------------------------
-// Copyright (C) 2014-2021 Cisco and/or its affiliates. All rights reserved.
-// Copyright (C) 2005-2013 Sourcefire, Inc.
-//
-// 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.
-//--------------------------------------------------------------------------
-
-// service_ssh.cc author Sourcefire Inc.
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include "service_ssh.h"
-
-#include "app_info_table.h"
-#include "application_ids.h"
-
-#define SSH_PORT 22
-
-#define SSH_BANNER "SSH-"
-#define SERVICE_SSH_MSG_KEYXINIT 20
-#define SERVICE_SSH_MSG_IGNORE 2
-#define SERVICE_SSH_MSG_PUBLIC_KEY 2
-#define SERVICE_SSH_KEY_STRINGS 10
-#define SSH_MAX_FIELDS 10
-#define SSH_MAX_BANNER_LENGTH 255
-
-#define SSH_VERSION_2 2
-#define SSH_VERSION_1 1
-#define MINIMUM_SSH_VERS_LEN 4
-
-enum SSHState
-{
- SSH_STATE_BANNER,
- SSH_STATE_KEY,
- SSH_STATE_DONE
-};
-
-enum SSHHeaderState
-{
- SSH_HEADER_BEGIN,
- SSH_HEADER_PLEN,
- SSH_HEADER_CODE,
- SSH_IGNORE,
- SSH_PADDING,
- SSH_KEYX_HEADER_FINISH,
- SSH_FIELD_LEN_BEGIN,
- SSH_FIELD_DATA_BEGIN,
- SSH_PAYLOAD_BEGIN
-};
-
-enum OldSSHHeaderState
-{
- OLD_SSH_HEADER_BEGIN,
- OLD_SSH_HEADER_PLEN,
- OLD_SSH_HEADER_FIND_CODE,
- OLD_SSH_HEADER_CODE,
- OLD_SSH_PUBLIC_KEY
-};
-
-struct ServiceSSHData
-{
- SSHState state;
- SSHHeaderState hstate;
- OldSSHHeaderState oldhstate;
- unsigned len;
- unsigned pos;
- unsigned field;
- unsigned field_len;
- unsigned read_data;
- union
- {
- uint32_t len;
- uint8_t raw_len[4];
- } l;
- char* vendor;
- char* version;
- unsigned ssh_version;
- uint8_t plen;
- uint8_t code;
-};
-
-#pragma pack(1)
-
-struct ServiceSSHKeyString
-{
- uint32_t len;
- uint8_t data;
-};
-
-struct ServiceSSHMsg
-{
- uint32_t len;
- uint8_t plen;
- uint8_t code;
-};
-
-struct ServiceSSHKeyExchange
-{
- ServiceSSHMsg msg;
- uint8_t cookie[16];
-};
-
-struct ServiceSSHKeyExchangeV1
-{
- uint32_t len;
- uint8_t code;
-};
-
-struct ServiceSSHKeyExchangeFinal
-{
- uint8_t kex_pkt;
- uint32_t future;
-};
-
-#pragma pack()
-
-SshServiceDetector::SshServiceDetector(ServiceDiscovery* sd)
-{
- handler = sd;
- name = "ssh";
- proto = IpProtocol::TCP;
- detectorType = DETECTOR_TYPE_DECODER;
-
- tcp_patterns =
- {
- { (const uint8_t*)SSH_BANNER, sizeof(SSH_BANNER) - 1, 0, 0, 0 },
- };
-
- appid_registry =
- {
- { APP_ID_SSH, APPINFO_FLAG_SERVICE_ADDITIONAL }
- };
-
- service_ports =
- {
- { SSH_PORT, IpProtocol::TCP, false }
- };
-
- handler->register_detector(name, this, proto);
-}
-
-
-static int ssh_validate_pubkey(const uint8_t* data, uint16_t size, ServiceSSHData* ss)
-{
- uint16_t offset = 0;
- const ServiceSSHMsg* skx;
-
- while (offset < size)
- {
- switch (ss->oldhstate)
- {
- case OLD_SSH_HEADER_BEGIN:
- ss->l.raw_len[ss->pos] = data[offset];
- ss->pos++;
- if (ss->pos == sizeof(skx->len))
- {
- ss->len = ntohl(ss->l.len);
- ss->oldhstate = OLD_SSH_HEADER_PLEN;
- }
- break;
- case OLD_SSH_HEADER_PLEN:
- if (size > (ss->len + sizeof(skx->len)))
- ss->plen = size - (ss->len + sizeof(skx->len));
- else
- ss->plen = 0;
- ss->oldhstate = OLD_SSH_HEADER_FIND_CODE;
- // fallthrough
- case OLD_SSH_HEADER_FIND_CODE:
- if (ss->pos == ss->plen + sizeof(skx->len))
- {
- ss->oldhstate = OLD_SSH_HEADER_CODE;
- ss->code = data[offset];
- }
- ss->pos++;
- break;
- case OLD_SSH_HEADER_CODE:
- if (ss->code == SERVICE_SSH_MSG_PUBLIC_KEY)
- {
- ss->oldhstate = OLD_SSH_PUBLIC_KEY;
- ss->pos++;
- }
- else
- return APPID_NOMATCH;
- ss->len = ss->len + ss->plen + sizeof(skx->len);
- if (ss->len > 35000)
- return APPID_NOMATCH;
- break;
- case OLD_SSH_PUBLIC_KEY:
- ss->pos++;
- if (ss->pos >= ss->len)
- {
- offset++;
- if (offset == size)
- return APPID_SUCCESS;
- return APPID_NOMATCH;
- }
- break;
- }
- offset++;
- }
- return APPID_INPROCESS;
-}
-
-static int ssh_validate_keyx(const uint8_t* data, uint16_t size, ServiceSSHData* ss)
-{
- uint16_t offset = 0;
- const ServiceSSHMsg* skx;
- const ServiceSSHKeyString* sks;
- const ServiceSSHKeyExchange* skex;
-
- while (offset < size)
- {
- switch (ss->hstate)
- {
- case SSH_HEADER_BEGIN:
- ss->l.raw_len[ss->pos] = data[offset];
- ss->pos++;
- if (ss->pos == sizeof(skx->len))
- {
- ss->len = ntohl(ss->l.len);
- ss->hstate = SSH_HEADER_PLEN;
- }
- break;
- case SSH_HEADER_PLEN:
- ss->plen = data[offset];
- ss->hstate = SSH_HEADER_CODE;
- ss->pos++;
- break;
- case SSH_HEADER_CODE:
- ss->code = data[offset];
- if (ss->code == SERVICE_SSH_MSG_KEYXINIT)
- {
- ss->pos = 0;
- ss->hstate = SSH_KEYX_HEADER_FINISH;
- ss->read_data = ss->plen + sizeof(skex->cookie) + sizeof(skx->len);
- }
- else if (ss->code == SERVICE_SSH_MSG_IGNORE)
- {
- ss->pos = sizeof(skx->len) + 2;
- ss->hstate = SSH_IGNORE;
- }
- else
- return APPID_NOMATCH;
- ss->len = ntohl(ss->l.len) + sizeof(skx->len);
- if (ss->len > 35000)
- return APPID_NOMATCH;
- break;
- case SSH_IGNORE:
- ss->pos++;
- if (ss->pos >= ss->len)
- {
- ss->hstate = SSH_HEADER_BEGIN;
- ss->pos = 0;
- }
- break;
- case SSH_KEYX_HEADER_FINISH:
- ss->pos++;
- if (ss->pos >= sizeof(skex->cookie))
- {
- ss->hstate = SSH_FIELD_LEN_BEGIN;
- ss->pos = 0;
- }
- break;
- case SSH_FIELD_LEN_BEGIN:
- ss->l.raw_len[ss->pos] = data[offset];
- ss->pos++;
- if (ss->pos >= sizeof(sks->len))
- {
- ss->pos = 0;
- ss->field_len = ntohl(ss->l.len);
- ss->read_data += ss->field_len + sizeof(sks->len);
- if (ss->read_data > ss->len)
- return APPID_NOMATCH;
- if (ss->field_len)
- ss->hstate = SSH_FIELD_DATA_BEGIN;
- else
- {
- ss->field++;
- if (ss->field >= 10)
- ss->hstate = SSH_PAYLOAD_BEGIN;
- }
- }
- break;
- case SSH_FIELD_DATA_BEGIN:
- ss->pos++;
- if (ss->pos >= ss->field_len)
- {
- ss->field++;
- if (ss->field >= 10)
- ss->hstate = SSH_PAYLOAD_BEGIN;
- else
- ss->hstate = SSH_FIELD_LEN_BEGIN;
- ss->pos = 0;
- }
- break;
- case SSH_PAYLOAD_BEGIN:
- if (ss->pos >= offsetof(ServiceSSHKeyExchangeFinal, future))
- {
- ss->l.raw_len[ss->pos - offsetof(ServiceSSHKeyExchangeFinal, future)] =
- data[offset];
- }
- ss->pos++;
- if (ss->pos >= sizeof(ServiceSSHKeyExchangeFinal))
- {
- if (ss->l.len != 0)
- return APPID_NOMATCH;
- ss->hstate = SSH_PADDING;
- ss->pos = 0;
- }
- break;
- case SSH_PADDING:
- ss->pos++;
- if (ss->pos >= ss->plen)
- {
- offset++;
- if (offset == size)
- return APPID_SUCCESS;
- return APPID_NOMATCH;
- }
- break;
- }
- offset++;
- }
- return APPID_INPROCESS;
-}
-
-static void ssh_free_state(void* data)
-{
- ServiceSSHData* sd = (ServiceSSHData*)data;
-
- if (sd)
- {
- if (sd->vendor)
- {
- snort_free(sd->vendor);
- sd->vendor = nullptr;
- }
- if (sd->version)
- {
- snort_free(sd->version);
- sd->version = nullptr;
- }
- snort_free(sd);
- }
-}
-
-int SshServiceDetector::validate(AppIdDiscoveryArgs& args)
-{
- ServiceSSHData* ss;
- uint16_t offset;
- int retval;
- const char* ven;
- const char* ver;
- const char* end;
- unsigned len;
- int client_major;
- const uint8_t* data = args.data;
- uint16_t size = args.size;
-
- if (!size)
- goto inprocess;
-
- ss = (ServiceSSHData*)data_get(args.asd);
- if (!ss)
- {
- ss = (ServiceSSHData*)snort_calloc(sizeof(ServiceSSHData));
- data_add(args.asd, ss, &ssh_free_state);
- ss->state = SSH_STATE_BANNER;
- ss->hstate = SSH_HEADER_BEGIN;
- ss->oldhstate = OLD_SSH_HEADER_BEGIN;
- }
-
- if (args.dir != APP_ID_FROM_RESPONDER)
- {
- if (!ss->ssh_version)
- {
- if ((size_t)size > (sizeof(SSH_BANNER)-1+MINIMUM_SSH_VERS_LEN) &&
- !strncmp(SSH_BANNER, (const char*)data, sizeof(SSH_BANNER)-1))
- {
- data += (sizeof(SSH_BANNER)-1);
- if (!isdigit(*data))
- goto not_compatible;
- else
- client_major = *data;
- data++;
- if (*data != '.')
- goto not_compatible;
- switch (client_major)
- {
- case 0x31:
- if (*(data+1) == 0x39 && *(data+2) == 0x39)
- ss->ssh_version = SSH_VERSION_2;
- else
- ss->ssh_version = SSH_VERSION_1;
- break;
- case 0x32:
- ss->ssh_version = SSH_VERSION_2;
- break;
- default:
- goto not_compatible;
- }
- }
- }
- goto inprocess;
- }
-
- switch (ss->state)
- {
- case SSH_STATE_BANNER:
- offset = 0;
- ss->state = SSH_STATE_KEY;
- for (;; )
- {
- /* SSH-v-\n where v is at least 1 character */
- if ((size_t)(size-offset) < ((sizeof(SSH_BANNER)-1)+3))
- {
- goto fail;
- }
- if (!strncmp(SSH_BANNER, (const char*)data+offset, sizeof(SSH_BANNER)-1))
- {
- unsigned blen = sizeof(SSH_BANNER)-1;
- offset += sizeof(SSH_BANNER)-1;
- for (;
- offset<size && blen<=SSH_MAX_BANNER_LENGTH;
- offset++, blen++)
- {
- if (data[offset] == '-')
- break;
- if (!isprint(data[offset]) || isspace(data[offset]))
- {
- goto fail;
- }
- }
- offset++;
- blen++;
- if (offset >= size || blen > SSH_MAX_BANNER_LENGTH)
- {
- goto fail;
- }
- ven = (const char*)&data[offset];
- for (;
- offset<size && blen<=SSH_MAX_BANNER_LENGTH;
- offset++, blen++)
- {
- if (data[offset] == 0x0D || data[offset] == 0x0A)
- {
- if (data[offset] == 0x0D)
- {
- if (offset+1 >= size)
- goto fail;
- if (data[offset+1] != 0x0A)
- goto fail;
- }
- end = (const char*)&data[offset];
- if (ven == end)
- goto inprocess;
- for (ver=ven; ver < end && *ver && *ver != '_' && *ver != '-'; ver++)
- ;
- if (ver < (end - 1) && isdigit(*(ver+1)))
- {
- len = ver - ven;
- ss->vendor = (char*)snort_alloc(len+1);
- memcpy(ss->vendor, ven, len);
- ss->vendor[len] = 0;
- ver++;
- len = end - ver;
- ss->version = (char*)snort_alloc(len+1);
- memcpy(ss->version, ver, len);
- ss->version[len] = 0;
- }
- else
- {
- len = end - ven;
- ss->version = (char*)snort_calloc(len+1);
- memcpy(ss->version, ven, len);
- ss->version[len] = 0;
- }
- goto inprocess;
- }
- else if (!isprint(data[offset]))
- goto fail;
- }
- goto fail;
- }
- else
- {
- for (; offset<size; offset++)
- {
- if (data[offset] == 0x0a)
- {
- offset++;
- break;
- }
- }
- }
- }
- break;
- case SSH_STATE_KEY:
- switch (ss->ssh_version)
- {
- case SSH_VERSION_2:
- retval = ssh_validate_keyx(data, size, ss);
- break;
- case SSH_VERSION_1:
- retval = ssh_validate_pubkey(data, size, ss);
- break;
- default:
- goto fail;
- }
- goto done;
- default:
- break;
- }
- goto fail;
-
-done:
- switch (retval)
- {
- case APPID_INPROCESS:
-inprocess:
- service_inprocess(args.asd, args.pkt, args.dir);
- return APPID_INPROCESS;
-
- case APPID_SUCCESS:
- return add_service(args.change_bits, args.asd, args.pkt, args.dir, APP_ID_SSH,
- ss->vendor, ss->version, nullptr);
-
- case APPID_NOMATCH:
-fail:
- fail_service(args.asd, args.pkt, args.dir);
- return APPID_NOMATCH;
-
-not_compatible:
- incompatible_data(args.asd, args.pkt, args.dir);
- return APPID_NOT_COMPATIBLE;
-
- default:
- return retval;
- }
-}
-
}
else if (wizard)
flow.set_clouseau(wizard);
+ else if (!flow.flags.svc_event_generated)
+ {
+ DataBus::publish(FLOW_NO_SERVICE_EVENT, DetectionEngine::get_current_packet());
+ flow.flags.svc_event_generated = true;
+ }
}
void Stuff::apply_assistant(Flow& flow, const char* service)
opportunistic_tls_event.h
sip_events.h
smb_events.h
+ ssh_events.h
)
add_library( pub_sub OBJECT
--- /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_events.h author Daniel McGarvey <danmcgar@cisco.com>
+
+#ifndef SSH_EVENTS_H
+#define SSH_EVENTS_H
+
+// This event allows the SSH service inspector to publish extracted metadata
+// for use by data bus subscribers
+
+#include "service_inspectors/ssh/ssh.h"
+
+#define SSH_EVENT "ssh_event"
+
+enum SshEventType {
+ SSH_VERSION_STRING,
+ SSH_VALIDATION
+};
+
+enum SshValidationResult {
+ SSH_NOT_FINISHED,
+ SSH_VALID_KEXINIT,
+ SSH_INVALID_VERSION,
+ SSH_INVALID_KEXINIT
+};
+
+class SshEvent : public snort::DataEvent
+{
+public:
+ SshEvent(const SshEventType event_type, const SshValidationResult result,
+ const std::string& version_str, const uint8_t direction, const snort::Packet* packet) :
+ event_type(event_type), result(result), version_str(version_str), direction(direction), packet(packet)
+ { }
+
+ SshEventType get_event_type() const
+ { return event_type; }
+
+ SshValidationResult get_validation_result() const
+ { return result; }
+
+ const std::string& get_version_str() const
+ { return version_str; }
+
+ uint8_t get_direction() const
+ { return direction; }
+
+ const snort::Packet* get_packet() override
+ { return packet; }
+
+private:
+ const SshEventType event_type;
+ const SshValidationResult result;
+ const std::string version_str;
+ const uint8_t direction;
+ const snort::Packet* packet;
+};
+
+#endif
#include "log/messages.h"
#include "profiler/profiler.h"
#include "protocols/packet.h"
+#include "pub_sub/ssh_events.h"
#include "stream/stream.h"
#include "ssh_module.h"
* Function prototype(s)
*/
static void snort_ssh(SSH_PROTO_CONF* GlobalConf, Packet* p);
-static unsigned int ProcessSSHProtocolVersionExchange(SSH_PROTO_CONF*, SSHData*, Packet*, uint8_t);
-static unsigned int ProcessSSHKeyExchange(SSHData*, Packet*, uint8_t, unsigned int);
-static unsigned int ProcessSSHKeyInitExchange(SSHData*, Packet*, uint8_t, unsigned int);
+static bool process_ssh_version_string(SSH_PROTO_CONF* config, SSHData* sessionp, Packet* p, uint8_t direction);
+static bool process_ssh1_key_exchange(SSHData *sessionp, Packet *p, uint8_t direction);
+static bool process_ssh2_kexinit(SSHData *sessionp, Packet *p, uint8_t direction);
+static bool process_ssh2_key_exchange(SSHData *sessionp, Packet *p, uint8_t direction);
unsigned SshFlowData::inspector_id = 0;
{
session = {};
sshstats.concurrent_sessions++;
- if(sshstats.max_concurrent_sessions < sshstats.concurrent_sessions)
+ if (sshstats.max_concurrent_sessions < sshstats.concurrent_sessions)
sshstats.max_concurrent_sessions = sshstats.concurrent_sessions;
}
return fd ? &fd->session : nullptr;
}
-/* Returns the true length of the ssh packet, including
- * the ssh packet header and all padding.
- *
- * If the packet length is invalid, 0 is returned.
- * The return value is never larger than buflen.
- *
- * PARAMETERS:
- * p: Pointer to the SSH packet.
- * buflen: the size of packet buffer.
-*/
-static unsigned int SSHPacket_GetLength(const SSH2Packet* p, size_t buflen)
-{
- unsigned int ssh_length;
-
- if (buflen < sizeof(SSH2Packet))
- return 0;
-
- ssh_length = ntohl(p->packet_length);
- if ((ssh_length < sizeof(SSH2Packet) + 1) || ssh_length > SSH2_PACKET_MAX_SIZE)
- return 0;
-
- /* Everything after packet length field (including padding) is included in the packet_length */
- ssh_length += sizeof(p->packet_length);
-
- if (buflen < ssh_length)
- return buflen; /* truncated */
-
- return ssh_length;
-}
-
// Main runtime entry point for SSH inspector.
-
static void snort_ssh(SSH_PROTO_CONF* config, Packet* p)
{
Profile profile(sshPerfStats);
// means we've already missed packets) set missed packets flag and make
// sure we don't do any more reassembly on this session
if ( p->test_session_flags(SSNFLAG_MIDSTREAM)
- || Stream::missed_packets(p->flow, SSN_DIR_BOTH) )
+ or Stream::missed_packets(p->flow, SSN_DIR_BOTH) )
{
// Order only matters if the packets are not encrypted
if ( !(sessp->state_flags & SSH_FLG_SESS_ENCRYPTED ))
sshstats.total_bytes += p->dsize;
uint8_t direction;
+ uint8_t pkt_direction;
uint32_t search_dir_ver;
uint32_t search_dir_keyinit;
if ( p->is_from_server() )
{
direction = SSH_DIR_FROM_SERVER;
+ pkt_direction = PKT_FROM_SERVER;
search_dir_ver = SSH_FLG_SERV_IDSTRING_SEEN;
search_dir_keyinit = SSH_FLG_SERV_PKEY_SEEN | SSH_FLG_SERV_KEXINIT_SEEN;
}
else
{
direction = SSH_DIR_FROM_CLIENT;
+ pkt_direction = PKT_FROM_CLIENT;
search_dir_ver = SSH_FLG_CLIENT_IDSTRING_SEEN;
search_dir_keyinit = SSH_FLG_CLIENT_SKEY_SEEN | SSH_FLG_CLIENT_KEXINIT_SEEN;
}
- unsigned int offset = 0;
-
- if ( !(sessp->state_flags & SSH_FLG_SESS_ENCRYPTED ))
+ if (!(sessp->state_flags & SSH_FLG_SESS_ENCRYPTED))
{
// If server and client have not performed the protocol
// version exchange yet, must look for version strings.
- if ( !(sessp->state_flags & search_dir_ver) )
+ if (!(sessp->state_flags & search_dir_ver))
{
- offset = ProcessSSHProtocolVersionExchange(config, sessp, p, direction);
- if (!offset)
- // Error processing protovers exchange msg
- return;
-
- // found protocol version.
- // Stream reassembly might have appended an ssh packet,
- // such as the key exchange init.
- // Thus call ProcessSSHKeyInitExchange() too.
+ bool valid_version = process_ssh_version_string(config, sessp, p, direction);
+ if (valid_version)
+ {
+ std::string proto_string((const char *)(p->data), p->dsize);
+ SshEvent event(SSH_VERSION_STRING, SSH_NOT_FINISHED, proto_string, pkt_direction, p);
+ DataBus::publish(SSH_EVENT, event, p->flow);
+ }
+ else
+ {
+ SshEvent event(SSH_VALIDATION, SSH_INVALID_VERSION, "", pkt_direction, p);
+ DataBus::publish(SSH_EVENT, event, p->flow);
+ }
}
-
- // Expecting to see the key init exchange at this point
- // (in SSH2) or the actual key exchange if SSH1
- if ( !(sessp->state_flags & search_dir_keyinit) )
+ else if (!(sessp->state_flags & search_dir_keyinit))
{
- offset = ProcessSSHKeyInitExchange(sessp, p, direction, offset);
-
- if (!offset)
+ bool keyx_valid = false;
+ switch (sessp->version)
+ {
+ case SSH_VERSION_1:
+ keyx_valid = process_ssh1_key_exchange(sessp, p, direction);
+ break;
+ case SSH_VERSION_2:
+ keyx_valid = process_ssh2_kexinit(sessp, p, direction);
+ break;
+ default:
+ // key exchange packet sent before version was determined
+ DetectionEngine::queue_event(GID_SSH, SSH_EVENT_VERSION);
+ break;
+ }
+ if (keyx_valid)
+ {
+ SshEvent event(SSH_VALIDATION, SSH_VALID_KEXINIT, "", pkt_direction, p);
+ DataBus::publish(SSH_EVENT, event, p->flow);
+ }
+ else
{
- if ( !(sessp->state_flags & SSH_FLG_SESS_ENCRYPTED ))
- return;
+ SshEvent event(SSH_VALIDATION, SSH_INVALID_KEXINIT, "", pkt_direction, p);
+ DataBus::publish(SSH_EVENT, event, p->flow);
+ sessp->state_flags |= SSH_FLG_SESS_ENCRYPTED;
}
}
-
- // If SSH2, need to process the actual key exchange msgs.
- // The actual key exchange type was negotiated in the
- // key exchange init msgs. SSH1 won't arrive here.
- offset = ProcessSSHKeyExchange(sessp, p, direction, offset);
- if (!offset)
- return;
+ else
+ {
+ bool keyx_valid = process_ssh2_key_exchange(sessp, p, direction);
+ // FIXIT-M
+ // Originally, appid only looked at the kexinit packet for validation.
+ // We may want to produce an additional event for validation of the
+ // entire key exchange.
+ if (!keyx_valid)
+ sessp->state_flags |= SSH_FLG_SESS_ENCRYPTED;
+ }
}
-
- if ( (sessp->state_flags & SSH_FLG_SESS_ENCRYPTED ))
+ else
{
// Traffic on this session is currently encrypted.
// Two of the major SSH exploits, SSH1 CRC-32 and
{
if ( direction == SSH_DIR_FROM_CLIENT )
{
- if (!offset)
- sessp->num_client_bytes += p->dsize;
-
- else
- sessp->num_client_bytes += (p->dsize - offset);
-
+ sessp->num_client_bytes += p->dsize;
if ( sessp->num_client_bytes >= config->MaxClientBytes )
{
// Probable exploit in progress.
if (sessp->version == SSH_VERSION_1)
DetectionEngine::queue_event(GID_SSH, SSH_EVENT_CRC32);
-
else
DetectionEngine::queue_event(GID_SSH, SSH_EVENT_RESPOVERFLOW);
Stream::stop_inspection(p->flow, p, SSN_DIR_BOTH, -1, 0);
}
}
-
else
{
// Have seen a server response, so this appears to be a valid
sessp->num_client_bytes = 0;
}
}
-
else
{
// Have already examined more than the limit
}
}
-/* Checks if the string 'str' is 'max' bytes long or longer.
- * Returns 0 if 'str' is less than or equal to 'max' bytes;
- * returns 1 otherwise.
-*/
-
-static inline int SSHCheckStrlen(const char* str, int max)
-{
- if ( memchr(str, '\0', max) )
- return 0; /* str size is <= max bytes */
-
- return 1;
-}
-
-/* Attempts to process current packet as a protocol version exchange
- * packet. This function will be called if either the client or server
- * protocol version message (or both) has not been sent.
- *
- * PARAMETERS:
- *
- * sessionp: Pointer to SSH data for packet's session.
- * p: Pointer to the packet to inspect.
- * direction: Which direction the packet is going.
- *
- * RETURNS: offset processed
- */
-static unsigned int ProcessSSHProtocolVersionExchange(SSH_PROTO_CONF* config, SSHData* sessionp,
- Packet* p, uint8_t direction)
+static bool process_ssh_version_string(
+ SSH_PROTO_CONF* config, SSHData* sessionp, Packet* p, uint8_t direction)
{
- const char* version_stringp = (const char*)p->data;
- const char* version_end;
-
- /* Get the version. */
- if ( p->dsize >= 6 &&
- !strncasecmp(version_stringp, "SSH-1.", 6))
+ if (p->dsize > config->MaxServerVersionLen)
{
- if (( p->dsize > 7 ) && ( version_stringp[6] == '9')
- && (version_stringp[7] == '9'))
+ DetectionEngine::queue_event(GID_SSH, SSH_EVENT_SECURECRT);
+ // SSH_MAX_BANNER_LEN is 255, the maximum specified by the SSH protocol.
+ // MaxServerVersionLen defaults to 80,
+ // but there may be valid version strings that are longer due to comments.
+ if (p->dsize > SSH_MAX_BANNER_LEN)
{
- /* SSH 1.99 which is the same as SSH2.0 */
- sessionp->version = SSH_VERSION_2;
+ return false;
}
- else
+ }
+ if (p->dsize < SSH_MIN_BANNER_LEN
+ or memcmp(p->data, SSH_BANNER, sizeof(SSH_BANNER)-1) != 0)
+ {
+ // according to the SSH specification,
+ // the server can send lines before the version string
+ // as long as they don't start with "SSH-",
+ // so we will ignore them.
+ return true;
+ }
+ const char *proto_ver = (const char *)p->data + sizeof(SSH_BANNER) - 1;
+ const char *proto_ver_end = (const char *)memchr(proto_ver, '-', p->dsize - sizeof(SSH_BANNER));
+ if (!proto_ver_end)
+ {
+ DetectionEngine::queue_event(GID_SSH, SSH_EVENT_VERSION);
+ return false;
+ }
+
+ if (proto_ver[0] == '2' and proto_ver[1] == '.')
+ {
+ sessionp->version = SSH_VERSION_2;
+ }
+ else if (proto_ver[0] == '1' and proto_ver[1] == '.')
+ {
+ // version 1.99 == compatibility mode for 2.0
+ // determine version from client in this case
+ if (direction == SSH_DIR_FROM_CLIENT)
{
sessionp->version = SSH_VERSION_1;
}
-
- /* CAN-2002-0159 */
- /* Verify the version string is not greater than
- * the configured maximum.
- * We've already verified the first 6 bytes, so we'll start
- * check from &version_string[6] */
- /* First make sure the data itself is sufficiently large */
- if ((p->dsize > config->MaxServerVersionLen) &&
- /* CheckStrlen will check if the version string up to
- * MaxServerVersionLen+1 since there's no reason to
- * continue checking after that point*/
- (SSHCheckStrlen(&version_stringp[6], config->MaxServerVersionLen-6)))
+ else if (proto_ver[2] != '9' or proto_ver[3] != '9')
{
- DetectionEngine::queue_event(GID_SSH, SSH_EVENT_SECURECRT);
+ sessionp->version = SSH_VERSION_1;
}
}
- else if ( p->dsize >= 6 &&
- !strncasecmp(version_stringp, "SSH-2.", 6))
- {
- sessionp->version = SSH_VERSION_2;
- }
else
{
- /* unknown version */
- sessionp->version = SSH_VERSION_UNKNOWN;
-
DetectionEngine::queue_event(GID_SSH, SSH_EVENT_VERSION);
-
- return 0;
+ sessionp->version = SSH_VERSION_UNKNOWN;
+ return false;
}
/* Saw a valid protocol exchange message. Mark the session
sessionp->state_flags |= SSH_FLG_CLIENT_IDSTRING_SEEN;
break;
}
+ return true;
- version_end = (const char*)memchr(version_stringp, '\n', p->dsize);
- if (version_end)
- return ((version_end - version_stringp) + 1);
- /* incomplete version string, should end with \n or \r\n for sshv2 */
- return p->dsize;
}
-/* Called to process SSH1 key exchange or SSH2 key exchange init
- * messages. On failure, inspection will be continued, but the packet
- * will be alerted on, and ignored.
- *
- * PARAMETERS:
- *
- * sessionp: Pointer to SSH data for packet's session.
- * p: Pointer to the packet to inspect.
- * direction: Which direction the packet is going.
- *
- * RETURNS: offset processed
- */
-static unsigned int ProcessSSHKeyInitExchange(SSHData* sessionp, Packet* p,
- uint8_t direction, unsigned int offset)
+static bool process_ssh1_key_exchange(SSHData *sessionp, Packet *p, uint8_t direction)
{
- const SSH2Packet* ssh2p = nullptr;
- uint16_t dsize = p->dsize;
- const unsigned char* data = p->data;
- unsigned int ssh_length = 0;
-
- if (dsize < sizeof(SSH2Packet) || (dsize < (offset + sizeof(SSH2Packet)))
- || (dsize <= offset))
- return 0;
-
- dsize -= offset;
- data += offset;
-
- if ( sessionp->version == SSH_VERSION_1 )
+ if (p->dsize < SSH1_KEYX_MIN_SIZE)
{
- uint32_t length;
- uint8_t padding_length;
- uint8_t message_type;
-
- /*
- * Validate packet data.
- * First 4 bytes should have the SSH packet length,
- * minus any padding.
- */
- if ( dsize < 4 )
- {
- {
- DetectionEngine::queue_event(GID_SSH, SSH_EVENT_PAYLOAD_SIZE);
- }
+ DetectionEngine::queue_event(GID_SSH, SSH_EVENT_PAYLOAD_SIZE);
+ return false;
+ }
+ uint32_t payload_length = ntohl(*(const uint32_t *)(p->data));
+ uint8_t padding = 8 - (payload_length % 8);
+ uint8_t code = p->data[sizeof(uint32_t) + padding];
- return 0;
- }
- /*
- * SSH1 key exchange is very simple and
- * consists of only two messages, a server
- * key and a client key message.`
- */
- memcpy(&length, data, sizeof(length));
- length = ntohl(length);
+ if (p->dsize != sizeof(uint32_t) + padding + payload_length
+ or p->dsize > SSH_PACKET_MAX_SIZE)
+ {
+ DetectionEngine::queue_event(GID_SSH, SSH_EVENT_PAYLOAD_SIZE);
+ return false;
+ }
- /* Packet data should be larger than length, due to padding. */
- if ( dsize < length )
+ switch (code)
+ {
+ case SSH_MSG_V1_SMSG_PUBLIC_KEY:
+ if (direction == SSH_DIR_FROM_SERVER)
{
- {
- DetectionEngine::queue_event(GID_SSH, SSH_EVENT_PAYLOAD_SIZE);
- }
-
- return 0;
+ sessionp->state_flags |= SSH_FLG_SERV_PKEY_SEEN;
}
-
- padding_length = (uint8_t)(8 - (length % 8));
-
- /*
- * With the padding calculated, verify data is sufficiently large
- * to include the message type.
- */
- if ( dsize < (padding_length + 4 + 1 + offset))
- {
- if (offset == 0)
- {
- DetectionEngine::queue_event(GID_SSH, SSH_EVENT_PAYLOAD_SIZE);
- }
-
- return 0;
+ else
+ {
+ DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
+ return false;
}
-
- message_type = *( (const uint8_t*)(data + padding_length + 4));
-
- switch ( message_type )
+ break;
+ case SSH_MSG_V1_CMSG_SESSION_KEY:
+ if (direction == SSH_DIR_FROM_CLIENT)
{
- case SSH_MSG_V1_SMSG_PUBLIC_KEY:
- if ( direction == SSH_DIR_FROM_SERVER )
- {
- sessionp->state_flags |=
- SSH_FLG_SERV_PKEY_SEEN;
- }
- else
- {
- /* Server msg not from server. */
- DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
- }
- break;
- case SSH_MSG_V1_CMSG_SESSION_KEY:
- if ( direction == SSH_DIR_FROM_CLIENT )
- {
- sessionp->state_flags |=
- SSH_FLG_CLIENT_SKEY_SEEN;
- }
- else
- {
- /* Client msg not from client. */
- DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
- }
- break;
- default:
- /* Invalid msg type*/
- break;
+ sessionp->state_flags |= SSH_FLG_CLIENT_SKEY_SEEN;
}
-
- /* Once the V1 key exchange is done, remainder of
- * communications are encrypted.
- */
- ssh_length = length + padding_length + sizeof(length) + offset;
-
- if ( (sessionp->state_flags & SSH_FLG_V1_KEYEXCH_DONE) ==
- SSH_FLG_V1_KEYEXCH_DONE )
+ else
{
- sessionp->state_flags |= SSH_FLG_SESS_ENCRYPTED;
+ DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
+ return false;
}
+ break;
}
- else if ( sessionp->version == SSH_VERSION_2 )
+ if ((sessionp->state_flags & SSH_FLG_V1_KEYEXCH_DONE) == SSH_FLG_V1_KEYEXCH_DONE)
{
- /* We want to overlay the data on our data packet struct,
- * so first verify that the data size is big enough.
- * This may legitimately occur such as in the case of a
- * retransmission.
- */
- if ( dsize < sizeof(SSH2Packet) )
- {
- return 0;
- }
-
- /* Overlay the SSH2 binary data packet struct on the packet */
- ssh2p = (const SSH2Packet*)data;
- if ( dsize < SSH2_HEADERLEN + 1)
- {
- /* Invalid packet length. */
-
- return 0;
- }
-
- ssh_length = offset + ntohl(ssh2p->packet_length) + sizeof(ssh2p->packet_length);
+ sessionp->state_flags |= SSH_FLG_SESS_ENCRYPTED;
+ }
+ return true;
+}
- switch ( data[SSH2_HEADERLEN] )
+static bool process_ssh2_kexinit(SSHData *sessionp, Packet *p, uint8_t direction)
+{
+ uint16_t dsize = p->dsize;
+ unsigned int ssh_length = 0;
+ if (dsize < sizeof(SSH2KeyExchange) or dsize > SSH_PACKET_MAX_SIZE)
+ {
+ DetectionEngine::queue_event(GID_SSH, SSH_EVENT_PAYLOAD_SIZE);
+ return false;
+ }
+ const SSH2KeyExchange* ssh_pkt = (const SSH2KeyExchange*)p->data;
+ ssh_length = ntohl(ssh_pkt->msg.len) + sizeof(uint32_t);
+ if (ssh_length != dsize)
+ {
+ DetectionEngine::queue_event(GID_SSH, SSH_EVENT_PAYLOAD_SIZE);
+ return false;
+ }
+ switch(ssh_pkt->msg.code)
+ {
+ case SSH_MSG_KEXINIT:
+ sessionp->state_flags |=
+ (direction == SSH_DIR_FROM_SERVER ?
+ SSH_FLG_SERV_KEXINIT_SEEN :
+ SSH_FLG_CLIENT_KEXINIT_SEEN);
+ break;
+ case SSH_MSG_IGNORE:
+ return true;
+ default:
+ return false;
+ }
+ uint16_t total_length = sizeof(SSH2KeyExchange);
+ const uint8_t *data = p->data + sizeof(SSH2KeyExchange);
+ for (int i = 0; i < NUM_KEXINIT_LISTS; i++)
+ {
+ uint32_t list_length = ntohl(*((const uint32_t*)data)) + sizeof(uint32_t);
+ if (list_length > ssh_length or total_length + list_length > ssh_length)
{
- case SSH_MSG_KEXINIT:
- sessionp->state_flags |=
- (direction == SSH_DIR_FROM_SERVER ?
- SSH_FLG_SERV_KEXINIT_SEEN :
- SSH_FLG_CLIENT_KEXINIT_SEEN );
- break;
- default:
- /* Unrecognized message type. */
- break;
+ DetectionEngine::queue_event(GID_SSH, SSH_EVENT_PAYLOAD_SIZE);
+ return false;
}
+ total_length += list_length;
+ data += list_length;
}
- else
+ total_length += sizeof(SSHKeyExchangeFinal) + ssh_pkt->msg.plen;
+ if (total_length != ssh_length)
{
- return 0;
+ DetectionEngine::queue_event(GID_SSH, SSH_EVENT_PAYLOAD_SIZE);
+ return false;
}
-
- if (ssh_length < p->dsize)
- return ssh_length;
- else
- return 0;
+ const SSHKeyExchangeFinal* final = (const SSHKeyExchangeFinal*)data;
+ if (final->future)
+ {
+ // using an unsupported future version
+ return false;
+ }
+ return true;
}
-/* Called to process SSH2 key exchange msgs (key exch init msgs already
- * processed earlier). On failure, inspection will be continued, but the
- * packet will be alerted on, and ignored.
- *
- * PARAMETERS:
- *
- * sessionp: Pointer to SSH data for packet's session.
- * p: Pointer to the packet to inspect.
- * direction: Which direction the packet is going.
- *
- * RETURNS: offset processed
- */
-static unsigned int ProcessSSHKeyExchange(SSHData* sessionp, Packet* p,
- uint8_t direction, unsigned int offset)
+static bool process_ssh2_key_exchange(SSHData *sessionp, Packet *p, uint8_t direction)
{
uint16_t dsize = p->dsize;
- const unsigned char* data = p->data;
- bool next_packet = true;
- unsigned int npacket_offset = 0;
+ const unsigned char *data = p->data;
- if (dsize < sizeof(SSH2Packet) || (dsize < (offset + sizeof(SSH2Packet)))
- || (dsize <= offset))
+ if (dsize < sizeof(SSH2Packet))
{
- return 0;
+ return false;
}
- dsize -= offset;
- data += offset;
+ const SSH2Packet *ssh2p = (const SSH2Packet *)data;
+ unsigned ssh_length = ntohl(ssh2p->packet_length) + sizeof(uint32_t);
- while (next_packet)
+ if (ssh_length < sizeof(SSH2Packet)
+ or ssh_length != dsize
+ or ssh_length > SSH_PACKET_MAX_SIZE)
{
- const SSH2Packet* ssh2p = (const SSH2Packet*)(data + npacket_offset);
- unsigned ssh_length = SSHPacket_GetLength(ssh2p, dsize);
+ /* Invalid packet length. */
+ DetectionEngine::queue_event(GID_SSH, SSH_EVENT_PAYLOAD_SIZE);
+ return false;
+ }
- if (ssh_length == 0)
+ switch (ssh2p->packet_data)
+ {
+ case SSH_MSG_KEXDH_INIT:
+ if (direction == SSH_DIR_FROM_CLIENT)
{
- if ( sessionp->state_flags & SSH_FLG_SESS_ENCRYPTED )
- {
- return ( npacket_offset + offset );
- }
- {
- /* Invalid packet length. */
- DetectionEngine::queue_event(GID_SSH, SSH_EVENT_PAYLOAD_SIZE);
- }
-
- return 0;
+ sessionp->state_flags |=
+ SSH_FLG_KEXDH_INIT_SEEN;
}
-
- switch (data[npacket_offset + SSH2_HEADERLEN] )
+ else
{
- case SSH_MSG_KEXDH_INIT:
- if ( direction == SSH_DIR_FROM_CLIENT )
- {
- sessionp->state_flags |=
- SSH_FLG_KEXDH_INIT_SEEN;
- }
- else
- {
- /* Client msg from server. */
- DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
- }
- break;
- case SSH_MSG_KEXDH_REPLY:
- if ( direction == SSH_DIR_FROM_SERVER )
- {
- /* KEXDH_REPLY has the same msg
- * type as the new style GEX_REPLY
- */
- sessionp->state_flags |=
- SSH_FLG_KEXDH_REPLY_SEEN |
- SSH_FLG_GEX_REPLY_SEEN;
- }
- else
- {
- /* Server msg from client. */
- DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
- }
- break;
- case SSH_MSG_KEXDH_GEX_REQ:
- if ( direction == SSH_DIR_FROM_CLIENT )
- {
- sessionp->state_flags |=
- SSH_FLG_GEX_REQ_SEEN;
- }
- else
- {
- /* Server msg from client. */
- DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
- }
- break;
- case SSH_MSG_KEXDH_GEX_GRP:
- if ( direction == SSH_DIR_FROM_SERVER )
- {
- sessionp->state_flags |=
- SSH_FLG_GEX_GRP_SEEN;
- }
- else
- {
- /* Client msg from server. */
- DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
- }
- break;
- case SSH_MSG_KEXDH_GEX_INIT:
- if ( direction == SSH_DIR_FROM_CLIENT )
- {
- sessionp->state_flags |=
- SSH_FLG_GEX_INIT_SEEN;
- }
- else
- {
- /* Server msg from client. */
- DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
- }
- break;
- case SSH_MSG_NEWKEYS:
- /* This message is required to complete the
- * key exchange. Both server and client should
- * send one, but as per Alex Kirk's note on this,
- * in some implementations the server does not
- * actually send this message. So receiving a new
- * keys msg from the client is sufficient.
+ /* Client msg from server. */
+ DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
+ }
+ break;
+ case SSH_MSG_KEXDH_REPLY:
+ if (direction == SSH_DIR_FROM_SERVER)
+ {
+ /* KEXDH_REPLY has the same msg
+ * type as the new style GEX_REPLY
*/
- if ( direction == SSH_DIR_FROM_CLIENT )
- {
- sessionp->state_flags |= SSH_FLG_CLIENT_NEWKEYS_SEEN;
- }
- else
- {
- sessionp->state_flags |= SSH_FLG_SERVER_NEWKEYS_SEEN;
- }
- break;
- default:
- /* Unrecognized message type. Possibly encrypted */
- sessionp->state_flags |= SSH_FLG_SESS_ENCRYPTED;
- return ( npacket_offset + offset);
+ sessionp->state_flags |=
+ SSH_FLG_KEXDH_REPLY_SEEN |
+ SSH_FLG_GEX_REPLY_SEEN;
}
-
- /* If either an old-style or new-style Diffie Helman exchange
- * has completed, the session will enter encrypted mode.
- */
- if (( (sessionp->state_flags &
- SSH_FLG_V2_DHOLD_DONE) == SSH_FLG_V2_DHOLD_DONE )
- || ( (sessionp->state_flags &
- SSH_FLG_V2_DHNEW_DONE) == SSH_FLG_V2_DHNEW_DONE ))
+ else
{
- sessionp->state_flags |= SSH_FLG_SESS_ENCRYPTED;
- if (ssh_length < dsize)
- {
- if ( ssh_length >= 4 )
- {
- npacket_offset += ssh_length;
- dsize -= ssh_length;
- continue;
- }
- return ( npacket_offset + offset );
- }
- else
- return 0;
+ /* Server msg from client. */
+ DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
}
-
- if ((ssh_length < dsize) && (ssh_length >= 4))
+ break;
+ case SSH_MSG_KEXDH_GEX_REQ:
+ if (direction == SSH_DIR_FROM_CLIENT)
+ {
+ sessionp->state_flags |=
+ SSH_FLG_GEX_REQ_SEEN;
+ }
+ else
+ {
+ /* Server msg from client. */
+ DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
+ }
+ break;
+ case SSH_MSG_KEXDH_GEX_GRP:
+ if (direction == SSH_DIR_FROM_SERVER)
{
- npacket_offset += ssh_length;
- dsize -= ssh_length;
+ sessionp->state_flags |=
+ SSH_FLG_GEX_GRP_SEEN;
}
else
{
- next_packet = false;
- npacket_offset = 0;
+ /* Client msg from server. */
+ DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
}
+ break;
+ case SSH_MSG_KEXDH_GEX_INIT:
+ if (direction == SSH_DIR_FROM_CLIENT)
+ {
+ sessionp->state_flags |=
+ SSH_FLG_GEX_INIT_SEEN;
+ }
+ else
+ {
+ /* Server msg from client. */
+ DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
+ }
+ break;
+ case SSH_MSG_NEWKEYS:
+ /* This message is required to complete the
+ * key exchange. Both server and client should
+ * send one, but as per Alex Kirk's note on this,
+ * in some implementations the server does not
+ * actually send this message. So receiving a new
+ * keys msg from the client is sufficient.
+ */
+ if (direction == SSH_DIR_FROM_CLIENT)
+ {
+ sessionp->state_flags |= SSH_FLG_CLIENT_NEWKEYS_SEEN;
+ }
+ else
+ {
+ sessionp->state_flags |= SSH_FLG_SERVER_NEWKEYS_SEEN;
+ }
+ break;
+ default:
+ return false;
}
- return (npacket_offset + offset);
+ /* If either an old-style or new-style Diffie Helman exchange
+ * has completed, the session will enter encrypted mode.
+ */
+ if (((sessionp->state_flags & SSH_FLG_V2_DHOLD_DONE) == SSH_FLG_V2_DHOLD_DONE)
+ or ((sessionp->state_flags & SSH_FLG_V2_DHNEW_DONE) == SSH_FLG_V2_DHNEW_DONE))
+ {
+ sessionp->state_flags |= SSH_FLG_SESS_ENCRYPTED;
+ }
+ return true;
}
//-------------------------------------------------------------------------
SSHData session;
};
+#define SSH_BANNER "SSH-"
// Length of SSH2 header, in bytes.
#define SSH2_HEADERLEN (5)
// Length of SSH2 Padding, in bytes.
// Length of SSH2 packet, in bytes.
#define SSH2_PACKET_LEN (SSH2_HEADERLEN - SSH2_PADDING_LEN)
#define SSH2_PACKET_MAX_SIZE (256 * 1024)
+#define SSH_PACKET_MAX_SIZE 35000
+#define SSH_MAX_BANNER_LEN 255
+#define SSH2_COOKIE_SIZE 16
+#define NUM_KEXINIT_LISTS 10
+#define SSH_MIN_BANNER_LEN 9 //SSH-2.0-*\n
+#define SSH1_KEYX_MIN_SIZE (4 + 8 + 1) // length + padding + message
+
+#pragma pack(1)
+struct SSHKeyString
+{
+ uint32_t len;
+ uint8_t data;
+};
+
+struct SSHMsg
+{
+ uint32_t len;
+ uint8_t plen;
+ uint8_t code;
+};
+
+struct SSH2KeyExchange
+{
+ SSHMsg msg;
+ uint8_t cookie[16];
+};
+
+struct SSH1KeyExchangeV1
+{
+ uint32_t len;
+ uint8_t code;
+};
+
+struct SSHKeyExchangeFinal
+{
+ uint8_t kex_pkt;
+ uint32_t future;
+};
struct SSH2Packet
{
uint32_t packet_length; // Length not including this field or the mesg auth code (mac)
uint8_t padding_length; // Length of padding section.
- char packet_data[1]; // Variable length packet payload + padding + MAC.
+ uint8_t packet_data; // Variable length packet payload + padding + MAC.
};
+#pragma pack()
// SSH v1 message types (of interest)
#define SSH_MSG_V1_SMSG_PUBLIC_KEY 2
#define SSH_MSG_V1_CMSG_SESSION_KEY 3
// SSH v2 message types (of interest)
+#define SSH_MSG_IGNORE 2
#define SSH_MSG_KEXINIT 20
#define SSH_MSG_NEWKEYS 21
#define SSH_MSG_KEXDH_INIT 30
#define SSH_MSG_KEXDH_REPLY 31
-
#define SSH_MSG_KEXDH_GEX_REQ 34
#define SSH_MSG_KEXDH_GEX_GRP 33
#define SSH_MSG_KEXDH_GEX_INIT 32
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)
+ if (remain_bytes != 0)
{
- 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;
- }
+ *fp = remain_bytes;
+ return StreamSplitter::FLUSH;
+ }
+ const SSH2Packet* sshp = (const SSH2Packet*)data;
+ uint32_t ssh_len = ntohl(sshp->packet_length) + SSH2_PACKET_LEN;
+ if (ssh_len > len)
+ {
+ remain_bytes = ssh_len - len;
+ return StreamSplitter::SEARCH;
+ }
+ else
+ {
+ *fp = ssh_len;
+ return StreamSplitter::FLUSH;
}
-exit_loop:
- *fp = offset;
- return StreamSplitter::FLUSH;
}
else
{
{
if (flags & PKT_FROM_SERVER)
{
- // Do not scan if sever new keys message seen.
+ // Do not scan if server new keys message seen.
if (sessp->state_flags & SSH_FLG_SERVER_NEWKEYS_SEEN)
{
return SEARCH;
class SshSplitter : public snort::StreamSplitter
{
public:
- SshSplitter(bool c2s);
+ SshSplitter(bool c2s) : StreamSplitter(c2s)
+ { }
Status scan(snort::Packet*, const uint8_t* data, uint32_t len,
uint32_t flags, uint32_t* fp) override;
Status ssh2_scan(SSHData* sessp, const uint8_t* data, uint32_t len,
uint32_t flags, uint32_t* fp);
- uint32_t state;
+ SshPafState state = SSH_PAF_VER_EXCHANGE;
uint32_t client_remain_bytes = 0;
uint32_t server_remain_bytes = 0;
};
count_miss(pkt->flow);
trace_logf(wizard_trace, pkt, "%s streaming search abandoned\n", to_server() ? "c2s" : "s2c");
wizard_processed_bytes = 0;
+ if (!pkt->flow->flags.svc_event_generated)
+ {
+ DataBus::publish(FLOW_NO_SERVICE_EVENT, pkt);
+ pkt->flow->flags.svc_event_generated = true;
+ }
return ABORT;
}
if ( wand.spell )
bookmark = wand.spell->book.get_bookmark();
+ // FIXIT-L Ideally, this event should be raised after wizard aborts its search. However, this
+ // could take multiple packets because wizard needs wizard.max_search_depth payload bytes before
+ // it aborts. This is an issue for AppId which consumes this event. AppId is required to declare
+ // unknown service as soon as it can so that the flow actions (such as IPS block, etc) don't get
+ // delayed. Because AppId depends on wizard only for SSH detection and SSH inspector can be
+ // attached very early, event is raised here after first scan. In the future, wizard should be
+ // enhanced to abort sooner if it can't detect service.
+ if (!pkt->flow->service and !pkt->flow->flags.svc_event_generated)
+ {
+ DataBus::publish(FLOW_NO_SERVICE_EVENT, pkt);
+ pkt->flow->flags.svc_event_generated = true;
+ }
+
// ostensibly continue but splitter will be swapped out upon hit
return SEARCH;
}