ThreatMon Threat Feed
Fetches Indicators of Compromise (IOCs) from the ThreatMon IOC platform and ingests them into Cortex as indicators.
Data Enrichment & Threat Intelligence · ThreatMon Threat Feed · Feed
Details
| ID | ThreatMon Threat Feed |
|---|---|
| Provider | ThreatMon |
| Category | Data Enrichment & Threat Intelligence |
| From Version | 6.10.0 |
| Docker Image | demisto/python3:3.12.13.10404775 |
| Supported Modules | Agentix XSIAM |
README
Fetches Indicators of Compromise (IOCs) from the ThreatMon IOC platform and ingests them into Cortex as indicators.
This integration was integrated and tested with the ThreatMon IOC API.
Configure ThreatMon Threat Feed in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL | The ThreatMon IOC API base URL. | True |
| API Token | The ThreatMon API token used to authenticate against the IOC API. | True |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False | |
| Fetch indicators | False | |
| Data Type to Fetch | The type of IOC data to fetch from ThreatMon. | False |
| Maximum number of indicators per fetch | The maximum number of indicators to fetch in a single run. | False |
| Collection IDs | A comma-separated list of ThreatMon collection IDs to filter the fetch by. Leave empty to fetch from all collections. | False |
| Indicator Reputation | Indicators from this integration instance will be marked with this reputation. | False |
| Source Reliability | Reliability of the source providing the intelligence data. | True |
| Traffic Light Protocol Color | The Traffic Light Protocol (TLP) designation to apply to indicators fetched from the feed. | False |
| Feed Fetch Interval | False | |
| Tags | Supports CSV values. | False |
| Bypass exclusion list | When selected, the exclusion list is ignored for indicators from this feed. This means that if an indicator from this feed is on the exclusion list, the indicator might still be added to the system. | False |
Fetched Indicators
The feed maps the ThreatMon ioc_type field to Cortex indicator types as follows.
| ThreatMon ioc_type | Cortex indicator type |
|---|---|
Contains ip |
IP |
Contains domain |
Domain |
Contains url |
URL |
Contains file or hash |
File |
For IP indicators, the extracted_ip field is preferred over ioc_value, and any port suffix is stripped, so that 1.2.3.4:8080 is ingested as 1.2.3.4.
The following indicator fields are populated from the ThreatMon response.
| Indicator field | ThreatMon field |
|---|---|
| description | A summary built from source, confidence level, severity, status, ISP, resolved IPs, categories, tags, timestamp and score |
| tags | tags, categories and source, merged with the ThreatMon tag |
| modified | updated_at, falling back to timestamp and then created_at |
| confidence | confidence_level |
| threatseverity | severity |
| status | status |
| isp | isp |
| resolvedips | resolved_ips |
| geolocation | geo_location |
| trafficlightprotocol | The Traffic Light Protocol Color parameter |
The feed is incremental. On every run it stores the newest indicator timestamp it has seen, and on subsequent runs it skips indicators that are not newer than that timestamp.
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.
threatmon-get-indicators
Gets a sample of indicators from the ThreatMon feed. This command is used mainly for testing and debugging, and does not create indicators in the system.
Base Command
threatmon-get-indicators
Input
| Argument Name | Description | Required |
|---|---|---|
| limit | The maximum number of indicators to return. Default is 10. | Optional |
Context Output
There is no context output for this command.
Indicators are also ingested automatically according to the Feed Fetch Interval parameter.
Troubleshooting
- Test failed - Verify the API token and that the Server URL is reachable from the Cortex engine.
- No indicators fetched - Verify that the configured Collection IDs contain data, and that Data Type to Fetch is not filtering out the indicators you expect. Because the feed is incremental, a run returns nothing when the API has not published indicators newer than the previous run.
<~PLATFORM>
License Requirements
The following configuration parameters require one of these licenses: Cortex XSIAM or Agentix:
- Fetch indicators
- Incremental Feed
- Bypass exclusion list
</~PLATFORM>
Configuration parameters
url— Server URL (required)credentials— (required)insecure— Trust any certificate (not secure)proxy— Use system proxy settingsfeed— Fetch indicatorsdata_type— Data Type to Fetchlimit— Maximum number of indicators per fetchcollection_ids— Collection IDsfeedReputation— Indicator ReputationfeedReliability— Source Reliability (required)tlp_color— Traffic Light Protocol ColorfeedIncremental— Incremental FeedfeedExpirationPolicy—feedExpirationInterval—feedFetchInterval— Feed Fetch IntervalfeedBypassExclusionList— Bypass exclusion listfeedTags— Tags
Commands (1)
-
threatmon-get-indicatorsGets a sample of indicators from the ThreatMon feed. This command is used mainly for testing and debugging, and does not create indicators in the system.
import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 import traceback from typing import Any DEFAULT_BASE_URL = "https://ioc.threatmonit.io" DEFAULT_LIMIT = 500 CREATE_INDICATORS_BATCH_SIZE = 2000 FEED_SOURCE_TAG = "ThreatMon" class Client(BaseClient): """ Client class to interact with the ThreatMon IOC API. """ def __init__(self, base_url: str, api_token: str, verify: bool, proxy: bool): super().__init__(base_url=base_url, verify=verify, proxy=proxy) self.api_token = api_token def get_daily_iocs(self, data_type: str = "all", size: int = DEFAULT_LIMIT, collection_ids: str = None) -> dict: """ Fetches daily IOC details from the API. """ params: dict[str, Any] = {"api_token": self.api_token, "data_type": data_type, "size": size} if collection_ids: params["collection_id"] = collection_ids return self._http_request( method="GET", url_suffix="/api/daily-ioc-details/", params=params, headers={"Accept": "application/json"} ) def strip_port(value: str) -> str: """ Strips a trailing port from an indicator value while keeping IPv6 addresses intact. Handles the following forms: - ``[2001:db8::1]:443`` -> ``2001:db8::1`` - ``1.2.3.4:8080`` -> ``1.2.3.4`` - ``2001:db8::1`` -> ``2001:db8::1`` (unbracketed IPv6, left untouched) """ if value.startswith("["): return value[1:].split("]", 1)[0] # Only strip when there is a single colon, i.e. an IPv4:port pair. Unbracketed # IPv6 addresses contain multiple colons and must not be split. if value.count(":") == 1: return value.split(":", 1)[0] return value def map_indicator_type(ioc_type: str) -> str | None: """ Maps a ThreatMon ioc_type to an XSOAR FeedIndicatorType. Returns ``None`` when the ioc_type is missing or not recognized, so the caller can skip the indicator instead of ingesting it with an invalid type. """ if ioc_type: ioc_type = ioc_type.lower() if "ip" in ioc_type: return FeedIndicatorType.IP elif "domain" in ioc_type: return FeedIndicatorType.Domain elif "url" in ioc_type: return FeedIndicatorType.URL elif "file" in ioc_type or "hash" in ioc_type: return FeedIndicatorType.File return None def parse_indicator(ioc: dict, feed_tags: list, tlp_color: str | None) -> dict | None: """ Parses a ThreatMon IOC object into an XSOAR indicator. Returns ``None`` when the indicator has no value or its type cannot be determined, so the caller can skip it instead of failing the whole fetch. """ value = ioc.get("ioc_value") ioc_type = ioc.get("ioc_type", "") mapped_type = map_indicator_type(ioc_type) if not value or not mapped_type: return None # Prefer the extracted IP for IP indicators, to avoid a port number in the value. extracted_ip = ioc.get("extracted_ip") if mapped_type == FeedIndicatorType.IP: value = extracted_ip or value if value: value = strip_port(value) source = ioc.get("source") or [] tags = ioc.get("tags") or [] categories = ioc.get("categories") or [] # Combine tags from the API and from the feed settings, removing duplicates. combined_tags = list(dict.fromkeys([t for t in (tags + categories + source + feed_tags) if t])) # Construct the description showing all metadata. description_parts = [] if source: description_parts.append(f"Source: {', '.join(source) if isinstance(source, list) else source}") if ioc.get("confidence_level"): description_parts.append(f"Confidence Level: {ioc.get('confidence_level')}") if ioc.get("severity"): description_parts.append(f"Severity: {ioc.get('severity')}") if ioc.get("status"): description_parts.append(f"Status: {ioc.get('status')}") if ioc.get("isp"): description_parts.append(f"ISP: {ioc.get('isp')}") resolved_ips = ioc.get("resolved_ips") if resolved_ips: description_parts.append(f"Resolved IPs: {', '.join(resolved_ips)}") if categories: description_parts.append(f"Categories: {', '.join(categories)}") if tags: description_parts.append(f"Tags: {', '.join(tags)}") if ioc.get("timestamp"): description_parts.append(f"Timestamp: {ioc.get('timestamp')}") if ioc.get("score") is not None: description_parts.append(f"API Score: {ioc.get('score')}") description = "\n".join(description_parts) fields = { "tags": combined_tags, "description": description, "modified": ioc.get("updated_at") or ioc.get("timestamp") or ioc.get("created_at"), "confidence": ioc.get("confidence_level"), "threatseverity": ioc.get("severity"), "status": ioc.get("status"), "isp": ioc.get("isp"), "resolvedips": resolved_ips, "geolocation": ioc.get("geo_location"), "threat_score": ioc.get("score"), "threatscore": ioc.get("score"), "extractedip": extracted_ip, "extracted_ip": extracted_ip, } if tlp_color: fields["trafficlightprotocol"] = tlp_color return { "value": value, "type": mapped_type, "service": FEED_SOURCE_TAG, "rawJSON": ioc, "fields": fields, } def calculate_verdict(feed_reputation: str) -> int: """ Calculates the XSOAR DBot score based on the feedReputation setting. """ mapping = {"Unknown": 0, "None": 0, "Benign": 1, "Good": 1, "Suspicious": 2, "Malicious": 3, "Bad": 3} return mapping.get(feed_reputation, 3) def build_indicators(client: Any, params: dict, limit: int, last_timestamp: str | None = None) -> tuple[list, str | None]: """ Fetches IOCs from the API and builds XSOAR indicators. Returns the list of indicators and the newest timestamp seen, so both the fetch and the get-indicators commands can share the same building logic. """ data_type = params.get("data_type") or "all" tlp_color = params.get("tlp_color") feed_reputation = params.get("feedReputation") or "Bad" collection_ids = params.get("collection_ids") feed_tags = [FEED_SOURCE_TAG] + argToList(params.get("feedTags")) response = client.get_daily_iocs(data_type=data_type, size=limit, collection_ids=collection_ids) iocs = response.get("iocs", []) or [] indicators = [] latest_timestamp = last_timestamp for ioc in iocs: # Track the newest timestamp to skip previously fetched IOCs. ioc_timestamp = ioc.get("created_at") or ioc.get("timestamp") or ioc.get("updated_at") if last_timestamp and ioc_timestamp and ioc_timestamp <= last_timestamp: continue parsed = parse_indicator(ioc, feed_tags=feed_tags, tlp_color=tlp_color) if parsed: parsed["score"] = calculate_verdict(feed_reputation) indicators.append(parsed) if ioc_timestamp and (not latest_timestamp or ioc_timestamp > latest_timestamp): latest_timestamp = ioc_timestamp return indicators, latest_timestamp def test_module(client: Any, params: dict) -> str: """ Tests API connectivity and that a returned IOC can be parsed into an indicator. """ try: data_type = params.get("data_type") or "all" response = client.get_daily_iocs(data_type=data_type, size=1) iocs = response.get("iocs", []) or [] if iocs: # Exercise the indicator builder to make sure the response shape is understood. parse_indicator(iocs[0], feed_tags=[FEED_SOURCE_TAG], tlp_color=params.get("tlp_color")) return "ok" except Exception as e: return f"Test failed: {str(e)}" def fetch_indicators_command( client: Any, params: dict, limit: int, last_run: dict ) -> tuple[list[dict[str, Any]], dict[str, Any]]: """ Fetches indicators from the ThreatMon API for the feed fetch interval. """ new_last_run = last_run.copy() if last_run else {} last_timestamp = new_last_run.get("last_timestamp") try: indicators, latest_timestamp = build_indicators(client, params, limit, last_timestamp) if latest_timestamp: new_last_run["last_timestamp"] = latest_timestamp except Exception as e: demisto.error(f"Error fetching indicators from ThreatMon: {str(e)}\n{traceback.format_exc()}") raise e return indicators, new_last_run def get_indicators_command(client: Any, params: dict, args: dict) -> CommandResults: """ Retrieves a sample of indicators from the feed to the war room, for testing and debugging. Does not create indicators in the system. """ limit = arg_to_number(args.get("limit")) or 10 indicators, _ = build_indicators(client, params, limit) indicators = indicators[:limit] if not indicators: return CommandResults(readable_output="### No indicators were found.") table = [ { "Value": indicator.get("value"), "Type": indicator.get("type"), "Score": indicator.get("score"), "Tags": ", ".join(indicator.get("fields", {}).get("tags", [])), } for indicator in indicators ] human_readable = tableToMarkdown(f"Indicators from {FEED_SOURCE_TAG}:", table, removeNull=True) return CommandResults( readable_output=human_readable, raw_response=[indicator.get("rawJSON") for indicator in indicators], ) def main(): params = demisto.params() base_url = (params.get("url") or "").strip() or DEFAULT_BASE_URL api_token = params.get("api_token") or params.get("credentials", {}).get("password") if not api_token: # Fallback to the credentials identifier if the token was placed there. api_token = params.get("credentials", {}).get("identifier") if not api_token: return_error("Missing API Token (api_token).") verify_cert = not argToBoolean(params.get("insecure", False)) proxy = argToBoolean(params.get("proxy", False)) client = Client(base_url=base_url, api_token=api_token, verify=verify_cert, proxy=proxy) command = demisto.command() demisto.debug(f"Command being called is {command}") try: if command == "test-module": return_results(test_module(client, params)) elif command == "threatmon-get-indicators": return_results(get_indicators_command(client, params, demisto.args())) elif command == "fetch-indicators": last_run = demisto.getLastRun() or {} limit = arg_to_number(params.get("limit")) or arg_to_number(params.get("max_fetch")) or DEFAULT_LIMIT indicators, new_last_run = fetch_indicators_command(client, params, limit, last_run) # Send the indicators to XSOAR in batches to avoid overloading the server. for indicators_batch in batch(indicators, batch_size=CREATE_INDICATORS_BATCH_SIZE): demisto.createIndicators(indicators_batch) demisto.setLastRun(new_last_run) else: raise DemistoException(f"Unsupported command: {command}") except Exception as e: demisto.error(traceback.format_exc()) return_error(f"Failed to execute {command} command.\nError: {str(e)}") if __name__ in ("__main__", "__builtin__", "builtins"): main()