diff --git a/examples/nessus_integration/README.md b/examples/nessus_integration/README.md new file mode 100644 index 0000000..e5db231 --- /dev/null +++ b/examples/nessus_integration/README.md @@ -0,0 +1,31 @@ +## Cyberwatch & Nessus Integration + +This script will retrieve vulnerabilities of assets on Nessus and import them into Cyberwatch automatically. + +For this, sets of credentials for the Nessus and Cyberwatch APIs will be required to allow the retrieval of data from Nessus, and the creation of assets on Cyberwatch. + +Given the technical constraints, only the list of vulnerabilities for each asset on Nessus can be exported to Cyberwatch, without any association related to the affected technologies and recommended corrective actions. + +### Use case + +Let's consider the following Nessus assets. + +![image](img/image1.png) + +After executing the script, these assets will be available on Cyberwatch, along with their associated list of vulnerabilities. + +![image](img/image2.png) + +The vulnerabilities come from the Reference Information panel of the Nessus plugins results. However, not all plugins associate vulnerabilities with their results. + +![image](img/image3.png) + +The name of the assets created on Cyberwatch can be customized, particularly to customize the string "Nessus |" in front of it. + +_Caution : When using the script, all assets starting with the identifier ('Nessus |' by default) will be deleted, make sure no conflict can happen with any other unwanted asset._ + +### How to use + +1. Generate a set of [Nessus](https://docs.tenable.com/vulnerability-management/Content/Settings/my-account/GenerateAPIKey.htm#To-generate-API-keys-for-your-own-account:) and [Cyberwatch](https://docs.cyberwatch.fr/help/en/API_documentation/API_use/#authenticate-to-the-api-and-test-your-connection) Api keys ; +2. Fill out the `api.conf` file accordingly ; +3. Launch the script without any needed arguments. diff --git a/examples/nessus_integration/cbw_helper.py b/examples/nessus_integration/cbw_helper.py new file mode 100644 index 0000000..cf51a70 --- /dev/null +++ b/examples/nessus_integration/cbw_helper.py @@ -0,0 +1,147 @@ +from cyberwatch_api import Cyberwatch_Pyhelper +import json +import requests +import configparser +import urllib3 +urllib3.disable_warnings(category=urllib3.exceptions.InsecureRequestWarning) + +def jprint(text): + print(json.dumps(text, indent=4)) + return json.dumps(text, indent=4) + +def cbw_createAirgapAsset(HOSTNAME, TECHNOLOGIES): + output = "HOSTNAME:{}\n".format(HOSTNAME) + # for TECHNOLOGIE in TECHNOLOGIES: + # (package, version) = TECHNOLOGIE.split("_") + # output += "NVD_APPLICATION:(from nessus : unanalyzed) {}|{}\n".format(package, version) + + apiResponse = Cyberwatch_Pyhelper().request( + method="POST", + endpoint="/api/v2/cbw_scans/scripts", + body_params={ + "output" : output + }, + timeout=90, + verify_ssl=False + ) + result = next(apiResponse) + + if 'error' in result: + raise "ERROR : " + result["error"]["message"] + return result.json()["server_id"] + + +def cbw_deleteAsset(id): + apiResponse = Cyberwatch_Pyhelper().request( + method="DELETE", + endpoint="/api/v3/assets/servers/{}".format(id), + verify_ssl=False, + ) + + return next(apiResponse).json() + +def cbw_retrieveSecurityIssue(sid): + apiResponse = Cyberwatch_Pyhelper().request( + method="GET", + endpoint="/api/v3/security_issues", + verify_ssl=False, + body_params={ + "sid" : sid, + } + ) + + return next(apiResponse).json() + +def cbw_deleteSecurityIssue(id): + apiResponse = Cyberwatch_Pyhelper().request( + method="DELETE", + endpoint="/api/v3/security_issues/{}".format(id), + verify_ssl=False, + ) + + return next(apiResponse).json() + +def cbw_createSecurityIssue(sid, title, cve_announcements, servers): + apiResponse = Cyberwatch_Pyhelper().request( + method="POST", + endpoint="/api/v3/security_issues", + verify_ssl=False, + body_params={ + "sid" : sid, + "title" : title, + "cve_announcements" : cve_announcements, + "servers" : servers, + } + ) + + return next(apiResponse).json() + +def cbw_refreshAssetAnalysis(server_id): + apiResponse = Cyberwatch_Pyhelper().request( + method="PUT", + endpoint="/api/v3/vulnerabilities/servers/{}/refresh".format(server_id), + verify_ssl=False, + ) + + return next(apiResponse).json() + +def cbw_clearNessusData(): + ##### + # Clear All Nessus Security Issues + ##### + + apiResponse = Cyberwatch_Pyhelper().request( + method="GET", + endpoint="/api/v3/security_issues", + verify_ssl=False, + ) + + security_issues = [] + for page in apiResponse: + security_issues = security_issues + page.json() + + for security_issue in security_issues: + if security_issue["title"].startswith(PARSE_CONFIG()["SCRIPT_ASSET_IDENTIFIER"]): + print("Deleting Security Issue {}".format(security_issue["title"])) + cbw_deleteSecurityIssue(security_issue["id"]) + + ##### + # Clear All Nessus Assets + ##### + + apiResponse = Cyberwatch_Pyhelper().request( + method="GET", + endpoint="/api/v3/assets/servers", + verify_ssl=False, + ) + print("Retrieving assets : 0") + assets = [] + for page in apiResponse: + assets = assets + page.json() + print("\033[A\033[A\nRetrieving assets : " + str(len(assets))) + + for asset in assets: + if asset["hostname"].startswith(PARSE_CONFIG()["SCRIPT_ASSET_IDENTIFIER"]): + print("Deleting Asset {}".format(asset["hostname"])) + cbw_deleteAsset(asset["id"]) + +def PARSE_CONFIG(): + config = configparser.ConfigParser() + config.read('api.conf') + NESSUS_API_KEY = config['nessus']['api_key'] + NESSUS_SECRET_KEY = config['nessus']['secret_key'] + NESSUS_URL = config['nessus']['url'] + SCRIPT_DEBUG = config['script']['debug'] + SCRIPT_ASSET_IDENTIFIER = config['script']['asset_identifier'] + + return { + "NESSUS_API_KEY" : NESSUS_API_KEY, + "NESSUS_SECRET_KEY" : NESSUS_SECRET_KEY, + "NESSUS_URL" : NESSUS_URL, + "SCRIPT_DEBUG" : "true" in SCRIPT_DEBUG.lower(), + "SCRIPT_ASSET_IDENTIFIER" : SCRIPT_ASSET_IDENTIFIER + } + +def NESSUS_API(URL): + nessus_config = PARSE_CONFIG() + return requests.get(URL, headers={'X-ApiKeys': f'accessKey={nessus_config["NESSUS_API_KEY"]}; secretKey={nessus_config["NESSUS_SECRET_KEY"]}'}, verify=False) diff --git a/examples/nessus_integration/img/image1.png b/examples/nessus_integration/img/image1.png new file mode 100644 index 0000000..55f9fed Binary files /dev/null and b/examples/nessus_integration/img/image1.png differ diff --git a/examples/nessus_integration/img/image2.png b/examples/nessus_integration/img/image2.png new file mode 100644 index 0000000..4a30ffc Binary files /dev/null and b/examples/nessus_integration/img/image2.png differ diff --git a/examples/nessus_integration/img/image3.png b/examples/nessus_integration/img/image3.png new file mode 100644 index 0000000..e60ff6a Binary files /dev/null and b/examples/nessus_integration/img/image3.png differ diff --git a/examples/nessus_integration/main.py b/examples/nessus_integration/main.py new file mode 100644 index 0000000..0776718 --- /dev/null +++ b/examples/nessus_integration/main.py @@ -0,0 +1,96 @@ +from cyberwatch_api import Cyberwatch_Pyhelper +from cbw_helper import * +import sys +import urllib3 +urllib3.disable_warnings(category=urllib3.exceptions.InsecureRequestWarning) + +##### +# Initializing global variables +##### +NESSUS_URL = PARSE_CONFIG()["NESSUS_URL"] +DEBUG = PARSE_CONFIG()["SCRIPT_DEBUG"] +NESSUS_ASSETS = {} + +##### +# Retrieving all Nessus Scans +##### + +SCANS = NESSUS_API('{}/scans'.format(NESSUS_URL)).json()["scans"] + +for SCAN in SCANS: + print("[+] Nessus - Retrieving scan {}".format(SCAN["id"])) + ##### + # Retrieve all hosts associated to the current scan + ##### + HOSTS = NESSUS_API('{}/scans/{}'.format(NESSUS_URL, SCAN["id"])).json()["hosts"] + + for HOST in HOSTS: + print("\t Host : {}".format(HOST["hostname"])) + ##### + # Clean the previous host data + ##### + CVE_LIST = [] + + ##### + # Get the current host informations and plugins list + ##### + HOST_ID = HOST["host_id"] + HOST_NAME = HOST["hostname"] + HOST_INFORMATIONS = NESSUS_API('{}/scans/{}/hosts/{}'.format(NESSUS_URL, SCAN["id"], HOST_ID)).json() + HOST_PLUGINS_LIST = list(set([CVE["plugin_id"] for CVE in HOST_INFORMATIONS["vulnerabilities"] if CVE["plugin_id"]])) + print("\t\t Plugins : {}".format(len(HOST_PLUGINS_LIST))) + + ##### + # For each plugin, retrieve the associated CVEs + ##### + for PLUGIN in HOST_PLUGINS_LIST: + try: + URL = '{}/scans/{}/hosts/{}/plugins/{}'.format(NESSUS_URL, SCAN["id"], HOST_ID, PLUGIN) + RESPONSE = NESSUS_API(URL) + CVE_LIST = list(set(CVE_LIST + next(ref["values"]["value"] for ref in NESSUS_API(URL).json()["info"]["plugindescription"]["pluginattributes"]["ref_information"]["ref"] if ref["name"] == "cve"))) + except: + next + + ##### + # If it is the first time we find the host, we save it. Otherwise, we just append the found CVEs + ##### + + if HOST_NAME not in NESSUS_ASSETS: + NESSUS_ASSETS[HOST_NAME] = { + "id" : HOST_ID, + "hostname" : HOST_NAME, + "cve_list" : CVE_LIST, + } + else: + NESSUS_ASSETS[HOST_NAME]["cve_list"] = list(set(NESSUS_ASSETS[HOST_NAME]["cve_list"] + CVE_LIST)) + + print("\t\t Total CVEs : {}".format(len(NESSUS_ASSETS[HOST_NAME]["cve_list"]))) + +##### +# Clean cyberwatch data related to nessus +##### +print("[+] Cleaning all related Nessus data on Cyberwatch") +cbw_clearNessusData() + +##### +# Foreach found nessus host, we create a security issue linking all the found CVEs, then we associate it to the matching airgap asset +##### + +for NESSUS_ASSET_ID, NESSUS_ASSET in NESSUS_ASSETS.items(): + ##### + # Creating or updating the airgap asset + ##### + CBW_ASSET_HOSTNAME = "Nessus | {}".format(NESSUS_ASSET["hostname"]) + CBW_ASSET_ID = cbw_createAirgapAsset(CBW_ASSET_HOSTNAME, []) + print("[+] Cyberwatch - Creating asset {}".format(CBW_ASSET_HOSTNAME)) + + ##### + # Creating the security issue + ##### + CBW_HOST_SID = "Nessus_{}".format(NESSUS_ASSET["hostname"]) + CBW_HOST_SID_TITLE = "Nessus | {}".format(NESSUS_ASSET["hostname"]) + + if bool(cbw_retrieveSecurityIssue(CBW_HOST_SID)): # Security Issues Already Exists + cbw_deleteSecurityIssue(cbw_retrieveSecurityIssue(CBW_HOST_SID)[0]["id"]) + + cbw_createSecurityIssue(CBW_HOST_SID, CBW_HOST_SID_TITLE, NESSUS_ASSET["cve_list"], [CBW_ASSET_ID]) \ No newline at end of file