From: Mike Stepanek (mstepane) Date: Fri, 4 Oct 2019 18:01:56 +0000 (-0400) Subject: Merge pull request #1767 in SNORT/snort3 from ~MASHASAN/snort3:zone_fitering to master X-Git-Tag: 3.0.0-262~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=64e7d8c162c358f2527563d5ee9101572d2bbf7b;p=thirdparty%2Fsnort3.git Merge pull request #1767 in SNORT/snort3 from ~MASHASAN/snort3:zone_fitering to master Squashed commit of the following: commit c4da727760f9b485dd4cc83f936ed70efeeb2225 Author: Masud Hasan Date: Wed Sep 25 07:43:54 2019 -0400 discovery_filter: Supporting zone matching --- diff --git a/src/helpers/discovery_filter.cc b/src/helpers/discovery_filter.cc index 8e5f70ae2..552387b37 100644 --- a/src/helpers/discovery_filter.cc +++ b/src/helpers/discovery_filter.cc @@ -37,10 +37,6 @@ using namespace snort; using namespace std; -#define DF_APP "app" -#define DF_HOST "host" -#define DF_USER "user" - #define DF_APP_CHECKED 0x01 #define DF_APP_MONITORED 0x02 #define DF_HOST_CHECKED 0x04 @@ -57,23 +53,26 @@ DiscoveryFilter::DiscoveryFilter(const string& conf_path) if ( !in_stream ) return; + vartable = sfvt_alloc_table(); // create empty table when configuration is given uint32_t line_num = 0; - string line, config_type, config_key, config_value; while ( in_stream ) { - line = ""; + string line(""); getline(in_stream, line); ++line_num; if ( line.empty() or line.front() == '#' ) continue; istringstream line_stream(line); - config_type = config_key = config_value = ""; - line_stream >> config_type >> config_key >> config_value; + string config_type(""); + line_stream >> config_type; if ( config_type == "config" ) { + string config_key(""), config_value(""); + line_stream >> config_key >> config_value; + if ( config_key.empty() or config_value.empty() ) { WarningMessage("Discovery Filter: Empty configuration items at line %u from %s\n", @@ -81,30 +80,68 @@ DiscoveryFilter::DiscoveryFilter(const string& conf_path) continue; } + if ( config_key.find("Analyze", 0, sizeof("Analyze")-1) != 0 ) + continue; + + int64_t zone = DF_ANY_ZONE; + string config_zone(""); + line_stream >> config_zone; + if ( !config_zone.empty() and config_zone != "-1" ) + { + if (config_zone == "0") + zone = 0; + else + { + zone = strtol(config_zone.c_str(), nullptr, 0); + if ( zone < 1 or zone >= DF_ANY_ZONE ) + { + WarningMessage("Discovery Filter: Invalid zone at line %u from %s;" + " supported range is -1 (any) to %d\n", line_num, + conf_path.c_str(), DF_ANY_ZONE-1); + continue; + } + } + } + // host or user discovery will also enable application discovery if ( config_key == "AnalyzeApplication" ) { - add_ip(DF_APP, config_value); + add_ip(DF_APP, zone, config_value); } else if ( config_key == "AnalyzeHost" ) { - add_ip(DF_APP, config_value); - add_ip(DF_HOST, config_value); + add_ip(DF_APP, zone, config_value); + add_ip(DF_HOST, zone, config_value); } else if ( config_key == "AnalyzeUser" ) { - add_ip(DF_APP, config_value); - add_ip(DF_USER, config_value); + add_ip(DF_APP, zone, config_value); + add_ip(DF_USER, zone, config_value); } else if ( config_key == "AnalyzeHostUser" or config_key == "Analyze" ) { - add_ip(DF_APP, config_value); - add_ip(DF_HOST, config_value); - add_ip(DF_USER, config_value); + add_ip(DF_APP, zone, config_value); + add_ip(DF_HOST, zone, config_value); + add_ip(DF_USER, zone, config_value); } } } + // Merge any-zone rules to zone-based rules + for (int type = DF_APP; type < DF_MAX; ++type) + { + auto any_list = get_list((FilterType)type, DF_ANY_ZONE); + if (!any_list) + continue; + for (auto& zone_entry : zone_list[type]) + { + if (zone_entry.second != any_list and + sfvar_add(zone_entry.second, any_list) != SFIP_SUCCESS) + WarningMessage("Discovery Filter: Failed to add any network list " + "to zone network list for type %d", type); + } + } + in_stream.close(); } @@ -134,7 +171,7 @@ bool DiscoveryFilter::is_user_monitored(const Packet* p, uint8_t* flag) return is_monitored(p, DF_USER, *flag, DF_USER_CHECKED, DF_USER_MONITORED); } -bool DiscoveryFilter::is_monitored(const Packet* p, const char* type, uint8_t& flag, +bool DiscoveryFilter::is_monitored(const Packet* p, FilterType type, uint8_t& flag, uint8_t checked, uint8_t monitored) { if ( flag & checked ) @@ -152,7 +189,7 @@ bool DiscoveryFilter::is_monitored(const Packet* p, const char* type, uint8_t& f return false; } -bool DiscoveryFilter::is_monitored(const Packet* p, const char* type) +bool DiscoveryFilter::is_monitored(const Packet* p, FilterType type) { if ( !vartable ) return true; // when not configured, 'any' ip/port/zone are monitored by default @@ -161,58 +198,69 @@ bool DiscoveryFilter::is_monitored(const Packet* p, const char* type) // Keep an unordered map of where exclusion object holds pointers // to ip-list from vartable for each direction (src/dst) and protocol (tcp/udp). - // To do zone-based filtering, keep an unordered map of where - // the pointer refers to a list from vartable. The list itself should be created - // during parsing by appending zone id to the name of ip-list. Absence of the list - // for a particular zone means lookup the 'any' (-1) zone list. + if (zone_list[type].empty()) + return false; // the configuration did not have this type of rule - if ( !varip or strcmp(varip->name, type) ) - { - varip = sfvt_lookup_var(vartable, type); - if ( !varip ) - return false; - } + auto zone = p->pkth->ingress_group; + if ( zone == DAQ_PKTHDR_UNKNOWN or zone < 0 ) + zone = DF_ANY_ZONE; + auto varip = get_list(type, zone, true); + if (!varip and zone != DF_ANY_ZONE) + varip = get_list(type, DF_ANY_ZONE, true); - return sfvar_ip_in(varip, p->ptrs.ip_api.get_src()); // check source ip only + return sfvar_ip_in(varip, p->ptrs.ip_api.get_src()); // source ip only } -void DiscoveryFilter::add_ip(const char* name, string ip) +void DiscoveryFilter::add_ip(FilterType type, ZoneType zone, string& ip) { - if ( !vartable ) - vartable = sfvt_alloc_table(); - else if ( !varip or strcmp(varip->name, name) ) - varip = sfvt_lookup_var(vartable, name); - + auto varip = get_list(type, zone); if ( varip ) sfvt_add_to_var(vartable, varip, ip.c_str()); else { - ip = " " + ip; - ip = name + ip; - sfvt_add_str(vartable, ip.c_str(), &varip); + string named_ip = to_string((int)type); + named_ip += "_"; + named_ip += to_string(zone); + named_ip += " "; + named_ip += ip; + + if ( sfvt_add_str(vartable, named_ip.c_str(), &varip) == SFIP_SUCCESS ) + zone_list[type].emplace(zone, varip); } } +sfip_var_t* DiscoveryFilter::get_list(FilterType type, ZoneType zone, bool exclude_empty) +{ + auto& list = zone_list[type]; + auto entry = list.find(zone); + + // If head is empty and the boolean flag is true, treat every IP as excluded. The flag + // is not used during parsing when we are still building, it is used during searching. + if ( entry == list.end() or (exclude_empty and entry->second->head == nullptr) ) + return nullptr; + return entry->second; +} + #ifdef UNIT_TEST TEST_CASE("Discovery Filter", "[is_monitored]") { string conf("test.txt"); ofstream out_stream(conf.c_str()); out_stream << "config Error\n"; // invalid - out_stream << "config AnalyzeUser ::/0 3\n"; // any ipv6, zone 3 - out_stream << "config AnalyzeApplication 0.0.0.0/0 -1\n"; // any ipv4, any zone + out_stream << "config AnalyzeUser ::/0 0\n"; // any ipv6, zone 0 + out_stream << "config AnalyzeApplication 1.1.1.0/24 -1\n"; // targeted ipv4, any zone out_stream.close(); Packet p; SfIp ip; - ip.set("1.1.1.1"); + ip.set("1.1.1.1"); // zone 0 by default p.ptrs.ip_api.set(ip, ip); DiscoveryFilter df(conf); // Without flag - CHECK(df.is_app_monitored(&p, nullptr) == true); - CHECK(df.is_host_monitored(&p, nullptr) == false); - CHECK(df.is_user_monitored(&p, nullptr) == false); + CHECK(df.is_app_monitored(&p, nullptr) == true); // any zone rule for app is added to zone 0 + CHECK(df.is_host_monitored(&p, nullptr) == false); // no rule for host + CHECK(df.is_user_monitored(&p, nullptr) == false); // no any zone rule for user // With flag uint8_t flag = 0; @@ -236,4 +284,86 @@ TEST_CASE("Discovery Filter", "[is_monitored]") remove("test.txt"); } + +TEST_CASE("Discovery Filter Empty Configuration", "[is_monitored_config]") +{ + string conf("test_empty_analyze.txt"); + ofstream out_stream(conf.c_str()); + out_stream << "Error\n"; // invalid + out_stream << "config AnalyzeNothing ::/0 3\n"; // invalid + out_stream.close(); + + Packet p; + SfIp ip; + ip.set("1.1.1.1"); + p.ptrs.ip_api.set(ip, ip); + DiscoveryFilter df(conf); + + CHECK(df.is_app_monitored(&p, nullptr) == false); + CHECK(df.is_host_monitored(&p, nullptr) == false); + CHECK(df.is_user_monitored(&p, nullptr) == false); + + remove("test_empty_analyze.txt"); +} + +TEST_CASE("Discovery Filter Zone", "[is_monitored_zone_vs_ip]") +{ + string conf("test_zone_ip.txt"); + ofstream out_stream(conf.c_str()); + out_stream << "config AnalyzeHost 1.1.1.1 -1\n"; // zone any + out_stream << "config AnalyzeHost 1.1.1.2 0\n"; // zone 0 + out_stream << "config AnalyzeHost 1.1.1.3 2\n"; // zone 2 + out_stream << "config AnalyzeHost 1.1.1.4 -3\n"; // zone out of range + out_stream << "config AnalyzeHost 1.1.1.5 2147483648\n"; // zone out of range + out_stream << "config AnalyzeHost 1.1.1.6 kidding\n"; // zone invalid + out_stream.close(); + + Packet p; + SfIp ip1, ip2, ip3, ip4, ip5, ip6, ip7; + ip1.set("1.1.1.1"); + ip2.set("1.1.1.2"); + ip3.set("1.1.1.3"); + ip4.set("1.1.1.4"); + ip5.set("1.1.1.5"); + ip6.set("1.1.1.6"); + ip7.set("1.1.1.7"); + const DAQ_PktHdr_t* saved_hdr = p.pkth; + DAQ_PktHdr_t z_undefined, z1, z2; + z_undefined.ingress_group = DAQ_PKTHDR_UNKNOWN; + z1.ingress_group = 1; + z2.ingress_group = 2; + DiscoveryFilter df(conf); + + p.ptrs.ip_api.set(ip1, ip7); // ip from undefined zone matches zone any list + p.pkth = &z_undefined; + CHECK(df.is_app_monitored(&p, nullptr) == true); // analyze host enables application discovery + CHECK(df.is_host_monitored(&p, nullptr) == true); + CHECK(df.is_user_monitored(&p, nullptr) == false); + + p.pkth = &z2; // the ip is not in zone 2 list, but it is in zone any list + CHECK(df.is_host_monitored(&p, nullptr) == true); + + p.ptrs.ip_api.set(ip3, ip7); // the ip matches zone 2 list + CHECK(df.is_host_monitored(&p, nullptr) == true); + + p.pkth = &z1; // no zone 1 list and the ip is not in zone any list + CHECK(df.is_host_monitored(&p, nullptr) == false); + + p.ptrs.ip_api.set(ip1, ip7); // no zone 1 list, but the ip is in zone any list + CHECK(df.is_host_monitored(&p, nullptr) == true); + + p.pkth = saved_hdr; + p.ptrs.ip_api.set(ip2, ip7); // the ip matches zone 0 list + CHECK(df.is_host_monitored(&p, nullptr) == true); + + // no match since the configuration for these ip addresses were invalid + p.ptrs.ip_api.set(ip4, ip7); + CHECK(df.is_host_monitored(&p, nullptr) == false); + p.ptrs.ip_api.set(ip5, ip7); + CHECK(df.is_host_monitored(&p, nullptr) == false); + p.ptrs.ip_api.set(ip6, ip7); + CHECK(df.is_host_monitored(&p, nullptr) == false); + + remove("test_zone_ip.txt"); +} #endif diff --git a/src/helpers/discovery_filter.h b/src/helpers/discovery_filter.h index ec32c2d7f..36127750f 100644 --- a/src/helpers/discovery_filter.h +++ b/src/helpers/discovery_filter.h @@ -21,10 +21,17 @@ #ifndef DISCOVERY_FILTER_H #define DISCOVERY_FILTER_H +#include + #include "protocols/packet.h" #include "sfip/sf_ipvar.h" #include "sfip/sf_vartable.h" +enum FilterType { DF_APP, DF_HOST, DF_USER, DF_MAX }; + +typedef int32_t ZoneType; // matching daq header +#define DF_ANY_ZONE INT32_MAX + // Holds configurations to filter traffic discovery based network address, port, and zone class DiscoveryFilter { @@ -38,13 +45,14 @@ public: bool is_user_monitored(const snort::Packet* p, uint8_t* flag = nullptr); private: - bool is_monitored(const snort::Packet* p, const char* type, uint8_t& flag, + bool is_monitored(const snort::Packet* p, FilterType type, uint8_t& flag, uint8_t checked, uint8_t monitored); - bool is_monitored(const snort::Packet* p, const char* type); - void add_ip(const char* name, std::string ip); + bool is_monitored(const snort::Packet* p, FilterType type); + void add_ip(FilterType type, ZoneType zone, std::string& ip); + sfip_var_t* get_list(FilterType type, ZoneType zone, bool exclude_empty = false); + std::unordered_map zone_list[DF_MAX]; vartable_t* vartable = nullptr; - sfip_var_t* varip = nullptr; }; #endif diff --git a/src/main/modules.cc b/src/main/modules.cc index d449ed77c..15db0e043 100644 --- a/src/main/modules.cc +++ b/src/main/modules.cc @@ -1831,7 +1831,7 @@ bool RateFilterModule::end(const char*, int idx, SnortConfig* sc) static const Parameter single_rule_state_params[] = { { "action", Parameter::PT_ENUM, - "log | pass | alert | drop | block | reset | inherit", "inherit", + "log | pass | alert | drop | block | reset | react | reject | rewrite | inherit", "inherit", "apply action if rule matches or inherit from rule definition" }, { "enable", Parameter::PT_ENUM, "no | yes | inherit", "inherit", diff --git a/src/sfip/sf_ipvar.cc b/src/sfip/sf_ipvar.cc index eabc982cf..9ba09b483 100644 --- a/src/sfip/sf_ipvar.cc +++ b/src/sfip/sf_ipvar.cc @@ -418,8 +418,7 @@ static sfip_node_t* merge_lists(sfip_node_t* list1, sfip_node_t* list2, uint16_t return listHead; } -/* Deep copy of src added to dst */ -static SfIpRet sfvar_add(sfip_var_t* dst, sfip_var_t* src) +SfIpRet sfvar_add(sfip_var_t* dst, sfip_var_t* src) { sfip_var_t* copiedvar; @@ -722,6 +721,9 @@ SfIpRet sfvar_parse_iplist(vartable_t* table, sfip_var_t* var, neg_ip = !neg_ip; } + if (!*str) + return SFIP_ARG_ERR; + /* Find end of this token */ for (end = str+1; *end && !isspace((int)*end) && *end != LIST_CLOSE && *end != ','; diff --git a/src/sfip/sf_ipvar.h b/src/sfip/sf_ipvar.h index d45bb18db..357a06361 100644 --- a/src/sfip/sf_ipvar.h +++ b/src/sfip/sf_ipvar.h @@ -97,6 +97,9 @@ struct vartable_t uint32_t id; }; +/* Deep copy of src added to dst */ +SfIpRet sfvar_add(sfip_var_t* dst, sfip_var_t* src); + /* Creates a new variable that is an alias of another variable * Does a "deep" copy so it owns it's own pointers */ sfip_var_t* sfvar_deep_copy(const sfip_var_t*);