]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #4556: appid: adding thresholds to brute-force detection
authorOleksandr Stepanov -X (ostepano - SOFTSERVE INC at Cisco) <ostepano@cisco.com>
Fri, 17 Jan 2025 15:16:38 +0000 (15:16 +0000)
committerChris Sherwin (chsherwi) <chsherwi@cisco.com>
Fri, 17 Jan 2025 15:16:38 +0000 (15:16 +0000)
Merge in SNORT/snort3 from ~OSTEPANO/snort3:brute_force_limits to master

Squashed commit of the following:

commit 8d5dd47ef76e699b6db9900599c2a9227710721d
Author: Oleksandr Stepanov <ostepano@cisco.com>
Date:   Thu Dec 19 09:34:57 2024 -0500

    appid: adding thresholds to brute-force detection

src/network_inspectors/appid/app_info_table.cc
src/network_inspectors/appid/appid_config.cc
src/network_inspectors/appid/appid_config.h
src/network_inspectors/appid/appid_inspector.cc
src/network_inspectors/appid/appid_module.cc
src/network_inspectors/appid/service_plugins/service_discovery.cc
src/network_inspectors/appid/service_state.cc
src/network_inspectors/appid/service_state.h
src/network_inspectors/appid/test/service_state_test.cc
src/time/clock_defs.h

index 739863eef86884064219c42f8c903b93d93b07c8..cbc9e0f3e53271953167c4bd484005ad3e10453b 100644 (file)
@@ -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")))
             {
index a636516838046243acbab448fc28f6f2512cd1c6..9bbc238e6a99d80d923f346014d801337172527a 100644 (file)
@@ -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()
index c211b4901eccf9c2ee2e3d3b0a92c773e7d05c6c..3d89ba4eebf1fe5f3dc37761173e00061ea0692d 100644 (file)
 #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);
index 1a3d58f252b9ba91d9b2edf206373f4608c98749..64bbe8dc1b35c3efdad620fd38c63de8636645e4 100644 (file)
@@ -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)
index 8656484a1d8e03f9b09272e6ef1246ce467e911c..573dcf5ae1b1579a4f63de5d33fa9a663c4f9ce6 100644 (file)
@@ -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 != &current_odp_ctxt);
     pkt_thread_odp_ctxt = &current_odp_ctxt;
 
index 23ee797b21622d6bb409823f7e9725fde39abed7..ceff934b503b8b89b67277a68626003eaea7b22b 100644 (file)
@@ -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 ) ) )
index eed404a694b46fcf97b44b935679bb1fb8548371..eebfda9234677c232d02f72c1c45d7ea0bc0f165 100644 (file)
@@ -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,
index fd228b446bcfc61a2446deee7fece59be0683471..b5b6cfc8de94bd4243861bc3b0131944bf54f82f 100644 (file)
@@ -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);
index 3bbfebfce64c156979f2ab994189a70f54b9b426..c228e58103e364a3b1a55fb1be0998ecdd156305 100644 (file)
@@ -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();
     }
 };
 
index 2eb9fa53cacac74d0f23e2f73168a1f071f8ca45..bd2a482edf44cf2753c7681ecbcd9be8d701a2db 100644 (file)
@@ -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())