Anomali Enterprise

Use Anomali Match to search indicators and enrich domains.

Data Enrichment & Threat Intelligence · Anomali Enterprise

Details

IDAnomali Enterprise
ProviderAnomali
CategoryData Enrichment & Threat Intelligence
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix 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 settings
  • integrationReliability — Source Reliability
  • feedExpirationPolicy
  • feedExpirationInterval

Commands (4)

  • anomali-enterprise-dga-domain-status

    The search domains Domain Generation Algorithm (DGA).

  • anomali-enterprise-retro-forensic-search

    Initiates a forensic search of the indicators.

  • anomali-enterprise-retro-forensic-search-results

    Retrieves the forensic search results.

  • domain

    The search domains Domain Generation Algorithm (DGA). Includes DBotScore and domain information.

import demistomock as demisto
import pytest
from Anomali_Enterprise import *

VENDOR_NAME = "Anomali Enterprise"


@pytest.fixture(autouse=True)
def handle_calling_context(mocker):
    mocker.patch.object(demisto, "callingContext", {"context": {"IntegrationBrand": VENDOR_NAME}})


def test_domain_command_benign(mocker):
    """
    Given:
        - a domain

    When:
        - mocking the server response for a benign domain, running domain_command

    Then:
        - validating that the domain score is unknown
        - validating the returned context data

    """
    client = Client(server_url="test", username="test", password="1234", verify=True, proxy=False)
    return_data = {"data": {"test.com": {"malware_family": "", "probability": 0}}, "result": "success"}
    mocker.patch.object(client, "domain_request", return_value=return_data)
    command_results = domain_command(client, args={"domain": "test.com"})
    output = command_results[0].to_context().get("EntryContext", {})
    dbot_key = "DBotScore(val.Indicator && val.Indicator == obj.Indicator && val.Vendor == obj.Vendor && val.Type == obj.Type)"
    expected_result = {
        "Domain": [{"Name": "test.com"}],
        "DBotScore": [{"Indicator": "test.com", "Type": "domain", "Vendor": "Anomali Enterprise", "Score": 0}],
    }
    assert output.get("Domain(val.Name && val.Name == obj.Name)", []) == expected_result.get("Domain")
    assert output.get(dbot_key, []) == expected_result.get("DBotScore")


def test_domain_command_suspicious(mocker):
    """
    Given:
        - a domain

    When:
        - mocking the server response for a suspicious domain, running domain_command

    Then:
        - validating that the domain score is suspicious
        - validating the returned context data, including the suspicious context

    """
    client = Client(server_url="test", username="test", password="1234", verify=True, proxy=False)
    return_data = {"data": {"suspicious.com": {"malware_family": "my_suspicious", "probability": 0.4}}, "result": "success"}
    mocker.patch.object(client, "domain_request", return_value=return_data)
    command_results = domain_command(client, args={"domain": "suspicious.com"})
    output = command_results[0].to_context().get("EntryContext", {})
    dbot_key = "DBotScore(val.Indicator && val.Indicator == obj.Indicator && val.Vendor == obj.Vendor && val.Type == obj.Type)"
    expected_result = {
        "Domain": [{"Name": "suspicious.com", "Tags": "DGA"}],
        "DBotScore": [{"Indicator": "suspicious.com", "Type": "domain", "Vendor": "Anomali Enterprise", "Score": 2}],
    }

    assert output.get("Domain(val.Name && val.Name == obj.Name)", []) == expected_result.get("Domain")
    assert output.get(dbot_key, []) == expected_result.get("DBotScore")


def test_domain_command_malicious(mocker):
    """
    Given:
        - a domain

    When:
        - mocking the server response for a malicious domain, running domain_command

    Then:
        - validating that the domain score is malicious
        - validating the returned context data, including the malicious context

    """
    client = Client(server_url="test", username="test", password="1234", verify=True, proxy=False)
    return_data = {"data": {"malicious.com": {"malware_family": "my_malware", "probability": 0.9}}, "result": "success"}
    mocker.patch.object(client, "domain_request", return_value=return_data)
    command_results = domain_command(client, args={"domain": "malicious.com"})
    output = command_results[0].to_context().get("EntryContext", {})
    dbot_key = "DBotScore(val.Indicator && val.Indicator == obj.Indicator && val.Vendor == obj.Vendor && val.Type == obj.Type)"
    expected_result = {
        "Domain": [
            {"Malicious": {"Description": "my_malware", "Vendor": "Anomali Enterprise"}, "Name": "malicious.com", "Tags": "DGA"}
        ],
        "DBotScore": [{"Indicator": "malicious.com", "Type": "domain", "Vendor": "Anomali Enterprise", "Score": 3}],
    }

    assert output.get("Domain(val.Name && val.Name == obj.Name)", []) == expected_result.get("Domain")
    assert output.get(dbot_key, []) == expected_result.get("DBotScore")


def test_start_search_job_command(mocker):
    """
    Given:
        - a from_, to_ and indicators to search

    When:
        - mocking the server response for the start of a job, running start_search_job

    Then:
        - validating the arguments are parsed correctly
        - validating the returned context data

    """
    client = Client(server_url="test", username="test", password="1234", verify=True, proxy=False)
    return_data = {"jobid": "1234"}
    mocker.patch.object(client, "start_search_job_request", return_value=return_data)
    command_results = start_search_job(client, args={"from": "1 month", "indicators": "8.8.8.8"})
    output = command_results.to_context().get("EntryContext", {})
    expected_result = {"status": "in progress", "job_id": "1234"}

    assert output["AnomaliEnterprise.ForensicSearch(val.job_id && val.job_id == obj.job_id)"] == expected_result


