ThreatX
The ThreatX integration allows automated enforcement and intel gathering actions.
Network Security · ThreatX
Details
| ID | ThreatX |
|---|---|
| Provider | A10 Networks |
| Category | Network Security |
| From Version | 5.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM EDR Cortex Cloud Cloud Runtime Security |
README
Use the ThreatX integration to enrich intel and automate enforcement actions on the ThreatX Next Gen WAF.
Use Cases
- Add and remove CIDR ranges and IP addresses to various block lists or the allow list
- Gather Entity metadata for intel enrichment and DBot scoring
- Set Entity notes for SOC integration or further automation
Configure ThreatX on Cortex XSOAR
- Navigate to Settings > Integrations > Servers & Services.
- Search for ThreatX.
- Click Add instance to create and configure a new integration instance.
- Name: a textual name for the integration instance.
- Customer Name: Contact the ThreatX SOC for your Customer Name
- ThreatX Server URL (e.g., https://provision.threatx.io/ )
- API Key: Contact the ThreatX SOC for your API Key
- DBot Score Threshold: Set the threshold ThreatX Risk score (1 to 100) which will be translated to Malicious DBot scores. Default is 70.
- Use system proxy settings
- Trust any certificate (not secure)
- Click Test to validate the URLs, token, and connection.
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.
- Temporarily block an IP address or CIDR: threatx-block-ip
- Unblock an IP address or CIDR: threatx-unblock-ip
- Block list an IP address or CIDR: threatx-blacklist-ip
- Remove an IP address or CIDR from the block list: threatx-unblacklist-ip
- Add an IP address or CIDR to allow list: threatx-whitelist-ip
- Remove an IP address or CIDR from the allow list: threatx-unwhitelist-ip
- Get entity information: threatx-get-entities
- Get entity notes: threatx-get-entity-notes
- Add a note to an entity: threatx-add-entity-note
1. Temporarily block an IP address or CIDR
Temporarily blocks an IP address or CIDR.
Base Command
threatx-block-ip
Input
| Argument Name | Description | Required |
|---|---|---|
| ip | IP address or CIDR, for example: "10.1.1.1" or "10.1.1.0/24". | Required |
| description | The description of the IP record in the block list. The default value is: "Added by ThreatX Cortex XSOAR Integration". | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| IP.Address | string | IP address or CIDR that was blocked. |
Command Example
!threatx-block-ip ip=12.12.12.12
Context Example
{
"IP": [
{
"Address": "12.12.12.12"
}
]
}
Human Readable Output
| Result |
|---|
| Blocklist entry for ip 12.12.12.12 added |
2. Unblock an IP address or CIDR
Unblocks a blocked IP address or CIDR.
Base Command
threatx-unblock-ip
Input
| Argument Name | Description | Required |
|---|---|---|
| ip | IP address or CIDR, for example: "10.1.1.1" or "10.1.1.0/24". | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| IP.Address | string | IP address or CIDR that was unblocked. |
Command Example
!threatx-unblock-ip ip=12.12.12.12
Context Example
{
"IP": [
{
"Address": "12.12.12.12"
}
]
}
Human Readable Output
| Result |
|---|
| Block list entry for ip 12.12.12.12 removed |
3. Block list an IP address or CIDR
Adds an IP address or CIDR to the block list.
Base Command
threatx-blacklist-ip
Input
| Argument Name | Description | Required |
|---|---|---|
| ip | IP address or CIDR in CSV format, for example: "10.1.1.1,10.1.1.0/24". | Required |
| description | The description of the IP record in the block list. The default value is: "Added by ThreatX Cortex XSOAR Integration". | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| IP.Address | string | IP address or CIDR that was added to the blacklist. |
Command Example
!threatx-blacklist-ip ip=12.12.12.12
Context Example
{
"IP": [
{
"Address": "12.12.12.12"
}
]
}
Human Readable Output
| Result |
|---|
| Block list entry for ip 12.12.12.12 added |
4. Remove an IP address or CIDR from the block list
Removes an IP address or CIDR from the block list.
Base Command
threatx-unblacklist-ip
Input
| Argument Name | Description | Required |
|---|---|---|
| ip | IP address or CIDR, for example: "10.1.1.1" or "10.1.1.0/24". | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| IP.Address | string | IP address or CIDR that was removed from the block list. |
Command Example
!threatx-unblacklist-ip ip=12.12.12.12
Context Example
{
"IP": [
{
"Address": "12.12.12.12"
}
]
}
Human Readable Output
| Result |
|---|
| Block list entry for ip 12.12.12.12 removed |
5. Add an IP address or CIDR to allow list
Adds an IP address or CIDR to the allow list.
Base Command
threatx-whitelist-ip
Input
| Argument Name | Description | Required |
|---|---|---|
| ip | IP address or CIDR, for example: "10.1.1.1" or "10.1.1.0/24". | Required |
| description | The description of the IP record in the allow list. The default value is: "Added by ThreatX Cortex XSOAR Integration". | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| IP.Address | string | IP address or CIDR was added to the allow list. |
Command Example
!threatx-whitelist-ip ip=12.12.12.12
Context Example
{
"IP": [
{
"Address": "12.12.12.12"
}
]
}
Human Readable Output
| Result |
|---|
| Whitelist entry for ip 12.12.12.12 added |
6. Remove an IP address or CIDR from the allow list
Removes an IP address or CIDR from the allow list.
Base Command
threatx-unwhitelist-ip
Input
| Argument Name | Description | Required |
|---|---|---|
| ip | IP address or CIDR, for example: "10.1.1.1" or "10.1.1.0/24". | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| IP.Address | string | IP address or CIDR that was removed from the allow list. |
Command Example
!threatx-unwhitelist-ip ip=12.12.12.12
Context Example
{
"IP": [
{
"Address": "12.12.12.12"
}
]
}
Human Readable Output
| Result |
|---|
| Allow list entry for ip 12.12.12.12 removed |
7. Get entity information
Returns high-level entity information by Entity ID, Entity Name, or Entity IP.
Base Command
threatx-get-entities
Input
| Argument Name | Description | Required |
|---|---|---|
| timeframe | Look-back timeframe for the query. Options are 1-Hour, 1-Day, 1-Week, 1-Month, or 3-Months. Note: long look-back timeframes for a large number of Entities can timeout. | Required |
| entity_name | CSV list of Entity names. | Optional |
| entity_id | CSV list of Entity ID hashes. | Optional |
| entity_ip | CSV list of Entity IP addresses. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Threatx.Entity.ID | string | ID hash of the Entity |
| Threatx.Entity.Name | string | Name of the Entity |
| Threatx.Entity.IP | string | IP address of the Entity |
| Threatx.Entity.Risk | integer | Risk score of the Entity |
Command Example
!threatx-get-entities timeframe=1-Day entity_name=CynicalGraaf,OveconfidentRas
Context Example
{
"Threatx": {
"Entity": [
{
"ID": "1061035762581303669",
"Name": "OveconfidentRas",
"Risk": 0,
"IP": [
201.93.212.87
]
},
{
"ID": "566056709675514809",
"Name": "CynicalGraaf",
"Risk": 0,
"IP": [
1.125.227.13
]
}
]
}
}
Human Readable Output
| ThreatX Risk Score | IP Addresses | Name | ID |
|---|---|---|---|
| 0 | 201.93.212.87 | OveconfidentRas | 1061035762581303669 |
| 55 | 1.125.227.13 | CynicalGraaf | 566056709675514809 |
8. Get entity notes
Returns the notes attached to an entity by Entity ID.
Base Command
threatx-get-entity-notes
Input
| Argument Name | Description | Required |
|---|---|---|
| entity_id | ID hash of the Entity. To retrieve this value, run the threatx-get-entities command. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Threatx.Entity.ID | string | ID hash of the Entity |
| Threatx.Entity.Note.content | string | Content of the Note |
| Threatx.Entity.Note.timestamp | string | Timestamp of the Note |
| Threatx.Entity.Note.username | string | Author of the Note |
Command Example
!threatx-get-entity-notes entity_id=566056709675514809
Context Example
{
"Threatx": {
"Entity": [
{
"ID": "566056709675514809",
"Note": [
{
"content": "Demisto test note.",
"entity_id": "566056709675514809",
"timestamp": "2019-05-31 18:41:09",
"username": "user@domain.com"
}
]
}
]
}
}
Human Readable Output
| Username | Timestamp | Entity Id | Content |
|---|---|---|---|
| user@domain.com | 2019-05-31 18:41:09 | 566056709675514809 | Demisto test note. |
| user@domain.com | 2019-05-30 23:36:23 | 566056709675514809 | this is a test note |
| user@domain.com | 2019-05-12 21:36:12 | 566056709675514809 | Another test note |
| user@domain.com | 2019-05-12 21:34:48 | 566056709675514809 | test-note-from-demisto |
9. Add a note to an entity
Adds a new note to an entity.
Base Command
threatx-add-entity-note
Input
| Argument Name | Description | Required |
|---|---|---|
| entity_id | ID hash of the Entity. To retrieve this value, run the threatx-get-entities command. | Required |
| message | Contents of the note. | Required |
Context Output
There is no context output for this command.
Command Example
!threatx-add-entity-note entity_id=566056709675514809 message="test note."
Human Readable Output
| Result |
|---|
| Note for Entity<id=566056709675514809> created |
Configuration parameters
url— ThreatX Server URL (e.g., https://provision.threatx.io) (required)customer_name— Customer Nameapi_key— API Keycredentials— Customer Namedbot_threshold— IP Threshold. Minimum risk score from ThreatX to consider the IP malicious. (required)proxy— Use system proxy settingsinsecure— Trust any certificate (not secure)
Commands (9)
-
threatx-add-entity-noteAdd a new note to the entity. For example: !threatx-add-entity-note entity_id=566056709695514809 message="test note"
-
threatx-blacklist-ipAdds an IP address or CIDR to the block list.
-
threatx-block-ipTemporarily blocks an IP address or CIDR. Default is 30 minutes.
-
threatx-get-entitiesGet high-level Entity information using the Entity ID, Entity Name, or Entity IP. For example: !threatx-get-entities timeframe=1-Day entity_name=CynicalGraaf,MJ12Bot entity_id= 566056709675514809 entity_ip=12.12.12.12,14.14.14.14. Note: long look-back timeframes for a large number of Entities can timeout.
-
threatx-get-entity-notesReturns the notes attached to an Entity, by Entity ID.
-
threatx-unblacklist-ipRemoves an IP or CIDR from the block list.
-
threatx-unblock-ipUnblocks a blocked IP address or CIDR.
-
threatx-unwhitelist-ipRemoves an IP address or CIDR from the allow list.
-
threatx-whitelist-ipAdds an IP address or CIDR to the allow list.
import demistomock as demisto from CommonServerPython import * from CommonServerUserPython import * """ IMPORTS """ import socket import struct import time from operator import itemgetter import requests import urllib3 # disable insecure warnings urllib3.disable_warnings() """ GLOBAL VARS """ CUSTOMER_NAME = "" API_KEY = "" BASE_URL = "" DBOT_THRESHOLD = 70 USE_SSL = True """ HELPER FUNCTIONS """ def http_request(url_suffix, commands=None): state = demisto.getIntegrationContext() session_token = state.get("session_token") if url_suffix != "/login": demisto.info(f"running request with url={BASE_URL + url_suffix} with commands={commands}") data = {"token": session_token, "customer_name": CUSTOMER_NAME} else: demisto.info(f"running request with url={BASE_URL + url_suffix}") data = {} if commands is not None: data.update(commands) res = requests.post(BASE_URL + url_suffix, verify=USE_SSL, json=data) if res.status_code != requests.codes.ok: if url_suffix == "/login": demisto.setIntegrationContext({"session_token": None, "token_expires": None}) demisto.info(f"{res.status_code} from server during login. Clearing session token cache.") raise DemistoException(f"HTTP {res.status_code} Error in API call to ThreatX service - {res.text}") resp_json = {} # type:dict try: resp_json = res.json() except ValueError: raise DemistoException(f"Could not parse the response from ThreatX: {res.text}") if "Ok" not in resp_json: if url_suffix == "/login": demisto.setIntegrationContext({"session_token": None, "token_expires": None}) raise DemistoException(f"Login response error - {res.text}.") raise DemistoException(res.text) if url_suffix == "/login" and "status" in resp_json["Ok"] and resp_json["Ok"]["status"] is not True: demisto.setIntegrationContext({"session_token": None, "token_expires": None}) raise DemistoException("Invalid credentials.") return resp_json["Ok"] @logger def initialize(): endpoint = "/login" commands = {"command": "login", "api_token": API_KEY} state = demisto.getIntegrationContext() if not state.get("session_token"): session_token = None token_expires = None else: session_token = state.get("session_token") token_expires = state.get("token_expires") demisto.info("Initializing request...") if session_token is None or (token_expires is not None and token_expires < int(time.time())): if session_token is None: demisto.info("Session token missing - getting new session token...") elif token_expires is not None and token_expires < int(time.time()): demisto.info("Session token expired - getting new session token...") r = http_request(endpoint, commands) demisto.setIntegrationContext({"session_token": r["token"], "token_expires": int(time.time() + (10 * 60))}) return demisto.info("Cached session token not expired.") return def pretty_ip(decimal_ip): """Convert decimal ip to dotted quad format""" packed_ip = struct.pack("!I", decimal_ip) return socket.inet_ntoa(packed_ip) def pretty_time(input_time): """Convert unix epoch time to human readable format""" return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(input_time)) def set_dbot_score(threatx_score): """Set the DBot Score based on the ThreatX risk score""" if threatx_score >= DBOT_THRESHOLD: return 3 elif threatx_score > 10: return 2 else: return 0 """ FUNCTIONS """ @logger def block_ip(ip, description): commands = {"command": "new_blocklist", "entry": {"ip": ip, "description": description, "created": int(time.time())}} return http_request("/lists", commands) @logger def block_ip_command(args): ips = args.get("ip", []) description = args.get("description", "Added by ThreatX Demisto Integration") results = [] errors = [] for ip in argToList(ips): try: ip_result = block_ip(ip, description) except Exception as error: demisto.error(f"failed block ip: {ip}\n{traceback.format_exc()}") errors.append(f"Failed to block ip: {ip} error: {error}") else: results.append(ip_result) if results: readable_outputs = tableToMarkdown("Block IP", results, ["Result"], removeNull=True) return_results( CommandResults( outputs=results, readable_output=readable_outputs, outputs_prefix="IP(val.Address === obj.Address).Address" ) ) if errors: return_error("\n".join(errors)) @logger def unblock_ip(ip): commands = {"command": "delete_blocklist", "ip": ip} return http_request("/lists", commands) @logger def unblock_ip_command(args): ip = args.get("ip", None) results = unblock_ip(ip) md = tableToMarkdown("Unblock IP", results, ["Result"], removeNull=True) ec = {"IP(val.Address === obj.Address)": {"Address": ip}} return_outputs(md, ec, results) @logger def blacklist_ip(ip, description): commands = {"command": "new_blacklist", "entry": {"ip": ip, "description": description, "created": int(time.time())}} return http_request("/lists", commands) @logger def blacklist_ip_command(args): ips = args.get("ip", None) description = args.get("description", "Added by ThreatX Demisto Integration") results = [] errors = [] for ip in argToList(ips): try: ip_result = blacklist_ip(ip, description) except Exception as error: demisto.error(f"failed adding ip: {ip} to balcklist\n{traceback.format_exc()}") errors.append(f"Failed to add ip: {ip} to blacklist error: {error}") else: results.append(ip_result) if results: readable_outputs = tableToMarkdown("Blacklist IP", results, ["Result"], removeNull=True) return_results( CommandResults( outputs=results, readable_output=readable_outputs, outputs_prefix="IP(val.Address === obj.Address).Address" ) ) if errors: return_error("\n".join(errors)) @logger def unblacklist_ip(ip): commands = {"command": "delete_blacklist", "ip": ip} return http_request("/lists", commands) @logger def unblacklist_ip_command(args): ip = args.get("ip", None) results = unblacklist_ip(ip) md = tableToMarkdown("Unblacklist IP", results, ["Result"], removeNull=True) ec = {"IP(val.Address === obj.Address)": {"Address": ip}} return_outputs(md, ec, results) @logger def whitelist_ip(ip, description): commands = {"command": "new_whitelist", "entry": {"ip": ip, "description": description, "created": int(time.time())}} return http_request("/lists", commands) @logger def whitelist_ip_command(args): ip = args.get("ip", None) description = args.get("description", "Added by ThreatX Demisto Integration") results = whitelist_ip(ip, description) md = tableToMarkdown("Whitelist IP", results, ["Result"], removeNull=True) ec = {"IP(val.Address === obj.Address)": {"Address": ip}} return_outputs(md, ec, results) @logger def unwhitelist_ip(ip): commands = {"command": "delete_whitelist", "ip": ip} return http_request("/lists", commands) @logger def unwhitelist_ip_command(args): ip = args.get("ip", None) results = unwhitelist_ip(ip) md = tableToMarkdown("Unwhitelist IP", results, ["Result"], removeNull=True) ec = {"IP(val.Address === obj.Address)": {"Address": ip}} return_outputs(md, ec, results) @logger def get_entities(entity_name, entity_id, entity_ip, timeframe): commands = {"command": "list", "query": {}} # type: dict if entity_name is not None: entity_names = entity_name.split(",") my_entity_name = {"codenames": entity_names} commands["query"].update(my_entity_name) if entity_id is not None: entity_ids = entity_id.split(",") my_entity_id = {"entity_ids": entity_ids} commands["query"].update(my_entity_id) if entity_ip is not None: entity_ips = entity_ip.split(",") my_entity_ip = {"ip_addresses": entity_ips} commands["query"].update(my_entity_ip) first_seen = None if timeframe is not None: if timeframe == "1-Hour": first_seen = int(time.time() - (60 * 60)) elif timeframe == "1-Day": first_seen = int(time.time() - (24 * 60 * 60)) elif timeframe == "1-Week": first_seen = int(time.time() - (7 * 24 * 60 * 60)) elif timeframe == "1-Month": first_seen = int(time.time() - (31 * 24 * 60 * 60)) if first_seen: my_timeframe = {"first_seen": first_seen} commands["query"].update(my_timeframe) return http_request("/entities", commands) @logger def get_entity_risk(entity_id): commands = {"command": "risk_changes", "id": entity_id} return http_request("/entities", commands) @logger def get_entities_command(args): entity_name = args.get("entity_name", None) entity_id = args.get("entity_id", None) entity_ip = args.get("entity_ip", None) timeframe = args.get("timeframe", None) results = get_entities(entity_name, entity_id, entity_ip, timeframe) dbot_scores = [] ip_enrich = [] human_readable = [] entities_context = [] for entity in results: risk_score = 0 e_risk = None # Grab the entity risk so we can set the Dbot score for the Actor IPs e_id = entity.get("id") if e_id: e_risk = get_entity_risk(e_id) if isinstance(e_risk, list) and e_risk and isinstance(e_risk[-1], dict) and "risk" in e_risk[-1]: risk_score = e_risk[-1]["risk"] entity["risk"] = risk_score iplist = [] for actor in entity.get("actors", []): if "ip_address" in actor: ipdot = pretty_ip(actor["ip_address"]) iplist.append(ipdot) actor["ip_address"] = ipdot if "interval_time_start" in actor: actor["interval_time_start"] = pretty_time(actor["interval_time_start"]) if "interval_time_stop" in actor: actor["interval_time_stop"] = pretty_time(actor["interval_time_stop"]) if "fingerprint" in actor and actor.get("fingerprint") is not None and "last_seen" in actor.get("fingerprint", {}): actor["fingerprint"]["last_seen"] = pretty_time(actor["fingerprint"]["last_seen"]) dbscore = set_dbot_score(risk_score) dbot_scores.append({"Vendor": "ThreatX", "Indicator": ipdot, "Type": "ip", "Score": dbscore}) if dbscore == 3: ip_enrich.append( { "Address": ipdot, "Malicious": {"Vendor": "ThreatX", "Description": "ThreatX risk score is " + str(risk_score)}, } ) else: ip_enrich.append({"Address": ipdot}) entities_context.append({"ID": e_id, "Name": entity["codename"], "IP": iplist, "Risk": risk_score}) human_readable.append( {"Name": entity["codename"], "ID": e_id, "IP Addresses": ", ".join(iplist), "ThreatX Risk Score": risk_score} ) ec = { "Threatx.Entity(val.ID && val.ID === obj.ID)": entities_context, "DBotScore": dbot_scores, "IP(val.Address === obj.Address)": ip_enrich, } return_outputs(tableToMarkdown("Entities", human_readable), ec, results) @logger def get_entity_notes(entity_id): commands = {"command": "notes", "id": entity_id} return http_request("/entities", commands) @logger def get_entity_notes_command(args): entity_id = args.get("entity_id", None) results = get_entity_notes(entity_id) # Reverse sort the list by timestamp sorted_results = sorted(results, key=itemgetter("timestamp"), reverse=True) # Replace dates with pretty format for note in sorted_results: if "timestamp" in note: note["timestamp"] = pretty_time(note["timestamp"]) md = tableToMarkdown("Entity Notes", sorted_results, headerTransform=string_to_table_header) ec = {"Threatx.Entity(val.ID && val.ID === obj.ID)": {"ID": entity_id, "Note": sorted_results}} return_outputs(md, ec, sorted_results) @logger def add_entity_note(entity_id, message): commands = {"command": "new_note", "note": {"entity_id": entity_id, "content": message}} return http_request("/entities", commands) @logger def add_entity_note_command(args): entity_id = args.get("entity_id", None) message = args.get("message", None) results = add_entity_note(entity_id, message) md = tableToMarkdown("New Entity Note", results, ["Result"], removeNull=True) return_outputs(md, {}, results) @logger def command_test_module(): results = http_request("/users", {"command": "list"}) if not isinstance(results, list): raise DemistoException("Unrecognized response from ThreatX.") if not results: raise DemistoException("Empty response from ThreatX.") if "username" not in results[0]: raise DemistoException("Unexpected response from ThreatX.") demisto.results("ok") """ EXECUTION CODE """ def main(): global CUSTOMER_NAME, API_KEY, BASE_URL, DBOT_THRESHOLD, USE_SSL params = demisto.params() url = params.get("url", "").strip("/") BASE_URL = url + "/tx_api/v1" CUSTOMER_NAME = params.get("credentials", {}).get("identifier") or params.get("customer_name") API_KEY = params.get("credentials", {}).get("password") or params.get("api_key") DBOT_THRESHOLD = int(params.get("dbot_threshold", 70)) USE_SSL = not params.get("insecure") command = demisto.command() demisto.info(f"command is {command}") args = demisto.args() try: handle_proxy() initialize() if command == "test-module": command_test_module() elif command == "threatx-block-ip": block_ip_command(args) elif command == "threatx-unblock-ip": unblock_ip_command(args) elif command == "threatx-blacklist-ip": blacklist_ip_command(args) elif command == "threatx-unblacklist-ip": unblacklist_ip_command(args) elif command == "threatx-whitelist-ip": whitelist_ip_command(args) elif command == "threatx-unwhitelist-ip": unwhitelist_ip_command(args) elif command == "threatx-get-entities": get_entities_command(args) elif command == "threatx-get-entity-notes": get_entity_notes_command(args) elif command == "threatx-add-entity-note": add_entity_note_command(args) except Exception as e: return_error(str(e), error=traceback.format_exc()) if __name__ in ("__main__", "__builtin__", "builtins"): main()