From 5a1e625a8d7ec1e2fea79adf1e66b6cf2d512bfa Mon Sep 17 00:00:00 2001 From: Timo Pollmeier Date: Wed, 20 Aug 2025 15:00:08 +0200 Subject: [PATCH 1/4] Fix result fetching in monthly-report script The result fetching in the monthly-report script is adjusted in the following ways: - The selected hosts now also include ones that were last updated after the requested month. Otherwise they would be missing even if they were also updated in the requested month. - The number of results for a host is no longer limited by the "Rows per page" setting. - Instead of counting all results, only the distinct vulnerabilities (VTs) are counted. The new counting method also considers only the latest occurrence of a vulnerability in case the severity level has changed in the meantime. Overall, this should make the script behave more as expected. The new result counts by distinct VT should be more informative than the previous number that could be influenced by how many times a host was scanned. --- scripts/monthly-report-gos24.10.gmp.py | 38 ++++++++++++++++++-------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/scripts/monthly-report-gos24.10.gmp.py b/scripts/monthly-report-gos24.10.gmp.py index 97654671..8e0fad94 100644 --- a/scripts/monthly-report-gos24.10.gmp.py +++ b/scripts/monthly-report-gos24.10.gmp.py @@ -36,7 +36,7 @@ def check_args(args: Namespace) -> None: def print_reports(gmp: Gmp, from_date: date, to_date: date) -> None: host_filter = ( f"rows=-1 and modified>{from_date.isoformat()} " - f"and modified<{to_date.isoformat()}" + f"and created<{to_date.isoformat()}" ) hosts_xml = gmp.get_hosts(filter_string=host_filter) @@ -62,21 +62,37 @@ def print_reports(gmp: Gmp, from_date: date, to_date: date) -> None: hostname = hostnames[0] results = gmp.get_results( - details=False, filter_string=f"host={ip} and severity>0.0" + details=False, + filter_string=( + f"rows=-1 host={ip} and severity>0.0" + f" and modified>{from_date.isoformat()}" + f" and modified<{to_date.isoformat()}" + ), ) - low = int(results.xpath('count(//result/threat[text()="Low"])')) - sum_low += low + unique_vt_results = results.xpath( + "result[" + " not (./nvt/@oid = preceding-sibling::result/nvt/@oid)" + "]" + ) + if len(unique_vt_results) == 0: + continue - medium = int(results.xpath('count(//result/threat[text()="Medium"])')) - sum_medium += medium + low = medium = high = critical = 0 + for result in unique_vt_results: + threat = result.findtext("threat") + if threat == "Critical": + critical += 1 + elif threat == "High": + high += 1 + elif threat == "Medium": + medium += 1 + elif threat == "Low": + low += 1 - high = int(results.xpath('count(//result/threat[text()="High"])')) + sum_low += low + sum_medium += medium sum_high += high - - critical = int( - results.xpath('count(//result/threat[text()="Critical"])') - ) sum_critical += critical best_os_cpe_report_id = host.xpath( From 1178f8a57f8cd837f93e3fff6335001cbcf36b40 Mon Sep 17 00:00:00 2001 From: Timo Pollmeier Date: Fri, 22 Aug 2025 08:33:36 +0200 Subject: [PATCH 2/4] Add monthly-report ++reports option, fix report id The monthly-report script now looks for reports in identifiers modified in the given month. A new ++reports option has been added to choose whether to skip getting the reports (only returning the vulnerability counts), get the last report in the selected month or get a list of all reports in the selected month. For this the argument parsing and help/usage text are changed to use argparse. This addresses the problem that the shown report ids could previously be outside the selected date range. --- scripts/monthly-report-gos24.10.gmp.py | 133 +++++++++++++++++-------- 1 file changed, 92 insertions(+), 41 deletions(-) diff --git a/scripts/monthly-report-gos24.10.gmp.py b/scripts/monthly-report-gos24.10.gmp.py index 8e0fad94..9d21d3f8 100644 --- a/scripts/monthly-report-gos24.10.gmp.py +++ b/scripts/monthly-report-gos24.10.gmp.py @@ -3,37 +3,16 @@ # SPDX-License-Identifier: GPL-3.0-or-later import sys -from argparse import Namespace -from datetime import date, timedelta +from argparse import ArgumentParser, Namespace, RawDescriptionHelpFormatter +from datetime import date, datetime, timedelta from gvm.protocols.gmp import Gmp from terminaltables import AsciiTable -def check_args(args: Namespace) -> None: - len_args = len(args.script) - 1 - if len_args < 2: - message = """ - This script will display all vulnerabilities from the hosts of the - reports in a given month! - It needs two parameters after the script name. - First one is the month and second one is the year. - Both parameters are plain numbers, so no text. - - Explicitly made for GOS 24.10. - - 1. -- month of the monthly report - 2. -- year of the monthly report - - Example: - $ gvm-script --gmp-username name --gmp-password pass \ - ssh --hostname scripts/monthly-report2.gmp.py 05 2019 - """ - print(message) - sys.exit() - - -def print_reports(gmp: Gmp, from_date: date, to_date: date) -> None: +def print_reports( + gmp: Gmp, from_date: date, to_date: date, reports_choice: str +) -> None: host_filter = ( f"rows=-1 and modified>{from_date.isoformat()} " f"and created<{to_date.isoformat()}" @@ -45,9 +24,30 @@ def print_reports(gmp: Gmp, from_date: date, to_date: date) -> None: sum_high = 0 sum_medium = 0 sum_low = 0 - table_data = [ - ["Hostname", "IP", "Bericht", "critical", "high", "medium", "low"] - ] + + table_header = ["Hostname", "IP", "Critical", "High", "Medium", "Low"] + if reports_choice == "last": + table_header = [ + "Hostname", + "IP", + "Report", + "Critical", + "High", + "Medium", + "Low", + ] + elif reports_choice == "list": + table_header = [ + "Hostname", + "IP", + "Reports", + "Critical", + "High", + "Medium", + "Low", + ] + + table_data = [table_header] for host in hosts_xml.xpath("asset"): ip = host.xpath("name/text()")[0] @@ -95,13 +95,35 @@ def print_reports(gmp: Gmp, from_date: date, to_date: date) -> None: sum_high += high sum_critical += critical - best_os_cpe_report_id = host.xpath( - 'host/detail/name[text()="best_os_cpe"]/../source/@id' - )[0] - - table_data.append( - [hostname, ip, best_os_cpe_report_id, critical, high, medium, low] - ) + if reports_choice == "none": + table_data.append([hostname, ip, critical, high, medium, low]) + else: + report_host_identifiers = host.xpath( + "identifiers/identifier[source/deleted = 0 and" + ' (source/type = "Report Host"' + ' or source/type = "Report Host Detail")]' + ) + report_ids = [] + for identifier in report_host_identifiers: + mod_date = datetime.fromisoformat( + identifier.findtext("modification_time") + ).date() + + if mod_date >= to_date or mod_date < from_date: + continue + + report_ids.append(identifier.find("source").get("id")) + if reports_choice == 'last': + break + + if reports_choice == 'last': + table_data.append( + [hostname, ip, report_ids[0], critical, high, medium, low] + ) + else: + table_data.append( + [hostname, ip, ',\n'.join(report_ids)+'\n', critical, high, medium, low] + ) table = AsciiTable(table_data) print(f"{table.table}\n") @@ -118,16 +140,45 @@ def print_reports(gmp: Gmp, from_date: date, to_date: date) -> None: def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=undefined-variable - check_args(args) + description_message = """ +This script will display all vulnerabilities from the hosts of the \ +reports in a given month and year. +These must be given after the script name as plain numbers. + +This version is explicitly made for GOS 24.10. + +Example: + $ gvm-script --gmp-username name --gmp-password pass \ +ssh --hostname scripts/monthly-report2.gmp.py 05 2019 + """ + + parser = ArgumentParser( + prog=("gvm-script [...] " + args.script[0]), + formatter_class=RawDescriptionHelpFormatter, + prefix_chars="+", + description=description_message, + ) + parser.add_argument("month", type=int, help="month of the monthly report") + parser.add_argument("year", type=int, help="year of the monthly report") + parser.add_argument( + "++reports", + choices=["none", "last", "list"], + default="last", + help=( + "what to show in the reports column:" + ' "none": do not show a reports column;' + ' "last": show the last report in the selected month;' + ' "list": show a list of reports in the selected month.' + ), + ) + script_args, _ = parser.parse_known_args(args.script[1:]) - month = int(args.script[1]) - year = int(args.script[2]) - from_date = date(year, month, 1) + from_date = date(script_args.year, script_args.month, 1) to_date = from_date + timedelta(days=31) # To have the first day in month to_date = to_date.replace(day=1) - print_reports(gmp, from_date, to_date) + print_reports(gmp, from_date, to_date, script_args.reports) if __name__ == "__gmp__": From c29400debdc0dc704a023314ba46aad30eb9cbdc Mon Sep 17 00:00:00 2001 From: Timo Pollmeier Date: Thu, 28 Aug 2025 08:52:59 +0200 Subject: [PATCH 3/4] Move "none" table header in monthly-report to else case MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Björn Ricks --- scripts/monthly-report-gos24.10.gmp.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/monthly-report-gos24.10.gmp.py b/scripts/monthly-report-gos24.10.gmp.py index 9d21d3f8..e027163a 100644 --- a/scripts/monthly-report-gos24.10.gmp.py +++ b/scripts/monthly-report-gos24.10.gmp.py @@ -25,7 +25,6 @@ def print_reports( sum_medium = 0 sum_low = 0 - table_header = ["Hostname", "IP", "Critical", "High", "Medium", "Low"] if reports_choice == "last": table_header = [ "Hostname", @@ -46,6 +45,8 @@ def print_reports( "Medium", "Low", ] + else: + table_header = ["Hostname", "IP", "Critical", "High", "Medium", "Low"] table_data = [table_header] From 5ef4752594c14c2910c4a38e26bf85b48bbfda59 Mon Sep 17 00:00:00 2001 From: Timo Pollmeier Date: Thu, 28 Aug 2025 08:49:54 +0200 Subject: [PATCH 4/4] Format monthly-report script, remove unused import --- scripts/monthly-report-gos24.10.gmp.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/scripts/monthly-report-gos24.10.gmp.py b/scripts/monthly-report-gos24.10.gmp.py index e027163a..23d601d8 100644 --- a/scripts/monthly-report-gos24.10.gmp.py +++ b/scripts/monthly-report-gos24.10.gmp.py @@ -2,7 +2,6 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -import sys from argparse import ArgumentParser, Namespace, RawDescriptionHelpFormatter from datetime import date, datetime, timedelta @@ -109,21 +108,29 @@ def print_reports( mod_date = datetime.fromisoformat( identifier.findtext("modification_time") ).date() - + if mod_date >= to_date or mod_date < from_date: continue - + report_ids.append(identifier.find("source").get("id")) - if reports_choice == 'last': + if reports_choice == "last": break - if reports_choice == 'last': + if reports_choice == "last": table_data.append( [hostname, ip, report_ids[0], critical, high, medium, low] ) else: table_data.append( - [hostname, ip, ',\n'.join(report_ids)+'\n', critical, high, medium, low] + [ + hostname, + ip, + ",\n".join(report_ids) + "\n", + critical, + high, + medium, + low, + ] ) table = AsciiTable(table_data)