Anomali Enterprise
Use Anomali Match to search indicators and enrich domains.
Data Enrichment & Threat Intelligence · Anomali Enterprise
Details
| ID | Anomali Enterprise |
|---|---|
| Provider | Anomali |
| Category | Data Enrichment & Threat Intelligence |
| From Version | 5.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
Use Anomali Match to search indicators and enrich domains.
Configure Anomali Match in Cortex
| Parameter | Description | Required |
|---|---|---|
| url | Server URL (e.g., https://www.test.com) | True |
| credentials | Username | True |
| insecure | Trust any certificate (not secure) | False |
| proxy | Use system proxy settings | 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.
anomali-enterprise-retro-forensic-search
Initiates a forensic search of the indicators.
Base Command
anomali-enterprise-retro-forensic-search
Input
| Argument Name | Description | Required |
|---|---|---|
| from | The time the indicators first appeared, in the format: <number> <time unit>, e.g., 1 hour, 30 minutes. Default is 1 day ago. | Optional |
| to | The time the indicators last appeared, in the format: <number> <time unit>, e.g., 1 hour, 30 minutes. Default is now. | Optional |
| indicators | A comma-separated list of indicators to search. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| AnomaliEnterprise.ForensicSearch.job_id | String | The job ID of the search. |
| AnomaliEnterprise.ForensicSearch.status | String | The status of the search. |
Command Example
!anomali-enterprise-retro-forensic-search indicators=1.1.1.1 from="1 month"
Context Example
{
"AnomaliEnterprise": {
"ForensicSearch": {
"job_id": "job1271604409989806",
"status": "in progress"
}
}
}
Human Readable Output
Forensic search started
job_id status job1271604409989806 in progress
anomali-enterprise-retro-forensic-search-results
Retrieves the forensic search results.
Base Command
anomali-enterprise-retro-forensic-search-results
Input
| Argument Name | Description | Required |
|---|---|---|
| job_id | The forensic search job ID. | Required |
| limit | Limit the stream results to return. Default is 20. | Optional |
| verbose | Whether to print the stream results to the War Room. Default is “true”. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AnomaliEnterprise.ForensicSearch.job_id | String | The job ID of the search. |
| AnomaliEnterprise.ForensicSearch.status | String | The status of the search. |
| AnomaliEnterprise.ForensicSearch.scannedEvents | Number | The number of scanned events. |
| AnomaliEnterprise.ForensicSearch.processedFiles | Number | The number of processed files. |
| AnomaliEnterprise.ForensicSearch.result_file_name | String | The matched file name. |
| AnomaliEnterprise.ForensicSearch.totalMatches | Number | The number of total matches. |
| AnomaliEnterprise.ForensicSearch.complete | Bool | Whether the search was complete. |
| AnomaliEnterprise.ForensicSearch.category | String | The search category. |
| AnomaliEnterprise.ForensicSearch.streamResults | Unknown | The stream results for the search. |
Command Example
!anomali-enterprise-retro-forensic-search-results job_id=job1251604409794526
Context Example
{
"AnomaliEnterprise": {
"ForensicSearch": {
"category": "forensic_api_result",
"complete": true,
"job_id": "job1251604409794526",
"processedFiles": 1,
"result_file_name": "org0_20201103_job1251604409794526_result.tar.gz",
"scannedEvents": 361295,
"status": "completed",
"streamResults": [
{
"age": "",
"confidence": "",
"count": "1",
"event.dest": "1.1.1.1",
"event.src": "1.1.1.1",
"event_time": "2020-10-14T09:10:00.000+0000",
"indicator": "",
"itype": "",
"severity": ""
}
],
"totalFiles": 1,
"totalMatches": 1
}
}
}
Human Readable Output
Forensic search metadata
status job_id category totalFiles scannedEvents completed job1251604409794526 forensic_api_result 1 361295
Forensic search results
count event.dest event.src event_time 1 1.1.1.1 1.1.1.1 2020-10-14T09:10:00.000+0000
anomali-enterprise-dga-domain-status
The search domains Domain Generation Algorithm (DGA).
Base Command
anomali-enterprise-dga-domain-status
Input
| Argument Name | Description | Required |
|---|---|---|
| domains | A comma-separated list of domains to search. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| AnomaliEnterprise.DGA.domain | String | The domain that was checked. |
| AnomaliEnterprise.DGA.malware_family | String | The malware family associated with the domain. |
| AnomaliEnterprise.DGA.domain | Number | The probability of the domain being malicious. |
Command Example
!anomali-enterprise-dga-domain-status domains=amazon.com
Context Example
{
"AnomaliEnterprise": {
"DGA": {
"domain": "amazon.com",
"malware_family": "",
"probability": 0
}
}
}
Human Readable Output
Domains DGA
domain probability amazon.com 0
domain
The search domains Domain Generation Algorithm (DGA). Includes DBotScore and domain information.
There is no distinction between benign to unknown domains in Anomali Enterprise.
The Domain reputation is calculated per the product documentation.
if malware family exists and prob > 0.6 the reputation is Malicious,
if malware family exists and prob < 0.6 the reputation is Suspicious,
else, the reputation is Unknown.
Base Command
domain
Input
| Argument Name | Description | Required |
|---|---|---|
| domain | A comma-separated list of domains to search. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AnomaliEnterprise.DGA.domain | String | The domain that was checked. |
| AnomaliEnterprise.DGA.malware_family | String | The malware family associated with the domain. |
| AnomaliEnterprise.DGA.domain | Number | The probability of the domain being malicious. |
| DBotScore.Indicator | String | The indicator that was tested. |
| DBotScore.Type | String | The indicator type. |
| DBotScore.Vendor | String | The vendor used to calculate the score. |
| DBotScore.Score | Number | The actual score. |
| Domain.Name | String | The domain name. For example, “google.com”. |
| Domain.Malicious.Vendor | String | The vendor that reported that the domain is malicious. |
| Domain.Malicious.Description | String | A description of the malicious domain. |
Command Example
!domain domain=google.com
Context Example
{
"AnomaliEnterprise": {
"DGA": {
"domain": "google.com",
"malware_family": "",
"probability": 0
}
},
"DBotScore": {
"Indicator": "google.com",
"Score": 0,
"Type": "domain",
"Vendor": "Anomali Enterprise"
},
"Domain": {
"Name": "google.com"
}
}
Human Readable Output
Domains DGA
domain probability google.com 0
Configuration parameters
url— Server URL (e.g., https://www.test.com) (required)credentials— Username (required)insecure— Trust any certificate (not secure)proxy— Use system proxy settingsintegrationReliability— Source ReliabilityfeedExpirationPolicy—feedExpirationInterval—
Commands (4)
-
anomali-enterprise-dga-domain-statusThe search domains Domain Generation Algorithm (DGA).
-
anomali-enterprise-retro-forensic-searchInitiates a forensic search of the indicators.
-
anomali-enterprise-retro-forensic-search-resultsRetrieves the forensic search results.
-
domainThe search domains Domain Generation Algorithm (DGA). Includes DBotScore and domain information.
import urllib3 from CommonServerPython import * # Disable insecure warnings urllib3.disable_warnings() VENDOR_NAME = "Anomali Enterprise" """ CLIENT CLASS """ class Client(BaseClient): """ Client to use in the Anomali Enterprise integration. Overrides BaseClient """ def __init__(self, server_url: str, username: str, password: str, verify: bool, proxy: bool): headers = {"Content-Type": "application/json", "ae-authorization": f"{username}:{password}"} super().__init__(base_url=server_url, verify=verify, proxy=proxy, headers=headers) self._username = username self._password = password def start_search_job_request(self, from_: str, to_: str, indicators: list[str]) -> dict: """Initiate a search job. Args: from_: from which time to initiate the search to_: to which time to initiate the search indicators: indicators to search Returns: Response from API. """ data = {"username": self._username, "password": self._password, "from": from_, "to": to_, "indicators": indicators} return self._http_request(method="POST", url_suffix="/api/v1/mars/forensic", headers=self._headers, json_data=data) def get_search_job_result_request(self, job_id: str) -> dict: """Retrieve a search job results. Args: job_id: the search job uuid Returns: Response from API. """ params = {"jobid": job_id} return self._http_request(method="GET", url_suffix="/api/v1/mars/forensic", headers=self._headers, params=params) def domain_request(self, domain: list[str]) -> dict: """Retrieve information regarding a domain. Args: domain: the domain name to search Returns: Response from API. """ data = {"username": self._username, "password": self._password, "domains": domain} return self._http_request(method="POST", url_suffix="/api/v1/mars/dga_score", headers=self._headers, json_data=data) """ COMMAND FUNCTIONS """ def module(client: Client) -> str: """ Performs basic get request """ response = client.domain_request(argToList("google.com")) if response.get("result") != "success": raise Exception( "To Use Anomali Enterprise, make sure you are using the current username and password " "and have the needed permissions." ) return "ok" def start_search_job(client: Client, args: dict) -> CommandResults: """Start a search job for IOCs. Args: client: Client object with request. args: Usually demisto.args() Returns: CommandResults. """ from_ = str(args.get("from", "1 day")) to_ = str(args.get("to", "")) indicators = argToList(args.get("indicators")) timestamp_format = "%Y-%m-%dT%H:%M:%S.%f" from_iso = parse_date_range(from_, date_format=timestamp_format)[0] if to_: to_iso = parse_date_range(to_, date_format=timestamp_format)[0] else: to_iso = datetime.now().strftime(timestamp_format) response = client.start_search_job_request(from_iso, to_iso, indicators) start_search_outputs = {"status": "in progress", "job_id": response.get("jobid", "")} return CommandResults( outputs_prefix="AnomaliEnterprise.ForensicSearch", outputs_key_field="job_id", outputs=start_search_outputs, readable_output=tableToMarkdown(name="Forensic search started:", t=start_search_outputs, removeNull=True), raw_response=response, ) def get_search_job_result(client: Client, args: dict) -> list[CommandResults]: """Get the search job result. Args: client: Client object with request. args: Usually demisto.args() Returns: CommandResults. """ job_ids = argToList(args.get("job_id")) limit = int(args.get("limit", "20")) verbose = args.get("verbose", "true") == "true" command_results: list = [] for job_id in job_ids: response = client.get_search_job_result_request(job_id) if "error" in response: raise Exception(f"{response.get('error')!s}. Job ID might have expired.") outputs = response outputs.update({"job_id": job_id}) if not response.get("complete"): human_readable = f"job ID: {job_id} is still in progress." outputs.update({"status": "in progress"}) else: if response.get("totalMatches"): headers = ["status", "job_id", "category", "totalFiles", "scannedEvents"] human_readable = tableToMarkdown(name="Forensic search metadata:", t=response, headers=headers, removeNull=True) if verbose: human_readable += tableToMarkdown( name="Forensic search results:", t=response.get("streamResults", [])[:limit], removeNull=True ) if "streamResults" in outputs: outputs["streamResults"] = outputs.get("streamResults", [])[:limit] # limit the outputs to the context else: human_readable = f"No matches found for the given job ID: {job_id}." response.update({"status": "completed"}) command_result = CommandResults( outputs_prefix="AnomaliEnterprise.ForensicSearch", outputs_key_field="job_id", outputs=response, readable_output=human_readable, raw_response=response, ) command_results.append(command_result) return command_results def dga_domain_status(client: Client, args: dict) -> CommandResults: """Search domain DGA status. Args: client: Client object with request. args: Usually demisto.args() Returns: CommandResults. """ domains = argToList(str(args.get("domains"))) response = client.domain_request(domains) domains_data = response.get("data", {}) outputs = [] for domain in domains: output = { "domain": domain, "malware_family": domains_data.get(domain, {}).get("malware_family"), "probability": domains_data.get(domain, {}).get("probability"), } outputs.append(output) return CommandResults( outputs_prefix="AnomaliEnterprise.DGA", outputs_key_field="domain", outputs=outputs, readable_output=tableToMarkdown(name="Domains DGA:", t=outputs, removeNull=True), raw_response=response, ) def domain_command(client: Client, args: dict) -> list[CommandResults]: """Search domain DGA status. Args: client: Client object with request. args: Usually demisto.args() Returns: CommandResults and DBotScore. """ domain_list = argToList(args.get("domain")) response = client.domain_request(domain_list) domains_data = response.get("data", {}) command_results_list = [] for domain in domain_list: output = { "domain": domain, "malware_family": domains_data.get(domain, {}).get("malware_family"), "probability": domains_data.get(domain, {}).get("probability"), } score = calculate_dbot_score(domains_data.get(domain, {})) dbot_score = Common.DBotScore( indicator=domain, indicator_type=DBotScoreType.DOMAIN, integration_name=VENDOR_NAME, score=score, malicious_description=str(output.get("malware_family", "")), reliability=demisto.params().get("integrationReliability"), ) domain = Common.Domain( domain=domain, dbot_score=dbot_score, tags="DGA" if score in [Common.DBotScore.SUSPICIOUS, Common.DBotScore.BAD] else None, ) command_results = CommandResults( outputs_prefix="AnomaliEnterprise.DGA", outputs_key_field="domain", outputs=output, readable_output=tableToMarkdown(name="Domains DGA:", t=output, removeNull=True), indicator=domain, raw_response=response, ) command_results_list.append(command_results) return command_results_list def calculate_dbot_score(domain_data: dict) -> int: """There is no distinction between benign to unknown domains in Anomali Enterprise malware family exists and prob > 0.6 -> 3 malware family exists and prob < 0.6 -> 2 else -> 0 Args: domain_data: the domain data Returns: DBot Score. """ score = Common.DBotScore.NONE if domain_data.get("malware_family", {}): if float(domain_data.get("probability", 0)) > 0.6: score = Common.DBotScore.BAD else: score = Common.DBotScore.SUSPICIOUS return score """ MAIN FUNCTION """ def main() -> None: """ Parse and validates integration params, runs integration commands. """ params = demisto.params() server_url = params.get("url") username = params.get("credentials", {}).get("identifier") password = params.get("credentials", {}).get("password") verify = not params.get("insecure", False) proxy = params.get("proxy") is True command = demisto.command() LOG(f"Command being called in {VENDOR_NAME} is: {command}") try: client = Client(server_url=server_url, username=username, password=password, verify=verify, proxy=proxy) commands = { "anomali-enterprise-retro-forensic-search": start_search_job, "anomali-enterprise-retro-forensic-search-results": get_search_job_result, "anomali-enterprise-dga-domain-status": dga_domain_status, "domain": domain_command, } if command == "test-module": return_results(module(client)) elif command in commands: return_results(commands[command](client, demisto.args())) else: raise NotImplementedError(f'Command "{command}" is not implemented.') except Exception as err: return_error(f"Failed to execute {command} command. Error: {err!s} \n tracback: {traceback.format_exc()}") """ ENTRY POINT """ if __name__ in ["__main__", "builtin", "builtins"]: main()