Zerohack XDR
The companion integration for Zerohack XDR. Current versions allow the user to collect data from the XDR and later versions will support data exfiltration to XDR.
Network Security · Zerohack XDR
Details
| ID | Zerohack XDR |
|---|---|
| Provider | Zerohack |
| Category | Network Security |
| From Version | 5.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
Integration and Setup Instructions
The companion integration for Zerohack XDR.
Current versions allow the user to collect data from the XDR and later versions will support data exfiltration to XDR.
This integration was integrated and tested with version 1.0 of Zerohack XDR
Create API key on Zerohack XDR for Cortex XSOAR
-
Navigate to Side Panel > Administration > Integration > Key Management.

-
Click on Create API key.

-
Click on Drop down of Select application.

-
Click on Palo Alto XSOAR.

-
Select API Type “Full Control”.

-
Click on Create Api.

-
Copy your API key.

Configure Zerohack XDR on Cortex XSOAR
- Navigate to Settings on bottom left corner of dashboard > Integrations > Servers & Services.

- Search for Zerohack XDR.

-
Click Add instance to create and configure a new integration instance.

Parameter Description Required Fetch incidents False Incident type False Maximum number of incidents per fetch This number determines how many incidents must be fetched with each API call. It is suggested you keep it below 100. False Zerohack XDR API Key This API key can be generated from your zerohack XDR account. Please ensure that you fill this field before you test the integration. True First fetch time This parameter decides how many old events you want to fetch when starting the integration. False Trust any certificate (not secure) False Incidents Fetch Interval True Minimum Severity This parameter defines the lowest severity level (xdr) to use for fetching incidents. True -
Click on Gear Icon.

- Click Test to validate the URLs, token, and connection.

- Access the Fetch results.


Commands
You can execute these commands from the Cortex XSOAR 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.
zerohack-get-latest-incident
Fetch a single incident of your choice of severity level to study the incidents structure before you start continously fecthing incidents.
Base Command
zerohack-get-latest-incident
Input
| Argument Name | Description | Required |
|---|---|---|
| severity_level | The severity level helps in extracting latest incident of a specific severity. | Required |

Context Output
"message_type": "success",
"data_len": 1,
"data": [
{
"tcp_port": "22.0",
"ip_rep": "unknown",
"dl_threat_class": "Potentially Bad Traffic",
"ml_severity": 2,
"attacker_mac": "ff:ff:ff:ff:ff:ff",
"geoip_region_name": null,
"target_mac_address": "ff:ff:ff:ff:ff:ff",
"geoip_postal_code": null,
"timezone": "UTC-0.0",
"dl_severity": 2,
"geoip_asn_number": null,
"platform": "aws",
"attack_os": "Linux 2.2.x-3.x",
"ids_threat_severity": 2.0,
"geoip_country_name": null,
"packet_id": 464887,
"attacker_ip": "8.8.8.8",
"geoip_location_properties": null,
"udp_port": "nan",
"geoip_longitude": null,
"geoip_location_string": null,
"service_name": "TCP",
"geoip_latitude": null,
"ids_threat_type": "ET SCAN Potential SSH Scan OUTBOUND",
"ml_accuracy": 82,
"attack_epoch_time": 1662120908000,
"type_of_threat": "Lateral Movement",
"ml_threat_class": "Misc Attack",
"target_os": "Linux 2.2.x-3.x",
"@timestamp": "2022-09-02 12:18:26.22009012",
"attack_timestamp": "2022-09-02 12:15:08",
"target_ip": "8.8.8.8",
"dl_accuracy": 80,
"geoip_city": null,
"geoip_region_code": null,
"ids_threat_class": "Attempted Information Leak",
"icmp_port": null,
"geoip_location_array": null,
"geoip_country_code": null,
"geoip_asn_name": null
}
]

