Digital Defense FrontlineVM
Use the Digital Defense FrontlineVM to identify and evaluate the security and business risks of network devices and applications deployed as premise, cloud, or hybrid network-based implementations.
Vulnerability Management · Digital Defense Frontline VM
Details
| ID | Digital Defense FrontlineVM |
|---|---|
| Provider | Fortra |
| Category | Vulnerability Management |
| From Version | 5.0.0 |
| Docker Image | demisto/python3:3.12.8.3296088 |
| Supported Modules | Agentix XSIAM |
README
Use the Digital Defense Frontline VM to identify and evaluate the security and business risks of network devices and applications deployed as premise, cloud, or hybrid network-based implementations.
This integration was integrated and tested with version 6.2.4 of Digital Defense FrontlineVM
Configure Digital Defense FrontlineVM in Cortex
| Parameter | Description | Required |
|---|---|---|
| frontlineURL | Frontline VM URL | True |
| insecure | Trust any certificate (not secure) | False |
| apiToken | API Token to access Frontline VM | True |
| isFetch | Fetch incidents | False |
| incidentType | Incident type | False |
| incidentSeverity | Minimum vulnerability severity for fetching incidents | False |
| incidentFrequency | Rate at which to check vulnerability events when fetching incidents | True |
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
frontline-get-assets
Retrieves the asset’s information from Frontline VM.
Base Command
frontline-get-assets
Input
| Argument Name | Description | Required |
|---|---|---|
| ip_address | The IP address for which to return assets. | Optional |
| label_name | The label name for which to return assets. | Optional |
| max_days_since_scan | The number of days (retroactive) since the last scan. | Optional |
| hostname | The hostname for which to return assets. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| FrontlineVM.Hosts | unknown | The host data from Frontline.Cloud. |
| FrontlineVM.Hosts.ID | unknown | The ID number of the host. |
| FrontlineVM.Hosts.Hostname | unknown | The hostname of the asset. |
| FrontlineVM.Hosts.IP | unknown | The IP address of the host. |
| FrontlineVM.Hosts.DNSHostname | unknown | The DNS hostname of the host. |
| FrontlineVM.Hosts.MAC | unknown | The MAC address of the host. |
| FrontlineVM.Hosts.OS | unknown | The operating system of the host. |
| FrontlineVM.Hosts.OSType | unknown | The operating system type of the host. |
| FrontlineVM.Hosts.CriticalVulnCount | unknown | The severity count of critical vulnerabilities. |
Command Example
#### Human Readable Output
### frontline-get-vulns
***
Retrieves vulnerability information from Frontline VM.
#### Base Command
`frontline-get-vulns`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| min_severity | The minimum severity level for which to return vulnerabilities. This argument overrides the "severity" argument when used together. Can be: "critical","high","medium","low","trivial", or "info". | Optional |
| severity | Returns all vulnerabilities from Frontline with the specified severity level. Can be: "critical","high","medium","low","trivial", or "info". | Optional |
| max_days_since_created | Retrieves vulnerabilities found prior to the specified date (in days). | Optional |
| min_days_since_created | Retrieves vulnerabilities found after the specified date (in days). | Optional |
| host_id | Retrieves vulnerabilities from a specific host based on the Host ID. | Optional |
| ip_address | The IP address of the host for which to retrieve the vulnerability data. | Optional |
#### Context Output
| **Path** | **Type** | **Description** |
| --- | --- | --- |
| FrontlineVM.Vulns | unknown | Retrieved vulnerability data pulled from Frontline.Cloud. |
| FrontlineVM.Stat | unknown | The statistical overview of vulnerabilities pulled. |
| FrontlineVM.Vulns.vuln-id | unknown | The ID of the vulnerability. |
| FrontlineVM.Vulns.hostname | unknown | The hostname of the asset. |
| FrontlineVM.Vulns.ip-address | unknown | The IP address of the asset. |
| FrontlineVM.Vulns.vuln-title | unknown | The title of the vulnerability. |
| FrontlineVM.Vulns.date-created | unknown | The date the vulnerability was created. |
| FrontlineVM.Vulns.ddi-severity | unknown | The severity level of the vulnerability. |
| FrontlineVM.Vulns.vuln-info | unknown | Information related to the vulnerability. |
#### Command Example
Human Readable Output
frontline-scan-asset
Performs a scan on the specified asset.
Base Command
frontline-scan-asset
Input
| Argument Name | Description | Required |
|---|---|---|
| ip_address | The IP address of the asset to scan. | Optional |
| scan_policy | The policy of the scan (case sensitive). | Optional |
| ip_range_start | The IP address start range of the asset to scan. | Optional |
| ip_range_end | The IP address end range of the asset to scan. | Optional |
| scan_name | The name of this scan to run in FrontlineVM. Default value will be “Cortex XSOAR Scan [<asset_ip_address>]” | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| FrontlineVM.Scan.ID | unknown | The ID number of the scan. |
| FrontlineVM.Scan.Name | unknown | The name of the scan. |
| FrontlineVM.Scan.Policy | unknown | The policy name of the scan. |
| FrontlineVM.Scan.IP | unknown | The IP address of the scan (can be a single IP address or a range of IP addresses). |
Command Example
``````
Human Readable Output
Configuration parameters
frontlineURL— Frontline VM URL (required)insecure— Trust any certificate (not secure)apiToken— API Token to access Frontline VM (required)isFetch— Fetch incidentsincidentType— Incident typeincidentFetchInterval— Incidents Fetch IntervalincidentSeverity— Minimum vulnerability severity for fetching incidentsincidentFrequency— Rate at which to check vulnerability events when fetching incidents (required)
Commands (3)
-
frontline-get-assetsRetrieves the asset's information from Frontline VM.
-
frontline-get-vulnsRetrieves vulnerability information from Frontline VM.
-
frontline-scan-assetPerforms a scan on the specified asset.
import demistomock as demisto from CommonServerPython import * import json import math import re import requests import signal import socket import struct import urllib3 from datetime import datetime, timedelta, UTC from typing import Any # disable insecure warnings urllib3.disable_warnings() # Params: VERIFY_SSL = not demisto.params().get("insecure", False) API_TOKEN = demisto.params().get("apiToken") INCIDENT_VULN_MIN_SEVERITY = demisto.params().get("incidentSeverity") INCIDENT_FREQUENCY = demisto.params().get("incidentFrequency") def get_base_url(): """Removes forward slash from end of url input""" url = demisto.params().get("frontlineURL") url = re.sub(r"\/$", "", url) return url # Endpoints: BASE_URL = get_base_url() VULN_ENDPOINT = BASE_URL + "/api/scanresults/active/vulnerabilities/" HOST_ENDPOINT = BASE_URL + "/api/scanresults/active/hosts/" SCAN_ENDPOINT = BASE_URL + "/api/scans/" # FrontlineVM (FVM) header for API authorization when performing: API_AUTH_HEADER = {"Authorization": "Token " + str(API_TOKEN)} # Minimum time to timeout functions (5 mins) MIN_TIMEOUT = 300 # seconds # HEADERS for Demisto command outputs: VULN_DATA_HEADERS = ["vuln-id", "hostname", "ip-address", "vuln-title", "date-created", "ddi-severity", "vuln-info"] HOST_HEADERS = ["ID", "Hostname", "IP", "DNSHostname", "MAC", "OS", "OSType", "CriticalVulnCount"] SCAN_HEADERS = ["ID", "Name", "IP", "Policy"] """HELPER FUNCTIONS""" class EndOfTime(Exception): """Raised when functions timeout""" def function_timeout(signum, frame): """ Used to raise EndOfTime exception for timeout functions. """ raise EndOfTime("Function has timed out") def get_function_timeout_time(data_count): """ Returns time (in seconds) to timeout function based upon the amount of data to pull. """ timeout_time = math.ceil(data_count / 2) if timeout_time < MIN_TIMEOUT: timeout_time = MIN_TIMEOUT return timeout_time def get_all_data(first_page): """ Retrieves all data if multiple pages are present in API request. """ request_url = first_page.get("next") have_all_data = False current_data = {} # type: Dict[str, Any] all_data = [] # type: List[Dict] while not have_all_data: resp = requests.get(url=request_url, headers=API_AUTH_HEADER, timeout=30, verify=VERIFY_SSL) if not resp.ok: msg = "FrontlineVM get_all_data -- status code: " + str(resp.status_code) demisto.debug(msg) resp.raise_for_status() current_data = json.loads(resp.text) all_data.extend(current_data.get("results", [])) if current_data.get("next"): request_url = current_data.get("next") else: have_all_data = True return all_data def get_fvm_data(request_url, **kwargs): """Retrieves data from FrontlineVM API""" data = [] # type: List current_data = {} # type: Dict resp = requests.get(request_url, headers=API_AUTH_HEADER, timeout=30, verify=VERIFY_SSL, **kwargs) resp.raise_for_status() current_data = json.loads(resp.text) data.extend(current_data.get("results", [])) # if there is a next page of data, iterate through pages to get all data: if current_data.get("next"): # setup a timeout for get_all_data function: data_count = current_data.get("count") timeout_time = get_function_timeout_time(data_count) signal.signal(signal.SIGALRM, function_timeout) signal.alarm(timeout_time) try: all_data = get_all_data(current_data) data.extend(all_data) except EndOfTime: return_error("Error: FrontlineVM get_fvm_data function exceeds timeout time.") except Exception as err: return_error("Error: FrontlineVM get_fvm_data failed. \n" + str(err)) return data def parse_params(param_dict): """ This parses the given dictionary and modifies it to comply with our API endpoint queries of indexing multiple queries (?_0_first_query=value0_1_query1=value_2_query2=value2) """ param_index = 0 new_param_dict = {} for key in param_dict: # 'ordering' key shouldn't be using the same param indexing as queries. if key == "ordering": continue new_key = "_" + str(param_index) + "_" + key new_param_dict[new_key] = param_dict[key] param_index += 1 return new_param_dict def get_query_date_param(day_input): """Returns a datetime object of days from now to given day_input""" now = datetime.utcnow() query_date = (now - timedelta(days=int(day_input))).replace(hour=0, minute=0, second=0, microsecond=0) query_date = datetime.strftime(query_date, "%Y-%m-%dT%H:%M:%SZ") return query_date def get_fetch_frequency(): """Returns the INCIDENT_FREQUENCY as a datetime object.""" fetch_frequency = INCIDENT_FREQUENCY.split() demisto.debug("FrontlineVM get_fetch_incident_td -- using frequency: " + str(fetch_frequency)) if "min" in str(fetch_frequency[1]): return timedelta(minutes=int(fetch_frequency[0])) return timedelta(hours=int(fetch_frequency[0])) def create_vuln_event_object(vuln): """Creates a vulnerability event object given raw vulnerability data.""" vuln_event = {} vuln_event["vuln-id"] = vuln.get("id") vuln_event["hostname"] = vuln.get("hostname") vuln_event["ip-address"] = vuln.get("ip_address") vuln_event["port"] = vuln.get("port") vuln_event["scan-id"] = vuln.get("scan_id") vuln_event["vuln-title"] = vuln.get("title") vuln_event["date-created"] = vuln.get("active_view_date_created") vuln_event["ddi-severity"] = vuln["severities"]["ddi"] vuln_event["vuln-info"] = vuln.get("data") return vuln_event def vulns_to_incident(vulns, last_start_time): """ Iterate through vulnerabilities and create incident if vulnerability has been created since last start_time. """ incidents = [] for vuln in vulns: # get vulnerability active view (av) date created values: av_date_created_str = vuln.get("active_view_date_created") av_date_created = datetime.strptime(av_date_created_str, "%Y-%m-%dT%H:%M:%S.%fZ") # Create incident if vuln created after last run start time: if av_date_created > last_start_time: vuln_event = create_vuln_event_object(vuln) incident = { "name": vuln.get("title"), "occurred": vuln.get("active_view_date_created"), "details": vuln.get("data"), "rawJSON": json.dumps(vuln_event), } incidents.append(incident) return incidents def fetch_vulnerabilities(last_start_time_str): """Pulls vulnerability data for fetch_incidents.""" # Pull vulnerabilities: req_params = {} req_params["lte_vuln_severity_ddi"] = str(INCIDENT_VULN_MIN_SEVERITY) req_params["gte_vuln_date_created"] = last_start_time_str req_params["ordering"] = "active_view_date_created" req_params = parse_params(req_params) vulns = get_fvm_data(VULN_ENDPOINT, params=req_params) return vulns def fetch_incidents(): """Method to fetch Demisto incidents by pulling any new vulnerabilities found.""" try: new_start_time = datetime.utcnow() # may be used to update new start_time if no incidents found. new_start_time_str = new_start_time.strftime("%Y-%m-%dT%H:%M:%S.%fZ") incidents: list[dict[str, Any]] = [] last_run = demisto.getLastRun() # Check if last_run exists and has a start_time to continue: if last_run and last_run.get("start_time", False): last_start_time_str = last_run.get("start_time") # last start time as string last_start_time = datetime.strptime(last_start_time_str, "%Y-%m-%dT%H:%M:%S.%fZ") fetch_frequency = get_fetch_frequency() # gets user set frequency as datetime object # Return empty list if time since last_start_time has not exceeded frequency time: if (datetime.utcnow() - last_start_time) < fetch_frequency: debug_msg = "Time since last_start_time has not exceeded frequency time (" + str(fetch_frequency) + "). " debug_msg += "Sending empty list of incidents." demisto.debug("FrontlineVM fetch_incidents -- " + debug_msg) demisto.incidents(incidents) return # Begin fetching incidents: debug_msg = "Time since last_start_time exceeds frequency time (" + str(fetch_frequency) + "). Fetching incidents. " debug_msg += "Continuing from last start_time: " + str(last_start_time_str) demisto.debug("FrontlineVM fetch_incident -- " + debug_msg) # Fetch vulnerabilities and create incidents: vulns = fetch_vulnerabilities(last_start_time_str) if vulns: demisto.debug("FrontlineVM fetch_incidents -- vulnerabilities found, getting incidents.") incidents = vulns_to_incident(vulns, last_start_time) if len(incidents) > 0: # Reference the last fetched incident as the new_start_time: last_incident = incidents[-1] new_start_time_str = str(last_incident.get("occurred")) else: demisto.debug("FrontlineVM fetch_incidents -- no new vulnerabilities found, no incidents created.") demisto.info("FrontlineVM fetch_incident -- new start_time: " + str(new_start_time_str)) demisto.setLastRun({"start_time": new_start_time_str}) demisto.incidents(incidents) except Exception as err: return_error("Error: FrontlineVM fetching_incidents -- " + str(err)) def get_assets(ip_address, hostname, label_name, max_days_since_scan): """Returns a list of hosts from Frontline.Cloud based on user input from Arguments""" # Prepare parameters for Frontline API request: req_params = {} if ip_address: req_params["eq_host_ip_address"] = str(ip_address) if hostname: req_params["iexact_host_hostname"] = str(hostname) if label_name: req_params["eq_host_labels"] = str(label_name) if max_days_since_scan: try: query_date = get_query_date_param(max_days_since_scan) req_params["gte_host_date_created"] = str(query_date) except ValueError: debug_msg = "incorrect data type input for argument max_days_since_scan, should be number of days" demisto.debug("FrontlineVM get_assets -- " + debug_msg) return_error("Error: max_days_since_scan value should be a number representing days.") req_params = parse_params(req_params) hosts = get_fvm_data(HOST_ENDPOINT, params=req_params) return hosts def get_asset_output(host_list): """Get and prepare output from list of raw host data""" # Condensing Host data for HumanReadable and EntryContext: host_obj_list = [] for host in host_list: host_obj = {} host_obj["ID"] = host.get("id", None) host_obj["Hostname"] = host.get("hostname", "") host_obj["IP"] = host.get("ip_address", "") host_obj["DNSHostname"] = host.get("dns_name", "") host_obj["MAC"] = host.get("mac_address", "") host_obj["OS"] = host.get("os") host_obj["OSType"] = host.get("os_type") host_obj["CriticalVulnCount"] = host["active_view_vulnerability_severity_counts"]["weighted"]["ddi"]["counts"]["critical"] host_obj_list.append(host_obj) return host_obj_list def get_assets_command(): """Pulls host information from Frontline.Cloud""" # Get Arguments: ip_address = demisto.args().get("ip_address") hostname = demisto.args().get("hostname") label_name = demisto.args().get("label_name") max_days_since_scan = demisto.args().get("max_days_since_scan") hosts = get_assets(ip_address, hostname, label_name, max_days_since_scan) asset_output = get_asset_output(hosts) asset_entry_context = {"FrontlineVM.Hosts(val.ID && val.ID == obj.ID)": asset_output} asset_output_tablename = "FrontlineVM: Assets Found" demisto.results( { # indicates entry type to the War room "Type": entryTypes["note"], # raw data callable from War Room CLI with "raw-response=true" "Contents": hosts, # format of the content from the Contents field "ContentsFormat": formats["json"], # content that displays in the War Room: "HumanReadable": tableToMarkdown(asset_output_tablename, asset_output, headers=HOST_HEADERS, removeNull=True), # Format of the content from the HumanReadable field "ReadableContentsFormat": formats["markdown"], # Data added to the investigation context (Output Context), which you can use in playbooks "EntryContext": asset_entry_context, } ) def get_vulns(severity, min_severity, max_days_since_created, min_days_since_created, host_id): """Pull vulnerability data based upon user inputted parameters.""" # Prepare parameters for Frontline API request: req_params = {} if min_severity and severity: msg = "Selecting both 'min_severity' and 'severity' will yield to the minimum severity." demisto.debug("FrontlineVM get_vulns -- " + msg) if min_severity: req_params["lte_vuln_severity_ddi"] = str(min_severity) elif severity: req_params["eq_vuln_severity_ddi"] = str(severity) if max_days_since_created: try: query_date = get_query_date_param(max_days_since_created) req_params["lte_vuln_active_view_date_first_created"] = str(query_date) except ValueError: debug_msg = "incorrect input type for argument max_days_since_created, should be number of days" demisto.debug("FrontlineVM get_vulns -- " + debug_msg) return_error("Error: max_days_since_created value should be a number representing days.") if min_days_since_created: try: query_date = get_query_date_param(min_days_since_created) req_params["gte_vuln_date_created"] = str(query_date) except ValueError: debug_msg = "incorrect input type for argument min_days_since_created, should be number of days" demisto.debug("FrontlineVM get_vulns -- " + debug_msg) return_error("Error: min_days_since_created value should be a number representing days.") if host_id: vuln_endpoint = HOST_ENDPOINT + str(host_id) + "/vulnerabilities/" else: vuln_endpoint = VULN_ENDPOINT req_params = parse_params(req_params) vulns = get_fvm_data(vuln_endpoint, params=req_params) return vulns def create_vuln_obj(vuln): """Create condensed vulnerability object from raw vulnerability data.""" vuln_obj = {} vuln_obj["vuln-id"] = vuln.get("id") vuln_obj["hostname"] = vuln.get("hostname") vuln_obj["ip-address"] = vuln.get("ip_address") vuln_obj["vuln-title"] = vuln.get("title") vuln_obj["date-created"] = vuln.get("active_view_date_created") vuln_obj["ddi-severity"] = vuln["severities"]["ddi"] vuln_obj["vuln-info"] = vuln.get("data") return vuln_obj def get_vuln_outputs(vuln_list): """Get and prepare output from list of raw vulnerability data""" vuln_stat_output = {} # type: Dict vuln_stat_output["Vulnerabilities"] = len(vuln_list) vuln_data_list = [] # Condensing Vulns for HumanReadable and EntryContext: for vuln in vuln_list: vuln_obj = create_vuln_obj(vuln) vuln_severity = str(vuln["severities"]["ddi"]).capitalize() if vuln_stat_output.get(vuln_severity): vuln_stat_output[vuln_severity] += 1 else: vuln_stat_output[vuln_severity] = 1 vuln_data_list.append(vuln_obj) return { "data_output": vuln_data_list, # condensed vuln data pulled from Frontline.Cloud "stat_output": vuln_stat_output, # statistical vulnerability data } def get_host_id_from_ip_address(ip_address): """ Get host ID within Frontline.Cloud given IP address. Host ID used to pull vulnerability data for that specific host. """ hosts_with_given_ip = get_fvm_data(HOST_ENDPOINT, params={"_0_eq_host_ip_address": str(ip_address)}) if len(hosts_with_given_ip) < 1: msg = "Host not found within Frontline.Cloud given host IP Address. Host will not be included in querying vulnerabilities" demisto.error("Frontline.Cloud get_host_id_from_ip_address -- " + msg) # print to demisto log in ERROR demisto.debug("Frontline.Cloud get_host_id_from_ip_address -- " + msg) first_relevant_host = hosts_with_given_ip[0] return first_relevant_host.get("id") def get_vulns_command(): """Pulls vulnerability information from Frontline.Cloud""" # Get Arugments: severity = demisto.args().get("severity") min_severity = demisto.args().get("min_severity") max_days_since_created = demisto.args().get("max_days_since_created") min_days_since_created = demisto.args().get("min_days_since_created") host_id = demisto.args().get("host_id") ip_address = demisto.args().get("ip_address") if ip_address: host_id = get_host_id_from_ip_address(ip_address) vulns = get_vulns(severity, min_severity, max_days_since_created, min_days_since_created, host_id) # get both vuln data and vuln statistical output output = get_vuln_outputs(vulns) # Vuln Data Output: vuln_data_table_name = "FrontlineVM: Vulnerabilities Found" vuln_data_output = output.get("data_output") # Vuln Statistical Output: vuln_stat_table_name = "FrontlineVM: Vulnerability Statisctics" vuln_stat_output = output.get("stat_output") vuln_stat_headers = list(vuln_stat_output.keys()) demisto.results( [ { "Type": entryTypes["note"], "Contents": vulns, "ContentsFormat": formats["json"], "HumanReadable": tableToMarkdown( vuln_data_table_name, vuln_data_output, headers=VULN_DATA_HEADERS, removeNull=True ), "ReadableContentsFormat": formats["markdown"], "EntryContext": {"FrontlineVM.Vulns(val.vuln-id && val.vuln-id == obj.vuln-id)": vuln_data_output}, }, { "Type": entryTypes["note"], "Contents": vuln_stat_output, "ContentsFormat": formats["json"], "HumanReadable": tableToMarkdown(vuln_stat_table_name, vuln_stat_output, headers=vuln_stat_headers), "EntryContext": {"FrontlineVM.VulnStats(1>0)": vuln_stat_output}, }, ] ) def ip_address_to_number(ip_address): """ This is used sp explain this is used for our API Convert an IPv4 address from dotted-quad string format to 32-bit packed binary format, as a bytes object four characters in length. This is specifically used for creating scan payloads when sending POST requests using our FrontlineVM API within the build_scan method. """ return struct.unpack("!L", socket.inet_aton(ip_address))[0] def ip_number_to_address(ip_number): """ Convert a 32-bit packed IPv4 address (a bytes-like object four bytes in length) to its standard dotted-quad string representation. This is specifically used for creating scan payloads when sending POST requests using our FrontlineVM API within the build_scan method. """ return socket.inet_ntoa(struct.pack("!L", ip_number)) def get_network_data(): """Get network data. Used to perform scan.""" try: url = BASE_URL + "/api/networkprofiles/?_0_eq_networkprofile_internal=True" resp = requests.get(url, headers=API_AUTH_HEADER, verify=VERIFY_SSL) resp.raise_for_status() return json.loads(resp.text) except Exception as err: return_error("Error: getting network data -- " + str(err)) return [] # placed to satisfy pylint (inconsistent-return-statements error) def get_scan_data(network_data, low_ip, high_ip): """Iterate through network data to find appropriate scanner profile to use to perform scan.""" for profile in network_data: # If there is no scanner using this profile, then continue to the next profile if len(profile.get("scanner_names", "")) == 0: continue scanner_id = profile.get("scanner_ids")[0] scanner_url = BASE_URL + "/api/scanners/" + str(scanner_id) + "/" scanner_resp = requests.get(scanner_url, headers=API_AUTH_HEADER, verify=VERIFY_SSL) scanner_resp.raise_for_status() scanner = json.loads(scanner_resp.text) if scanner.get("status", "") == "online": url = BASE_URL + "/api/networkprofiles/" + str(profile["id"]) + "/rules/" profile_data = [] # type: List have_all_data = False while not have_all_data: resp = requests.get(url, headers=API_AUTH_HEADER, verify=VERIFY_SSL) resp.raise_for_status() current_data = json.loads(resp.text) profile_data.extend(current_data.get("results", [])) if current_data.get("next", None): url = current_data.get("next") else: have_all_data = True for rule in profile_data: if rule.get("ip_address_range", None): rule_high_ip_num = rule["ip_address_range"]["high_ip_number"] rule_low_ip_num = rule["ip_address_range"]["low_ip_number"] if (rule_high_ip_num >= high_ip) and (rule_low_ip_num <= low_ip): return {"profile_id": profile["id"]} return_error("Error: no scanner profile found for given ip range(s).") return {} # placed to satisfy pylint (inconsistent-return-statements error) def get_business_group(): """Get business group data if user account allows businessgroups setting.""" demisto.debug("FrontlineVM get_business_group -- checking if user allows business groups.") # Getting users's FrontlineVM session/account info: url = BASE_URL + "/api/session/" user_session = requests.get(url, headers=API_AUTH_HEADER, verify=VERIFY_SSL) user_session.raise_for_status() data = json.loads(user_session.text) if data.get("account_allow_businessgroups_setting"): business_groups_url = BASE_URL + "/api/businessgroups/?_0_eq_businessgroup_name=Enterprise Admins" bus_resp = requests.get(business_groups_url, headers=API_AUTH_HEADER, verify=VERIFY_SSL) if bus_resp.ok: bus_data = json.loads(bus_resp.text) return bus_data[0] return None def get_correct_ip_order(low_ip_address, high_ip_address): """Checks if user input ip address is in correct order (low-high).""" low_ip_number = ip_address_to_number(low_ip_address) high_ip_number = ip_address_to_number(high_ip_address) # if low_ip != high_ip, user inputed two different IP addresses -> range of assets to scan. if (low_ip_address != high_ip_address) and (low_ip_number > high_ip_number): low_ip_number, high_ip_number = high_ip_number, low_ip_number low_ip_address, high_ip_address = high_ip_address, low_ip_address return { "low_ip_number": low_ip_number, "low_ip_address": low_ip_address, "high_ip_number": high_ip_number, "high_ip_address": high_ip_address, } def build_scan(low_ip_address, high_ip_address, scan_policy, scan_name): """Prepare scan data payload for POST request.""" # check order of given ip address and assign accordingly asset_ips = get_correct_ip_order(low_ip_address, high_ip_address) low_ip_number = asset_ips.get("low_ip_number") low_ip_address = asset_ips.get("low_ip_address") high_ip_number = asset_ips.get("high_ip_number") high_ip_address = asset_ips.get("high_ip_address") # Get client's network and available scanner info to perform scan: scan_policy = str(scan_policy) network_data = get_network_data() scanner_data = get_scan_data(network_data, low_ip_number, high_ip_number) # Set time for scan: now = datetime.now(UTC) time_zone = "UTC" tzoffset = 0 scan = {} # type: Dict[str, Any] # Scan name will change if user is scanning range (low ip address not equal to high ip address) if scan_name is not None: scan["name"] = str(scan_name)[:100] elif low_ip_address == high_ip_address: scan["name"] = "Cortex XSOAR Scan " + " [" + str(low_ip_address) + "]" else: scan["name"] = "Cortex XSOAR Scan " + "[" + str(low_ip_address) + "-" + str(high_ip_address) + "]" scan["description"] = "New network device auto scan launch from Demisto." # Setting the schedule of the scan: scan["schedule"] = { "id": None, "end_date": None, "start_date": now.strftime("%Y-%m-%dT%H:%M:%S%z"), "recurring": False, "recurrence_rules": [], "timezone": time_zone, "timezone_offset": tzoffset, } scan["workflow"] = "va_workflow" scan["exclude_from_active_view"] = False scan["notify"] = False scan["internal"] = True scan["recipients"] = [] scan["scan_policy"] = scan_policy scan["scan_speed"] = "normal" scan["asset_groups"] = [] scan["asset_filter"] = {} # If users' FrontlineVM account allows business groups, include the business group ID: business_group = get_business_group() if business_group: scan["businessgroups"] = [{"id": business_group["id"]}] # Set the network target for this scan: scan["adhoc_targets"] = [] scan["adhoc_targets"].append( { "rule_action": "include", "network_profile_id": int(scanner_data["profile_id"]), "inclusion": "full", "ip_address_range": { "low_ip_address": low_ip_address, "high_ip_address": high_ip_address, "low_ip_number": low_ip_number, "high_ip_number": high_ip_number, "ipv6": False, "dhcp": False, "fragile": False, "cidr_block": None, }, } ) return scan def scan_asset(ip_address, scan_policy, scan_name, ip_range_start, ip_range_end): """Build scan payload and make POST request to perform scan.""" try: if ip_address: low_ip_address = ip_address high_ip_address = ip_address elif ip_range_start and ip_range_end: low_ip_address = ip_range_start high_ip_address = ip_range_end else: low_ip_address = "" high_ip_address = "" msg = "Invalid arguments. Must input either a single ip_address or range of ip addresses to scan." demisto.debug(msg) return_error(msg) if ip_address and (ip_range_start or ip_range_end): msg = "Inputting a single 'ip_address' and a range of addresses will yield to the single ip_address to scan" demisto.debug("FrontlineVM scan_asset -- " + msg) scan_payload = build_scan(low_ip_address, high_ip_address, scan_policy, scan_name) header = {} header["Authorization"] = "Token " + str(API_TOKEN) header["Content-Type"] = "application/json;charset=utf-8" resp = requests.post(SCAN_ENDPOINT, data=json.dumps(scan_payload), headers=header, verify=VERIFY_SSL) if resp.ok: scan_data = json.loads(resp.text) else: scan_data = None msg = "ERROR: Scan request returned with status code: " + str(resp.status_code) demisto.debug("FrontlineVM scan_asset -- " + msg) return_error(msg) return scan_data except Exception as err: return_error("Error: FrontlineVM scan_asset failed " + str(err)) def scan_policy_exists(policy_selected): """Check whether user input scan policy exists within their Frontline.Cloud account.""" policy_url = SCAN_ENDPOINT + "policies" demisto.debug("FrontlineVM scan_policy_exists -- checking if user defined policy exists within Frontline.Cloud") try: resp = requests.get(policy_url, headers=API_AUTH_HEADER, verify=VERIFY_SSL) resp.raise_for_status() data = json.loads(resp.text) return any(policy_selected == policy.get("name", "") for policy in data) except Exception as err: return_error("Error: FrontlineVM scan_policy_exists failed " + str(err)) def get_ip_addresses_from_scan_data(scan_response): """ Retrieve low and high ip address values from scan data. Checking that each key/value pair exists in nested dictionary """ adhoc_target_list = scan_response.get("adhoc_targets") adhoc_target = adhoc_target_list[0] if adhoc_target_list else None ip_address_range = adhoc_target.get("ip_address_range") if adhoc_target else None low_ip_address = ip_address_range.get("low_ip_address") if ip_address_range else None high_ip_address = ip_address_range.get("high_ip_address") if ip_address_range else None return {"low": low_ip_address, "high": high_ip_address} def scan_asset_command(): """Peform scan on Frontline.Cloud""" ip_address = demisto.args().get("ip_address") policy_name = str(demisto.args().get("scan_policy")) scan_name = demisto.args().get("scan_name") ip_range_start = demisto.args().get("ip_range_start") ip_range_end = demisto.args().get("ip_range_end") if not scan_policy_exists(policy_name): return_error("Error: Scan Policy entered '" + policy_name + "' does not exist.") try: scan_response = scan_asset(ip_address, policy_name, scan_name, ip_range_start, ip_range_end) # Gather IP addresses from scan response data: ip_addresses = get_ip_addresses_from_scan_data(scan_response) low_ip = ip_addresses.get("low") high_ip = ip_addresses.get("high") # Condense Scan data for HumanReadable and EntryContext with scan_output: scan_output = {} # Build appropriate headers for HumanReadable output, dependent on if user is scanning one asset or a range of assets: is_only_one_asset = low_ip == high_ip if is_only_one_asset: scan_output["IP"] = low_ip else: scan_output["IP"] = low_ip + "-" + high_ip scan_output["ID"] = scan_response.get("id") scan_output["Name"] = scan_response.get("name") scan_output["Policy"] = scan_response.get("scan_policy") # Linking Context entry_context = {"FrontlineVM.Scans(val.ID && val.ID == obj.ID)": {"Scan": scan_output}} output = { "Type": entryTypes["note"], # War room "Contents": scan_response, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown("FrontlineVM: Performing Scan", scan_output, headers=SCAN_HEADERS, removeNull=True), "EntryContext": entry_context, } demisto.results(output) except Exception as err: return_error("Error performing scan. Exception: " + str(err)) def test_module(): """Test integration method""" session_url = BASE_URL + "/api/session/" resp = requests.get(session_url, headers=API_AUTH_HEADER, verify=VERIFY_SSL) if resp.ok: demisto.results("ok") else: return_error("Error: Test method failed. Invalid API Token.") def main(): """Integration main method""" LOG(f"command is {demisto.command()}") try: if demisto.command() == "test-module": test_module() if demisto.command() == "frontline-get-assets": get_assets_command() if demisto.command() == "frontline-get-vulns": get_vulns_command() if demisto.command() == "frontline-scan-asset": scan_asset_command() if demisto.command() == "fetch-incidents": fetch_incidents() except Exception as err: LOG(err) LOG.print_log(verbose=False) return_error("Error: " + str(err)) # python2 uses __builtin__ python3 uses builtins if __name__ == "__builtin__" or __name__ == "builtins": main()