http_transaction_end_event.cc
quic_events.cc
sip_events.cc
+ ssh_events.cc
)
install (FILES ${PUB_SUB_INCLUDES}
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2024-2025 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.cc author Cisco
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "ssh_events.h"
+
+SshEventType SshEvent::get_event_type() const
+{ return event_type; }
+
+SshValidationResult SshEvent::get_validation_result() const
+{ return result; }
+
+const std::string& SshEvent::get_version_str() const
+{ return version_str; }
+
+uint8_t SshEvent::get_direction() const
+{ return direction; }
+
+const snort::Packet* SshEvent::get_packet() const
+{ return packet; }
+
+const char* SshEvent::get_login_direction() const
+{ return login_direction; }
+
+uint8_t SshEvent::get_ssh_version() const
+{ return ssh_version; }
+
+const char* SshAlgoEvent::get_kex_algorithms() const
+{ return algos.named.kex_algorithms; }
+
+const char* SshAlgoEvent::get_server_host_key_algorithms() const
+{ return algos.named.server_host_key_algorithms; }
+
+const char* SshAlgoEvent::get_encryption_algorithms_client_to_server() const
+{ return algos.named.encryption_algorithms_client_to_server; }
+
+const char* SshAlgoEvent::get_encryption_algorithms_server_to_client() const
+{ return algos.named.encryption_algorithms_server_to_client; }
+
+const char* SshAlgoEvent::get_mac_algorithms_client_to_server() const
+{ return algos.named.mac_algorithms_client_to_server; }
+
+const char* SshAlgoEvent::get_mac_algorithms_server_to_client() const
+{ return algos.named.mac_algorithms_server_to_client; }
+
+const char* SshAlgoEvent::get_compression_algorithms_client_to_server() const
+{ return algos.named.compression_algorithms_client_to_server; }
+
+const char* SshAlgoEvent::get_compression_algorithms_server_to_client() const
+{ return algos.named.compression_algorithms_server_to_client; }
+
+uint8_t SshAlgoEvent::get_direction() const
+{ return direction; }
+
// for use by data bus subscribers
#include "framework/data_bus.h"
-#include "service_inspectors/ssh/ssh.h"
+#include "service_inspectors/ssh/ssh_types.h"
-struct SshEventIds { enum : unsigned { STATE_CHANGE, num_ids }; };
+struct SshEventIds { enum : unsigned { STATE_CHANGE, ALGORITHM, num_ids }; };
const snort::PubKey ssh_pub_key { "ssh", SshEventIds::num_ids };
SSH_INVALID_KEXINIT
};
-class SshEvent : public snort::DataEvent
+class SO_PUBLIC 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)
+ const std::string& version_str, const uint8_t direction,
+ const snort::Packet* packet, const char* login_direction, uint8_t ssh_version) :
+ event_type(event_type), result(result), version_str(version_str),
+ direction(direction), packet(packet), login_direction(login_direction),
+ ssh_version(ssh_version)
{ }
- 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() const override
- { return packet; }
+ SshEventType get_event_type() const;
+ SshValidationResult get_validation_result() const;
+ const std::string& get_version_str() const;
+ uint8_t get_direction() const;
+ const snort::Packet* get_packet() const override;
+ const char* get_login_direction() const;
+ uint8_t get_ssh_version() const;
private:
const SshEventType event_type;
const std::string version_str;
const uint8_t direction;
const snort::Packet* packet;
+ const char* login_direction;
+ const uint8_t ssh_version;
+};
+
+class SO_PUBLIC SshAlgoEvent : public snort::DataEvent
+{
+public:
+ union Algorithms
+ {
+ struct
+ {
+ const char* kex_algorithms;
+ const char* server_host_key_algorithms;
+ const char* encryption_algorithms_client_to_server;
+ const char* encryption_algorithms_server_to_client;
+ const char* mac_algorithms_client_to_server;
+ const char* mac_algorithms_server_to_client;
+ const char* compression_algorithms_client_to_server;
+ const char* compression_algorithms_server_to_client;
+ } named;
+ const char* unnamed[NUM_KEXINIT_LISTS];
+ };
+
+ SshAlgoEvent(const Algorithms& algos, uint8_t dir) : algos(algos), direction(dir)
+ { }
+
+ const char* get_kex_algorithms() const;
+ const char* get_server_host_key_algorithms() const;
+ const char* get_encryption_algorithms_client_to_server() const;
+ const char* get_encryption_algorithms_server_to_client() const;
+ const char* get_mac_algorithms_client_to_server() const;
+ const char* get_mac_algorithms_server_to_client() const;
+ const char* get_compression_algorithms_client_to_server() const;
+ const char* get_compression_algorithms_server_to_client() const;
+ uint8_t get_direction() const;
+
+private:
+ const Algorithms& algos;
+ uint8_t direction;
};
#endif
SOURCES
../eve_process_event.h
)
+add_cpputest( pub_sub_ssh_events_test
+ SOURCES
+ ../ssh_events.cc
+)
+
add_cpputest( pub_sub_http_transaction_end_event_test
SOURCES
../http_transaction_end_event.cc
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2024-2025 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.
+//--------------------------------------------------------------------------
+
+// pub_sub_ssh_events_test.cc author Cisco
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string>
+#include <cstring>
+
+#include "pub_sub/ssh_events.h"
+#include "service_inspectors/ssh/ssh.h"
+#include "protocols/packet.h"
+
+#include <CppUTest/CommandLineTestRunner.h>
+#include <CppUTest/TestHarness.h>
+
+using namespace snort;
+
+namespace snort
+{
+Packet::Packet(bool)
+ : flow(nullptr), packet_flags(0), xtradata_mask(0), proto_bits(0), alt_dsize(0), num_layers(0),
+ ip_proto_next(IpProtocol::PROTO_NOT_SET), disable_inspect(true), sect(PS_NONE), active_inst(nullptr), pkth(nullptr),
+ pkt(nullptr), layers(nullptr), user_inspection_policy_id(0), user_ips_policy_id(0), user_network_policy_id(0),
+ inspection_started_timestamp(0), vlan_idx(0), ts_packet_flags(0), allocated(false), daq_msg(nullptr)
+{ }
+Packet::~Packet() = default;
+}
+
+TEST_GROUP(pub_sub_ssh_events_test) { };
+
+TEST(pub_sub_ssh_events_test, ssh_algo_event_complete_access)
+{
+ const char* kex_algos = "Diffie-sha256,Diffie-sha512";
+ const char* host_key_algos = "SSH2-sha512,SSH1-sha256,SSHFP";
+ const char* cipher_c2s = "ctrk-SHA256,ctrl-sha512";
+ const char* cipher_s2c = "XSHA256,XSHA1";
+ const char* mac_c2s = "HMAC-SHA256,HMAC-sha512";
+ const char* mac_s2c = "HMAC-sha512,HMAC-SHA256";
+ const char* comp_c2s = "none,OpenSSH";
+ const char* comp_s2c = "none,OpenSSH";
+
+ SshAlgoEvent::Algorithms algos;
+ memset(&algos, 0, sizeof(algos));
+
+ algos.named.kex_algorithms = kex_algos;
+ algos.named.server_host_key_algorithms = host_key_algos;
+ algos.named.encryption_algorithms_client_to_server = cipher_c2s;
+ algos.named.encryption_algorithms_server_to_client = cipher_s2c;
+ algos.named.mac_algorithms_client_to_server = mac_c2s;
+ algos.named.mac_algorithms_server_to_client = mac_s2c;
+ algos.named.compression_algorithms_client_to_server = comp_c2s;
+ algos.named.compression_algorithms_server_to_client = comp_s2c;
+
+ SshAlgoEvent event(algos, PKT_FROM_CLIENT);
+
+ CHECK(event.get_direction() == PKT_FROM_CLIENT);
+
+ CHECK(event.get_kex_algorithms() == kex_algos);
+ CHECK(event.get_server_host_key_algorithms() == host_key_algos);
+ CHECK(event.get_encryption_algorithms_client_to_server() == cipher_c2s);
+ CHECK(event.get_encryption_algorithms_server_to_client() == cipher_s2c);
+ CHECK(event.get_mac_algorithms_client_to_server() == mac_c2s);
+ CHECK(event.get_mac_algorithms_server_to_client() == mac_s2c);
+ CHECK(event.get_compression_algorithms_client_to_server() == comp_c2s);
+ CHECK(event.get_compression_algorithms_server_to_client() == comp_s2c);
+}
+
+TEST(pub_sub_ssh_events_test, ssh_state_change_event)
+{
+ Packet test_packet;
+ const std::string version_str = "SSH-2.0-OpenSSH_8.0";
+
+ SshEvent inbound_event(SSH_VERSION_STRING, SSH_NOT_FINISHED, version_str,
+ PKT_FROM_CLIENT, &test_packet, "inbound", SSH_VERSION_2);
+
+ CHECK(inbound_event.get_event_type() == SSH_VERSION_STRING);
+ CHECK(inbound_event.get_validation_result() == SSH_NOT_FINISHED);
+ CHECK(inbound_event.get_version_str() == version_str);
+ CHECK(inbound_event.get_direction() == PKT_FROM_CLIENT);
+ CHECK(inbound_event.get_packet() == &test_packet);
+ STRCMP_EQUAL("inbound", inbound_event.get_login_direction());
+ CHECK(inbound_event.get_ssh_version() == SSH_VERSION_2);
+
+ SshEvent outbound_event(SSH_VALIDATION, SSH_VALID_KEXINIT, version_str,
+ PKT_FROM_SERVER, &test_packet, "outbound", SSH_VERSION_1);
+
+ CHECK(outbound_event.get_event_type() == SSH_VALIDATION);
+ CHECK(outbound_event.get_validation_result() == SSH_VALID_KEXINIT);
+ CHECK(outbound_event.get_direction() == PKT_FROM_SERVER);
+ CHECK(outbound_event.get_packet() == &test_packet);
+ STRCMP_EQUAL("outbound", outbound_event.get_login_direction());
+ CHECK(outbound_event.get_ssh_version() == SSH_VERSION_1);
+}
+
+int main(int argc, char** argv)
+{
+ return CommandLineTestRunner::RunAllTests(argc, argv);
+}
+set (SSH_INCLUDES
+ ssh_types.h
+)
+
set( FILE_LIST
ssh.cc
ssh.h
ssh_module.h
ssh_splitter.cc
ssh_splitter.h
+ ${SSH_INCLUDES}
)
if (STATIC_INSPECTORS)
add_dynamic_module(ssh inspectors ${FILE_LIST})
endif (STATIC_INSPECTORS)
+
+install(FILES ${SSH_INCLUDES}
+ DESTINATION "${INCLUDE_INSTALL_PATH}/service_inspectors/ssh/"
+)
if (!(sessp->state_flags & SSH_FLG_SESS_ENCRYPTED))
{
+ const char* login_direction = "";
// If server and client have not performed the protocol
// version exchange yet, must look for version strings.
if (!(sessp->state_flags & search_dir_ver))
{
+ const SfIp* server_ip = &p->flow->server_ip;
+ const SfIp* client_ip = &p->flow->client_ip;
+
+ if (server_ip->is_private() != client_ip->is_private())
+ {
+ login_direction = server_ip->is_private() ? "inbound" : "outbound";
+ }
+
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);
+ SshEvent event(SSH_VERSION_STRING, SSH_NOT_FINISHED, proto_string, pkt_direction, p, login_direction,
+ sessp->version);
DataBus::publish(pub_id, SshEventIds::STATE_CHANGE, event, p->flow);
}
else
{
- SshEvent event(SSH_VALIDATION, SSH_INVALID_VERSION, "", pkt_direction, p);
+ SshEvent event(SSH_VALIDATION, SSH_INVALID_VERSION, "", pkt_direction, p, login_direction,
+ sessp->version);
DataBus::publish(pub_id, SshEventIds::STATE_CHANGE, event, p->flow);
if (sessp->version == NON_SSH_TRAFFIC)
sessp->ssh_aborted = true;
}
if (keyx_valid)
{
- SshEvent event(SSH_VALIDATION, SSH_VALID_KEXINIT, "", pkt_direction, p);
+ SshEvent event(SSH_VALIDATION, SSH_VALID_KEXINIT, "", pkt_direction, p, login_direction,
+ sessp->version);
DataBus::publish(pub_id, SshEventIds::STATE_CHANGE, event, p->flow);
}
else
{
- SshEvent event(SSH_VALIDATION, SSH_INVALID_KEXINIT, "", pkt_direction, p);
+ SshEvent event(SSH_VALIDATION, SSH_INVALID_KEXINIT, "", pkt_direction, p, login_direction,
+ sessp->version);
DataBus::publish(pub_id, SshEventIds::STATE_CHANGE, event, p->flow);
sessp->state_flags |= SSH_FLG_SESS_ENCRYPTED;
}
default:
return false;
}
+
+ SshAlgoEvent::Algorithms algos;
+
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)
+ uint32_t list_length = ntohl(*((const uint32_t*)data));
+ uint32_t total_list_length = list_length + sizeof(uint32_t);
+
+ if (total_list_length > ssh_length or total_length + total_list_length > ssh_length)
{
DetectionEngine::queue_event(GID_SSH, SSH_EVENT_PAYLOAD_SIZE);
return false;
}
- total_length += list_length;
- data += list_length;
+
+ if (list_length)
+ algos.unnamed[i] = (const char*)(data + sizeof(uint32_t));
+
+ total_length += total_list_length;
+ data += total_list_length;
}
total_length += sizeof(SSHKeyExchangeFinal) + ssh_pkt->msg.plen;
if (total_length != ssh_length)
// using an unsupported future version
return false;
}
+
+ if (ssh_pkt->msg.code == SSH_MSG_KEXINIT)
+ {
+ uint8_t pkt_direction = direction == SSH_DIR_FROM_CLIENT ? PKT_FROM_CLIENT : PKT_FROM_SERVER;
+ SshAlgoEvent event(algos, pkt_direction);
+ DataBus::publish(pub_id, SshEventIds::ALGORITHM, event, p->flow);
+ }
+
return true;
}
#include "flow/flow.h"
#include "protocols/packet.h"
+#include "ssh_types.h"
// FIXIT-L move these to ssh.cc
// Session state flags for SSHData::state_flags
#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
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2024-2025 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_types.h - author Cisco
+
+#ifndef SSH_TYPES_H
+#define SSH_TYPES_H
+
+constexpr int NUM_KEXINIT_LISTS = 10;
+
+#endif