Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add network scanner detector #8910

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion include/Host.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ class Host : public GenericHashEntry,
char *msg;
} customHostAlert;

struct {
std::deque<time_t> tokens;
u_int32_t capacity;
time_t timeWindow;
} netscanFlow;

Mutex m;
u_int32_t mac_last_seen;
u_int8_t num_resolve_attempts;
Expand Down Expand Up @@ -227,7 +233,8 @@ class Host : public GenericHashEntry,
inline u_int32_t getNumBlacklistedAsSrvReset() const {
return getNumBlacklistedAsSrv() - getCheckpointBlacklistedAsSrv();
}

inline u_int32_t getNetscanTokens() { return (netscanFlow.tokens.size()); };
inline void resetNetscanTokens() { netscanFlow.tokens.clear(); };
inline bool isDhcpServer() const {
return (host_services_bitmap & (1 << HOST_IS_DHCP_SERVER));
}
Expand Down Expand Up @@ -543,6 +550,7 @@ class Host : public GenericHashEntry,
void updateSNMPAlertsCounter(time_t when, bool snmp_sent);
void updateSynAckAlertsCounter(time_t when, bool synack_sent);
void updateFinAckAlertsCounter(time_t when, bool finack_sent);
void networkScan(time_t t, IpAddress *_srv_ip);

