diff --git a/docs/changelog.rst b/docs/changelog.rst index b5cf7dc..43dd6dc 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,8 @@ Changelog ========= +1.4.1 - Small bugfixes and code refactoring + 1.4.0 - Use Word template for report building 1.3.1 - Add charts to Word document using matplotlib. Some code clean-up and small lay-out changes in Excel. diff --git a/openvasreporting/libs/config.py b/openvasreporting/libs/config.py index 07223dc..72fa9e6 100644 --- a/openvasreporting/libs/config.py +++ b/openvasreporting/libs/config.py @@ -6,12 +6,10 @@ """This file contains data structures""" -from pathlib import Path - class Config(object): def __init__(self, input_files, output_file="openvas_report", min_level="none", filetype="xlsx", - template="openvasreporting/src/openvas-template.docx"): + template=None): """ :param input_files: input file path :type input_files: list(str) @@ -43,7 +41,7 @@ def __init__(self, input_files, output_file="openvas_report", min_level="none", raise TypeError("Expected str, got '{}' instead".format(type(min_level))) if not isinstance(filetype, str): raise TypeError("Expected str, got '{}' instead".format(type(filetype))) - if not isinstance(template, str): + if template is not None and not isinstance(template, str): raise TypeError("Expected str, got '{}' instead".format(type(template))) self.input_files = input_files @@ -51,7 +49,7 @@ def __init__(self, input_files, output_file="openvas_report", min_level="none", else output_file self.min_level = min_level self.filetype = filetype - self.template = Path(template).resolve() + self.template = template @staticmethod def colors(): diff --git a/openvasreporting/libs/export.py b/openvasreporting/libs/export.py index 9dfea56..088addf 100644 --- a/openvasreporting/libs/export.py +++ b/openvasreporting/libs/export.py @@ -22,11 +22,12 @@ def exporters(): """ return { 'xlsx': export_to_excel, - 'docx': export_to_word + 'docx': export_to_word, + 'csv': export_to_csv } -def export_to_excel(vuln_info, template=None, output_file='openvas_report'): +def export_to_excel(vuln_info, template=None, output_file='openvas_report.xlsx'): """ Export vulnerabilities info in an Excel file. @@ -295,7 +296,7 @@ def __row_height(text, width): workbook.close() -def export_to_word(vuln_info, template, output_file='openvas_report'): +def export_to_word(vuln_info, template, output_file='openvas_report.docx'): """ Export vulnerabilities info in a Word file. @@ -306,7 +307,7 @@ def export_to_word(vuln_info, template, output_file='openvas_report'): :type output_file: str :param template: Path to Docx template - :type template: PosixPath, str + :type template: str :raises: TypeError """ @@ -316,7 +317,6 @@ def export_to_word(vuln_info, template, output_file='openvas_report'): import tempfile import os - from pathlib import PosixPath from docx import Document from docx.oxml.shared import qn, OxmlElement from docx.oxml.ns import nsdecls @@ -334,8 +334,11 @@ def export_to_word(vuln_info, template, output_file='openvas_report'): else: if not output_file: raise ValueError("output_file must have a valid name.") - if not isinstance(template, (str, PosixPath)): - raise TypeError("Expected str or PosixPath, got '{}' instead".format(type(template))) + if template is not None: + if not isinstance(template, str): + raise TypeError("Expected str, got '{}' instead".format(type(template))) + else: + template = 'openvasreporting/src/openvas-template.docx' # TODO Move to function to de-duplicate this vuln_info.sort(key=lambda key: key.cvss, reverse=True) @@ -581,3 +584,71 @@ def __label_bars(barcontainer): cells[2].text = "No port info" document.save(output_file) + + +def export_to_csv(vuln_info, template=None, output_file='openvas_report.csv'): + """ + Export vulnerabilities info in a Comma Separated Values (csv) file + + :param vuln_info: Vulnerability list info + :type vuln_info: list(Vulnerability) + + :param template: Not supported in csv-output + :type template: NoneType + + :param output_file: Filename of the csv file + :type output_file: str + + :raises: TypeError, NotImplementedError + """ + + import csv + + if not isinstance(vuln_info, list): + raise TypeError("Expected list, got '{}' instead".format(type(vuln_info))) + else: + for x in vuln_info: + if not isinstance(x, Vulnerability): + raise TypeError("Expected Vulnerability, got '{}' instead".format(type(x))) + if not isinstance(output_file, str): + raise TypeError("Expected str, got '{}' instead".format(type(output_file))) + else: + if not output_file: + raise ValueError("output_file must have a valid name.") + if template is not None: + raise NotImplementedError("Use of template is not supported in CSV-output.") + + # TODO Move to function to de-duplicate this + vuln_info.sort(key=lambda key: key.cvss, reverse=True) + + with open(output_file, 'w') as csvfile: + fieldnames = ['hostname', 'ip', 'port', 'protocol', + 'vulnerability', 'cvss', 'threat', 'family', + 'description', 'detection', 'insight', 'impact', 'affected', 'solution', 'solution_type', + 'vuln_id', 'cve', 'references'] + writer = csv.DictWriter(csvfile, dialect='excel', fieldnames=fieldnames) + writer.writeheader() + + for vuln in vuln_info: + for (host, port) in vuln.hosts: + rowdata = { + 'hostname': host.host_name, + 'ip': host.ip, + 'port': port.number, + 'protocol': port.protocol, + 'vulnerability': vuln.name, + 'cvss': vuln.cvss, + 'threat': vuln.level, + 'family': vuln.family, + 'description': vuln.description, + 'detection': vuln.detect, + 'insight': vuln.insight, + 'impact': vuln.impact, + 'affected': vuln.affected, + 'solution': vuln.solution, + 'solution_type': vuln.solution_type, + 'vuln_id': vuln.vuln_id, + 'cve': ' - '.join(vuln.cves), + 'references': ' - '.join(vuln.references) + } + writer.writerow(rowdata) diff --git a/openvasreporting/openvasreporting.py b/openvasreporting/openvasreporting.py index 186e191..8bcee15 100644 --- a/openvasreporting/openvasreporting.py +++ b/openvasreporting/openvasreporting.py @@ -20,31 +20,59 @@ def main(): parser.add_argument("-i", "--input", nargs="*", dest="input_files", help="OpenVAS XML reports", required=True) parser.add_argument("-o", "--output", dest="output_file", help="Output file, no extension", required=False, default="openvas_report") - parser.add_argument("-l", "--level", dest="min_level", help="Minimal level (c, h, m, l, n)", required=False, - default="n") + parser.add_argument("-l", "--level", dest="min_lvl", help="Minimal level (c, h, m, l, n)", required=False, + default="none") parser.add_argument("-f", "--format", dest="filetype", help="Output format (xlsx)", required=False, default="xlsx") parser.add_argument("-t", "--template", dest="template", help="Template file for docx export", required=False) args = parser.parse_args() - min_lvl = args.min_level.lower()[0] + config = create_config(args.input_files, args.output_file, args.min_lvl, args.filetype, args.template) + + convert(config) + + +def create_config(input_files, output_file="openvas_report", min_lvl="none", filetype="xlsx", template=None): + """ + Create config file to be used by converter. + + :param input_files: input XML file(s) to be converted + :type input_files: str + + :param output_file: output filename for report + :type output_file: str + + :param min_lvl: minimal threat level to be included in the report + :type min_lvl: str + + :param filetype: filetype of the output file + :type filetype: str + + :param template: template to be used in case of export to docx filetype + :type template: str + + :raises: ValueError + + :return: config file to be passed to converter + :rtype: Config + """ + + min_lvl = min_lvl.lower()[0] if min_lvl in Config.levels().keys(): min_lvl = Config.levels()[min_lvl] else: raise ValueError("Invalid value for level parameter, \ - must be one of: c[ritical], h[igh], m[edium], l[low], n[one]") + must be one of: c[ritical], h[igh], m[edium], l[low], n[one]") - if args.filetype not in exporters().keys(): - raise ValueError("Filetype not supported, got {}, expecting one of {}".format(args.filetype, + if filetype not in exporters().keys(): + raise ValueError("Filetype not supported, got {}, expecting one of {}".format(filetype, exporters().keys())) - if args.template is not None: - config = Config(args.input_files, args.output_file, min_lvl, args.filetype, args.template) + if template is not None: + return Config(input_files, output_file, min_lvl, filetype, template) else: - config = Config(args.input_files, args.output_file, min_lvl, args.filetype) - - convert(config) + return Config(input_files, output_file, min_lvl, filetype) def convert(config): diff --git a/setup.py b/setup.py index fd22148..6cffe57 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ setup( name='OpenVAS Reporting', description='A tool to convert OpenVAS XML into reports.', - version='1.4.0', + version='1.4.1', long_description=long_description, long_description_content_type='text/markdown', author='TheGroundZero (@DezeStijn)',