Stamus

[Get Declaration of Compromises from Stamus Security Platform and build Incidents. Then get related artifacts, events and Host Insight information].

Network Security · Stamus

Details

IDStamus
ProviderStamus Networks
CategoryNetwork Security
From Version6.9.0
Docker Imagedemisto/python3:3.12.13.10116658

README

[Get Declaration of Compromises from Stamus Security Platform and build Incidents. Then get related artifacts, events and Host Insight information]
This integration was integrated and tested with version 39.0.1 of Stamus Security Platform

Configure Stamus in Cortex

Parameter Description Required
Stamus Central Server   True
API Key The API Key to use for connection True
Trust any certificate (not secure)   False
Use system proxy settings   False
Incident type   False
Fetch incidents   False
Maximum number of incidents per fetch   False
First fetch time   False

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.

stamus-check-ioc


[Get events with IOC key/value filter]

Base Command

stamus-check-ioc

Input

Argument Name Description Required
indicator_key [Indicator of Compromise key]. Required
indicator_value [Indicator of Compromise value]. Required

Context Output

Path Type Description
StamusIntegration.IOC String [Fetch events matching an IOC.]
StamusIntegration.IOC.timestamp String [Timestamp of the event]
StamusIntegration.IOC.src_ip String [Source IP of the event]
StamusIntegration.IOC.dest_ip String [Destination IP of the event]
StamusIntegration.IOC.event_type String [Type of the event - can be multitude, example: HTTP,SMB,DNS,Flow,TLS,KRB5,FTP etc]

stamus-get-host-insight


[Get Host Insights information]

Base Command

stamus-get-host-insight

Input

Argument Name Description Required
ip [IP to get Host Insights information]. Required

Context Output

Path Type Description
StamusIntegration.HostInsights String [Fetch information about a host known by Host Insight module]
StamusIntegration.HostInsights.ip String [Stamus Host Insights IP address]
StamusIntegration.HostInsights.host_id.client_service.first_seen String [Timestamp of first time seen]
StamusIntegration.HostInsights.host_id.client_service String [Client network service detected]
StamusIntegration.HostInsights.host_id.services.proto String [Network services protocol]
StamusIntegration.HostInsights.host_id.services.port String [Network services port]
StamusIntegration.HostInsights.host_id.services.values.first_seen String [Network services for the corresponding application protocol first time seen]
StamusIntegration.HostInsights.host_id.services.values.last_seen String [Network services for the corresponding application protocol last time seen]
StamusIntegration.HostInsights.host_id.services.values.app_proto String [Network services application layer protocol]
StamusIntegration.HostInsights.host_id.services.services_count Number [Number of network services detected on the host]
StamusIntegration.HostInsights.host_id.client_service.name String [Type of client network service detected - can be HTTP,KRB5,TLS,DCERPC,SMB etc]
StamusIntegration.HostInsights.host_id.hostname.host String [Hostname detected on the host]
StamusIntegration.HostInsights.host_id.username.user String [Username detected loggin in on the host]
StamusIntegration.HostInsights.host_id.http.user_agent.agent String [HTTP User-Agent detected being used from the host]
StamusIntegration.HostInsights.host_id.tls.ja3.hash String [TLS JA3 hash detected being used from the host]
StamusIntegration.HostInsights.host_id.tls.ja3s.hash String [TLS JA3S hash detected being used from the host]

stamus-get-doc-events


[Get events for a Declaration of Compromise using the Stamus ID]

Base Command

stamus-get-doc-events

Input

Argument Name Description Required
id [Stamus ID used to get related information]. Required

Context Output

Path Type Description
StamusIntegration.RelatedEvents String [Get events for a Declaration of Compromise.]
StamusIntegration.RelatedEvents.timestamp String [Timestamp of the Stamus event]
StamusIntegration.RelatedEvents.stamus.asset String [Stamus asset]
StamusIntegration.RelatedEvents.offender String [Offender, against the Stamus asset]
StamusIntegration.RelatedEvents.killchain String [Killchain stage]
StamusIntegration.RelatedEvents.method String [Stamus method triggered]
StamusIntegration.RelatedEvents.info String [Extra Information]
StamusIntegration.RelatedEvents.src_ip String [Source IP of the event]
StamusIntegration.RelatedEvents.dest_ip String [Destination IP of the event]
StamusIntegration.RelatedEvents.app_proto String [Application protocol of the event]