Configuration parameters
isFetch— Fetch incidentsincidentType— Incident typemax_fetch— Maximum number of incidents per fetchapikey— Zerohack XDR API Key (required)first_fetch— First fetch timeinsecure— Trust any certificate (not secure)incidentFetchInterval— Incidents Fetch Interval (required)min_severity— Minimum Severity (required)
Commands (1)
-
zerohack-get-latest-incidentFetch a single incident of your choice of severity level to study the incidents structure before you start continously fecthing incidents.
import json import math from datetime import datetime from typing import cast import demistomock as demisto import urllib3 from CommonServerPython import * from CommonServerUserPython import * # Disable insecure warnings urllib3.disable_warnings() """ CONSTANTS """ # These severities go from info to high. (Reverse of Cortex severities notation.) ZEROHACK_SEVERITIES = ["4", "3", "2", "1"] ZEROHACK_XDR_API_BASE_URL = "https://xdr.zerohack.in/api" MAX_INCIDENTS_TO_FETCH = 200 DATE_FORMAT = "%Y-%m-%d %H:%M:%S" """ CLIENT CLASS """ class Client(BaseClient): """ This class is responsible for dealing with the XDR api. It performs all the fetching related commands andensure proper pre processing ebfore forward events. """ def __init__( self, api_key: Optional[str], base_url: Optional[str], proxy: Optional[bool], verify: Optional[bool], ): """ This function initializes the connection with the API server by collecting curcial information from the users. """ super().__init__(base_url=base_url, proxy=proxy, verify=verify) self.api_key = api_key if self.api_key: self._headers = {"Key": self.api_key} def get_alerts( self, severity_level: Optional[str] = None, max_results: Optional[int] = None, offset: Optional[int] = None, start_time: int = None, ): """ This function is responsible for fetching all the alerts from the zerohack XDR between given timestamps. it takes various inputs and formats the request parameters for macthing the XDR api format. :param severity_level: This variable sets the severity level to fetch alerts from the XDR. :type severity_level: ``Optional[str]`` :return: A dictionary containing XDR response. :rtype: ``Dict[str, Any]`` """ # Setting request parameters. request_params: Dict[str, Any] = {} if offset: request_params["offset"] = offset if max_results: request_params["limit"] = max_results if start_time: request_params["start_date"] = cast(str, start_time) request_params["severity"] = severity_level request_params["order_by"] = "asc" # Querying the alerts and appending them to a list. return self._http_request(method="GET", url_suffix="/xdr-api", params=request_params) def get_alert( self, severity_level: Optional[str] = None, max_results: Optional[int] = None, offset: Optional[int] = None, start_time: Optional[str] = None, ): """ This function can be used to retrieve a singular incident for a severity level. :return: A single function containing a XDR event of specified severity. :rtype: ``Dict[str, Any]`` """ max_results = 1 # Setting request parameters. request_params: Dict[str, Any] = {} if offset: request_params["offset"] = offset if max_results: request_params["limit"] = max_results if start_time: request_params["start_date"] = start_time request_params["severity"] = severity_level request_params["order_by"] = "asc" response = self._http_request(method="GET", url_suffix="/xdr-api", params=request_params) return response def test_connection(self): """ This is a special connection function designed to test if the connection is made and working correctly. This function is activated when you call the module Test function. :return: A list containing a single XDR event. :rtype: ``List[Dict[str, Any]]`` """ request_params: Dict[str, Any] = {} offset = 0 max_results = "1" request_params["offset"] = offset if max_results: request_params["limit"] = max_results return self._http_request(method="GET", url_suffix="/xdr-api", params=request_params) """ HELPER FUNCTIONS """ def convert_to_demisto_severity(severity: str): """ This function is designed to convert the Zerohack XDR severity to Cortex severity levels. :param severity: The severity level to be converted into :type severity: ``str`` :return converted_severity: The converted severiy level to cortex format. :rtype: ``int`` """ zerohack_severity = str(severity) converted_severity = { "4.0": IncidentSeverity.INFO, "3.0": IncidentSeverity.MEDIUM, "2.0": IncidentSeverity.HIGH, "1.0": IncidentSeverity.CRITICAL, }[zerohack_severity] return converted_severity """ COMMAND FUNCTIONS """ def test_module(client: Client): """ This function tests if the connection with the API is working correctly. :param client: The client object to use for connection. :type client: ``Client`` """ try: client.test_connection() except DemistoException as e: if "Forbidden" in str(e): return "Authorization Error: make sure API Key is correctly set" else: raise e return "ok" def fetch_incidents( client: Client, max_results: int, last_run: Dict[str, int], first_fetch: str, min_severity: str, ): """ This function continously fetches incidents from the Zerohack XDR api. :param client: The client object to use for connection. :type client: ``Client`` :param max_results: Maximum amount of results to fetch from the API per severity level. :type max_results: ``int`` :param last_run: This parameter contains the details about last time this integration was run. :type last_run: ``Dict[str, int]`` :param first_fetch_time: This parameter contains the time from when to fetch incidents details in case last run is not setup. :type first_fetch_time: ``str`` :param min_severity: This is minimum level of the severity you want to query the zerohack XDR for. :type min_severity: ``int`` :return: A tuple containing two elements: next_run (``Dict[str, int]``): Contains the timestamp that will be used in ``last_run`` on the next fetch. incidents (``List[dict]``): List of incidents that will be created in XSOAR. :rtype: ``Tuple[Dict[str, int], List[dict]]`` """ # A dictionary to store last fetch values for each severity. next_run = {} # Sorting the severity levels and creating a list. severity_levels = ZEROHACK_SEVERITIES[ZEROHACK_SEVERITIES.index(min_severity) :] severity_levels.sort() # Initializating the incidents dictionary and setting the severity levels. incidents: List[Dict[str, Any]] = [] for severity in severity_levels: # Get the last fetch time for each severity and if it fails then use the first fetch time. last_fetch = last_run.get(f"last_fetch_severity_{severity}", None) if last_fetch is None: # We know that the first_fetch can never be None. first_fetch_timestamp = arg_to_datetime(first_fetch) # Assert if argument parsing has not resulted in None. if first_fetch_timestamp is not None: last_fetch = cast(int, first_fetch_timestamp.timestamp()) else: last_fetch = cast(int, last_fetch) # Set the last incident time for this severity and start the calculations. last_incident_time = cast(int, last_fetch) last_fetch_timestamp = cast(int, datetime.fromtimestamp(cast(float, last_fetch))) # Calculate the required results from this severity level. required_results = math.floor(max_results / len(severity_levels)) # Fetch the response from the API. response = client.get_alerts( max_results=required_results, severity_level=severity, start_time=last_fetch_timestamp, ) response_status = response.get("message_type") if response_status != "d_not_f": response_data = response.get("data") for alert in response_data: attack_time = datetime.strptime(alert.get("attack_timestamp", "0"), DATE_FORMAT) incident_created_time = int((attack_time - datetime(1970, 1, 1)).total_seconds()) if last_fetch is not None and incident_created_time > last_fetch: incident_name = "Zerohack XDR " + alert["ids_threat_class"] incident = { "name": incident_name, "occurred": timestamp_to_datestring(incident_created_time), "type": alert["ids_threat_class"], "movement_type": alert["type_of_threat"], "platform": alert["platform"], "attacker_rep": alert["ip_rep"], "rawJSON": json.dumps(alert), "severity": convert_to_demisto_severity(alert.get("ids_threat_severity", "Low")), } demisto.debug(incident) incidents.append(incident) last_incident_time = incident_created_time # Based on the findings update the last fetch dictionary. if last_fetch == last_incident_time: demisto.debug(f"Couldnt find new incidents with {last_fetch}. Updating.") last_incident_time = last_incident_time + 1 next_run[f"last_fetch_severity_{severity}"] = last_incident_time else: next_run[f"last_fetch_severity_{severity}"] = last_incident_time return next_run, incidents def get_latest_incident(client: Client, severity_level: str): """ This function is responsible for fetching a single sample incident for study/inspection purposes by the analyser or the SOAR handler. It can be run in playground and it gives output in readable format so you can evaluate the incident format. :param client: The client object to use for connection. :type client: ``Client`` :param severity_level: This is the level of the severity you want to query the zerohack XDR for. :type severity_level: ``int`` :return incident: List of incidents that will be created in XSOAR. :rtype: ``List[dict]`` """ # Hard coding thge max results as we only need to send the latest output. max_results = 1 alert = client.get_alert(severity_level=severity_level, max_results=max_results, offset=0) incident_data = alert["data"][0] incident_name = "Zerohack XDR " + incident_data["ids_threat_class"] incident = { "name": incident_name, "occurred": incident_data["attack_timestamp"], "type": incident_data["ids_threat_class"], "movement_type": incident_data["type_of_threat"], "platform": incident_data["platform"], "attacker_rep": incident_data["ip_rep"], "rawJSON": json.dumps(str(incident_data)), "severity": convert_to_demisto_severity(incident_data.get("ids_threat_severity", "Low")), } return incident """ MAIN FUNCTION """ def main(): """ This function is the main control function. It is responsible for handling the core control logic of the XDR integration. This component handles the command input and fetching control. Apart from command control it alkso handles the inputs from the integration settings. """ # Collecting details for initializing the connection. api_key = demisto.params().get("apikey") base_url = ZEROHACK_XDR_API_BASE_URL verify_certificate = not demisto.params().get("insecure", False) proxy = demisto.params().get("proxy", False) demisto.debug(f"The command initiated is: {demisto.command()}") try: client = Client(api_key=api_key, base_url=base_url, verify=verify_certificate, proxy=proxy) # Integration test command. if demisto.command() == "test-module": result = test_module(client) return_results(result) # Incident continous fetch command. elif demisto.command() == "fetch-incidents": # Getting parameters and checking if they conflict with defaults. min_severity = demisto.params().get("min_severity", None) max_results = arg_to_number( arg=demisto.params().get("max_fetch"), arg_name="max_fetch", required=False, ) # Make sure first fetch can never be None. first_fetch = demisto.params().get("first_fetch", "1 day") if (not max_results) or (max_results > MAX_INCIDENTS_TO_FETCH): max_results = MAX_INCIDENTS_TO_FETCH # Calling the fetch incidents command. next_run, incidents = fetch_incidents( client=client, max_results=max_results, last_run=demisto.getLastRun(), first_fetch=first_fetch, min_severity=min_severity, ) # Setting the last fetch timestamp. demisto.setLastRun(next_run) # Inserting the incidents. if incidents != []: demisto.incidents(incidents) else: demisto.incidents([]) # Retrieve a sample incident of determined severity level. elif demisto.command() == "zerohack-get-latest-incident": arguments = demisto.args() severity_level = arguments["severity_level"] incident = get_latest_incident(client=client, severity_level=severity_level) demisto.incidents(incident) # Log exceptions and return errors except Exception as e: return_error(f"Failed to execute {demisto.command()} command.\nError:\n{e!s}") """ ENTRY POINT """ if __name__ in ("__main__", "__builtin__", "builtins"): main()