[VS] Fix scan duration column sort and add check on scan false positive. (#8038) (#8039)

This commit is contained in:
Nicolo Maio 2023-11-21 18:40:48 +01:00
parent 9668851b9c
commit 2519a8acb2
6 changed files with 211 additions and 109 deletions

View file

@ -74,6 +74,7 @@ local vs_rest_utils = require("vs_rest_utils")
-- redis-cli set "ntopng.prefs.vs.debug_enabled" "1"
-- systemctl restart ntopng
local debug_me = ntop.getCache("ntopng.prefs.vs.debug_enabled") == "1"
local test_me = false
local verbose = false
local vs_utils = {}
@ -282,23 +283,91 @@ end
-- ##############################################
local function analyze_ports_diff(ports_difference)
local function save_last_result(scan_result, scan_type,host,epoch)
if(scan_result ~= nil and not ntop.isClickHouseEnabled()) then
local handle = io.open(get_report_path(scan_type, host), "a")
local result = handle:write("\n"..i18n("hosts_stats.page_scan_hosts.inconsistency_state").."\n"..scan_result)
handle:close()
end
if (scan_result ~= nil and ntop.isClickHouseEnabled()) then
vs_db_utils.update_last_result(scan_result, scan_type, host, epoch)
end
end
-- ##############################################
local function verify_status_ports(possible_changed_ports, host, scan_type, open_ports_case, epoch)
local real_ports = {}
local scan_module = vs_utils.load_module(scan_type)
if (test_me) then
tprint("TROVATA inconsistent")
ntop.msleep(10000)
tprint("RESCANNING")
end
for _,possible_changed_port in ipairs(possible_changed_ports) do
local now,result,duration,scan_result,num_open_ports,num_vulnerabilities_found, cve, udp_ports, tcp_ports = scan_module:scan_host(host, possible_changed_port)
save_last_result(result, scan_type, host, epoch)
if debug_me then
tprint("SCANNED AGAIN HOST: "..host.." ON PORT: "..possible_changed_port)
end
if (open_ports_case and num_open_ports > 0) then
-- case open port and num_open_ports is > 0 so is a real open port
real_ports[#real_ports+1] = possible_changed_port
if debug_me then
tprint("IS A REAL OPEN")
end
elseif (open_ports_case == false and num_open_ports == 0) then
-- case closed port and num_open_ports is 0 so is a real closed port
real_ports[#real_ports+1] = possible_changed_port
if debug_me then
tprint("IS A REAL CLOSED")
end
end
end
return real_ports
end
-- ##############################################
local function analyze_ports_diff(ports_difference, host, scan_type, epoch)
local rsp = {}
local need_to_trigger_alert_after_changes_verification = ports_difference.trigger
if (ports_difference.trigger) then
if (debug_me) then
tprint("found ports differences")
tprint(ports_difference)
end
local open_ports_case = true
local real_open_ports = verify_status_ports(ports_difference.open_ports or {}, host, scan_type, open_ports_case, epoch )
local real_open_port_num = ternary(real_open_ports ~= nil and next(real_open_ports), #real_open_ports, 0)
rsp["open_ports"] = {
num = ports_difference.open_ports_num,
ports = format_port_list_to_string(ports_difference.open_ports)
num = real_open_port_num,
ports = format_port_list_to_string(real_open_ports)
}
open_ports_case = false
local real_closed_ports = verify_status_ports(ports_difference.closed_ports or {}, host, scan_type, open_ports_case, epoch)
local real_closed_ports_num = ternary(real_closed_ports ~= nil and next(real_closed_ports), #real_closed_ports, 0)
rsp["closed_ports"] = {
num = ports_difference.closed_ports_num,
ports = format_port_list_to_string(ports_difference.closed_ports)
num = real_closed_ports_num,
ports = format_port_list_to_string(real_closed_ports)
}
rsp["ports_case"] = ports_difference.case
if ((real_closed_ports_num == ports_difference.closed_ports_num) and (real_open_port_num == ports_difference.open_ports_num)) then
-- case ok we can trigger alert
else
-- false positive detected
if (debug_me) then
tprint("IT'S A FALSE POSITIVE!!!")
end
need_to_trigger_alert_after_changes_verification = false
end
if (debug_me) then
tprint(ports_difference.case)
end
@ -307,7 +376,45 @@ local function analyze_ports_diff(ports_difference)
tprint(ports_difference.trigger)
end
rsp["triggered"] = ports_difference.trigger
rsp["triggered"] = need_to_trigger_alert_after_changes_verification
return rsp
end
local function get_ports_changes(host, scan_type, old_data, new_data)
local is_tcp = false
local scan_type_log_label = "UDP"
local rsp = {}
if(scan_type == 'tcp_portscan' or scan_type == 'tcp_openports') then
is_tcp = true
scan_type_log_label = "TCP"
end
local old_ports = split_port_list(old_data, is_tcp)
local new_ports = split_port_list(new_data, is_tcp)
if (debug_me) then
tprint(scan_type_log_label.." OLD PORTS: ")
tprint(old_ports)
tprint(scan_type_log_label.." NEW PORTS: ")
tprint(new_ports)
end
local ports_differences = check_ports_diffences(#old_ports, old_ports,
#new_ports, new_ports)
local rsp_diff = analyze_ports_diff(ports_differences, host, scan_type, new_data.last_scan_time)
if (rsp_diff.triggered) then
rsp["open_ports"] = rsp_diff.open_ports
rsp["closed_ports"] = rsp_diff.closed_ports
rsp["ports_case"] = rsp_diff.ports_case
end
rsp["triggered"] = rsp_diff.triggered
rsp["measurement"] = "ports_changes_detected"
return rsp
end
@ -324,6 +431,11 @@ local function check_differences(host, host_name, scan_type, old_data, new_data)
return nil
end
-- retrocompatibility check
if (scan_type == 'tcp_openports') then
scan_type = 'tcp_portscan'
end
--[[
if tonumber(old_data.ports or 0) ~= tonumber(new_data.ports or 0) then
rsp["num_ports"] = {
@ -363,59 +475,23 @@ local function check_differences(host, host_name, scan_type, old_data, new_data)
end
end
-- Checking old_open_tcp_ports and new_open_tcp_ports
local tcp_old_ports = {}
local udp_old_ports = {}
local tcp_new_ports = {}
local udp_new_ports = {}
-- ***************************************************************
-- Checking differences beetwen old and new open ports and old and new closed ports
local differences = get_ports_changes(host, scan_type, old_data, new_data)
rsp["triggered"] = differences.triggered
rsp["measurement"] = "ports_changes_detected"
local prefix_key = "udp_"
if (scan_type == "tcp_portscan") then
tcp_old_ports = split_port_list(old_data, true)
tcp_new_ports = split_port_list(new_data, true)
if (debug_me) then
tprint("TCP OLD PORTS: ")
tprint(tcp_old_ports)
tprint("TCP NEW PORTS: ")
tprint(tcp_new_ports)
end
local tcp_ports_differences = check_ports_diffences(#tcp_old_ports, tcp_old_ports,
#tcp_new_ports, tcp_new_ports)
local rsp_tcp_diff = analyze_ports_diff(tcp_ports_differences)
if (rsp_tcp_diff.triggered) then
rsp["tcp_open_ports"] = rsp_tcp_diff.open_ports
rsp["tcp_closed_ports"] = rsp_tcp_diff.closed_ports
rsp["tcp_ports_case"] = rsp_tcp_diff.ports_case
end
rsp["triggered"] = rsp_tcp_diff.triggered
rsp["measurement"] = "ports_changes_detected"
elseif (scan_type == "udp_portscan") then
udp_old_ports = split_port_list(old_data, false)
udp_new_ports = split_port_list(new_data, false)
if (debug_me) then
tprint("UDP OLD PORTS: ")
tprint(udp_old_ports)
tprint("UDP NEW PORTS")
tprint(udp_new_ports)
end
local udp_ports_differences = check_ports_diffences(#udp_old_ports, udp_old_ports,
#udp_new_ports, udp_new_ports)
local rsp_udp_diff = analyze_ports_diff(udp_ports_differences)
if (rsp_udp_diff.triggered) then
rsp["udp_open_ports"] = rsp_udp_diff.open_ports
rsp["udp_closed_ports"] = rsp_udp_diff.closed_ports
rsp["udp_ports_case"] = rsp_udp_diff.ports_case
end
rsp["triggered"] = rsp_udp_diff.triggered
rsp["measurement"] = "ports_changes_detected"
prefix_key = "tcp_"
end
if (differences.triggered) then
rsp[prefix_key.."open_ports"] = differences.open_ports
rsp[prefix_key.."closed_ports"] = differences.closed_ports
rsp[prefix_key.."ports_case"] = differences.ports_case
end
-- ***************************************************************
if num_cve_solved > 0 then
rsp["num_cve_solved"] = num_cve_solved
@ -859,48 +935,7 @@ function vs_utils.save_host_to_scan(scan_type, host, scan_result, last_scan_time
-- In case the alert needs to be triggered, save the differences in order to lessen
-- the info dropped on redis
-- if is_ok_last_scan is nil then no prior scan was done, so do not trigger the alert
if trigger_alert and old_data and (not is_edit) then
local already_scanned = (old_data.last_scan and old_data.last_scan.epoch)
if already_scanned then
if debug_me then
-- traceError(TRACE_NORMAL,TRACE_CONSOLE, "Vulnerability Scan: checking for changes in host")
end
local old_cve_no_score = {}
for _,cve in ipairs(old_data.cve) do
old_cve_no_score[#old_cve_no_score+1] = split(cve,"|")[1]
end
local host_info_to_cache = check_differences(host, host_name,
scan_type,
{
vulnerabilities = old_data.num_vulnerabilities_found,
ports = old_data.num_open_ports,
cve = old_cve_no_score,
tcp_ports = {num_ports = old_data.tcp_ports, ports = old_data.tcp_ports_list },
udp_ports = {num_ports = old_data.udp_ports, ports = old_data.udp_ports_list}
},
{
vulnerabilities = num_vulnerabilities_found,
ports = num_open_ports,
cve = cve,
tcp_ports = tcp_ports,
udp_ports = udp_ports,
last_scan_time = last_scan_time
})
if host_info_to_cache then
if debug_me then
traceError(TRACE_NORMAL,TRACE_CONSOLE, "Vulnerability Scan detected change: enqueueing event to vulnerability_scan check\n")
end
ntop.rpushCache(scanned_hosts_changes_queue_key, json.encode(host_info_to_cache))
end
end
end
local epoch_id = 0
if isEmptyString(id) then
@ -950,7 +985,8 @@ function vs_utils.save_host_to_scan(scan_type, host, scan_result, last_scan_time
new_item.last_scan = {
epoch = last_scan_time,
--time = time_formatted,
duration = last_duration
duration = last_duration,
duration_epoch = last_duration
}
if is_ok_last_scan == vs_utils.scan_status.ok then
@ -994,16 +1030,16 @@ function vs_utils.save_host_to_scan(scan_type, host, scan_result, last_scan_time
local compare_info = vs_rest_utils.compare_scan_info_ntopng_info(host, scan_type, new_item.tcp_ports_list, new_item.udp_ports_list)
new_item.host_in_mem = compare_info.host_in_mem
local prefix_key = "udp_"
if (scan_type == "tcp_portscan" or scan_type == "tcp_openports") then
new_item.tcp_ports_unused = compare_info.tcp_ports_unused
new_item.tcp_ports_filtered = compare_info.tcp_ports_filtered
new_item.tcp_ports_case = compare_info.tcp_ports_case
elseif (scan_type == "udp_portscan") then
new_item.udp_ports_unused = compare_info.udp_ports_unused
new_item.udp_ports_filtered = compare_info.udp_ports_filtered
new_item.udp_ports_case = compare_info.udp_ports_case
prefix_key = "tcp_"
end
end
new_item[prefix_key.."ports_unused"] = compare_info[prefix_key.."ports_unused"]
new_item[prefix_key.."ports_filtered"] = compare_info[prefix_key.."ports_filtered"]
new_item[prefix_key.."ports_case"] = compare_info[prefix_key.."ports_case"]
end
-- edit case
ntop.setHashCache(host_to_scan_key, host_hash_key, json.encode(new_item))
@ -1035,6 +1071,50 @@ end
vs_db_utils.save_vs_result(scan_type, host, new_item.last_scan.epoch, json.encode(new_item), scan_result)
end
if trigger_alert and old_data and (not is_edit) then
local already_scanned = (old_data.last_scan and old_data.last_scan.epoch)
if already_scanned then
if debug_me then
-- traceError(TRACE_NORMAL,TRACE_CONSOLE, "Vulnerability Scan: checking for changes in host")
end
local old_cve_no_score = {}
for _,cve in ipairs(old_data.cve) do
old_cve_no_score[#old_cve_no_score+1] = split(cve,"|")[1]
end
local host_info_to_cache = check_differences(host, host_name,
scan_type,
{
vulnerabilities = old_data.num_vulnerabilities_found,
ports = old_data.num_open_ports,
cve = old_cve_no_score,
tcp_ports = {num_ports = old_data.tcp_ports, ports = old_data.tcp_ports_list },
udp_ports = {num_ports = old_data.udp_ports, ports = old_data.udp_ports_list},
old_epoch = old_data.last_scan.epoch
},
{
vulnerabilities = num_vulnerabilities_found,
ports = num_open_ports,
cve = cve,
tcp_ports = tcp_ports,
udp_ports = udp_ports,
last_scan_time = last_scan_time
})
if host_info_to_cache then
if debug_me then
traceError(TRACE_NORMAL,TRACE_CONSOLE, "Vulnerability Scan detected change: enqueueing event to vulnerability_scan check\n")
end
ntop.rpushCache(scanned_hosts_changes_queue_key, json.encode(host_info_to_cache))
end
end
end
return result, new_item.id
end