Configuration parameters

  • url — Stamus Central Server (required)
  • credentials — (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • isFetch — Fetch incidents
  • max_fetch — Maximum number of incidents per fetch
  • first_fetch — First fetch time

Commands (3)

  • stamus-check-ioc

    [Get events with IOC key/value filter].

  • stamus-get-doc-events

    [Get events for a Declaration of Compromise using the Stamus ID].

  • stamus-get-host-insight

    [Get Host Insights information].

"""Stamus Integration for Cortex XSOAR (aka Demisto)

This is an integration with Stamus Security Platform.
"""

# mocks: is removed by xsoar server
from collections.abc import Iterable
from datetime import datetime
from typing import Any

import demistomock as demisto

#
import urllib3
from CommonServerPython import *  # noqa # pylint: disable=unused-wildcard-import

from CommonServerUserPython import *  # noqa

# Disable insecure warnings
urllib3.disable_warnings()

""" CONSTANTS """

DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.%f%z"
PAGE_SIZE = 200  # https://xsoar.pan.dev/docs/integrations/fetching-incidents#fetch-limit
ARRAY_ITEMS = ["client_service", "hostname", "username", "http.user_agent", "tls.ja3", "ssh.client", "roles"]
ITEM_KEY = {
    "client_service": "name",
    "hostname": "host",
    "username": "user",
    "http.user_agent": "agent",
    "tls.ja3": "hash",
    "ssh.client": "software_version",
    "roles": "name",
    "services": "app_proto",
}
FIELDS_SUBSTITUTION = (
    ["http.user_agent", "http_user_agent"],
    ["http.user_agent_count", "http_user_agent_count"],
    ["tls.ja3", "tls_ja3"],
    ["tls.ja3_count", "tls_ja3_count"],
    ["ssh.client", "ssh_client"],
    ["ssh.client_count", "ssh_client_count"],
)
FIELDS_SUBSTITUTION_DICT = {
    "http.user_agent": "http_user_agent",
    "http.user_agent_count": "http_user_agent_count",
    "tls.ja3": "tls_ja3",
    "tls.ja3_count": "tls_ja3_count",
    "ssh.client": "ssh_client",
    "ssh.client_count": "ssh_client_count",
}

""" CLIENT CLASS """


class Client(BaseClient):
    """Client class to interact with the service API

    This Client implements API calls, and does not contain any XSOAR logic.
    Should only do requests and return data.
    It inherits from BaseClient defined in CommonServer Python.
    Most calls use _http_request() that handles proxy, SSL verification, etc.
    For this  implementation, no special attributes defined
    """

    def test_connection(self):
        self._http_request(method="GET", params={}, url_suffix="/rest/")

    def get_events(self, _id: str) -> dict[str, Any]:
        """Get all related events with _id that comes from incidents (dbotMirrorId)

        Args:
            _id (str): ID used to get all related events

        Returns:
            dict: dict containing all related events
        """
        return self._http_request(
            method="GET", params={}, url_suffix=f"/rest/appliances/threat_history_incident/{_id}/get_events"
        )

    def get_by_ioc(self, key, value):
        """Get events from Stamus Central Server

        Args:
            ip (str): IP address

        Returns:
            dict: dict containing Host ID informations
        """
        return self._http_request(
            method="GET", params={"qfilter": f"{key}:{escape(value)}"}, url_suffix="/rest/rules/es/events_tail/"
        )

    def get_host_id(self, ip: str) -> dict[str, Any]:
        """Get host id from Stamus Central Server

        Args:
            ip (str): IP address

        Returns:
            dict: dict containing Host ID informations
        """
        return self._http_request(method="GET", params={}, url_suffix=f"/rest/appliances/host_id/{ip}")

    def get_incidents(self, timestamp: int) -> Iterable:
        """Get incidents from Stamus Central Server

        Args:
            timestamp (int): start timestamp (epoch in seconds) for the search.

        Returns:
            list: list of kill chains changes
        """
        params = {"timestamp": str(timestamp), "page_size": PAGE_SIZE}

        response = self._http_request(method="GET", params=params, url_suffix="/rest/appliances/threat_history_incident/")

        yield from response.get("results", [])


""" HELPER FUNCTIONS """


def escape(string):
    """
    Escape other elasticsearch reserved characters
    """
    return (
        string.replace("=", r"\=")
        .replace("+", r"\+")
        .replace("-", r"\-")
        .replace("&", r"\&")
        .replace("|", r"\|")
        .replace("!", r"\!")
        .replace("(", r"\(")
        .replace(")", r"\)")
        .replace("{", r"\{")
        .replace("}", r"\}")
        .replace("[", r"\[")
        .replace("]", r"\]")
        .replace("^", r"\^")
        .replace('"', r"\"")
        .replace("~", r"\~")
        .replace(":", r"\:")
        .replace("/", r"\/")
        .replace("\\", r"\\")
    )


def convert_to_demisto_severity(severity: int) -> int:
    """
    Maps severity to Cortex XSOAR severity.
    Converts stamus alert severity level to Cortex XSOAR incident
    severity (1 to 4) => (Low to CRITICAL).

    Args:
        severity (str): severity as returned from the HelloWorld API.
        first_fetch_time (int): The first fetch time as configured in the integration params.

    Returns:
        int: Cortex XSOAR Severity (1 to 4)
    """
    return {
        1: IncidentSeverity.CRITICAL,
        2: IncidentSeverity.HIGH,
        3: IncidentSeverity.MEDIUM,
        4: IncidentSeverity.LOW,
    }.get(severity, IncidentSeverity.UNKNOWN)


def get_command_results(data: dict[str, Any], table: str, context: str) -> CommandResults:
    return CommandResults(readable_output=table, outputs_prefix=f"StamusIntegration.{context}", outputs=data)


# TODO: ADD HERE ANY HELPER FUNCTION YOU MIGHT NEED (if any)


""" COMMAND FUNCTIONS """


def fetch_by_ioc(client: Client, args: dict[str, Any]) -> CommandResults:
    """Fetch events from indicator of compromise"""
    results = client.get_by_ioc(args["indicator_key"], args["indicator_value"]).get("results", [])
    table = tableToMarkdown("IOC Matches", results, headers=["timestamp", "src_ip", "dest_ip", "event_type"])
    return get_command_results(results, table, "IOC")


def fetch_events(client: Client, args: dict[str, Any]) -> CommandResults:
    """Fetch events from an incident ID"""
    results = client.get_events(str(args.get("id"))).get("results", [])
    for result in results:
        result["method"] = result.get("alert", {}).get("signature", "algorithmic detection")
        result["info"] = ""
        if result.get("hostname_info"):
            result["info"] = f"Hostname: {result.get('hostname_info', {}).get('host', 'unknown')}"
        result["asset"] = result.get("stamus", {}).get("asset", "unknown")
        result["offender"] = result.get("stamus", {}).get("source", "unknown")
        result["killchain"] = result.get("stamus", {}).get("kill_chain", "unknown")
    headers = ["timestamp", "asset", "offender", "killchain", "method", "info", "src_ip", "dest_ip", "app_proto"]
    table = tableToMarkdown("Individual Events List", results, headers=headers)
    return get_command_results(results, table, "RelatedEvents")


def linearize_host_id(host: dict) -> list:
    """Host Insights structure is nested and this function convert it to
    a suite of events.
    """
    host_info = []
    host_data = host["host_id"]
    item_data = {"ip": host["ip"]}
    item_data["event_type"] = "discovery"
    item_data["first_seen"] = host_data["first_seen"]
    if "last_seen" in host_data:
        item_data["last_seen"] = host_data["last_seen"]
    if "net_info" in host_data:
        item_data["net_info"] = host_data["net_info"]
    host_services = host_data.get("services")
    if host_services is not None:
        item_data["type"] = "service"
        for service in host_services:
            for value in service["values"]:
                item_data["service"] = value
                item_data["service"]["proto"] = service["proto"]
                item_data["service"]["port"] = service["port"]
                item_data["value"] = value["app_proto"]
                if "+" in value["first_seen"] and value["first_seen"][-3] != ":":
                    item_data["timestamp"] = value["first_seen"][:-2] + ":" + value["first_seen"][-2:]
                else:
                    item_data["timestamp"] = value["first_seen"]
                host_info.append(item_data.copy())
        item_data.pop("service")
    for key in ARRAY_ITEMS:
        if key in host_data:
            item_data["type"] = FIELDS_SUBSTITUTION_DICT.get(key, key)
            for item in host_data[key]:
                if key in FIELDS_SUBSTITUTION_DICT:
                    item_data[FIELDS_SUBSTITUTION_DICT[key]] = item
                else:
                    item_data[key] = item
                item_data["value"] = item[ITEM_KEY[key]]
                item_data["timestamp"] = item["first_seen"]
                host_info.append(item_data.copy())
            item_data.pop("timestamp")
            if key in FIELDS_SUBSTITUTION_DICT:
                item_data.pop(FIELDS_SUBSTITUTION_DICT[key])
            else:
                item_data.pop(key)
    return host_info


def fetch_host_id(client: Client, args: dict[str, Any]) -> CommandResults:
    """Fetch host_id info from an IP"""
    result = client.get_host_id(str(args.get("ip")))
    host_info = linearize_host_id(result)

    table = tableToMarkdown("Host Insight", host_info, headers=["timestamp", "ip", "type", "value"])

    return get_command_results(result, table, "HostInsights")


def fetch_incidents(client: Client, timestamp: int) -> tuple[dict[str, int], list[dict]]:
    """Fetch last alerts and build new incident structs"""

    incidents: list[dict] = []
    next_run = {"timestamp": timestamp}

    for idx, alert in enumerate(client.get_incidents(timestamp)):
        incident = {
            "name": f'{alert.get("target")}_incident_{idx}',
            # Stamus ID used to get related events
            "dbotMirrorId": str(alert.get("id")),
            "details": alert.get("threat_description"),
            "occurred": alert.get("timestamp"),
            "rawJSON": json.dumps(alert),
            "type": alert.get("target_type"),
        }
        incidents.append(incident)

        timestamp = int(datetime.strptime(alert["timestamp"], DATE_FORMAT).timestamp())
        if timestamp > next_run["timestamp"]:
            next_run["timestamp"] = timestamp + 1

    return next_run, incidents


def test_module(client: Client) -> str:
    """Tests API connectivity and authentication'

    Returning 'ok' indicates that the integration works like it is supposed to.
    Connection to the service is successful.
    Raises exceptions if something goes wrong.

    :type client: ``Client``
    :param Client: client to use

    :return: 'ok' if test passed, anything else will fail the test.
    :rtype: ``str``
    """

    client.test_connection()
    return "ok"


# TODO: ADD additional command functions that translate XSOAR inputs/outputs to Client


""" MAIN FUNCTION """


def main() -> None:
    """main function, parses params and runs command functions

    :return:
    :rtype:
    """

    params = demisto.params()
    args = demisto.args()
    api_token = params.get("credentials", {}).get("password")
    server_url = params.get("url")
    proxy = params.get("proxy", False)
    verify_certificate = not params.get("insecure", False)

    headers = {"Authorization": f"Token {api_token}"}

    try:
        client = Client(base_url=server_url, verify=verify_certificate, headers=headers, proxy=proxy)

        demisto.debug(f"Command being called is {demisto.command()}")

        if demisto.command() == "test-module":
            # This is the call made when pressing the integration Test button.
            result = test_module(client)
            return_results(result)

        elif demisto.command() == "fetch-incidents":
            first_fetch_time = arg_to_datetime(
                arg=params.get("first_fetch", "3 days"), arg_name="First fetch time", required=True
            )
            if first_fetch_time is None:
                raise Exception("Invalid first fetch time")

            timestamp = int(first_fetch_time.timestamp())
            last_fetch = demisto.getLastRun().get("timestamp", None)
            timestamp = timestamp if last_fetch is None else int(last_fetch)

            next_run, incidents = fetch_incidents(
                client=client,
                timestamp=timestamp,
            )

            demisto.setLastRun(next_run)
            demisto.incidents(incidents)

        elif demisto.command() == "stamus-get-host-insight":
            return_results(fetch_host_id(client, args))

        elif demisto.command() == "stamus-get-doc-events":
            return_results(fetch_events(client, args))

        elif demisto.command() == "stamus-check-ioc":
            return_results(fetch_by_ioc(client, args))

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