From: Mike Stepanek (mstepane) Date: Tue, 8 Oct 2019 19:59:19 +0000 (-0400) Subject: Merge pull request #1781 in SNORT/snort3 from ~SMINUT/snort3:port_filtering to master X-Git-Tag: 3.0.0-262~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9fa51bab33af421a3f5c54dd3a7b4a1bf339de0d;p=thirdparty%2Fsnort3.git Merge pull request #1781 in SNORT/snort3 from ~SMINUT/snort3:port_filtering to master Squashed commit of the following: commit 2c0edc886d3066a8543de6df6e9fd80cea677905 Author: Silviu Minut Date: Fri Oct 4 16:23:21 2019 -0400 helpers: implement port exclusion in discovery filter. --- diff --git a/src/helpers/discovery_filter.cc b/src/helpers/discovery_filter.cc index 552387b37..3fadcd432 100644 --- a/src/helpers/discovery_filter.cc +++ b/src/helpers/discovery_filter.cc @@ -25,10 +25,12 @@ #include "discovery_filter.h" #include +#include #include #include #include "log/messages.h" +#include "protocols/protocol_ids.h" #ifdef UNIT_TEST #include "catch/snort_catch.h" @@ -125,6 +127,50 @@ DiscoveryFilter::DiscoveryFilter(const string& conf_path) add_ip(DF_USER, zone, config_value); } } + else if ( config_type == "portexclusion" ) + { + string dir_str, proto_str, port_str, ip; + line_stream >> dir_str >> proto_str >> port_str >> ip; + + uint16_t port = strtol(port_str.c_str(), NULL, 10); + if ( port == 0 ) + { + WarningMessage("Discovery Filter: Invalid port at line %u from %s;", + line_num, conf_path.c_str()); + continue; + } + + protoent* pt = getprotobyname(proto_str.c_str()); + if ( pt == nullptr ) + { + WarningMessage("Discovery Filter: Invalid protocol at line %u from %s;", + line_num, conf_path.c_str()); + continue; + } + + // Port exclusion is done from a session standpoint rather than + // from a packet standpoint. An illustrative example is the + // "Discovery Filter Port Exclusion" test. + + if ( dir_str == "dst" ) + add_ip(Direction::SERVER, (uint16_t) pt->p_proto, port, ip); + else if ( dir_str == "src" ) + add_ip(Direction::CLIENT, (uint16_t) pt->p_proto, port, ip); + else if ( dir_str == "both" ) + { + add_ip(Direction::SERVER, (uint16_t) pt->p_proto, port, ip); + add_ip(Direction::CLIENT, (uint16_t) pt->p_proto, port, ip); + } + else + { + WarningMessage("Discovery Filter: Invalid direction %s at line %u from %s;" + " supported values are src and dst\n", dir_str.c_str(), + line_num, conf_path.c_str()); + continue; + } + + } + } // Merge any-zone rules to zone-based rules @@ -133,7 +179,7 @@ DiscoveryFilter::DiscoveryFilter(const string& conf_path) auto any_list = get_list((FilterType)type, DF_ANY_ZONE); if (!any_list) continue; - for (auto& zone_entry : zone_list[type]) + for (auto& zone_entry : zone_ip_list[type]) { if (zone_entry.second != any_list and sfvar_add(zone_entry.second, any_list) != SFIP_SUCCESS) @@ -194,11 +240,12 @@ bool DiscoveryFilter::is_monitored(const Packet* p, FilterType type) if ( !vartable ) return true; // when not configured, 'any' ip/port/zone are monitored by default - // Do port-based filtering first, which is independent of application/host/user 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). + // port exclusion + if ( is_port_excluded(p) ) + return false; - if (zone_list[type].empty()) + // check zone + if (zone_ip_list[type].empty()) return false; // the configuration did not have this type of rule auto zone = p->pkth->ingress_group; @@ -211,6 +258,53 @@ bool DiscoveryFilter::is_monitored(const Packet* p, FilterType type) return sfvar_ip_in(varip, p->ptrs.ip_api.get_src()); // source ip only } +bool DiscoveryFilter::is_port_excluded(const snort::Packet* p) +{ + // Port exclusion: if the ip is in the port x protocol list, return true. + uint32_t key; + const SfIp* ip; + uint16_t port; + auto proto = p->ptrs.ip_api.proto(); + + if ( port_ip_list[Direction::CLIENT].empty() and port_ip_list[Direction::SERVER].empty() ) + return false; + + if ( !(proto == IpProtocol::TCP or proto == IpProtocol::UDP) or + p->ptrs.sp == 0 or p->ptrs.dp == 0 ) + return false; + + if ( p->is_from_client() ) + { + port = p->ptrs.sp; + ip = p->ptrs.ip_api.get_src(); + key = proto_port_key(to_utype(proto), port); + if ( sfvar_ip_in(get_port_list(Direction::CLIENT, key), ip) ) + return true; + + port = p->ptrs.dp; + ip = p->ptrs.ip_api.get_dst(); + key = proto_port_key(to_utype(proto), port); + if ( sfvar_ip_in(get_port_list(Direction::SERVER, key), ip) ) + return true; + } + else if ( p->is_from_server() ) + { + port = p->ptrs.dp; + ip = p->ptrs.ip_api.get_dst(); + key = proto_port_key(to_utype(proto), port); + if ( sfvar_ip_in(get_port_list(Direction::CLIENT, key), ip) ) + return true; + + port = p->ptrs.sp; + ip = p->ptrs.ip_api.get_src(); + key = proto_port_key(to_utype(proto), port); + if ( sfvar_ip_in(get_port_list(Direction::SERVER, key), ip) ) + return true; + } + + return false; +} + void DiscoveryFilter::add_ip(FilterType type, ZoneType zone, string& ip) { auto varip = get_list(type, zone); @@ -225,13 +319,13 @@ void DiscoveryFilter::add_ip(FilterType type, ZoneType zone, string& ip) named_ip += ip; if ( sfvt_add_str(vartable, named_ip.c_str(), &varip) == SFIP_SUCCESS ) - zone_list[type].emplace(zone, varip); + zone_ip_list[type].emplace(zone, varip); } } sfip_var_t* DiscoveryFilter::get_list(FilterType type, ZoneType zone, bool exclude_empty) { - auto& list = zone_list[type]; + auto& list = zone_ip_list[type]; auto entry = list.find(zone); // If head is empty and the boolean flag is true, treat every IP as excluded. The flag @@ -241,7 +335,43 @@ sfip_var_t* DiscoveryFilter::get_list(FilterType type, ZoneType zone, bool exclu return entry->second; } +void DiscoveryFilter::add_ip(Direction dir, uint16_t proto, uint16_t port, const string& ip) +{ + uint32_t key = proto_port_key(proto, port); + + // find it in the local cache first: + auto varip = get_port_list(dir, key); + if ( varip ) + sfvt_add_to_var(vartable, varip, ip.c_str()); + else + { + string named_ip = to_string(dir); + named_ip += "_"; + named_ip += to_string(proto); + named_ip += "_"; + named_ip += to_string(port); + named_ip += " "; + named_ip += ip; + + if ( sfvt_add_str(vartable, named_ip.c_str(), &varip) == SFIP_SUCCESS ) + port_ip_list[dir].emplace(key, varip); + } +} + +sfip_var_t* DiscoveryFilter::get_port_list(Direction dir, uint32_t key) +{ + auto& list = port_ip_list[dir]; + auto entry = list.find(key); + return entry == list.end() ? nullptr : entry->second; +} + #ifdef UNIT_TEST + +bool is_port_excluded_test(DiscoveryFilter& df, Packet* p) +{ + return df.is_port_excluded(p); +} + TEST_CASE("Discovery Filter", "[is_monitored]") { string conf("test.txt"); @@ -366,4 +496,89 @@ TEST_CASE("Discovery Filter Zone", "[is_monitored_zone_vs_ip]") remove("test_zone_ip.txt"); } + +TEST_CASE("Discovery Filter Port Exclusion", "[portexclusion]") +{ + uint16_t a_port = 1234; + uint16_t b_port = 80; + + string a_ip_str = "10.0.0.1"; + SfIp aip; + aip.set(a_ip_str.c_str()); + + string b_ip_str = "10.0.0.2"; + SfIp bip; + bip.set(b_ip_str.c_str()); + + ip::IP4Hdr ab_hdr; // A -> B IPV4 header + ip::IP4Hdr ba_hdr; // B -> A IPV4 header + + ab_hdr.ip_proto = IpProtocol::TCP; + ab_hdr.ip_src = aip.get_ip4_value(); + ab_hdr.ip_dst = bip.get_ip4_value(); + + ba_hdr.ip_proto = IpProtocol::TCP; + ba_hdr.ip_src = bip.get_ip4_value(); + ba_hdr.ip_dst = aip.get_ip4_value(); + + string conf("discovery_filter.conf"); + ofstream out(conf.c_str()); + + // portexclusion dst tcp 80 10.0.0.2" + // + // Exclude traffic outgoing to or returning from 10.0.0.2:80, i.e. traffic + // in which 10.0.0.2 is the responder (server). + // + // This will not exclude traffic initiated by 10.0.0.2 from port 80 though. + + out << "portexclusion dst tcp " << b_port << " " << b_ip_str << endl; + out.close(); + + DiscoveryFilter df(conf); + + Packet p; + p.ptrs.type = PktType::TCP; + + // Positive test: A = initiator (client), B = responder (server) + // exclude A <-> B:b_port traffic + + // A -> B:b_port + p.ptrs.ip_api.set(&ab_hdr); + p.ptrs.sp = a_port; + p.ptrs.dp = b_port; + p.packet_flags = 0x0; + p.packet_flags |= PKT_FROM_CLIENT; + CHECK(is_port_excluded_test(df, &p) == true); + + // A:any <- B:b_port + p.ptrs.ip_api.set(&ba_hdr); + p.ptrs.sp = b_port; + p.ptrs.dp = a_port; + p.packet_flags = 0x0; + p.packet_flags |= PKT_FROM_SERVER; + CHECK(is_port_excluded_test(df, &p) == true); + + + // Negative test: B = initiator (client), A = responder (server) + // do not exclude A <-> B:b_port + + // A <- B:b_port + p.ptrs.ip_api.set(&ba_hdr); + p.ptrs.sp = b_port; + p.ptrs.dp = a_port; + p.packet_flags = 0x0; + p.packet_flags |= PKT_FROM_CLIENT; + CHECK(is_port_excluded_test(df, &p) == false); + + // A -> B:b_port + p.ptrs.ip_api.set(&ab_hdr); + p.ptrs.sp = a_port; + p.ptrs.dp = b_port; + p.packet_flags = 0x0; + p.packet_flags |= PKT_FROM_SERVER; + CHECK(is_port_excluded_test(df, &p) == false); + + remove(conf.c_str()); +} + #endif diff --git a/src/helpers/discovery_filter.h b/src/helpers/discovery_filter.h index 36127750f..3335772e8 100644 --- a/src/helpers/discovery_filter.h +++ b/src/helpers/discovery_filter.h @@ -45,14 +45,36 @@ public: bool is_user_monitored(const snort::Packet* p, uint8_t* flag = nullptr); private: + + enum Direction { CLIENT, SERVER, NUM_DIRECTIONS }; + 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, 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]; + // add ip for port exclusion + void add_ip(Direction dir, uint16_t proto, uint16_t port, const std::string& ip); + sfip_var_t* get_port_list(Direction dir, uint32_t key); + + inline uint32_t proto_port_key(uint16_t proto, uint16_t port) const + { + return (proto << 16) | port; + } + + bool is_port_excluded(const snort::Packet* p); + + std::unordered_map zone_ip_list[DF_MAX]; vartable_t* vartable = nullptr; + + // Internal cache for sfip_var_t indexed by protocol x port, for port + // exclusion. + std::unordered_map port_ip_list[NUM_DIRECTIONS]; + +#ifdef UNIT_TEST + friend bool is_port_excluded_test(DiscoveryFilter& df, snort::Packet* p); +#endif }; #endif