def test_get_search_job_result_command_with_matches(mocker):
    """
    Given:
        - a job_id

    When:
        - mocking the server response for getting the results of a job with matches, running get_search_job_result

    Then:
        - validating the returned context data

    """
    client = Client(server_url="test", username="test", password="1234", verify=True, proxy=False)
    return_data = {
        "status": "completed",
        "category": "forensic_api_result",
        "totalFiles": 1,
        "streamResults": [
            {
                "count": "1",
                "indicator": "",
                "itype": "",
                "severity": "",
                "event_time": "2020-10-14T09:10:00.000+0000",
                "age": "",
                "event.dest": "8.8.8.8",
                "confidence": "",
                "event.src": "8.8.8.8",
            }
        ],
        "scannedEvents": 269918,
        "result_file_name": "org0_1234_job1234_result.tar.gz",
        "complete": True,
        "processedFiles": 1,
        "totalMatches": 1,
    }
    mocker.patch.object(client, "get_search_job_result_request", return_value=return_data)
    command_results = get_search_job_result(client, args={"job_id": "111"})[0]
    output = command_results.to_context().get("EntryContext", {})
    expected_result = {
        "status": "completed",
        "category": "forensic_api_result",
        "totalFiles": 1,
        "streamResults": [
            {
                "count": "1",
                "indicator": "",
                "itype": "",
                "severity": "",
                "event_time": "2020-10-14T09:10:00.000+0000",
                "age": "",
                "event.dest": "8.8.8.8",
                "confidence": "",
                "event.src": "8.8.8.8",
            }
        ],
        "scannedEvents": 269918,
        "result_file_name": "org0_1234_job1234_result.tar.gz",
        "complete": True,
        "processedFiles": 1,
        "totalMatches": 1,
        "job_id": "111",
    }

    assert output.get("AnomaliEnterprise.ForensicSearch(val.job_id && val.job_id == obj.job_id)", []) == expected_result


def test_get_search_job_result_command_with_matches_and_limit(mocker):
    """
    Given:
        - a job_id

    When:
        - mocking the server response for getting the results of a job with matches, running get_search_job_result
        - limit the stream results

    Then:
        - validating that the context was limited

    """
    client = Client(server_url="test", username="test", password="1234", verify=True, proxy=False)
    return_data = {
        "status": "completed",
        "category": "forensic_api_result",
        "totalFiles": 1,
        "streamResults": [
            {
                "count": "1",
                "indicator": "",
                "itype": "",
                "severity": "",
                "event_time": "2020-10-14T09:10:00.000+0000",
                "age": "",
                "event.dest": "8.8.8.8",
                "confidence": "",
                "event.src": "8.8.8.8",
            },
            {
                "count": "1",
                "indicator": "",
                "itype": "",
                "severity": "",
                "event_time": "2020-11-14T09:10:00.000+0000",
                "age": "",
                "event.dest": "8.8.8.8",
                "confidence": "",
                "event.src": "8.8.8.8",
            },
            {
                "count": "1",
                "indicator": "",
                "itype": "",
                "severity": "",
                "event_time": "2020-12-14T09:10:00.000+0000",
                "age": "",
                "event.dest": "8.8.8.8",
                "confidence": "",
                "event.src": "8.8.8.8",
            },
        ],
        "scannedEvents": 269918,
        "result_file_name": "org0_1234_job1234_result.tar.gz",
        "complete": True,
        "processedFiles": 1,
        "totalMatches": 3,
    }
    mocker.patch.object(client, "get_search_job_result_request", return_value=return_data)
    command_results = get_search_job_result(client, args={"job_id": "111", "limit": "2"})[0]
    output = command_results.to_context().get("EntryContext", {})

    assert len(output["AnomaliEnterprise.ForensicSearch(val.job_id && val.job_id == obj.job_id)"]["streamResults"]) == 2


def test_get_search_job_result_command_without_matches(mocker):
    """
    Given:
        - a job_id

    When:
        - mocking the server response for getting the results of a job without matches, running get_search_job_result

    Then:
        - validating the returned context data
        - validating the returned human readable

    """
    client = Client(server_url="test", username="test", password="1234", verify=True, proxy=False)
    return_data = {
        "totalFiles": 0,
        "streamResults": [],
        "scannedEvents": 269918,
        "complete": True,
        "processedFiles": 0,
        "totalMatches": 0,
    }
    mocker.patch.object(client, "get_search_job_result_request", return_value=return_data)
    command_results = get_search_job_result(client, args={"job_id": "222"})[0]

    output = command_results.to_context().get("EntryContext", {})
    expected_result = {
        "status": "completed",
        "totalFiles": 0,
        "streamResults": [],
        "scannedEvents": 269918,
        "complete": True,
        "processedFiles": 0,
        "totalMatches": 0,
        "job_id": "222",
    }
    assert output.get("AnomaliEnterprise.ForensicSearch(val.job_id && val.job_id == obj.job_id)", []) == expected_result

    hr_ = command_results.to_context().get("HumanReadable", "")
    assert hr_ == "No matches found for the given job ID: 222."


def test_get_search_job_result_command_expired_job_id(mocker):
    """
    Given:
        - a job_id

    When:
        - mocking the server response for an expired job id, running get_search_job_result

    Then:
        - validating the raised error

    """
    client = Client(server_url="test", username="test", password="1234", verify=True, proxy=False)
    return_data = {"error": "Error: Cannot find the jobId: job222"}
    mocker.patch.object(client, "get_search_job_result_request", return_value=return_data)

    with pytest.raises(Exception, match="Error: Cannot find the jobId: job222. Job ID might have expired."):
        get_search_job_result(client, args={"job_id": "job222"})