def __write_report(self): # Header print >>self.__fd, "" print >>self.__fd, "--= %s =--" % self.__colorize("Report", "cyan") print >>self.__fd, "" # Summary start_time, stop_time, run_time = parse_audit_times( *get_audit_times() ) host_count = Database.count(Data.TYPE_RESOURCE, Resource.RESOURCE_DOMAIN) host_count += Database.count(Data.TYPE_RESOURCE, Resource.RESOURCE_IP) vuln_count = Database.count(Data.TYPE_VULNERABILITY) print >>self.__fd, "-# %s #- " % self.__colorize("Summary", "yellow") print >>self.__fd, "" print >>self.__fd, "Audit started: %s" % self.__colorize(start_time, "yellow") print >>self.__fd, "Audit ended: %s" % self.__colorize(stop_time, "yellow") print >>self.__fd, "Execution time: %s" % self.__colorize(run_time, "yellow") print >>self.__fd, "" print >>self.__fd, "Scanned hosts: %s" % self.__colorize(str(host_count), "yellow") print >>self.__fd, "Vulnerabilities: %s" % self.__colorize(str(vuln_count), "red" if vuln_count else "yellow") print >>self.__fd, "" # Audit scope if self.__show_data or not self.__console: table = Texttable() scope_domains = ["*." + r for r in Config.audit_scope.roots] scope_domains.extend(Config.audit_scope.domains) if Config.audit_scope.addresses: table.add_row(("IP addresses", "\n".join(Config.audit_scope.addresses))) if scope_domains: table.add_row(("Domains", "\n".join(scope_domains))) if Config.audit_scope.web_pages: table.add_row(("Web pages", "\n".join(Config.audit_scope.web_pages))) if table._rows: self.__fix_table_width(table) print >>self.__fd, "-# %s #- " % self.__colorize("Audit Scope", "yellow") print >>self.__fd, "" print >>self.__fd, table.draw() print >>self.__fd, "" # Discovered hosts if self.__show_data: need_header = True for domain in self.__iterate(Data.TYPE_RESOURCE, Resource.RESOURCE_DOMAIN): table = Texttable() self.__add_related(table, domain, Data.TYPE_RESOURCE, Resource.RESOURCE_IP, "IP Address") self.__add_related(table, domain, Data.TYPE_INFORMATION, Information.INFORMATION_GEOLOCATION, "Location") self.__add_related(table, domain, Data.TYPE_INFORMATION, Information.INFORMATION_WEB_SERVER_FINGERPRINT, "Web Server") self.__add_related(table, domain, Data.TYPE_INFORMATION, Information.INFORMATION_OS_FINGERPRINT, "OS Fingerprint") if table._rows: if need_header: need_header = False print >>self.__fd, "-# %s #- " % self.__colorize("Hosts", "yellow") print >>self.__fd, "" table.header(("Domain Name", domain.hostname)) self.__fix_table_width(table) text = table.draw() if self.__color: text = colorize_substring(text, domain.hostname, "red" if domain.get_links(Data.TYPE_VULNERABILITY) else "green") print >>self.__fd, text print >>self.__fd, "" for ip in self.__iterate(Data.TYPE_RESOURCE, Resource.RESOURCE_IP): table = Texttable() self.__add_related(table, ip, Data.TYPE_RESOURCE, Resource.RESOURCE_DOMAIN, "Domain Name") self.__add_related(table, ip, Data.TYPE_INFORMATION, Information.INFORMATION_GEOLOCATION, "Location") self.__add_related(table, ip, Data.TYPE_INFORMATION, Information.INFORMATION_WEB_SERVER_FINGERPRINT, "Web Server") self.__add_related(table, ip, Data.TYPE_INFORMATION, Information.INFORMATION_OS_FINGERPRINT, "OS Fingerprint") self.__add_related(table, ip, Data.TYPE_INFORMATION, Information.INFORMATION_PORTSCAN, "Port Scan") self.__add_related(table, ip, Data.TYPE_INFORMATION, Information.INFORMATION_TRACEROUTE, "Network Route") if table._rows: if need_header: need_header = False print >>self.__fd, "-# %s #- " % self.__colorize("Hosts", "yellow") print >>self.__fd, "" table.header(("IP Address", ip.address)) self.__fix_table_width(table) text = table.draw() if self.__color: text = colorize_substring(text, ip.address, "red" if ip.get_links(Data.TYPE_VULNERABILITY) else "green") print >>self.__fd, text print >>self.__fd, "" # Web servers if self.__show_data and Database.count(Data.TYPE_RESOURCE, Resource.RESOURCE_BASE_URL): print >>self.__fd, "-# %s #- " % self.__colorize("Web Servers", "yellow") print >>self.__fd, "" crawled = defaultdict(list) vulnerable = [] for url in self.__iterate(Data.TYPE_RESOURCE, Resource.RESOURCE_URL): crawled[url.hostname].append(url.url) if self.__color and url.get_links(Data.TYPE_VULNERABILITY): vulnerable.append(url) for url in self.__iterate(Data.TYPE_RESOURCE, Resource.RESOURCE_BASE_URL): table = Texttable() table.header(("Base URL", url.url)) self.__add_related(table, url, Data.TYPE_INFORMATION, Information.INFORMATION_WEB_SERVER_FINGERPRINT, "Server") self.__add_related(table, url, Data.TYPE_INFORMATION, Information.INFORMATION_OS_FINGERPRINT, "Platform") urls = crawled[url.hostname] if urls: urls.sort() table.add_row(("Visited URLs", "\n".join(urls))) if table._rows: self.__fix_table_width(table) text = table.draw() if self.__color: p = text.find("\n") p = text.find("\n", p + 1) p = text.find("\n", p + 1) if p > 0: text = colorize_substring(text[:p], url.url, "red" if url.get_links(Data.TYPE_VULNERABILITY) else "green") + text[p:] for u in vulnerable: if u != url.url: text = colorize_substring(text, u, "red") print >>self.__fd, text print >>self.__fd, "" # Emails if self.__show_data: emails = { e.address: "red" if e.get_links(Data.TYPE_VULNERABILITY) else "green" for e in self.__iterate(Data.TYPE_RESOURCE, Resource.RESOURCE_EMAIL) } if emails: print >>self.__fd, "-# %s #- " % self.__colorize("Email Addresses", "yellow") print >>self.__fd, "" for e in sorted(emails): print >>self.__fd, "* " + self.__colorize(e, emails[e]) print >>self.__fd, "" # Vulnerabilities print >>self.__fd, "-# %s #- " % self.__colorize("Vulnerabilities", "yellow") print >>self.__fd, "" count = Database.count(Data.TYPE_VULNERABILITY) if count: if self.__show_data: print >>self.__fd, self.__colorize("%d vulnerabilities found!" % count, "red") print >>self.__fd, "" vuln_types = { v.display_name: v.vulnerability_type for v in self.__iterate(Data.TYPE_VULNERABILITY) } titles = vuln_types.keys() titles.sort() if "Uncategorized Vulnerability" in titles: titles.remove("Uncategorized Vulnerability") titles.append("Uncategorized Vulnerability") for title in titles: data_subtype = vuln_types[title] print >>self.__fd, "-- %s (%s) -- " % (self.__colorize(title, "cyan"), data_subtype) print >>self.__fd, "" for vuln in self.__iterate(Data.TYPE_VULNERABILITY, data_subtype): table = Texttable() table.header(("Occurrence ID", vuln.identity)) w = len(table.draw()) table.add_row(("Title", vuln.title)) targets = [str(x) for x in vuln.associated_resources] for info in vuln.associated_informations: targets.extend(str(x) for x in info.associated_resources) table.add_row(("Found By", get_plugin_name(vuln.plugin_id))) p = len(table.draw()) table.add_row(("Level", vuln.level)) table.add_row(("Impact", vuln.impact)) table.add_row(("Severity", vuln.severity)) table.add_row(("Risk", vuln.risk)) q = len(table.draw()) if vuln.cvss_base: table.add_row(("CVSS Base", vuln.cvss_base)) if vuln.cvss_base_vector: table.add_row(("CVSS Base Vector", vuln.cvss_base_vector)) if len(targets) > 1: targets.sort() table.add_row(("Locations", "\n".join(targets))) else: table.add_row(("Location", targets[0])) table.add_row(("Description", vuln.description)) table.add_row(("Solution", vuln.solution)) taxonomy = [] if vuln.bid: taxonomy.extend(vuln.bid) if vuln.cve: taxonomy.extend(vuln.cve) if vuln.cwe: taxonomy.extend(vuln.cwe) if vuln.osvdb: taxonomy.extend(vuln.osvdb) if vuln.sa: taxonomy.extend(vuln.sa) if vuln.sectrack: taxonomy.extend(vuln.sectrack) if vuln.xf: taxonomy.extend(vuln.xf) if taxonomy: table.add_row(("Taxonomy", "\n".join(taxonomy))) if vuln.references: table.add_row(("References", "\n".join(sorted(vuln.references)))) details = vuln.display_properties["Details"] if details: props = details.keys() props.sort() table.add_row(("Additional details", "\n".join(("%s: %s" % (x, details[x])) for x in props))) self.__fix_table_width(table) text = table.draw() if self.__color: text_1 = text[:w] text_3 = text[p:q] text_1 = colorize_substring(text_1, vuln.identity, vuln.level.lower()) for lvl in Vulnerability.VULN_LEVELS: if lvl in text_3: text_3 = colorize_substring(text_3, lvl, lvl.lower()) text = text_1 + text[w:p] + text_3 + text[q:] print >>self.__fd, text print >>self.__fd, "" else: print >>self.__fd, self.__colorize("No vulnerabilities found.", "green") print >>self.__fd, ""
def __write_report(self): # Header print >>self.__fd, "" print >>self.__fd, "--= %s =--" % self.__colorize("Report", "cyan") print >>self.__fd, "" # Summary start_time, stop_time, run_time = parse_audit_times( *get_audit_times() ) host_count = Database.count(Data.TYPE_RESOURCE, Domain.data_subtype) host_count += Database.count(Data.TYPE_RESOURCE, IP.data_subtype) vuln_count = Database.count(Data.TYPE_VULNERABILITY) print >>self.__fd, "-# %s #- " % self.__colorize("Summary", "yellow") print >>self.__fd, "" print >>self.__fd, "Audit started: %s" % self.__colorize(start_time, "yellow") print >>self.__fd, "Audit ended: %s" % self.__colorize(stop_time, "yellow") print >>self.__fd, "Execution time: %s" % self.__colorize(run_time, "yellow") print >>self.__fd, "" print >>self.__fd, "Scanned hosts: %s" % self.__colorize(str(host_count), "yellow") print >>self.__fd, "Vulnerabilities: %s" % self.__colorize(str(vuln_count), "red" if vuln_count else "yellow") print >>self.__fd, "" # Audit scope if self.__show_data or not self.__console: table = Texttable() scope_domains = ["*." + r for r in Config.audit_scope.roots] scope_domains.extend(Config.audit_scope.domains) if Config.audit_scope.addresses: table.add_row(("IP addresses", "\n".join(Config.audit_scope.addresses))) if scope_domains: table.add_row(("Domains", "\n".join(scope_domains))) if Config.audit_scope.web_pages: table.add_row(("Web pages", "\n".join(Config.audit_scope.web_pages))) if table._rows: self.__fix_table_width(table) print >>self.__fd, "-# %s #- " % self.__colorize("Audit Scope", "yellow") print >>self.__fd, "" print >>self.__fd, table.draw() print >>self.__fd, "" # Discovered hosts if self.__show_data: need_header = True for domain in self.__iterate(Data.TYPE_RESOURCE, Domain.data_subtype): table = Texttable() self.__add_related(table, domain, Data.TYPE_RESOURCE, IP.data_subtype, "IP Address") self.__add_related(table, domain, Data.TYPE_INFORMATION, Geolocation.data_subtype, "Location") self.__add_related(table, domain, Data.TYPE_INFORMATION, WebServerFingerprint.data_subtype, "Web Server") self.__add_related(table, domain, Data.TYPE_INFORMATION, OSFingerprint.data_subtype, "OS Fingerprint") if table._rows: if need_header: need_header = False print >>self.__fd, "-# %s #- " % self.__colorize("Hosts", "yellow") print >>self.__fd, "" table.header(("Domain Name", domain.hostname)) self.__fix_table_width(table) text = table.draw() if self.__color: text = colorize_substring(text, domain.hostname, "red" if domain.get_links(Data.TYPE_VULNERABILITY) else "green") print >>self.__fd, text print >>self.__fd, "" for ip in self.__iterate(Data.TYPE_RESOURCE, IP.data_subtype): table = Texttable() self.__add_related(table, ip, Data.TYPE_RESOURCE, Domain.data_subtype, "Domain Name") self.__add_related(table, ip, Data.TYPE_RESOURCE, MAC.data_subtype, "MAC Address") self.__add_related(table, ip, Data.TYPE_RESOURCE, BSSID.data_subtype, "WiFi 802.11 BSSID") self.__add_related(table, ip, Data.TYPE_INFORMATION, Geolocation.data_subtype, "Location") self.__add_related(table, ip, Data.TYPE_INFORMATION, WebServerFingerprint.data_subtype, "Web Server") self.__add_related(table, ip, Data.TYPE_INFORMATION, OSFingerprint.data_subtype, "OS Fingerprint") self.__add_related(table, ip, Data.TYPE_INFORMATION, ServiceFingerprint.data_subtype, "Service") self.__add_related(table, ip, Data.TYPE_INFORMATION, Portscan.data_subtype, "Port Scan") self.__add_related(table, ip, Data.TYPE_INFORMATION, Traceroute.data_subtype, "Network Route") if table._rows: if need_header: need_header = False print >>self.__fd, "-# %s #- " % self.__colorize("Hosts", "yellow") print >>self.__fd, "" table.header(("IP Address", ip.address)) self.__fix_table_width(table) text = table.draw() if self.__color: text = colorize_substring(text, ip.address, "red" if ip.get_links(Data.TYPE_VULNERABILITY) else "green") print >>self.__fd, text print >>self.__fd, "" # Web servers if self.__show_data and Database.count(Data.TYPE_RESOURCE, BaseURL.data_subtype): print >>self.__fd, "-# %s #- " % self.__colorize("Web Servers", "yellow") print >>self.__fd, "" crawled = defaultdict(list) vulnerable = [] for url in self.__iterate(Data.TYPE_RESOURCE, URL.data_subtype): crawled[url.hostname].append(url.url) if self.__color and url.get_links(Data.TYPE_VULNERABILITY): vulnerable.append(url) for url in self.__iterate(Data.TYPE_RESOURCE, BaseURL.data_subtype): table = Texttable() table.header(("Base URL", url.url)) self.__add_related(table, url, Data.TYPE_INFORMATION, WebServerFingerprint.data_subtype, "Server") self.__add_related(table, url, Data.TYPE_INFORMATION, OSFingerprint.data_subtype, "Platform") urls = crawled[url.hostname] if urls: urls.sort() table.add_row(("Visited URLs", "\n".join(urls))) if table._rows: self.__fix_table_width(table) text = table.draw() if self.__color: p = text.find("\n") p = text.find("\n", p + 1) p = text.find("\n", p + 1) if p > 0: text = colorize_substring(text[:p], url.url, "red" if url.get_links(Data.TYPE_VULNERABILITY) else "green") + text[p:] for u in vulnerable: if u != url.url: text = colorize_substring(text, u, "red") print >>self.__fd, text print >>self.__fd, "" # Emails if self.__show_data: emails = { e.address: "red" if e.get_links(Data.TYPE_VULNERABILITY) else "green" for e in self.__iterate(Data.TYPE_RESOURCE, Email.data_subtype) } if emails: print >>self.__fd, "-# %s #- " % self.__colorize("Email Addresses", "yellow") print >>self.__fd, "" for e in sorted(emails): print >>self.__fd, "* " + self.__colorize(e, emails[e]) print >>self.__fd, "" # Vulnerabilities print >>self.__fd, "-# %s #- " % self.__colorize("Vulnerabilities", "yellow") print >>self.__fd, "" count = Database.count(Data.TYPE_VULNERABILITY) if count: if self.__show_data: print >>self.__fd, self.__colorize("%d vulnerabilities found!" % count, "red") print >>self.__fd, "" vuln_types = { v.display_name: v.vulnerability_type for v in self.__iterate(Data.TYPE_VULNERABILITY) } titles = vuln_types.keys() titles.sort() if "Uncategorized Vulnerability" in titles: titles.remove("Uncategorized Vulnerability") titles.append("Uncategorized Vulnerability") for title in titles: data_subtype = vuln_types[title] print >>self.__fd, "-- %s (%s) -- " % (self.__colorize(title, "cyan"), data_subtype) print >>self.__fd, "" for vuln in self.__iterate(Data.TYPE_VULNERABILITY, data_subtype): table = Texttable() table.header(("Occurrence ID", vuln.identity)) w = len(table.draw()) table.add_row(("Title", vuln.title)) ##targets = self.__gather_vulnerable_resources(vuln) targets = [vuln.target] table.add_row(("Found By", get_plugin_name(vuln.plugin_id))) p = len(table.draw()) table.add_row(("Level", vuln.level)) #table.add_row(("Impact", vuln.impact)) #table.add_row(("Severity", vuln.severity)) #table.add_row(("Risk", vuln.risk)) q = len(table.draw()) if vuln.cvss_base: table.add_row(("CVSS Base", vuln.cvss_base)) if vuln.cvss_score: table.add_row(("CVSS Score", vuln.cvss_score)) if vuln.cvss_vector: table.add_row(("CVSS Vector", vuln.cvss_vector)) if len(targets) > 1: targets.sort() table.add_row(("Locations", "\n".join(targets))) elif targets: table.add_row(("Location", targets[0])) table.add_row(("Description", vuln.description)) table.add_row(("Solution", vuln.solution)) taxonomy = [] if vuln.bid: taxonomy.extend(vuln.bid) if vuln.cve: taxonomy.extend(vuln.cve) if vuln.cwe: taxonomy.extend(vuln.cwe) if vuln.osvdb: taxonomy.extend(vuln.osvdb) if vuln.sa: taxonomy.extend(vuln.sa) if vuln.sectrack: taxonomy.extend(vuln.sectrack) if vuln.xf: taxonomy.extend(vuln.xf) if taxonomy: table.add_row(("Taxonomy", "\n".join(taxonomy))) if vuln.references: table.add_row(("References", "\n".join(sorted(vuln.references)))) details = vuln.display_properties.get("Details") if details: props = details.keys() props.sort() table.add_row(("Additional details", "\n".join(("%s: %s" % (x, details[x])) for x in props))) self.__fix_table_width(table) text = table.draw() if self.__color: text_1 = text[:w] text_3 = text[p:q] text_1 = colorize_substring(text_1, vuln.identity, vuln.level.lower()) for lvl in Vulnerability.VULN_LEVELS: if lvl in text_3: text_3 = colorize_substring(text_3, lvl, lvl.lower()) text = text_1 + text[w:p] + text_3 + text[q:] print >>self.__fd, text print >>self.__fd, "" else: print >>self.__fd, self.__colorize("No vulnerabilities found.", "green") print >>self.__fd, ""
def generate_report(self, output_file): Logger.log_more_verbose("Generating JSON database...") # Warn about --full not being supported by this plugin. if not Config.audit_config.only_vulns: Config.audit_config.only_vulns = True Logger.log_more_verbose( "Full report mode not supported, switching to brief mode.") # Hardcode the arguments for the JSON plugin. Config.plugin_args["mode"] = "dump" Config.plugin_args["command"] = "" # Get the report data. report_data = self.get_report_data() Logger.log_more_verbose("Postprocessing JSON database...") # Remove the false positives, if any. del report_data["false_positives"] # Gather all taxonomies into a single property. for vuln in report_data["vulnerabilities"].itervalues(): taxonomy = [] for prop in TAXONOMY_NAMES: taxonomy.extend(vuln.get(prop, [])) if taxonomy: taxonomy.sort() vuln["taxonomy"] = taxonomy # It's easier for the JavaScript code in the report to access the # vulnerabilities as an array instead of a map, so let's fix that. # Also, delete all properties we know aren't being used in the report. vulnerabilities = report_data["vulnerabilities"] sort_keys = [ (data["display_name"], data["plugin_id"], data["target_id"], data["identity"]) for data in vulnerabilities.itervalues() ] sort_keys.sort() report_data["vulnerabilities"] = [ { propname: propvalue for propname, propvalue in vulnerabilities[identity].iteritems() if propname in ( "display_name", "plugin_id", "target_id", "identity", "links", "data_type", "data_subtype", "title", "description", "solution", "taxonomy", "references", "level", "impact", "severity", "risk", ) } for _, _, _, identity in sort_keys ] vulnerabilities.clear() sort_keys = [] # Remove a bunch of data that won't be shown in the report anyway. for identity, data in report_data["informations"].items(): if data["information_category"] not in ( Information.CATEGORY_ASSET, Information.CATEGORY_FINGERPRINT, ): del report_data["informations"][identity] # Remove any dangling links we may have. links = set() for iterator in ( report_data["resources"].itervalues(), report_data["informations"].itervalues(), report_data["vulnerabilities"] ): links.update(data["identity"] for data in iterator) for iterator in ( report_data["resources"].itervalues(), report_data["informations"].itervalues(), report_data["vulnerabilities"] ): for data in iterator: tmp = set(data["links"]) tmp.intersection_update(links) data["links"] = sorted(tmp) tmp.clear() links.clear() # Now, let's go through all Data objects and try to resolve the # plugin IDs to user-friendly plugin names. plugin_map = dict() for iterator in ( report_data["resources"].itervalues(), report_data["informations"].itervalues(), report_data["vulnerabilities"] ): for data in iterator: if "plugin_id" in data: plugin_id = data["plugin_id"] if plugin_id not in plugin_map: plugin_map[plugin_id] = get_plugin_name(plugin_id) data["plugin_name"] = plugin_map[plugin_id] plugin_map.clear() # Calculate some statistics, so the JavaScript code doesn't have to. vulns_by_level = Counter() for level in Vulnerability.VULN_LEVELS: vulns_by_level[level] = 0 vulns_by_level.update( v["level"] for v in report_data["vulnerabilities"]) vulns_by_level = {k.title(): v for k, v in vulns_by_level.iteritems()} vulns_by_type = dict(Counter( v["display_name"] for v in report_data["vulnerabilities"] )) report_data["stats"] = { "resources": len(report_data["resources"]), "informations": len(report_data["informations"]), "vulnerabilities": len(report_data["vulnerabilities"]), "vulns_by_level": vulns_by_level, "vulns_by_type": vulns_by_type, } # It's better to show vulnerability levels as integers instead # of strings, so they can be sorted in the proper order. The # actual report shows them to the user as strings, but sorts # using the integer values. for vuln in report_data["vulnerabilities"]: vuln["level"] = Vulnerability.VULN_LEVELS.index(vuln["level"]) # Generate the ZIP file comment. comment = "Report generated with GoLismero %s at %s UTC\n"\ % (VERSION, report_data["summary"]["report_time"]) # Serialize the data and cleanup the unserialized version. if output_file.endswith(".zip"): serialized_data = json.dumps(report_data, sort_keys=True, indent=4) else: serialized_data = json.dumps(report_data) del report_data # Escape all HTML entities from the serialized data, # since the JSON library doesn't seem to do it. serialized_data = cgi.escape(serialized_data) # Get the directory where we can find our template. html_report = os.path.dirname(__file__) html_report = os.path.join(html_report, "html_report") html_report = os.path.abspath(html_report) if not html_report.endswith(os.path.sep): html_report += os.path.sep # Save the report data to disk. Logger.log_more_verbose("Writing report to disk...") # Save it as a zip file. if output_file.endswith(".zip"): with ZipFile(output_file, mode="w", compression=ZIP_DEFLATED, allowZip64=True) as zip: # Save the zip file comment. zip.comment = comment # Save the JSON data. arcname = os.path.join("js", "database.js") serialized_data = "data = " + serialized_data zip.writestr(arcname, serialized_data) del serialized_data # Copy the template dependencies into the zip file. for root, directories, files in os.walk(html_report): if root.endswith(os.path.sep + "backup"): continue for basename in files: if basename.endswith(".less"): continue if basename in ("index.html", "database.js"): continue filename = os.path.join(root, basename) arcname = filename[len(html_report):] if arcname == "index-orig.html": arcname = "index.html" zip.write(filename, arcname) # Save it as an HTML file with no dependencies. else: with open(os.path.join(html_report, "index.html"), "rb") as fd: template = fd.read() assert "%DATA%" in template serialized_data = template.replace("%DATA%", serialized_data) del template with open(output_file, "wb") as fd: fd.write(serialized_data)
def generate_report(self, output_file): Logger.log_more_verbose("Generating JSON database...") # Warn about --full not being supported by this plugin. if not Config.audit_config.only_vulns: Config.audit_config.only_vulns = True Logger.log_more_verbose( "Full report mode not supported, switching to brief mode.") # Hardcode the arguments for the JSON plugin. Config.plugin_args["mode"] = "dump" Config.plugin_args["command"] = "" # Get the report data. report_data = self.get_report_data() Logger.log_more_verbose("Postprocessing JSON database...") # Remove the false positives, if any. del report_data["false_positives"] # Gather all taxonomies into a single property. for vuln in report_data["vulnerabilities"].itervalues(): taxonomy = [] for prop in TAXONOMY_NAMES: taxonomy.extend(vuln.get(prop, [])) if taxonomy: taxonomy.sort() vuln["taxonomy"] = taxonomy # It's easier for the JavaScript code in the report to access the # vulnerabilities as an array instead of a map, so let's fix that. # Also, delete all properties we know aren't being used in the report. vulnerabilities = report_data["vulnerabilities"] sort_keys = [(data["display_name"], data["plugin_id"], data["target_id"], data["identity"]) for data in vulnerabilities.itervalues()] sort_keys.sort() report_data["vulnerabilities"] = [{ propname: propvalue for propname, propvalue in vulnerabilities[identity].iteritems() if propname in ( "display_name", "plugin_id", "target_id", "identity", "links", "data_type", "data_subtype", "title", "description", "solution", "taxonomy", "references", "level", "impact", "severity", "risk", ) } for _, _, _, identity in sort_keys] vulnerabilities.clear() sort_keys = [] # Remove a bunch of data that won't be shown in the report anyway. for identity, data in report_data["informations"].items(): if data["information_category"] not in ( Information.CATEGORY_ASSET, Information.CATEGORY_FINGERPRINT, ): del report_data["informations"][identity] # Remove any dangling links we may have. links = set() for iterator in (report_data["resources"].itervalues(), report_data["informations"].itervalues(), report_data["vulnerabilities"]): links.update(data["identity"] for data in iterator) for iterator in (report_data["resources"].itervalues(), report_data["informations"].itervalues(), report_data["vulnerabilities"]): for data in iterator: tmp = set(data["links"]) tmp.intersection_update(links) data["links"] = sorted(tmp) tmp.clear() links.clear() # Now, let's go through all Data objects and try to resolve the # plugin IDs to user-friendly plugin names. plugin_map = dict() for iterator in (report_data["resources"].itervalues(), report_data["informations"].itervalues(), report_data["vulnerabilities"]): for data in iterator: if "plugin_id" in data: plugin_id = data["plugin_id"] if plugin_id not in plugin_map: plugin_map[plugin_id] = get_plugin_name(plugin_id) data["plugin_name"] = plugin_map[plugin_id] plugin_map.clear() # Calculate some statistics, so the JavaScript code doesn't have to. vulns_by_level = Counter() for level in Vulnerability.VULN_LEVELS: vulns_by_level[level] = 0 vulns_by_level.update(v["level"] for v in report_data["vulnerabilities"]) vulns_by_level = {k.title(): v for k, v in vulns_by_level.iteritems()} vulns_by_type = dict( Counter(v["display_name"] for v in report_data["vulnerabilities"])) report_data["stats"] = { "resources": len(report_data["resources"]), "informations": len(report_data["informations"]), "vulnerabilities": len(report_data["vulnerabilities"]), "vulns_by_level": vulns_by_level, "vulns_by_type": vulns_by_type, } # It's better to show vulnerability levels as integers instead # of strings, so they can be sorted in the proper order. The # actual report shows them to the user as strings, but sorts # using the integer values. for vuln in report_data["vulnerabilities"]: vuln["level"] = Vulnerability.VULN_LEVELS.index(vuln["level"]) # Generate the ZIP file comment. comment = "Report generated with GoLismero %s at %s UTC\n"\ % (VERSION, report_data["summary"]["report_time"]) # Serialize the data and cleanup the unserialized version. if output_file.endswith(".zip"): serialized_data = json.dumps(report_data, sort_keys=True, indent=4) else: serialized_data = json.dumps(report_data) del report_data # Escape all HTML entities from the serialized data, # since the JSON library doesn't seem to do it. serialized_data = cgi.escape(serialized_data) # Get the directory where we can find our template. html_report = os.path.dirname(__file__) html_report = os.path.join(html_report, "html_report") html_report = os.path.abspath(html_report) if not html_report.endswith(os.path.sep): html_report += os.path.sep # Save the report data to disk. Logger.log_more_verbose("Writing report to disk...") # Save it as a zip file. if output_file.endswith(".zip"): with ZipFile(output_file, mode="w", compression=ZIP_DEFLATED, allowZip64=True) as zip: # Save the zip file comment. zip.comment = comment # Save the JSON data. arcname = os.path.join("js", "database.js") serialized_data = "data = " + serialized_data zip.writestr(arcname, serialized_data) del serialized_data # Copy the template dependencies into the zip file. for root, directories, files in os.walk(html_report): if root.endswith(os.path.sep + "backup"): continue for basename in files: if basename.endswith(".less"): continue if basename in ("index.html", "database.js"): continue filename = os.path.join(root, basename) arcname = filename[len(html_report):] if arcname == "index-orig.html": arcname = "index.html" zip.write(filename, arcname) # Save it as an HTML file with no dependencies. else: with open(os.path.join(html_report, "index.html"), "rb") as fd: template = fd.read() assert "%DATA%" in template serialized_data = template.replace("%DATA%", serialized_data) del template with open(output_file, "wb") as fd: fd.write(serialized_data)