diff --git a/faraday_agent_dispatcher/static/executors/official/cisco_cybervision.py b/faraday_agent_dispatcher/static/executors/official/cisco_cybervision.py index 96efb237..ec3b19a6 100644 --- a/faraday_agent_dispatcher/static/executors/official/cisco_cybervision.py +++ b/faraday_agent_dispatcher/static/executors/official/cisco_cybervision.py @@ -2,18 +2,26 @@ import os import sys import json +import time import datetime import requests from urllib3.exceptions import InsecureRequestWarning from faraday_agent_dispatcher.utils.severity_utils import severity_from_score -API_BASE = "/api/3.0/" +API_BASE = "/api/3.0" def log(msg, end="\n"): print(msg, file=sys.stderr, flush=True, end=end) +def parse_date(date_str): + try: + return datetime.datetime.strptime(date_str, "%Y-%m-%dT%H:%M:%SZ").timestamp() + except ValueError: + return "" + + def cybervision_report_composer(url, token, preset_list, asset_tags, vuln_tags): req_headers = {"accept": "application/json", "x-token-id": token} requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning) @@ -21,7 +29,7 @@ def cybervision_report_composer(url, token, preset_list, asset_tags, vuln_tags): presets_id = {} # STAGE 1 - get preset list - req_url = f"{url}{API_BASE}presets" + req_url = f"{url}{API_BASE}/presets" try: resp = requests.get(req_url, headers=req_headers, timeout=20, verify=False).json() except TimeoutError: @@ -35,26 +43,58 @@ def cybervision_report_composer(url, token, preset_list, asset_tags, vuln_tags): if preset["label"] == req_preset: presets_id[preset["label"]] = preset["id"] presets_queue.append(preset["id"]) + presets_id_inv = {v: k for k, v in presets_id.items()} # STAGE 2 - get all vulns per preset presets_vuln_collection = {} step_c = 1 step_s = 100 + + for _id in presets_queue: # post to update to latest computed data + req_refresh_url = f"{url}{API_BASE}/presets/{_id}/refreshData" + try: + resp = requests.post(req_refresh_url, headers=req_headers, timeout=20, verify=False) + except TimeoutError: + log("Can't reach Cyber Vision: connection timed out") + sys.exit(1) + for _id in presets_queue: + serv_c = 0 + while True: # wait until data is ready + req_test_url = f"{url}{API_BASE}/presets/{_id}/visualisations/vulnerability-list?page=1&size=5" + try: + resp = requests.get(req_test_url, headers=req_headers, timeout=20, verify=False) + if "Service unavailable" in resp.content.decode("UTF-8"): + if serv_c == 0: + log(f"Preset {presets_id_inv[_id]} data is not ready, waiting...") + serv_c += 1 + else: + log(f"Preset {presets_id_inv[_id]} data is ready!") + serv_c = 0 + break + if serv_c >= 60: + break + except TimeoutError: + log("Can't reach Cyber Vision: connection timed out") + sys.exit(1) + time.sleep(1) + + if serv_c >= 60: + log(f"Error: Preset {presets_id_inv[_id]} took many time to refresh data") + continue + step_c = 1 presets_vuln_collection[_id] = [] - while True: - req_url = f"{url}{API_BASE}presets/{_id}/visualisations/" f"vulnerability-list?page={step_c}&size={step_s}" + while True: # paged data fetch + req_url = f"{url}{API_BASE}/presets/{_id}/visualisations/vulnerability-list?page={step_c}&size={step_s}" try: resp = requests.get(req_url, headers=req_headers, timeout=20, verify=False) - if resp.content.decode("UTF-8") == "Service unavailable: data is not available yet": - raise ValueError(resp.content.decode("UTF-8")) resp = resp.json() except TimeoutError: log("Can't reach Cyber Vision: connection timed out") sys.exit(1) except ValueError as ve: - log(f"{str(ve)} at preset {_id}") + log(f"{str(ve)} at preset {presets_id_inv[_id]}") break if "error" in resp: log(f"API Error: {resp['error']}") @@ -67,6 +107,9 @@ def cybervision_report_composer(url, token, preset_list, asset_tags, vuln_tags): # STAGE 3 - processing vulns hosts = {} for pres_data in presets_id.items(): + if not pres_data[1] in presets_vuln_collection.keys(): + log(f"Error: No vulnerabilities loaded for {pres_data[0]} ({pres_data[1]})") + continue for vuln_pack in presets_vuln_collection[pres_data[1]]: for vuln in vuln_pack: if not vuln["device"]["label"] in hosts: @@ -94,10 +137,9 @@ def cybervision_report_composer(url, token, preset_list, asset_tags, vuln_tags): "data": vuln["fullDescription"], "status": "open", "cve": [x["cve"] for i, x in enumerate(vuln_pack) if x["title"] == vuln["title"]], - "run_date": datetime.datetime.strptime( - vuln["publishTime"], "%Y-%m-%dT%H:%M:%SZ" - ).timestamp(), + "run_date": parse_date(vuln["publishTime"]), "tags": vuln_tags, + "cwe": [], } ) data = {"hosts": [x[1] for x in hosts.items()]} diff --git a/faraday_agent_dispatcher/utils/severity_utils.py b/faraday_agent_dispatcher/utils/severity_utils.py index b14a6d6e..dd3fea58 100644 --- a/faraday_agent_dispatcher/utils/severity_utils.py +++ b/faraday_agent_dispatcher/utils/severity_utils.py @@ -16,6 +16,6 @@ def severity_from_score(score: float, max_score: float): return "medium" if max_score * 0.7 <= score < max_score * 0.9: return "high" - if max_score * 0.9 <= score < max_score: + if max_score * 0.9 <= score <= max_score: return "critical" - return "" + return "unclassified"