[VS] Add discrepancy info on email notification. (#8048)

This commit is contained in:
Nicolo Maio 2023-11-23 18:16:55 +01:00
parent fb0ff0850c
commit 5a3d1543b0
2 changed files with 173 additions and 55 deletions

View file

@ -789,7 +789,7 @@ end
-- Function to update counters of periodically scan or scan all
-- @param is_periodic (true -> is a periodic scan, false -> is a scan all)
local function update_scan_info_for_report(type_of_scan_execution, new_item, host_hash_key)
local function update_scan_info_for_report(type_of_scan_execution, new_item, host_hash_key, discrepancies)
-- select correctly redis keys
local redis_info_key = get_counter_periodic_all_scan_keys(type_of_scan_execution)
@ -831,19 +831,102 @@ local function update_scan_info_for_report(type_of_scan_execution, new_item, hos
end
end
if (info_json ~= {} and info_json.scanned_hosts ~= nil) then
if (new_item.is_ok_last_scan == vs_utils.scan_status.ok) then
if (new_item.is_ok_last_scan == vs_utils.scan_status.ok) then
if (info_json ~= {} and info_json.scanned_hosts ~= nil) then
-- count just in success case
info_json.scanned_hosts = tonumber(info_json.scanned_hosts) + 1
else
info_json.scanned_hosts = 1
end
elseif (new_item.is_ok_last_scan == vs_utils.scan_status.failed) then
if (info_json ~= {} and info_json.not_scanned_hosts ~= nil) then
-- count just in success case
info_json.scanned_hosts = tonumber(info_json.scanned_hosts) + 1
end
else
info_json.scanned_hosts = 1
info_json.not_scanned_hosts = tonumber(info_json.not_scanned_hosts) + 1
else
info_json.not_scanned_hosts = 1
end
end
if (info_json ~= {} and info_json.begin_epoch == nil) then
info_json.begin_epoch = os.time()
end
if discrepancies then
-- HAD DISCREPANCY INFO TO EMAIL REPORT
local cve_case = false
if (info_json ~= {} and info_json.num_cve_solved ~= nil and new_item.scan_type == 'cve') then
info_json.num_cve_solved = tonumber(info_json.num_cve_solved or 0) + tonumber(discrepancies.num_cve_solved or 0)
cve_case = true
else
info_json.num_cve_solved = 0
end
if ((info_json ~= {} and info_json.new_open_ports ~= nil) and (new_item.scan_type == 'tcp_portscan' or new_item.scan_type == 'tcp_openports') and discrepancies.tcp_open_ports and discrepancies.tcp_open_ports.num and discrepancies.tcp_open_ports.num>0) then
info_json.new_open_ports = tonumber(info_json.new_open_ports or 0) + tonumber(discrepancies.tcp_open_ports.num)
else
info_json.new_open_ports = 0
end
if (info_json ~= {} and info_json.new_open_ports ~= nil and new_item.scan_type == 'udp_portscan' and discrepancies.udp_open_ports and discrepancies.udp_open_ports.num and discrepancies.udp_open_ports.num>0) then
info_json.new_open_ports = tonumber(info_json.new_open_ports or 0) + tonumber(discrepancies.udp_open_ports.num or 0)
else
info_json.new_open_ports = 0
end
local host_discrepancies_details = ""
local port_type = "TCP"
local prefix_key = "tcp_"
if (new_item.scan_type == 'udp_portscan' and info_json ~= {}) then
port_type = "UDP"
prefix_key = "udp_"
end
if (not cve_case and info_json ~= {}) then
-- DISCREPANCY PORTS CASES
info_json.discrepancy_case = 'ports'
if (tonumber(discrepancies[prefix_key.."open_ports"].num or 0) > 0) then
-- take only open ports not the closed ports
info_json.new_open_ports = tonumber(info_json.new_open_ports or 0) + tonumber(discrepancies[prefix_key.."open_ports"].num or 0)
host_discrepancies_details = string.format("<br>Host %s has these %s ports open: %s.</br>",new_item.host,port_type,discrepancies[prefix_key.."open_ports"].ports)
if (isEmptyString(info_json.hosts_discrepancies_details)) then
info_json.hosts_discrepancies_details = host_discrepancies_details
else
info_json.hosts_discrepancies_details = string.format("%s%s",info_json.hosts_discrepancies_details,host_discrepancies_details)
end
else
info_json.new_open_ports = tonumber(info_json.new_open_ports or 0)
end
end
if (cve_case and info_json ~= {} and info_json.num_cve_solved and info_json.num_cve_solved > 0) then
-- DISCREPANCY CVE CASE
info_json.discrepancy_case = 'cves'
local cve_string = format_port_list_to_string(discrepancies.cve_solved)
host_discrepancies_details = string.format("<br>Host %s has solved these CVEs: %s.</br>",new_item.host,cve_string)
if (isEmptyString(info_json.hosts_discrepancies_details)) then
info_json.hosts_discrepancies_details = host_discrepancies_details
else
info_json.hosts_discrepancies_details = string.format("%s%s",info_json.hosts_discrepancies_details,host_discrepancies_details)
end
end
if (debug_me) then
if (info_json) then
tprint(info_json.hosts_discrepancies_details)
end
end
end
ntop.setCache(redis_info_key, json.encode(info_json))
end
@ -1107,15 +1190,7 @@ function vs_utils.save_host_to_scan(scan_type, host, scan_result, last_scan_time
local counts = vs_utils.update_ts_counters()
if (new_item.is_periodicity) then
update_scan_info_for_report(vs_utils.scan_in_exec_type.periodic_scan, new_item, host_hash_key)
end
if (new_item.is_all) then
update_scan_info_for_report(vs_utils.scan_in_exec_type.scan_all, new_item, host_hash_key)
end
remove_scanning_host({host=host, scan_type=scan_type, ports=ports})
-- save on db here
@ -1134,6 +1209,7 @@ function vs_utils.save_host_to_scan(scan_type, host, scan_result, last_scan_time
vs_db_utils.save_vs_result(scan_type, host, new_item.last_scan.epoch, json.encode(new_item), scan_result)
end
local host_info_differences
if trigger_alert and old_data and (not is_edit) then
local already_scanned = (old_data.last_scan and old_data.last_scan.epoch)
@ -1168,6 +1244,7 @@ function vs_utils.save_host_to_scan(scan_type, host, scan_result, last_scan_time
})
if host_info_to_cache then
host_info_differences = host_info_to_cache
if debug_me then
traceError(TRACE_NORMAL,TRACE_CONSOLE, "Vulnerability Scan detected change: enqueueing event to vulnerability_scan check\n")
@ -1177,6 +1254,17 @@ function vs_utils.save_host_to_scan(scan_type, host, scan_result, last_scan_time
end
end
end
if (new_item.is_periodicity) then
update_scan_info_for_report(vs_utils.scan_in_exec_type.periodic_scan, new_item, host_hash_key, host_info_differences)
end
if (new_item.is_all) then
update_scan_info_for_report(vs_utils.scan_in_exec_type.scan_all, new_item, host_hash_key, host_info_differences)
end
remove_scanning_host({host=host, scan_type=scan_type, ports=ports})
return result, new_item.id
end
@ -1256,7 +1344,17 @@ local function format_num_for_email(num, case)
local formatted_num = format_high_num_value_for_tables({num = num}, "num")
return(i18n("hosts_stats.page_scan_hosts.email.num_scanned_hosts", {num = formatted_num}))
end
elseif (case == 4) then
-- not scanned_hosts --> hosts unreachable
if (num == 0) then
return(i18n("hosts_stats.page_scan_hosts.email.no_scanned_hosts"))
else
local formatted_num = format_high_num_value_for_tables({num = num}, "num")
return(i18n("hosts_stats.page_scan_hosts.email.num_failed_scanned_hosts", {num = formatted_num}))
end
end
end
-- **********************************************************
@ -1276,16 +1374,31 @@ local function retrieve_email_info(exec_type)
tcp_ports = 0
}
end
if(debug_me) then
tprint(info_json)
end
local email_info = {
cve_num = tonumber(info_json.cves) or 0,
udp_ports = tonumber(info_json.udp_ports) or 0,
tcp_ports = tonumber(info_json.tcp_ports) or 0,
scanned_hosts = tonumber(info_json.scanned_hosts) or 0,
not_scanned_hosts = tonumber(info_json.not_scanned_hosts) or 0,
begin_epoch_t = tonumber(info_json.begin_epoch),
end_epoch_t = os.time(),
report_type = exec_type
report_type = exec_type,
-- has_dicrepancy must be true only if there are new open ports or cves fixed
has_discrepancy = ((info_json.new_open_ports or 0) > 0) or ((info_json.num_cve_solved or 0) > 0)
}
if (email_info.has_discrepancy) then
email_info.new_open_ports = info_json.new_open_ports or 0
email_info.fixed_cves = info_json.num_cve_solved or 0
local string_hosts_discrepancies_details = tostring(info_json.hosts_discrepancies_details)
email_info.discrepancies_details = string_hosts_discrepancies_details:gsub("%|","\n")
end
email_info.duration = email_info.end_epoch_t - email_info.begin_epoch_t
ntop.setCache(info_redis_key,json.encode({
@ -1293,7 +1406,8 @@ local function retrieve_email_info(exec_type)
udp_ports = 0,
tcp_ports = 0,
begin_epoch = 0,
scanned_hosts = 0
scanned_hosts = 0,
not_scanned_hosts = 0
}))
return email_info
end
@ -1306,6 +1420,7 @@ local function retrieve_report_info(date)
tcp_ports = 0,
udp_ports = 0,
scanned_hosts = 0,
not_scanned_hosts = 0
}
for _, item in ipairs(host_scanned_info) do
if (not isEmptyString(item.num_vulnerabilities_found)) then
@ -1314,7 +1429,10 @@ local function retrieve_report_info(date)
info.tcp_ports = info.tcp_ports + tonumber(item.tcp_ports)
info.udp_ports = info.udp_ports + tonumber(item.udp_ports)
-- plus 1 because start from 0
info.scanned_hosts = info.scanned_hosts + 1
info.not_scanned_hosts = info.not_scanned_hosts + 1
end
if (date) then
@ -1369,43 +1487,40 @@ function vs_utils.notify_scan_results(exec_type, periodicity)
ntop.setCache(hosts_scan_last_report_dates, json.encode({start_date = start_date_formatted, end_date = end_date_formatted}))
local email_body_i18n_key
if (periodicity and periodicity == "1day") then
notification_message = i18n("hosts_stats.page_scan_hosts.email.periodicity_scan_1_day_ended", {
cves = format_num_for_email(email_info.cve_num,0),
udp_ports = format_num_for_email(email_info.udp_ports,1),
tcp_ports = format_num_for_email(email_info.tcp_ports,2),
scanned_hosts = format_num_for_email(email_info.scanned_hosts, 3),
url = string.format(getHttpHost() .. ntop.getHttpPrefix() .. "/lua/enterprise/vulnerability_scan_report.lua?epoch_end=%u&epoch_begin=%u",report_date,report_date),
duration = duration_label,
start_date = start_date_formatted,
end_date = end_date_formatted
})
-- 1 day scan case
email_body_i18n_key = "hosts_stats.page_scan_hosts.email.periodicity_scan_1_day_ended"
elseif (periodicity and periodicity == "1week") then
notification_message = i18n("hosts_stats.page_scan_hosts.email.periodicity_scan_1_week_ended", {
cves = format_num_for_email(email_info.cve_num,0),
udp_ports = format_num_for_email(email_info.udp_ports,1),
tcp_ports = format_num_for_email(email_info.tcp_ports,2),
scanned_hosts = format_num_for_email(email_info.scanned_hosts, 3),
url = string.format(getHttpHost() .. ntop.getHttpPrefix() .. "/lua/enterprise/vulnerability_scan_report.lua?epoch_end=%u&epoch_begin=%u",report_date,report_date),
duration = duration_label,
start_date = start_date_formatted,
end_date = end_date_formatted
})
-- 1 week scan case
email_body_i18n_key = "hosts_stats.page_scan_hosts.email.periodicity_scan_1_week_ended"
else
-- on demand scan
notification_message = i18n("hosts_stats.page_scan_hosts.email.scan_all_ended", {
cves = format_num_for_email(email_info.cve_num,0),
udp_ports = format_num_for_email(email_info.udp_ports,1),
tcp_ports = format_num_for_email(email_info.tcp_ports,2),
scanned_hosts = format_num_for_email(email_info.scanned_hosts, 3),
url = string.format(getHttpHost() .. ntop.getHttpPrefix() .. "/lua/enterprise/vulnerability_scan_report.lua?epoch_end=%u&epoch_begin=%u",report_date,report_date),
duration = duration_label,
start_date = start_date_formatted,
end_date = end_date_formatted,
})
-- on demand case
email_body_i18n_key = "hosts_stats.page_scan_hosts.email.scan_all_ended"
end
notification_message = i18n(email_body_i18n_key, {
cves = format_num_for_email(email_info.cve_num,0),
udp_ports = format_num_for_email(email_info.udp_ports,1),
tcp_ports = format_num_for_email(email_info.tcp_ports,2),
scanned_hosts = format_num_for_email(email_info.scanned_hosts, 3),
not_scanned_hosts = format_num_for_email(email_info.not_scanned_hosts, 4),
url = string.format(getHttpHost() .. ntop.getHttpPrefix() .. "/lua/enterprise/vulnerability_scan_report.lua?epoch_end=%u&epoch_begin=%u",report_date,report_date),
duration = duration_label,
start_date = start_date_formatted,
end_date = end_date_formatted,
})
if (email_info.has_discrepancy) then
local discrepancies_info = i18n("hosts_stats.page_scan_hosts.email.discrepancy", {
new_ports_open = ternary(email_info.new_open_ports ~= 0, format_high_num_value_for_tables({num = email_info.new_open_ports}, "num"),"0"),
cves_fixed = ternary(email_info.fixed_cves ~= 0, format_high_num_value_for_tables({num = email_info.fixed_cves }, "num"),"0"),
hosts_discrepancy_details = email_info.discrepancies_details
})
notification_message = string.format("%s\n\n%s",notification_message,discrepancies_info)
end
if verbose then
traceError(TRACE_NORMAL,TRACE_CONSOLE, "Vulnerability Scan completed. Sending " .. title .."\n")
end
@ -2019,7 +2134,8 @@ function vs_utils.schedule_periodic_scan(periodicity)
udp_ports = 0,
tcp_ports = 0,
begin_epoch = os.time(),
scanned_hosts = 0
scanned_hosts = 0,
not_scanned_hosts = 0
}))
end