From: Shravan Rangarajuvenkata (shrarang) Date: Tue, 14 Dec 2021 20:38:57 +0000 (+0000) Subject: Pull request #3189: Roll AppId's SSH detector into SSH service inspector X-Git-Tag: 3.1.19.0~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e64ac7082bf91da7c02c96080340d6b8b17df466;p=thirdparty%2Fsnort3.git Pull request #3189: Roll AppId's SSH detector into SSH service inspector Merge in SNORT/snort3 from ~SHRARANG/snort3:appid_ssh to master Squashed commit of the following: commit 49d2ca8ea4b6b75607dc2169a41d0efff2490354 Author: Shravan Rangaraju Date: Tue Nov 30 23:11:23 2021 -0500 framework, appid: generate NO_SERVICE event when no inspector can be attached to a flow; wait for the event in appid before declaring service as unknown for the flow commit 7cfa805c36bae248f12dde37a4cdc073bd24a797 Author: Shravan Rangaraju Date: Tue Nov 30 17:14:55 2021 -0500 appid: remove hard-coded SSH client patterns which are available as part of ODP commit a9cdcc3457b03bfa5f37e5bd2c6ae252c11fe247 Author: Shravan Rangaraju Date: Tue Nov 30 14:59:27 2021 -0500 appid, ssh: Roll AppId's SSH detector into SSH service inspector --- diff --git a/src/flow/flow.h b/src/flow/flow.h index d3103c603..276485c42 100644 --- a/src/flow/flow.h +++ b/src/flow/flow.h @@ -484,6 +484,7 @@ public: // FIXIT-M privatize if possible 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; diff --git a/src/framework/data_bus.h b/src/framework/data_bus.h index 1707fa69a..cd1836132 100644 --- a/src/framework/data_bus.h +++ b/src/framework/data_bus.h @@ -139,6 +139,8 @@ private: #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" diff --git a/src/managers/inspector_manager.cc b/src/managers/inspector_manager.cc index 009cd538b..b18a0c998 100644 --- a/src/managers/inspector_manager.cc +++ b/src/managers/inspector_manager.cc @@ -1464,7 +1464,18 @@ void InspectorManager::bumble(Packet* p) 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 ) diff --git a/src/network_inspectors/appid/CMakeLists.txt b/src/network_inspectors/appid/CMakeLists.txt index fac670bb4..2b10e96e5 100644 --- a/src/network_inspectors/appid/CMakeLists.txt +++ b/src/network_inspectors/appid/CMakeLists.txt @@ -25,8 +25,6 @@ set ( CP_APPID_SOURCES 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 @@ -92,8 +90,6 @@ set ( SP_APPID_SOURCES 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 @@ -164,6 +160,8 @@ set ( APPID_SOURCES 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 @@ -174,6 +172,8 @@ set ( APPID_SOURCES 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 diff --git a/src/network_inspectors/appid/appid_config.cc b/src/network_inspectors/appid/appid_config.cc index 3d07f290e..9db516646 100644 --- a/src/network_inspectors/appid/appid_config.cc +++ b/src/network_inspectors/appid/appid_config.cc @@ -173,7 +173,6 @@ void OdpContext::initialize(AppIdInspector& inspector) sip_matchers.finalize_patterns(*this); ssl_matchers.finalize_patterns(); dns_matchers.finalize_patterns(); - ssh_matchers.finalize_patterns(); } void OdpContext::reload() diff --git a/src/network_inspectors/appid/appid_discovery.cc b/src/network_inspectors/appid/appid_discovery.cc index 110e99a3d..0df7d941e 100644 --- a/src/network_inspectors/appid/appid_discovery.cc +++ b/src/network_inspectors/appid/appid_discovery.cc @@ -248,6 +248,14 @@ bool AppIdDiscovery::do_pre_discovery(Packet* p, AppIdSession*& asd, AppIdInspec 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; diff --git a/src/network_inspectors/appid/appid_inspector.cc b/src/network_inspectors/appid/appid_inspector.cc index 28bcfa6d3..9d9bf96c1 100644 --- a/src/network_inspectors/appid/appid_inspector.cc +++ b/src/network_inspectors/appid/appid_inspector.cc @@ -44,7 +44,9 @@ #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" @@ -138,6 +140,10 @@ bool AppIdInspector::configure(SnortConfig* sc) 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; } diff --git a/src/network_inspectors/appid/appid_service_event_handler.cc b/src/network_inspectors/appid/appid_service_event_handler.cc new file mode 100644 index 000000000..6be33be16 --- /dev/null +++ b/src/network_inspectors/appid/appid_service_event_handler.cc @@ -0,0 +1,122 @@ +//-------------------------------------------------------------------------- +// 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 + +#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); +} diff --git a/src/network_inspectors/appid/service_plugins/service_ssh.h b/src/network_inspectors/appid/appid_service_event_handler.h similarity index 61% rename from src/network_inspectors/appid/service_plugins/service_ssh.h rename to src/network_inspectors/appid/appid_service_event_handler.h index f977bb685..a8ae433eb 100644 --- a/src/network_inspectors/appid/service_plugins/service_ssh.h +++ b/src/network_inspectors/appid/appid_service_event_handler.h @@ -1,6 +1,5 @@ //-------------------------------------------------------------------------- -// 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 @@ -17,21 +16,31 @@ // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. //-------------------------------------------------------------------------- -// service_ssh.h author Sourcefire Inc. +// appid_service_event_handler.h author Shravan Rangaraju -#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 diff --git a/src/network_inspectors/appid/appid_session.h b/src/network_inspectors/appid/appid_session.h index a0494e8fc..3f1da2acb 100644 --- a/src/network_inspectors/appid/appid_session.h +++ b/src/network_inspectors/appid/appid_session.h @@ -623,6 +623,26 @@ public: 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; @@ -643,6 +663,8 @@ private: 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 diff --git a/src/network_inspectors/appid/appid_ssh_event_handler.cc b/src/network_inspectors/appid/appid_ssh_event_handler.cc new file mode 100644 index 000000000..fe994f7ed --- /dev/null +++ b/src/network_inspectors/appid/appid_ssh_event_handler.cc @@ -0,0 +1,246 @@ +//-------------------------------------------------------------------------- +// 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 + +#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; + } +} diff --git a/src/network_inspectors/appid/client_plugins/client_app_ssh.h b/src/network_inspectors/appid/appid_ssh_event_handler.h similarity index 56% rename from src/network_inspectors/appid/client_plugins/client_app_ssh.h rename to src/network_inspectors/appid/appid_ssh_event_handler.h index bcd72b2c9..5ad70b0a5 100644 --- a/src/network_inspectors/appid/client_plugins/client_app_ssh.h +++ b/src/network_inspectors/appid/appid_ssh_event_handler.h @@ -1,6 +1,5 @@ //-------------------------------------------------------------------------- -// 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 @@ -16,20 +15,39 @@ // 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 -// 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 diff --git a/src/network_inspectors/appid/client_plugins/client_app_ssh.cc b/src/network_inspectors/appid/client_plugins/client_app_ssh.cc deleted file mode 100644 index 08bf3a83f..000000000 --- a/src/network_inspectors/appid/client_plugins/client_app_ssh.cc +++ /dev/null @@ -1,513 +0,0 @@ -//-------------------------------------------------------------------------- -// 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 -#include - -#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; -} - diff --git a/src/network_inspectors/appid/client_plugins/client_discovery.cc b/src/network_inspectors/appid/client_plugins/client_discovery.cc index 9daa7a68e..9ca686607 100644 --- a/src/network_inspectors/appid/client_plugins/client_discovery.cc +++ b/src/network_inspectors/appid/client_plugins/client_discovery.cc @@ -35,7 +35,6 @@ #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" @@ -62,7 +61,6 @@ void ClientDiscovery::initialize(AppIdInspector& inspector) new SipTcpClientDetector(this); new SipUdpClientDetector(this); new SmtpClientDetector(this); - new SshClientDetector(this); new TimbuktuClientDetector(this); new TnsClientDetector(this); new VncClientDetector(this); diff --git a/src/network_inspectors/appid/detector_plugins/ssh_patterns.cc b/src/network_inspectors/appid/detector_plugins/ssh_patterns.cc index bcb37e54a..451822e44 100644 --- a/src/network_inspectors/appid/detector_plugins/ssh_patterns.cc +++ b/src/network_inspectors/appid/detector_plugins/ssh_patterns.cc @@ -43,11 +43,3 @@ 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 index b10e338ff..00dd5d77a 100644 --- a/src/network_inspectors/appid/detector_plugins/ssh_patterns.h +++ b/src/network_inspectors/appid/detector_plugins/ssh_patterns.h @@ -16,7 +16,7 @@ // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. //-------------------------------------------------------------------------- -// ssh_patterns.cc author Daniel McGarvey +// ssh_patterns.h author Daniel McGarvey #ifndef SSH_PATTERNS_H #define SSH_PATTERNS_H @@ -25,7 +25,7 @@ * 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. */ @@ -43,7 +43,6 @@ public: 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; }; diff --git a/src/network_inspectors/appid/service_plugins/service_discovery.cc b/src/network_inspectors/appid/service_plugins/service_discovery.cc index ae28fa54d..f038848ab 100644 --- a/src/network_inspectors/appid/service_plugins/service_discovery.cc +++ b/src/network_inspectors/appid/service_plugins/service_discovery.cc @@ -66,7 +66,6 @@ #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" @@ -116,7 +115,6 @@ void ServiceDiscovery::initialize(AppIdInspector& inspector) new SipServiceDetector(this); new SmtpServiceDetector(this); new SnmpServiceDetector(this); - new SshServiceDetector(this); new SslServiceDetector(this); new TelnetServiceDetector(this); new TftpServiceDetector(this); @@ -438,7 +436,7 @@ int ServiceDiscovery::identify_service(AppIdSession& asd, Packet* p, 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()); @@ -511,7 +509,18 @@ int ServiceDiscovery::identify_service(AppIdSession& asd, Packet* p, /* 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 */ @@ -524,7 +533,7 @@ int ServiceDiscovery::identify_service(AppIdSession& asd, Packet* p, // 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; @@ -568,7 +577,7 @@ void ServiceDiscovery::clear_ftp_service_state() 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(); @@ -670,7 +679,7 @@ bool ServiceDiscovery::do_service_discovery(AppIdSession& asd, Packet* p, 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) @@ -691,7 +700,7 @@ bool ServiceDiscovery::do_service_discovery(AppIdSession& asd, Packet* p, { 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(); @@ -718,7 +727,7 @@ bool ServiceDiscovery::do_service_discovery(AppIdSession& asd, Packet* p, } } - return isTpAppidDiscoveryDone; + return is_discovery_done; } /**Called when service can not be identified on a flow but the checks failed on client request diff --git a/src/network_inspectors/appid/service_plugins/service_ssh.cc b/src/network_inspectors/appid/service_plugins/service_ssh.cc deleted file mode 100644 index 5e44361f2..000000000 --- a/src/network_inspectors/appid/service_plugins/service_ssh.cc +++ /dev/null @@ -1,555 +0,0 @@ -//-------------------------------------------------------------------------- -// 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) - { - goto fail; - } - ven = (const char*)&data[offset]; - for (; - offset= 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 (; offsetssh_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; - } -} - diff --git a/src/network_inspectors/binder/binder.cc b/src/network_inspectors/binder/binder.cc index e3ba2d873..c5b6ee2a3 100644 --- a/src/network_inspectors/binder/binder.cc +++ b/src/network_inspectors/binder/binder.cc @@ -453,6 +453,11 @@ void Stuff::apply_service(Flow& flow) } 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) diff --git a/src/pub_sub/CMakeLists.txt b/src/pub_sub/CMakeLists.txt index d825fc45c..10ddbec07 100644 --- a/src/pub_sub/CMakeLists.txt +++ b/src/pub_sub/CMakeLists.txt @@ -14,6 +14,7 @@ set (PUB_SUB_INCLUDES opportunistic_tls_event.h sip_events.h smb_events.h + ssh_events.h ) add_library( pub_sub OBJECT diff --git a/src/pub_sub/ssh_events.h b/src/pub_sub/ssh_events.h new file mode 100644 index 000000000..48d4a202a --- /dev/null +++ b/src/pub_sub/ssh_events.h @@ -0,0 +1,73 @@ +//-------------------------------------------------------------------------- +// 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 + +#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 diff --git a/src/service_inspectors/ssh/ssh.cc b/src/service_inspectors/ssh/ssh.cc index 90d060bca..8f482cc9c 100644 --- a/src/service_inspectors/ssh/ssh.cc +++ b/src/service_inspectors/ssh/ssh.cc @@ -34,6 +34,7 @@ #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" @@ -48,9 +49,10 @@ THREAD_LOCAL SshStats sshstats; * 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; @@ -58,7 +60,7 @@ SshFlowData::SshFlowData() : FlowData(inspector_id) { 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; } @@ -81,38 +83,7 @@ SSHData* get_session_data(const Flow* flow) 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); @@ -128,7 +99,7 @@ static void snort_ssh(SSH_PROTO_CONF* config, Packet* p) // 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 )) @@ -140,6 +111,7 @@ static void snort_ssh(SSH_PROTO_CONF* config, Packet* p) sshstats.total_bytes += p->dsize; uint8_t direction; + uint8_t pkt_direction; uint32_t search_dir_ver; uint32_t search_dir_keyinit; @@ -147,57 +119,77 @@ static void snort_ssh(SSH_PROTO_CONF* config, Packet* p) 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 @@ -211,25 +203,18 @@ static void snort_ssh(SSH_PROTO_CONF* config, Packet* p) { 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 @@ -237,7 +222,6 @@ static void snort_ssh(SSH_PROTO_CONF* config, Packet* p) sessp->num_client_bytes = 0; } } - else { // Have already examined more than the limit @@ -251,80 +235,59 @@ static void snort_ssh(SSH_PROTO_CONF* config, Packet* p) } } -/* 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 @@ -339,359 +302,236 @@ static unsigned int ProcessSSHProtocolVersionExchange(SSH_PROTO_CONF* config, SS 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; } //------------------------------------------------------------------------- diff --git a/src/service_inspectors/ssh/ssh.h b/src/service_inspectors/ssh/ssh.h index 6753d0ef1..f3484bf79 100644 --- a/src/service_inspectors/ssh/ssh.h +++ b/src/service_inspectors/ssh/ssh.h @@ -111,6 +111,7 @@ public: SSHData session; }; +#define SSH_BANNER "SSH-" // Length of SSH2 header, in bytes. #define SSH2_HEADERLEN (5) // Length of SSH2 Padding, in bytes. @@ -118,24 +119,63 @@ public: // 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 diff --git a/src/service_inspectors/ssh/ssh_splitter.cc b/src/service_inspectors/ssh/ssh_splitter.cc index e3fbf6aac..74e6d528c 100644 --- a/src/service_inspectors/ssh/ssh_splitter.cc +++ b/src/service_inspectors/ssh/ssh_splitter.cc @@ -26,50 +26,29 @@ 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 { @@ -91,7 +70,7 @@ StreamSplitter::Status SshSplitter::ssh2_scan(SSHData* sessp, { 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; diff --git a/src/service_inspectors/ssh/ssh_splitter.h b/src/service_inspectors/ssh/ssh_splitter.h index 2aa543edc..8234e86da 100644 --- a/src/service_inspectors/ssh/ssh_splitter.h +++ b/src/service_inspectors/ssh/ssh_splitter.h @@ -36,7 +36,8 @@ enum SshPafState 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; @@ -52,7 +53,7 @@ private: 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; }; diff --git a/src/service_inspectors/wizard/wizard.cc b/src/service_inspectors/wizard/wizard.cc index 16f02ea8e..703392a8d 100644 --- a/src/service_inspectors/wizard/wizard.cc +++ b/src/service_inspectors/wizard/wizard.cc @@ -205,6 +205,11 @@ StreamSplitter::Status MagicSplitter::scan( 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; } @@ -212,6 +217,19 @@ StreamSplitter::Status MagicSplitter::scan( 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; }