From: Oleksandr Stepanov -X (ostepano - SOFTSERVE INC at Cisco) Date: Fri, 14 Mar 2025 17:47:47 +0000 (+0000) Subject: Pull request #4623: appid: added flag to enable inspection of ooo packets X-Git-Tag: 3.7.2.0~12 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f7b5c5283c1b8c2a3d2601b5129ffa05f4caa9f0;p=thirdparty%2Fsnort3.git Pull request #4623: appid: added flag to enable inspection of ooo packets Merge in SNORT/snort3 from ~OSTEPANO/snort3:ssl_ooo_ch to master Squashed commit of the following: commit ec43974fa2a3ddc6acf1716f6c1bec0fb5dad657 Author: Oleksandr Stepanov Date: Mon Feb 17 07:34:48 2025 -0500 appid: added flag to inspect ooo packets --- diff --git a/src/network_inspectors/appid/app_info_table.cc b/src/network_inspectors/appid/app_info_table.cc index 81390a3c9..8a8cfeaa9 100644 --- a/src/network_inspectors/appid/app_info_table.cc +++ b/src/network_inspectors/appid/app_info_table.cc @@ -616,6 +616,14 @@ void AppInfoManager::load_odp_config(OdpContext& odp_ctxt, const char* path) odp_ctxt.brute_force_inprocess_threshold = brute_force_inprocess_threshold; } } + else if (!(strcasecmp(conf_key, "inspect_ooo_flows"))) + { + if (!(strcasecmp(conf_val, "enabled"))) + { + odp_ctxt.inspect_ooo_flows = true; + continue; + } + } /* App Priority bit set*/ else if (!(strcasecmp(conf_key, "app_priority"))) { diff --git a/src/network_inspectors/appid/appid_config.cc b/src/network_inspectors/appid/appid_config.cc index 43d22929d..20b6a5d42 100644 --- a/src/network_inspectors/appid/appid_config.cc +++ b/src/network_inspectors/appid/appid_config.cc @@ -226,6 +226,7 @@ void OdpContext::dump_appid_config() APPID_LOG(nullptr, TRACE_INFO_LEVEL, "Appid Config: appid_cpu_profiler %s\n", (appid_cpu_profiler ? "True" : "False")); APPID_LOG(nullptr, TRACE_INFO_LEVEL, "Appid Config: brute_force_inprocess_threshold %" PRId8" \n", brute_force_inprocess_threshold); APPID_LOG(nullptr, TRACE_INFO_LEVEL, "Appid Config: failed_state_expiration_secs %" PRId32" \n", failed_state_expiration_secs); + APPID_LOG(nullptr, TRACE_INFO_LEVEL, "Appid Config: inspect_ooo_flows %s\n", inspect_ooo_flows ? "True" : "False"); } bool OdpContext::is_appid_cpu_profiler_running() diff --git a/src/network_inspectors/appid/appid_config.h b/src/network_inspectors/appid/appid_config.h index f121a99a9..9468fd12c 100644 --- a/src/network_inspectors/appid/appid_config.h +++ b/src/network_inspectors/appid/appid_config.h @@ -146,6 +146,7 @@ public: bool recheck_for_portservice_appid = false; bool eve_http_client = true; bool appid_cpu_profiler = true; + bool inspect_ooo_flows = false; uint8_t brute_force_inprocess_threshold = DEFAULT_BRUTE_FORCE_INPROCESS_STATE_THRESHOLD; uint16_t max_packet_before_service_fail = DEFAULT_MAX_PKTS_BEFORE_SERVICE_FAIL; uint16_t max_packet_service_fail_ignore_bytes = DEFAULT_MAX_PKT_BEFORE_SERVICE_FAIL_IGNORE_BYTES; @@ -160,6 +161,7 @@ public: uint32_t host_port_app_cache_lookup_range = 100000; uint64_t max_bytes_before_service_fail = DEFAULT_MAX_BYTES_BEFORE_SERVICE_FAIL; FirstPktAppIdDiscovered first_pkt_appid_prefix = NO_APPID_FOUND; + OdpContext(const AppIdConfig&, snort::SnortConfig*); void initialize(AppIdInspector& inspector); diff --git a/src/network_inspectors/appid/appid_discovery.cc b/src/network_inspectors/appid/appid_discovery.cc index 436832a6b..e9e147e1e 100644 --- a/src/network_inspectors/appid/appid_discovery.cc +++ b/src/network_inspectors/appid/appid_discovery.cc @@ -346,8 +346,8 @@ bool AppIdDiscovery::do_pre_discovery(Packet* p, AppIdSession*& asd, AppIdInspec // Shut off service/client discoveries, since they skip not-ok data packets and // may keep failing on subsequent data packets causing performance degradation - if (!asd->get_session_flags(APPID_SESSION_MID) or - (p->ptrs.sp != 21 and p->ptrs.dp != 21)) // exception for ftp-control + if ((!asd->get_session_flags(APPID_SESSION_MID) or + (p->ptrs.sp != 21 and p->ptrs.dp != 21)) and !odp_ctxt.inspect_ooo_flows) // exception for ftp-control { asd->service_disco_state = APPID_DISCO_STATE_FINISHED; if (asd->get_payload_id() == APP_ID_NONE and @@ -772,7 +772,7 @@ bool AppIdDiscovery::do_discovery(Packet* p, AppIdSession& asd, IpProtocol proto } } // FIXIT-M - snort 2.x has added a check for midstream pickup to this, do we need that? - else if (protocol != IpProtocol::TCP or (p->packet_flags & PKT_STREAM_ORDER_OK)) + else if (protocol != IpProtocol::TCP or ((p->packet_flags & PKT_STREAM_ORDER_OK) || asd.get_session_flags(APPID_SESSION_OOO))) { if (asd.service_disco_state != APPID_DISCO_STATE_FINISHED) is_discovery_done = diff --git a/src/network_inspectors/appid/service_plugins/service_ssl.cc b/src/network_inspectors/appid/service_plugins/service_ssl.cc index 266c29116..1ca43a334 100644 --- a/src/network_inspectors/appid/service_plugins/service_ssl.cc +++ b/src/network_inspectors/appid/service_plugins/service_ssl.cc @@ -68,6 +68,7 @@ public: int certs_curr_len = 0; // Current amount of collected certificate data. uint8_t* cached_data = nullptr; uint16_t cached_len = 0; + bool cached_client_data = false; }; #pragma pack(1) @@ -194,25 +195,25 @@ ServiceSSLData::~ServiceSSLData() ssl_cache_free(cached_data, cached_len); } -static void parse_client_initiation(const uint8_t* data, uint16_t size, ServiceSSLData* ss) +static ParseCHResult parse_client_initiation(const uint8_t* data, uint16_t size, ServiceSSLData* ss) { const ServiceSSLV3Hdr* hdr3; uint16_t ver; /* Sanity check header stuff. */ if (size < sizeof(ServiceSSLV3Hdr)) - return; + return ParseCHResult::FAILED; hdr3 = (const ServiceSSLV3Hdr*)data; ver = ntohs(hdr3->version); if (hdr3->type != SSL_HANDSHAKE || (ver != 0x0300 && ver != 0x0301 && ver != 0x0302 && ver != 0x0303)) { - return; + return ParseCHResult::FAILED; } data += sizeof(ServiceSSLV3Hdr); size -= sizeof(ServiceSSLV3Hdr); - parse_client_hello_data(data, size, &ss->client_hello); + return parse_client_hello_data(data, size, &ss->client_hello); } static void save_ssl_cache(ServiceSSLData* ss, uint16_t size, const uint8_t* data) @@ -247,14 +248,52 @@ int SslServiceDetector::validate(AppIdDiscoveryArgs& args) uint16_t size = args.size; uint8_t* reallocated_data = nullptr; - if (ss->cached_len and ss->cached_data and (args.dir == APP_ID_FROM_RESPONDER)) + if (!size) + goto inprocess; + + ss = (ServiceSSLData*)data_get(args.asd); + if (!ss) + { + ss = (ServiceSSLData*)snort_calloc(sizeof(ServiceSSLData)); + data_add(args.asd, ss); + ss->state = SSL_STATE_INITIATE; + ss->cached_data = nullptr; + ss->cached_len = 0; + } + + if ( args.asd.get_session_flags(APPID_SESSION_OOO) + and ss->state == SSL_STATE_INITIATE + and args.dir == APP_ID_FROM_INITIATOR + and !(args.asd.scan_flags & SCAN_CERTVIZ_ENABLED_FLAG)) + { + if (ss->cached_data) + { + reallocated_data = (uint8_t*)snort_calloc(ss->cached_len + size, sizeof(uint8_t)); + memcpy(reallocated_data, args.data, args.size); + memcpy(reallocated_data + args.size, ss->cached_data, ss->cached_len); + size = ss->cached_len + args.size; + ssl_cache_free(ss->cached_data, ss->cached_len); + data = reallocated_data; + } + else + { + save_ssl_cache(ss, size, data); + ss->cached_client_data = true; + goto inprocess; + } + } + + if (ss->cached_len and ss->cached_data) { - reallocated_data = (uint8_t*)snort_calloc(ss->cached_len + size, sizeof(uint8_t)); - memcpy(reallocated_data, ss->cached_data, ss->cached_len); - memcpy(reallocated_data + ss->cached_len, args.data, args.size); - size = ss->cached_len + args.size; - ssl_cache_free(ss->cached_data, ss->cached_len); - data = reallocated_data; + if ( (ss->cached_client_data and (args.dir == APP_ID_FROM_INITIATOR)) or (!ss->cached_client_data and (args.dir == APP_ID_FROM_RESPONDER)) ) + { + reallocated_data = (uint8_t*)snort_calloc(ss->cached_len + size, sizeof(uint8_t)); + memcpy(reallocated_data, ss->cached_data, ss->cached_len); + memcpy(reallocated_data + ss->cached_len, args.data, args.size); + size = ss->cached_len + args.size; + ssl_cache_free(ss->cached_data, ss->cached_len); + data = reallocated_data; + } } /* Start off with a Client Hello from client to server. */ if (ss->state == SSL_STATE_INITIATE) @@ -264,8 +303,18 @@ int SslServiceDetector::validate(AppIdDiscoveryArgs& args) if (!(args.asd.scan_flags & SCAN_CERTVIZ_ENABLED_FLAG) and args.dir == APP_ID_FROM_INITIATOR) { - parse_client_initiation(data, size, ss); - goto inprocess; + auto parse_status = parse_client_initiation(data, size, ss); + if (parse_status == ParseCHResult::FRAGMENTED_PACKET) + { + save_ssl_cache(ss, size, data); + ss->cached_client_data = true; + ss->state = SSL_STATE_INITIATE; + goto inprocess; + } + else if (parse_status == ParseCHResult::FAILED) + { + goto inprocess; + } } } @@ -343,6 +392,7 @@ int SslServiceDetector::validate(AppIdDiscoveryArgs& args) if (size < sizeof(ServiceSSLV3Hdr)) { save_ssl_cache(ss, size, data); + ss->cached_client_data = false; goto inprocess; } @@ -368,6 +418,7 @@ int SslServiceDetector::validate(AppIdDiscoveryArgs& args) if (size < offsetof(ServiceSSLV3Record, version)) { save_ssl_cache(ss, size, data); + ss->cached_client_data = false; goto inprocess; } @@ -498,12 +549,6 @@ fail: return APPID_NOMATCH; success: - if (reallocated_data) - { - snort_free(reallocated_data); - reallocated_data = nullptr; - } - if (ss->server_cert.certs_data && ss->server_cert.certs_len) { if (!(args.asd.scan_flags & SCAN_CERTVIZ_ENABLED_FLAG) and @@ -513,6 +558,12 @@ success: } } + if (reallocated_data) + { + snort_free(reallocated_data); + reallocated_data = nullptr; + } + args.asd.set_session_flags(APPID_SESSION_SSL_SESSION); if (ss->client_hello.host_name || ss->server_cert.common_name || ss->server_cert.org_unit) { @@ -613,3 +664,4 @@ bool is_service_over_ssl(AppId appId) return false; } + diff --git a/src/protocols/ssl.cc b/src/protocols/ssl.cc index fe6babea6..c146c8428 100644 --- a/src/protocols/ssl.cc +++ b/src/protocols/ssl.cc @@ -612,61 +612,61 @@ bool IsSSL(const uint8_t* ptr, int len, int pkt_flags) return false; } -void parse_client_hello_data(const uint8_t* pkt, uint16_t size, SSLV3ClientHelloData* client_hello_data) +ParseCHResult parse_client_hello_data(const uint8_t* pkt, uint16_t size, SSLV3ClientHelloData* client_hello_data) { if (client_hello_data == nullptr) - return; + return ParseCHResult::FAILED; if (size < sizeof(ServiceSSLV3Record)) - return; + return ParseCHResult::FAILED; const ServiceSSLV3Record* rec = (const ServiceSSLV3Record*)pkt; uint16_t ver = ntohs(rec->version); if (rec->type != SSLV3RecordType::CLIENT_HELLO || (ver != 0x0300 && ver != 0x0301 && ver != 0x0302 && ver != 0x0303) || rec->length_msb) { - return; + return ParseCHResult::FAILED; } unsigned length = ntohs(rec->length) + offsetof(ServiceSSLV3Record, version); if (size < length) - return; + return ParseCHResult::FRAGMENTED_PACKET; pkt += sizeof(ServiceSSLV3Record); size -= sizeof(ServiceSSLV3Record); /* Session ID (1-byte length). */ if (size < 1) - return; + return ParseCHResult::FAILED; length = *((const uint8_t*)pkt); pkt += length + 1; if (size < (length + 1)) - return; + return ParseCHResult::FAILED; size -= length + 1; /* Cipher Suites (2-byte length). */ if (size < 2) - return; + return ParseCHResult::FAILED; length = ntohs(*((const uint16_t*)pkt)); pkt += length + 2; if (size < (length + 2)) - return; + return ParseCHResult::FAILED; size -= length + 2; /* Compression Methods (1-byte length). */ if (size < 1) - return; + return ParseCHResult::FAILED; length = *((const uint8_t*)pkt); pkt += length + 1; if (size < (length + 1)) - return; + return ParseCHResult::FAILED; size -= length + 1; /* Extensions (2-byte length) */ if (size < 2) - return; + return ParseCHResult::FAILED; length = ntohs(*((const uint16_t*)pkt)); pkt += 2; size -= 2; if (size < length) - return; + return ParseCHResult::FAILED; /* We need at least type (2 bytes) and length (2 bytes) in the extension. */ while (length >= 4) @@ -676,25 +676,27 @@ void parse_client_hello_data(const uint8_t* pkt, uint16_t size, SSLV3ClientHello { /* Found server host name. */ if (length < sizeof(ServiceSSLV3ExtensionServerName)) - return; + return ParseCHResult::FAILED; unsigned len = ntohs(ext->string_length); if ((length - sizeof(ServiceSSLV3ExtensionServerName)) < len) - return; + return ParseCHResult::FAILED; const uint8_t* str = pkt + offsetof(ServiceSSLV3ExtensionServerName, string_length) + sizeof(ext->string_length); client_hello_data->host_name = snort_strndup((const char*)str, len); - return; + return ParseCHResult::SUCCESS; } unsigned len = ntohs(ext->length) + offsetof(ServiceSSLV3ExtensionServerName, list_length); if (len > length) - return; + return ParseCHResult::FAILED; pkt += len; length -= len; } + + return ParseCHResult::FAILED; } bool parse_server_certificates(SSLV3ServerCertData* server_cert_data) diff --git a/src/protocols/ssl.h b/src/protocols/ssl.h index c127e5496..67340dc72 100644 --- a/src/protocols/ssl.h +++ b/src/protocols/ssl.h @@ -306,12 +306,20 @@ struct ServiceSSLV3ExtensionServerName namespace snort { + +enum class ParseCHResult +{ + SUCCESS = 0, + FRAGMENTED_PACKET = 1, + FAILED = 2 +}; + SO_PUBLIC uint32_t SSL_decode( const uint8_t* pkt, int size, uint32_t pktflags, uint32_t prevflags, uint8_t* alert_flags, uint16_t* partial_rec_len, int hblen, uint32_t* info_flags = nullptr, SSLV3ClientHelloData* data = nullptr, SSLV3ServerCertData* server_cert_data = nullptr); - void parse_client_hello_data(const uint8_t* pkt, uint16_t size, SSLV3ClientHelloData*); + ParseCHResult parse_client_hello_data(const uint8_t* pkt, uint16_t size, SSLV3ClientHelloData*); bool parse_server_certificates(SSLV3ServerCertData* server_cert_data); SO_PUBLIC bool IsTlsClientHello(const uint8_t* ptr, const uint8_t* end);