virtual void updateNetworkRTT(u_int32_t rtt_msecs) { return; }
inline void updateRoundTripTime(u_int32_t rtt_msecs) {
Expand Down
26 changes: 26 additions & 0 deletions include/host_alerts/NetworkScannerAlert.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#ifndef _NETWORK_SCANNER_ALERT_H_
#define _NETWORK_SCANNER_ALERT_H_
#include "ntop_includes.h"

class NetworkScannerAlert : public FlowHitsAlert {
private:
u_int16_t num_flows_tokens;
u_int64_t threshold;
ndpi_serializer* getAlertJSON(ndpi_serializer* serializer);

public:
static HostAlertType getClassType(){
return { host_alert_network_scanner, alert_category_network };
}

NetworkScannerAlert(HostCheck *c, Host *h, risk_percentage cli_pctg,
u_int16_t _num_flows_tokens, u_int64_t threshold, bool is_attacker);

~NetworkScannerAlert() {};

HostAlertType getAlertType() const { return getClassType(); }

u_int8_t getAlertScore() const { return SCORE_LEVEL_WARNING; }
};

#endif /* _NETWORK_SCANNER_ALERT_H_ */
1 change: 1 addition & 0 deletions include/host_alerts_includes.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
#include "host_alerts/ScanDetectionAlert.h"
#include "host_alerts/TrafficVolumeAlert.h"
#include "host_alerts/HostScannerAlert.h"
#include "host_alerts/NetworkScannerAlert.h"

/* Pro Alerts - do NOT use #ifdef as alerts must always be available */

Expand Down
27 changes: 27 additions & 0 deletions include/host_checks/NetworkScanner.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#ifndef _NETWORKSCANNER_H_
#define _NETWORKSCANNER_H_

#include "ntop_includes.h"

class NetworkScanner : public HostCheck{
protected:
u_int64_t threshold;
public:
NetworkScanner();
~NetworkScanner(){};

NetworkScannerAlert *allocAlert(HostCheck* c, Host* h,
risk_percentage cli_pctg,
u_int16_t num_flows_tokens,
u_int64_t _threshold,
bool _is_attacker) {
return new NetworkScannerAlert(c, h, cli_pctg, num_flows_tokens, _threshold, _is_attacker);
};

bool loadConfiguration(json_object *config);
void periodicUpdate(Host *h, HostAlert *engaged_alert);
HostCheckID getID() const { return host_check_network_scanner; }
std::string getName() const { return (std::string("network_scanner")); }
};

#endif
1 change: 1 addition & 0 deletions include/host_checks_includes.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
#include "host_checks/UnexpectedGateway.h"
#include "host_checks/DomainNamesContacts.h"
#include "host_checks/ScanDetection.h"
#include "host_checks/NetworkScanner.h"

#ifdef NTOPNG_PRO
#include "host_checks/DNSFlood.h"
Expand Down
2 changes: 2 additions & 0 deletions include/ntop_typedefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,7 @@ typedef enum {
host_alert_external_script = 27, /* Triggered from Lua (see rest/v2/trigger/host/alert.lua) */
host_alert_host_scanner = 28,
host_alert_server_ports_contacts = 29,
host_alert_network_scanner = 30,

MAX_DEFINED_HOST_ALERT_TYPE, /* Leave it as last member */
MAX_HOST_ALERT_TYPE = 32 /* Constrained by HostAlertBitmap */
Expand Down Expand Up @@ -636,6 +637,7 @@ typedef enum {
host_check_rx_only_host_scan,
host_check_server_ports_contacts,
host_check_unexpected_gateway,
host_check_network_scanner,

NUM_DEFINED_HOST_CHECKS, /* Leave it as last member */
} HostCheckID;
Expand Down
3 changes: 3 additions & 0 deletions scripts/locales/en.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1577,6 +1577,9 @@ local lang = {
["top_srv"] = "Hosts as server with most alerts",
["top_vlan"] = "VLANs with most alerts",
},
["network_scanner_description"] = "Trigger an alert when a host is potentially performing network scanning",
["network_scanner_title"] = "Network Scanner detector",
["network_scanner_message"] = "Potentially a network scanner",
},
["alerts_thresholds_config"] = {
["active_local_hosts"] = "Local Hosts Alert",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
local host_alert_keys = require "host_alert_keys"
local json = require("dkjson")
local alert_creators = require "alert_creators"
local classes = require "classes"
local alert = require "alert"

local host_alert_network_scanner = classes.class(alert)

host_alert_network_scanner.meta = {
alert_key = host_alert_keys.host_alert_network_scanner,
i18n_title = "alerts_dashboard.network_scanner_title",
icon = "fas fa-fw fa-life-ring",
}

function host_alert_network_scanner:init(metric, value, operator, threshold)
self.super:init()
self.alert_type_params = alert_creators.createThresholdCross(metric, value, operator, threshold)
end

function host_alert_network_scanner.format(ifid, alert, alert_type_params)
local alert_consts = require("alert_consts")
local entity = alert_consts.formatHostAlert(ifid, alert["ip"], alert["vlan_id"])
local value = string.format("%u", math.ceil(alert_type_params.num_flows_tokens or 0))
return i18n("alerts_dashboard.http_contacts_message", {
entity = entity,
value = value,
threshold = alert_type_params.threshold or 0,
})
end

return host_alert_network_scanner
1 change: 1 addition & 0 deletions scripts/lua/modules/alert_keys/host_alert_keys.lua
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ local host_alert_keys = {
host_alert_external_script = 27,
host_alert_host_scanner = 28,
host_alert_server_ports_contacts = 29,
host_alert_network_scanner = 30,

-- NOTE: Keep in sync with HostAlertTypeEnum in ntop_typedefs.h
}
Expand Down
27 changes: 27 additions & 0 deletions scripts/lua/modules/check_definitions/host/network_scanner.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
local checks = require("checks")
local host_alert_keys = require "host_alert_keys"
local alert_consts = require("alert_consts")
local field_units = require "field_units"

local network_scanner = {
category = checks.check_categories.network,
severity = alert_consts.get_printable_severities().warning,
default_enabled = false,
alert_id = host_alert_keys.host_alert_network_scanner,
default_value = {
operator = "gt",
threshold = 5,
},
gui = {
i18n_title = "alerts_dashboard.network_scanner_title",
i18n_description = "alerts_dashboard.network_scanner_description",
i18n_field_unit = checks.field_units.count,
input_builder = "threshold_cross",
i18n_field_unit = field_units.flows,
field_max = 65535,
field_min = 1,
field_operator = "gt",
}
}

return network_scanner
2 changes: 2 additions & 0 deletions src/Flow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ Flow::Flow(NetworkInterface *_iface,
if(cli_host) {
cli_host->incUses(), cli_host->incNumFlows(last_seen, true, isTCP());
cli_host->incCliContactedPorts(_srv_port);
if(isTCP() || isUDP() || isICMP())
cli_host->networkScan(last_seen,srv_host->get_ip());
cli_ip_addr = cli_host->get_ip();
} else {
/*
Expand Down
16 changes: 15 additions & 1 deletion src/Host.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,8 @@ void Host::housekeep(time_t t) {
void Host::initialize(Mac *_mac, int32_t _iface_idx, u_int16_t _vlanId,
u_int16_t observation_point_id) {
if(_vlanId == (u_int16_t)-1) _vlanId = 0;

netscanFlow.capacity = 50;
netscanFlow.timeWindow = 2;
vlan_id = _vlanId & 0xFFF; /* Cleanup any possible junk */
iface_index = _iface_idx;
host_pool_id_is_from_mac = false;
Expand Down Expand Up @@ -1494,6 +1495,19 @@ void Host::incNumFlows(time_t t, bool as_client, bool isTCP) {

/* *************************************** */

void Host::networkScan(time_t t, IpAddress *_srv_ip){
while (!netscanFlow.tokens.empty() && (t - netscanFlow.tokens.front()) > netscanFlow.timeWindow) {
netscanFlow.tokens.pop_front();
}
if(_srv_ip->isPrivateAddress()){
if (netscanFlow.tokens.size() < netscanFlow.capacity) {
netscanFlow.tokens.push_back(t);
}
}
}

/* *************************************** */

void Host::decNumFlows(time_t t, bool as_client, bool isTCP,
u_int16_t isTwhOver) {
if(as_client)
Expand Down
1 change: 1 addition & 0 deletions src/HostChecksLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ void HostChecksLoader::registerChecks() {
if ((fcb = new DomainNamesContacts())) registerCheck(fcb);
if ((fcb = new ICMPFlood())) registerCheck(fcb);
if ((fcb = new ScanDetection())) registerCheck(fcb);
if ((fcb = new NetworkScanner())) registerCheck(fcb);

#ifdef NTOPNG_PRO
if ((fcb = new ScoreAnomaly())) registerCheck(fcb);
Expand Down
16 changes: 16 additions & 0 deletions src/host_alerts/NetworkScannerAlert.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include "host_alerts_includes.h"

NetworkScannerAlert::NetworkScannerAlert(HostCheck* c, Host* h, risk_percentage cli_pctg,
u_int16_t _num_flows_tokens, u_int64_t _threshold,
bool _is_attacker)
: FlowHitsAlert(c, h, cli_pctg,_num_flows_tokens, _threshold, _is_attacker) {
num_flows_tokens = _num_flows_tokens;
threshold = _threshold;
};

ndpi_serializer* NetworkScannerAlert::getAlertJSON(ndpi_serializer* serializer) {
if (serializer == NULL) return NULL;
ndpi_serialize_string_uint32(serializer, "num_flows_tokens", (u_int32_t)num_flows_tokens);
ndpi_serialize_string_uint64(serializer, "threshold", threshold);
return serializer;
}
28 changes: 28 additions & 0 deletions src/host_checks/NetworkScanner.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#include "ntop_includes.h"
#include "host_checks_includes.h"
NetworkScanner::NetworkScanner()
: HostCheck(ntopng_edition_community,
false /* All interfaces */,
false /* Don't exclude for nEdge */,
false /* NOT only for nEdge */){};

void NetworkScanner::periodicUpdate(Host *h, HostAlert *engaged_alert) {
HostAlert *alert = engaged_alert;
u_int32_t num_flows_tokens = 0;
num_flows_tokens = h->getNetscanTokens();
if (num_flows_tokens > threshold) {
if (!alert)
alert = allocAlert(this, h, CLIENT_FAIR_RISK_PERCENTAGE, num_flows_tokens, threshold,true);
if (alert) {
h->triggerAlert(alert);
h->resetNetscanTokens();
}
}
}
bool NetworkScanner::loadConfiguration(json_object *config) {
json_object *json_threshold;
HostCheck::loadConfiguration(config);
if (json_object_object_get_ex(config, "threshold", &json_threshold))
threshold = json_object_get_int64(json_threshold);
return (true);
}
Loading