From: Oleksandr Stepanov -X (ostepano - SOFTSERVE INC at Cisco) Date: Fri, 17 Jan 2025 15:16:38 +0000 (+0000) Subject: Pull request #4556: appid: adding thresholds to brute-force detection X-Git-Tag: 3.6.2.0~5 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=35440ac3b89545e8477229f9ce2eea7bb3a8d1ce;p=thirdparty%2Fsnort3.git Pull request #4556: appid: adding thresholds to brute-force detection Merge in SNORT/snort3 from ~OSTEPANO/snort3:brute_force_limits to master Squashed commit of the following: commit 8d5dd47ef76e699b6db9900599c2a9227710721d Author: Oleksandr Stepanov Date: Thu Dec 19 09:34:57 2024 -0500 appid: adding thresholds to brute-force detection --- diff --git a/src/network_inspectors/appid/app_info_table.cc b/src/network_inspectors/appid/app_info_table.cc index 739863eef..cbc9e0f3e 100644 --- a/src/network_inspectors/appid/app_info_table.cc +++ b/src/network_inspectors/appid/app_info_table.cc @@ -572,6 +572,50 @@ void AppInfoManager::load_odp_config(OdpContext& odp_ctxt, const char* path) max_packet_service_fail_ignore_bytes; } } + else if (!(strcasecmp(conf_key, "failed_state_expiration_secs"))) + { + int32_t brute_force_failed_state_expiration_secs = atoi(conf_val); + if (brute_force_failed_state_expiration_secs < MIN_BRUTE_FORCE_FAILED_EXPIRATION_SECS) + { + appid_log(nullptr, TRACE_WARNING_LEVEL, "appid: invalid " + "failed_state_expiration_secs %" PRIi32 ", must be greater than " + "%u.\n", brute_force_failed_state_expiration_secs, + MIN_BRUTE_FORCE_FAILED_EXPIRATION_SECS); + } + else if (brute_force_failed_state_expiration_secs > MAX_BRUTE_FORCE_FAILED_EXPIRATION_SECS) + { + appid_log(nullptr, TRACE_WARNING_LEVEL, "appid: invalid " + "failed_state_expiration_secs %" PRIi32 ", must be less than " + "%u.\n", brute_force_failed_state_expiration_secs, + MAX_BRUTE_FORCE_FAILED_EXPIRATION_SECS); + } + else + { + odp_ctxt.failed_state_expiration_secs = brute_force_failed_state_expiration_secs; + } + } + else if (!(strcasecmp(conf_key, "brute_force_inprocess_threshold"))) + { + int32_t brute_force_inprocess_threshold = atoi(conf_val); + if (brute_force_inprocess_threshold < MIN_BRUTE_FORCE_INPROCESS_STATE_THRESHOLD) + { + appid_log(nullptr, TRACE_WARNING_LEVEL, "appid: invalid " + "brute_force_inprocess_state_threshold %" PRIi32 ", must be greater than " + "%u.\n", brute_force_inprocess_threshold, + MIN_BRUTE_FORCE_INPROCESS_STATE_THRESHOLD); + } + else if (brute_force_inprocess_threshold > MAX_BRUTE_FORCE_INPROCESS_STATE_THRESHOLD) + { + appid_log(nullptr, TRACE_WARNING_LEVEL, "appid: invalid " + "brute_force_inprocess_state_threshold %" PRIi32 ", must be less than " + "%u.\n", brute_force_inprocess_threshold, + MAX_BRUTE_FORCE_INPROCESS_STATE_THRESHOLD); + } + else + { + odp_ctxt.brute_force_inprocess_threshold = brute_force_inprocess_threshold; + } + } /* 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 a63651683..9bbc238e6 100644 --- a/src/network_inspectors/appid/appid_config.cc +++ b/src/network_inspectors/appid/appid_config.cc @@ -224,6 +224,8 @@ void OdpContext::dump_appid_config() appid_log(nullptr, TRACE_INFO_LEVEL, "Appid Config: max_packet_service_fail_ignore_bytes %" PRIu16" \n", max_packet_service_fail_ignore_bytes); appid_log(nullptr, TRACE_INFO_LEVEL, "Appid Config: eve_http_client %s\n", (eve_http_client ? "True" : "False")); 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); } 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 c211b4901..3d89ba4ee 100644 --- a/src/network_inspectors/appid/appid_config.h +++ b/src/network_inspectors/appid/appid_config.h @@ -59,6 +59,14 @@ #define DEFAULT_MAX_BYTES_BEFORE_SERVICE_FAIL 4096 #define DEFAULT_MAX_PKTS_BEFORE_SERVICE_FAIL 5 #define DEFAULT_MAX_PKT_BEFORE_SERVICE_FAIL_IGNORE_BYTES 10 + +#define DEFAULT_FAILED_STATE_EXPIRATION_SECS 7200 +#define MIN_BRUTE_FORCE_FAILED_EXPIRATION_SECS 0 +#define MAX_BRUTE_FORCE_FAILED_EXPIRATION_SECS 86400 + +#define DEFAULT_BRUTE_FORCE_INPROCESS_STATE_THRESHOLD 5 +#define MIN_BRUTE_FORCE_INPROCESS_STATE_THRESHOLD 1 +#define MAX_BRUTE_FORCE_INPROCESS_STATE_THRESHOLD 50 enum SnortProtoIdIndex @@ -133,23 +141,25 @@ public: bool ftp_userid_disabled = false; bool chp_body_collection_disabled = false; bool need_reinspection = false; + bool tp_allow_probes = false; + bool allow_port_wildcard_host_cache = false; + bool recheck_for_portservice_appid = false; + bool eve_http_client = true; + bool appid_cpu_profiler = true; + 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; AppId first_pkt_service_id = 0; AppId first_pkt_payload_id = 0; AppId first_pkt_client_id = 0; uint32_t chp_body_collection_max = 0; uint32_t rtmp_max_packets = 15; uint32_t max_tp_flow_depth = 5; - bool tp_allow_probes = false; + uint32_t failed_state_expiration_secs = DEFAULT_FAILED_STATE_EXPIRATION_SECS; uint32_t host_port_app_cache_lookup_interval = 10; uint32_t host_port_app_cache_lookup_range = 100000; - bool allow_port_wildcard_host_cache = false; - bool recheck_for_portservice_appid = false; uint64_t max_bytes_before_service_fail = DEFAULT_MAX_BYTES_BEFORE_SERVICE_FAIL; - 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; FirstPktAppIdDiscovered first_pkt_appid_prefix = NO_APPID_FOUND; - bool eve_http_client = true; - bool appid_cpu_profiler = true; OdpContext(const AppIdConfig&, snort::SnortConfig*); void initialize(AppIdInspector& inspector); diff --git a/src/network_inspectors/appid/appid_inspector.cc b/src/network_inspectors/appid/appid_inspector.cc index 1a3d58f25..64bbe8dc1 100644 --- a/src/network_inspectors/appid/appid_inspector.cc +++ b/src/network_inspectors/appid/appid_inspector.cc @@ -192,6 +192,7 @@ void AppIdInspector::tinit() odp_thread_local_ctxt->initialize(SnortConfig::get_conf()); AppIdServiceState::initialize(config->memcap); + AppIdServiceState::set_service_thresholds(pkt_thread_odp_ctxt->failed_state_expiration_secs, pkt_thread_odp_ctxt->brute_force_inprocess_threshold); assert(!pkt_thread_tp_appid_ctxt); pkt_thread_tp_appid_ctxt = ctxt.get_tp_appid_ctxt(); if (pkt_thread_tp_appid_ctxt) diff --git a/src/network_inspectors/appid/appid_module.cc b/src/network_inspectors/appid/appid_module.cc index 8656484a1..573dcf5ae 100644 --- a/src/network_inspectors/appid/appid_module.cc +++ b/src/network_inspectors/appid/appid_module.cc @@ -262,6 +262,7 @@ bool ACOdpContextSwap::execute(Analyzer&, void**) ServiceDiscovery::set_thread_local_ftp_service(); AppIdContext& ctxt = inspector.get_ctxt(); OdpContext& current_odp_ctxt = ctxt.get_odp_ctxt(); + AppIdServiceState::set_service_thresholds(current_odp_ctxt.failed_state_expiration_secs, current_odp_ctxt.brute_force_inprocess_threshold); assert(pkt_thread_odp_ctxt != ¤t_odp_ctxt); pkt_thread_odp_ctxt = ¤t_odp_ctxt; diff --git a/src/network_inspectors/appid/service_plugins/service_discovery.cc b/src/network_inspectors/appid/service_plugins/service_discovery.cc index 23ee797b2..ceff934b5 100644 --- a/src/network_inspectors/appid/service_plugins/service_discovery.cc +++ b/src/network_inspectors/appid/service_plugins/service_discovery.cc @@ -435,12 +435,17 @@ int ServiceDiscovery::identify_service(AppIdSession& asd, Packet* p, sds->set_reset_time(0); ServiceState sds_state = sds->get_state(); - if ( ((sds_state == ServiceState::FAILED) or (sds_state == ServiceState::SEARCHING_BRUTE_FORCE)) and - asd.get_session_flags(APPID_SESSION_WAIT_FOR_EXTERNAL)) + if ( ((sds_state == ServiceState::FAILED) or (sds_state == ServiceState::SEARCHING_BRUTE_FORCE)) ) { - if (appidDebug->is_active()) - LogMessage("AppIdDbg %s No service match, waiting for external detection\n", appidDebug->get_debug_session()); - return APPID_INPROCESS; + if ( asd.get_session_flags(APPID_SESSION_WAIT_FOR_EXTERNAL) ) + { + if ( appidDebug->is_active() ) + LogMessage("AppIdDbg %s No service match, waiting for external detection\n", appidDebug->get_debug_session()); + return APPID_INPROCESS; + } + + if ( sds->check_and_expire_failed_state() ) + sds_state = sds->get_state(); } if ( sds_state == ServiceState::FAILED ) @@ -540,6 +545,16 @@ int ServiceDiscovery::identify_service(AppIdSession& asd, Packet* p, } } + /*Move brute force iterator if provided detector failed*/ + if ( ret > APPID_INPROCESS ) + { + if ( !sds ) + sds = AppIdServiceState::add(ip, proto, port, group, asd.asid, asd.is_decrypted(), true); + + if ( sds->get_state() == ServiceState::SEARCHING_BRUTE_FORCE ) + sds->next_brute_force(proto); + } + /* Failed all candidates, or no detector identified after seeing bidirectional exchange */ if ( got_fail_service or ( ( ret != APPID_INPROCESS ) and !asd.service_detector and ( dir == APP_ID_FROM_RESPONDER ) ) ) diff --git a/src/network_inspectors/appid/service_state.cc b/src/network_inspectors/appid/service_state.cc index eed404a69..eebfda923 100644 --- a/src/network_inspectors/appid/service_state.cc +++ b/src/network_inspectors/appid/service_state.cc @@ -38,6 +38,8 @@ using namespace snort; static THREAD_LOCAL MapList* service_state_cache = nullptr; +static THREAD_LOCAL uint32_t failed_state_expiration_threshold_secs = 0; +static THREAD_LOCAL uint8_t brute_force_inprocess_threshold = 0; const size_t MapList::sz = sizeof(ServiceDiscoveryState) + sizeof(Map_t::value_type) + sizeof(Queue_t::value_type); @@ -48,6 +50,8 @@ ServiceDiscoveryState::ServiceDiscoveryState() last_detract.clear(); last_invalid_client.clear(); reset_time = 0; + failed_timestamp = TIME_POINT_ZERO; + brute_force_inprocess_count = 0; } ServiceDiscoveryState::~ServiceDiscoveryState() @@ -63,14 +67,34 @@ ServiceDetector* ServiceDiscoveryState::select_detector_by_brute_force(IpProtoco { if ( !tcp_brute_force_mgr ) tcp_brute_force_mgr = new AppIdDetectorList(IpProtocol::TCP, sd); - service = tcp_brute_force_mgr->next(); + + if ( ++brute_force_inprocess_count >= brute_force_inprocess_threshold ) + { + service = tcp_brute_force_mgr->next(); + brute_force_inprocess_count = 0; + } + else + { + service = tcp_brute_force_mgr->current(); + } + appid_log(CURRENT_PACKET, TRACE_DEBUG_LEVEL, "Brute-force state %s\n", service? "" : "failed - no more TCP detectors"); } else if (proto == IpProtocol::UDP) { if ( !udp_brute_force_mgr ) udp_brute_force_mgr = new AppIdDetectorList(IpProtocol::UDP, sd); - service = udp_brute_force_mgr->next(); + + if ( ++brute_force_inprocess_count > brute_force_inprocess_threshold ) + { + service = udp_brute_force_mgr->next(); + brute_force_inprocess_count = 0; + } + else + { + service = udp_brute_force_mgr->current(); + } + appid_log(CURRENT_PACKET, TRACE_DEBUG_LEVEL, "Brute-force state %s\n", service? "" : "failed - no more UDP detectors"); } else @@ -86,6 +110,8 @@ void ServiceDiscoveryState::set_service_id_valid(ServiceDetector* sd) { service = sd; reset_time = 0; + failed_timestamp = TIME_POINT_ZERO; + brute_force_inprocess_count = 0; if ( state != ServiceState::VALID ) { state = ServiceState::VALID; @@ -171,6 +197,8 @@ void ServiceDiscoveryState::set_service_id_failed(AppIdSession& asd, const SfIp* state = SEARCHING_BRUTE_FORCE; else state = FAILED; + + failed_timestamp = SnortClock::now(); } } @@ -188,6 +216,42 @@ void ServiceDiscoveryState::update_service_incompatible(const SfIp* ip) } } +bool ServiceDiscoveryState::check_and_expire_failed_state() +{ + if ( failed_timestamp == TIME_POINT_ZERO or !failed_state_expiration_threshold_secs ) + return false; + + if ( clock_usecs(TO_USECS(SnortClock::now() - failed_timestamp)) > (failed_state_expiration_threshold_secs * 1000000) ) + { + reset(); + return true; + } + + return false; +} + +void ServiceDiscoveryState::reset() +{ + state = ServiceState::SEARCHING_PORT_PATTERN; + last_detract.clear(); + last_invalid_client.clear(); + reset_time = 0; + failed_timestamp = TIME_POINT_ZERO; + invalid_client_count = 0; + valid_count = 0; + detract_count = 0; + service = nullptr; + brute_force_inprocess_count = 0; + + if ( tcp_brute_force_mgr ) + delete tcp_brute_force_mgr; + if ( udp_brute_force_mgr ) + delete udp_brute_force_mgr; + + tcp_brute_force_mgr = nullptr; + udp_brute_force_mgr = nullptr; +} + bool AppIdServiceState::initialize(size_t memcap) { if ( !service_state_cache ) @@ -201,10 +265,18 @@ bool AppIdServiceState::initialize(size_t memcap) return false; } +void AppIdServiceState::set_service_thresholds(uint32_t _failed_state_expiration_threshold_secs, uint8_t _brute_force_inprocess_threshold) +{ + failed_state_expiration_threshold_secs = _failed_state_expiration_threshold_secs; + brute_force_inprocess_threshold = _brute_force_inprocess_threshold; +} + void AppIdServiceState::clean() { delete service_state_cache; service_state_cache = nullptr; + failed_state_expiration_threshold_secs = 0; + brute_force_inprocess_threshold = 0; } ServiceDiscoveryState* AppIdServiceState::add(const SfIp* ip, IpProtocol proto, uint16_t port, diff --git a/src/network_inspectors/appid/service_state.h b/src/network_inspectors/appid/service_state.h index fd228b446..b5b6cfc8d 100644 --- a/src/network_inspectors/appid/service_state.h +++ b/src/network_inspectors/appid/service_state.h @@ -27,6 +27,7 @@ #include "protocols/protocol_ids.h" #include "sfip/sf_ip.h" +#include "time/clock_defs.h" #include "utils/cpp_macros.h" #include "utils/util.h" @@ -65,11 +66,20 @@ public: { ServiceDetector* detector = nullptr; - if ( dit != detectors->end()) + if ( dit != detectors->end() ) detector = (ServiceDetector*)(dit++)->second; return detector; } + ServiceDetector* current() + { + ServiceDetector* detector = nullptr; + + if ( dit != detectors->end() ) + detector = (ServiceDetector*)(dit)->second; + return detector; + } + void reset() { dit = detectors->begin(); @@ -90,6 +100,7 @@ public: void set_service_id_failed(AppIdSession& asd, const snort::SfIp* client_ip, unsigned invalid_delta = 0); void update_service_incompatible(const snort::SfIp* ip); + bool check_and_expire_failed_state(); ServiceState get_state() const { @@ -121,9 +132,20 @@ public: reset_time = resetTime; } + void next_brute_force(IpProtocol proto) + { + if ( proto == IpProtocol::TCP ) + tcp_brute_force_mgr->next(); + else if (proto == IpProtocol::UDP ) + udp_brute_force_mgr->next(); + brute_force_inprocess_count = 0; + } + Queue_t::iterator qptr; // Our place in service_state_queue private: + void reset(); + ServiceState state; ServiceDetector* service = nullptr; AppIdDetectorList* tcp_brute_force_mgr = nullptr; @@ -141,12 +163,15 @@ private: */ snort::SfIp last_invalid_client; time_t reset_time; + SnortClock::time_point failed_timestamp; + uint8_t brute_force_inprocess_count; }; class AppIdServiceState { public: static bool initialize(size_t memcap); + static void set_service_thresholds(uint32_t _failed_state_expiration_threshold_secs, uint8_t _brute_force_inprocess_threshold); static void clean(); static ServiceDiscoveryState* add(const snort::SfIp*, IpProtocol, uint16_t port, int16_t group, uint32_t asid, bool decrypted, bool do_touch = false); diff --git a/src/network_inspectors/appid/test/service_state_test.cc b/src/network_inspectors/appid/test/service_state_test.cc index 3bbfebfce..c228e5810 100644 --- a/src/network_inspectors/appid/test/service_state_test.cc +++ b/src/network_inspectors/appid/test/service_state_test.cc @@ -168,11 +168,13 @@ TEST_GROUP(service_state_tests) { appidDebug = new AppIdDebug(); appidDebug->activate(nullptr, nullptr, false); + AppIdServiceState::set_service_thresholds(0, 1); } void teardown() override { delete appidDebug; + AppIdServiceState::clean(); } }; diff --git a/src/time/clock_defs.h b/src/time/clock_defs.h index 2eb9fa53c..bd2a482ed 100644 --- a/src/time/clock_defs.h +++ b/src/time/clock_defs.h @@ -23,6 +23,7 @@ #ifdef USE_TSC_CLOCK #include "time/tsc_clock.h" using SnortClock = TscClock; +#define TIME_POINT_ZERO 0 #define CLOCK_ZERO 0 #define DURA_ZERO 0 #define TO_TICKS(t) (t) @@ -36,6 +37,7 @@ using SnortClock = TscClock; using hr_clock = std::chrono::high_resolution_clock; using SnortClock = hr_clock; inline long clock_scale() { return 1; } +#define TIME_POINT_ZERO SnortClock::time_point::min() #define CLOCK_ZERO 0_ticks #define DURA_ZERO Clock::duration::zero() #define TO_TICKS(t) (t.count())