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

IDZerohack XDR
ProviderZerohack
CategoryNetwork Security
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix 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

  1. Navigate to Side Panel > Administration > Integration > Key Management.

    Setup Account

  2. Click on Create API key.

    Setup Account

  3. Click on Drop down of Select application.

    Setup Account

  4. Click on Palo Alto XSOAR.

    Setup Account

  5. Select API Type “Full Control”.

    Setup Account

  6. Click on Create Api.

    Setup Account

  7. Copy your API key.

    Setup Account

Configure Zerohack XDR on Cortex XSOAR

  1. Navigate to Settings on bottom left corner of dashboard > Integrations > Servers & Services.

Setup Account

  1. Search for Zerohack XDR.

Setup Account

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

    Setup Account

    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
  2. Click on Gear Icon.

Setup Account

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

Setup Account

  1. Access the Fetch results.

Setup Account

Setup Account

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

Setup Account

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
    }
]

Setup Account

Configuration parameters

  • isFetch — Fetch incidents
  • incidentType — Incident type
  • max_fetch — Maximum number of incidents per fetch
  • apikey — Zerohack XDR API Key (required)
  • first_fetch — First fetch time
  • insecure — Trust any certificate (not secure)
  • incidentFetchInterval — Incidents Fetch Interval (required)
  • min_severity — Minimum Severity (required)

Commands (1)

  • 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.

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()