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
| ID | Stamus |
|---|---|
| Provider | Stamus Networks |
| Category | Network Security |
| From Version | 6.9.0 |
| Docker Image | demisto/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 settingsincidentType— Incident typeincidentFetchInterval— Incidents Fetch IntervalisFetch— Fetch incidentsmax_fetch— Maximum number of incidents per fetchfirst_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()