AlienVault OTX v2

Query Indicators of Compromise in AlienVault OTX.

Data Enrichment & Threat Intelligence · AlienVault OTX

Details

IDAlienVault OTX v2
ProviderAT&T Cybersecurity
CategoryData Enrichment & Threat Intelligence
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Query Indicators of Compromise in AlienVault OTX.

Configure AlienVault OTX v2 in Cortex

Parameter Description Required
Server address   True
API Token   False
Indicator Threshold. The minimum number of pulses to consider the indicator as malicious.   False
Maximum number of relationships for indicators If not provided, no relationships will be added. False
Source Reliability Reliability of the source providing the intelligence data. True
Create relationships Create relationships between indicators as part of Enrichment. False
Trust any certificate (not secure)   False
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.

Disclaimer

Retrieving Passive DNS history for high-volume indicators may time out and therefore be skipped.

ip


Queries an IP address in AlienVault OTX.

Base Command

ip

Input

Argument Name Description Required
ip The IP address to query. Required
threshold If the number of pulses is bigger than the threshold, the IP address is considered as malicious. If the threshold is not specified, the default indicator threshold is used, which is configured in the instance settings. Optional

Context Output

Path Type Description
IP.Address String The address of the IP.
IP.ASN String The autonomous system name for the IP address. For example, “AS8948”.
IP.Geo.Country String The country where the IP address is located.
IP.Geo.Location String The geolocation where the IP address is located, in the format: latitude:longitude.
AlienVaultOTX.IP.Reputation String The reputation of the IP address.
AlienVaultOTX.IP.IP String IP address
DBotScore.Score Number The actual score.
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.
DBotScore.Indicator String The indicator that was tested.
IP.Relationships.EntityA string The source of the relationship.
IP.Relationships.EntityB string The destination of the relationship.
IP.Relationships.Relationship string The name of the relationship.
IP.Relationships.EntityAType string The type of the source of the relationship.
IP.Relationships.EntityBType string The type of the destination of the relationship.

Command Example

!ip ip=98.136.103.23

Context Example

{
    "AlienVaultOTX": {
        "IP": {
            "IP": {
                "IP": "98.136.103.23",
                "Reputation": 0
            }
        }
    },
    "DBotScore": {
        "Indicator": "98.136.103.23",
        "Reliability": "C - Fairly reliable",
        "Score": 2,
        "Type": "ip",
        "Vendor": "AlienVault OTX v2"
    },
    "IP": {
        "ASN": "AS36647 YAHOO-GQ1",
        "Address": "98.136.103.23",
        "Geo": {
            "Country": "US",
            "Location": "37.751:-97.822"
        },
        "Relationships": [
            {
                "EntityA": "98.136.103.23",
                "EntityAType": "IP",
                "EntityB": "T1140 - Deobfuscate/Decode Files or Information",
                "EntityBType": "Attack Pattern",
                "Relationship": "indicator-of"
            },
            {
                "EntityA": "98.136.103.23",
                "EntityAType": "IP",
                "EntityB": "T1040 - Network Sniffing",
                "EntityBType": "Attack Pattern",
                "Relationship": "indicator-of"
            },
            {
                "EntityA": "98.136.103.23",
                "EntityAType": "IP",
                "EntityB": "T1053 - Scheduled Task/Job",
                "EntityBType": "Attack Pattern",
                "Relationship": "indicator-of"
            },
            {
                "EntityA": "98.136.103.23",
                "EntityAType": "IP",
                "EntityB": "T1060 - Registry Run Keys / Startup Folder",
                "EntityBType": "Attack Pattern",
                "Relationship": "indicator-of"
            },
            {
                "EntityA": "98.136.103.23",
                "EntityAType": "IP",
                "EntityB": "T1071 - Application Layer Protocol",
                "EntityBType": "Attack Pattern",
                "Relationship": "indicator-of"
            },
            {
                "EntityA": "98.136.103.23",
                "EntityAType": "IP",
                "entityB": "mojorojorestaurante.com",
                "entityBType": "URL",
                "Relationship": "indicator-of"
            },
            {
                "EntityA": "98.136.103.23",
                "EntityAType": "IP",
                "entityB": "nguyenhoangai-4g.xyz",
                "entityBType": "Domain",
                "Relationship": "indicator-of"
            },
            {
                "EntityA": "98.136.103.23",
                "EntityAType": "IP",
                "entityB": "0b4d4a7c35a185680bc5102bdd98218297e2cdf0a552bde10e377345f3622c1c",
                "entityBType": "File",
                "Relationship": "indicator-of"
            }
        ]
    }
}

Human Readable Output

AlienVault OTX v2 - Results for ips query

ASN Address Geo Relationships
AS36647 YAHOO-GQ1 98.136.103.23 Location: 37.751:-97.822
Country: US
{‘Relationship’: ‘indicator-of’, ‘EntityA’: ‘98.136.103.23’, ‘EntityAType’: ‘IP’, ‘EntityB’: ‘T1140 - Deobfuscate/Decode Files or Information’, ‘EntityBType’: ‘Attack Pattern’},
{‘Relationship’: ‘indicator-of’, ‘EntityA’: ‘98.136.103.23’, ‘EntityAType’: ‘IP’, ‘EntityB’: ‘T1040 - Network Sniffing’, ‘EntityBType’: ‘Attack Pattern’},
{‘Relationship’: ‘indicator-of’, ‘EntityA’: ‘98.136.103.23’, ‘EntityAType’: ‘IP’, ‘EntityB’: ‘T1053 - Scheduled Task/Job’, ‘EntityBType’: ‘Attack Pattern’},
{‘Relationship’: ‘indicator-of’, ‘EntityA’: ‘98.136.103.23’, ‘EntityAType’: ‘IP’, ‘EntityB’: ‘T1060 - Registry Run Keys / Startup Folder’, ‘EntityBType’: ‘Attack Pattern’},
{‘Relationship’: ‘indicator-of’, ‘EntityA’: ‘98.136.103.23’, ‘EntityAType’: ‘IP’, ‘EntityB’: ‘T1071 - Application Layer Protocol’, ‘EntityBType’: ‘Attack Pattern’}

domain


Queries a domain in AlienVault OTX.

Base Command

domain

Input

Argument Name Description Required
domain The domain to query. Required
threshold If the number of pulses is bigger than the threshold, the domain is considered as malicious. If the threshold is not specified, the default indicator threshold is used, which is configured in the instance settings. Optional

Context Output

Path Type Description
Domain.Name String The domain name. For example, “google.com”.
AlienVaultOTX.Domain.Alexa String Alexa URL for the domain data.
AlienVaultOTX.Domain.Whois String Whois URL for the domain data.
DBotScore.Indicator String The indicator that was tested.
DBotScore.Score Number The actual score.
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.
Domain.Relationships.EntityA string The source of the relationship.
Domain.Relationships.EntityB string The destination of the relationship.
Domain.Relationships.Relationship string The name of the relationship.
Domain.Relationships.EntityAType string The type of the source of the relationship.
Domain.Relationships.EntityBType string The type of the destination of the relationship.

Command Example

!domain domain=ahnlab.myfw.us

Context Example

{
    "AlienVaultOTX": {
        "Domain": {
            "Alexa": "http://www.alexa.com/siteinfo/ahnlab.myfw.us",
            "Name": "ahnlab.myfw.us",
            "Whois": "http://whois.domaintools.com/ahnlab.myfw.us"
        }
    },
    "DBotScore": {
        "Indicator": "ahnlab.myfw.us",
        "Reliability": "C - Fairly reliable",
        "Score": 0,
        "Type": "domain",
        "Vendor": "AlienVault OTX v2"
    },
    "Domain": {
        "Name": "ahnlab.myfw.us",
        "Relationships":[
            {
                "EntityA": "ahnlab.myfw.us",
                "EntityAType": "Domain",
                "EntityB": "b3558ad9f46b72a0319f11889870457dfd611cc4020dbc63945a92869581f774",
                "EntityBType": "File",
                "Relationship": "indicator-of"
            },
            {
                "EntityA": "ahnlab.myfw.us",
                "EntityAType": "Domain",
                "EntityB": "219c6da3c6555bba5a3c1138180351dd6d39bc14d3cb491e93a46bff6c5ca271",
                "EntityBType": "File",
                "Relationship": "indicator-of",
            },
            {
                "EntityA": "ahnlab.myfw.us",
                "EntityAType": "Domain",
                "EntityB": "98.136.103.23",
                "EntityBType": "IP",
                "Relationship": "indicator-of"
            },
            {
                "EntityA": "ahnlab.myfw.us",
                "EntityAType": "Domain",
                "EntityB": "ahnlab.myfw.us",
                "EntityBType": "IP",
                "Relationship": "indicator-of"
            }
        ]
    }
}

Human Readable Output

AlienVault OTX v2 - Results for Domain query

Alexa Name Whois
http://www.alexa.com/siteinfo/ahnlab.myfw.us ahnlab.myfw.us http://whois.domaintools.com/ahnlab.myfw.us

alienvault-search-ipv6


Queries IPv6 in AlienVault OTX.

Base Command

alienvault-search-ipv6

Input

Argument Name Description Required
ip The IP address to query. Required
threshold If the number of pulses is bigger than the threshold, the IP address is considered as malicious. If the threshold is not specified, the default indicator threshold is used, which is configured in the instance settings. Optional

Context Output

Path Type Description
IP.Address String The IP address.
IP.ASN String The autonomous system name for the IP address. For example, “AS8948”.
IP.AlienVaultOTX.Reputation String The IP reputation in AlienVault OTX.
DBotScore.Indicator String The indicator that was tested.
DBotScore.Score Number The actual score.
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.

Command Example

!alienvault-search-ipv6 ip=2001:4860:4860::8888

Context Example

{
    "AlienVaultOTX": {
        "IP": {
            "IP": {
                "IP": "2001:4860:4860::8888",
                "Reputation": 0
            }
        }
    },
    "DBotScore": {
        "Indicator": "2001:4860:4860::8888",
        "Reliability": "C - Fairly reliable",
        "Score": 2,
        "Type": "ip",
        "Vendor": "AlienVault OTX v2"
    },
    "IP": {
        "ASN": "AS15169 GOOGLE",
        "Address": "2001:4860:4860::8888",
        "Geo": {
            "Country": "US",
            "Location": "37.751:-97.822"
        },
        "Relationships": [
            {
                "EntityA": "2001:4860:4860::8888",
                "EntityAType": "IPv6",
                "EntityB": "T1071.004 - DNS",
                "EntityBType": "Attack Pattern",
                "Relationship": "indicator-of"
            },
            {
                "EntityA": "2001:4860:4860::8888",
                "EntityAType": "IPv6",
                "EntityB": "T1071.001 - Web Protocols",
                "EntityBType": "Attack Pattern",
                "Relationship": "indicator-of"
            },
            {
                "EntityA": "2001:4860:4860::8888",
                "EntityAType": "IPv6",
                "EntityB": "T1071 - Application Layer Protocol",
                "EntityBType": "Attack Pattern",
                "Relationship": "indicator-of"
            },
            {
                "EntityA": "2001:4860:4860::8888",
                "EntityAType": "IPv6",
                "EntityB": "T1071.003 - Mail Protocols",
                "EntityBType": "Attack Pattern",
                "Relationship": "indicator-of"
            },
            {
                "EntityA": "2001:4860:4860::8888",
                "EntityAType": "IPv6",
                "EntityB": "T1071.002 - File Transfer Protocols",
                "EntityBType": "Attack Pattern",
                "Relationship": "indicator-of"
            },
            {
                "EntityA": "2001:4860:4860::8888",
                "EntityAType": "IPv6",
                "EntityB": "TA0011 - Command and Control",
                "EntityBType": "Attack Pattern",
                "Relationship": "indicator-of"
            },
            {
                "EntityA": "2001:4860:4860::8888",
                "EntityAType": "IPv6",
                "EntityB": "T1048 - Exfiltration Over Alternative Protocol",
                "EntityBType": "Attack Pattern",
                "Relationship": "indicator-of"
            },
            {
                "EntityA": "2001:4860:4860::8888",
                "EntityAType": "IPv6",
                "EntityB": "T1041 - Exfiltration Over C2 Channel",
                "EntityBType": "Attack Pattern",
                "Relationship": "indicator-of"
            }
        ]
    }
}

Human Readable Output

AlienVault OTX v2 - Results for ips query

ASN Address Geo Relationships
AS15169 GOOGLE 2001:4860:4860::8888 Location: 37.751:-97.822
Country: US
{‘Relationship’: ‘indicator-of’, ‘EntityA’: ‘2001:4860:4860::8888’, ‘EntityAType’: ‘IPv6’, ‘EntityB’: ‘T1071.004 - DNS’, ‘EntityBType’: ‘Attack Pattern’},
{‘Relationship’: ‘indicator-of’, ‘EntityA’: ‘2001:4860:4860::8888’, ‘EntityAType’: ‘IPv6’, ‘EntityB’: ‘T1071.001 - Web Protocols’, ‘EntityBType’: ‘Attack Pattern’},
{‘Relationship’: ‘indicator-of’, ‘EntityA’: ‘2001:4860:4860::8888’, ‘EntityAType’: ‘IPv6’, ‘EntityB’: ‘T1071 - Application Layer Protocol’, ‘EntityBType’: ‘Attack Pattern’},
{‘Relationship’: ‘indicator-of’, ‘EntityA’: ‘2001:4860:4860::8888’, ‘EntityAType’: ‘IPv6’, ‘EntityB’: ‘T1071.003 - Mail Protocols’, ‘EntityBType’: ‘Attack Pattern’},
{‘Relationship’: ‘indicator-of’, ‘EntityA’: ‘2001:4860:4860::8888’, ‘EntityAType’: ‘IPv6’, ‘EntityB’: ‘T1071.002 - File Transfer Protocols’, ‘EntityBType’: ‘Attack Pattern’},
{‘Relationship’: ‘indicator-of’, ‘EntityA’: ‘2001:4860:4860::8888’, ‘EntityAType’: ‘IPv6’, ‘EntityB’: ‘TA0011 - Command and Control’, ‘EntityBType’: ‘Attack Pattern’},
{‘Relationship’: ‘indicator-of’, ‘EntityA’: ‘2001:4860:4860::8888’, ‘EntityAType’: ‘IPv6’, ‘EntityB’: ‘T1048 - Exfiltration Over Alternative Protocol’, ‘EntityBType’: ‘Attack Pattern’},
{‘Relationship’: ‘indicator-of’, ‘EntityA’: ‘2001:4860:4860::8888’, ‘EntityAType’: ‘IPv6’, ‘EntityB’: ‘T1041 - Exfiltration Over C2 Channel’, ‘EntityBType’: ‘Attack Pattern’}

alienvault-search-hostname


Searches for a host name in AlienVault OTX.

Base Command

alienvault-search-hostname

Input

Argument Name Description Required
hostname The host name to query. Required
threshold If the number of pulses is bigger than the threshold, the host name is considered as malicious. If the threshold is not specified, the default indicator threshold is used, which is configured in the instance settings. Optional

Context Output

Path Type Description
Endpoint.Hostname String The hostname that is mapped to the endpoint.
AlienVaultOTX.Endpoint.Hostname String The hostname that is mapped to the endpoint.
AlienVaultOTX.Endpoint.Alexa String The Alexa URL endpoint.
AlienVaultOTX.Endpoint.Whois String The Whois URL endpoint.
DBotScore.Score Number The actual score.
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.
DBotScore.Indicator String The indicator that was tested.

Command Example

!alienvault-search-hostname hostname=demisto.com

Context Example

{
    "AlienVaultOTX": {
        "Endpoint": {
            "Alexa": "http://www.alexa.com/siteinfo/demisto.com",
            "Hostname": "demisto.com",
            "Whois": "http://whois.domaintools.com/demisto.com"
        }
    },
    "DBotScore": {
        "Indicator": "demisto.com",
        "Reliability": "C - Fairly reliable",
        "Score": 0,
        "Type": "hostname",
        "Vendor": "AlienVault OTX v2"
    },
    "Endpoint": {
        "Hostname": "demisto.com"
    }
}

Human Readable Output

AlienVault OTX v2 - Results for Hostname query

Alexa Hostname Whois
http://www.alexa.com/siteinfo/demisto.com demisto.com http://whois.domaintools.com/demisto.com

file


Query a file in AlienVault OTX.

Base Command

file

Input

Argument Name Description Required
file The file hash to query. Required
threshold If the number of pulses is bigger than the threshold, the file is considered as malicious. If the threshold is not specified, the default indicator threshold is used, which is configured in the instance settings. Optional

Context Output

Path Type Description
File.MD5 String The MD5 hash of the file.
File.SHA1 String The SHA1 hash of the file.
File.SHA256 String The SHA256 hash of the file.
File.Malicious.PulseIDs String IDs of pulses which are marked as malicious.
File.Type String The file type, as determined by libmagic (same as displayed in file entries).
File.Size Number The size of the file in bytes.
File.SSDeep String The SSDeep hash of the file (same as displayed in file entries).
File.Relationships.EntityA string The source of the relationship.
File.Relationships.EntityB string The destination of the relationship.
File.Relationships.Relationship string The name of the relationship.
File.Relationships.EntityAType string The type of the source of the relationship.
File.Relationships.EntityBType string The type of the destination of the relationship.
DBotScore.Indicator String The indicator that was tested.
DBotScore.Score Number The actual score.
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.

Command Example

!file file=6c5360d41bd2b14b1565f5b18e5c203cf512e493

Context Example

{
    "DBotScore": {
        "Indicator": "6c5360d41bd2b14b1565f5b18e5c203cf512e493",
        "Reliability": "C - Fairly reliable",
        "Score": 0,
        "Type": "file",
        "Vendor": "AlienVault OTX v2"
    },
    "File": {
        "MD5": "2eb14920c75d5e73264f77cfa273ad2c",
        "Malicious": {
            "PulseIDs": []
        },
        "SHA1": "6c5360d41bd2b14b1565f5b18e5c203cf512e493",
        "SHA256": "4cf9322c49adebf63311a599dc225bbcbf16a253eca59bbe1a02e4ae1d824412",
        "SSDeep": "",
        "Size": "437760",
        "Type": "PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows"
    }
}

Human Readable Output

AlienVault OTX v2 - Results for File hash query

MD5 Malicious SHA1 SHA256 SSDeep Size Type
2eb14920c75d5e73264f77cfa273ad2c PulseIDs: 6c5360d41bd2b14b1565f5b18e5c203cf512e493 4cf9322c49adebf63311a599dc225bbcbf16a253eca59bbe1a02e4ae1d824412   437760 PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows

alienvault-search-cve


Query Common Vulnerabilities and Exposures (CVE) in AlienVault OTX.

Base Command

alienvault-search-cve

Input

Argument Name Description Required
cve_id The CVE to query. Required
threshold If the number of pulses is bigger than the threshold, the CVE is considered as malicious. If the threshold is not specified, the default indicator threshold is used, which is configured in the instance settings. Optional

Context Output

Path Type Description
CVE.ID String The ID of the CVE. For example, “CVE-2015-1653”.
CVE.CVSS String The CVSS of the CVE. For example, “10.0”.
CVE.Published String The timestamp of when the CVE was published.
CVE.Modified String The timestamp of when the CVE was last modified.
CVE.Description String A description of the CVE.
DBotScore.Score Number The actual score.
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.
DBotScore.Indicator String The indicator that was tested.

Command Example

!alienvault-search-cve cve_id=CVE-2014-0160

Context Example

{
    "CVE": {
        "CVSS": "5.0",
        "Description": "The (1) TLS and (2) DTLS implementations in OpenSSL 1.0.1 before 1.0.1g do not properly handle Heartbeat Extension packets, which allows remote attackers to obtain sensitive information from process memory via crafted packets that trigger a buffer over-read, as demonstrated by reading private keys, related to d1_both.c and t1_lib.c, aka the Heartbleed bug.",
        "ID": "CVE-2014-0160",
        "Modified": "2020-07-28T17:11:00",
        "Published": "2014-04-07T22:55:00"
    },
    "DBotScore": {
        "Indicator": "CVE-2014-0160",
        "Reliability": "C - Fairly reliable",
        "Score": 3,
        "Type": "cve",
        "Vendor": "AlienVault OTX v2"
    }
}

Human Readable Output

AlienVault OTX v2 - Results for Hostname query

CVSS Description ID Modified Published
5.0 The (1) TLS and (2) DTLS implementations in OpenSSL 1.0.1 before 1.0.1g do not properly handle Heartbeat Extension packets, which allows remote attackers to obtain sensitive information from process memory via crafted packets that trigger a buffer over-read, as demonstrated by reading private keys, related to d1_both.c and t1_lib.c, aka the Heartbleed bug. CVE-2014-0160 2020-07-28T17:11:00 2014-04-07T22:55:00

alienvault-get-related-urls-by-indicator


Returns related URLs by indicator.

Base Command

alienvault-get-related-urls-by-indicator

Input

Argument Name Description Required
indicator_type The indicator type. Can be: “IPv4”, “IPv6”, “domain”, “hostname”, or “url”. Possible values are: IPv4, IPv6, domain, hostname, url. Required
indicator The indicator for which to search related URLs. Required

Context Output

Path Type Description
AlienVaultOTX.URL.Data Unknown The path of the related URLs.

Command Example

!alienvault-get-related-urls-by-indicator indicator=8.8.8.8 indicator_type=IPv4

Context Example

{
    "AlienVaultOTX": {
        "URL": [
            {
                "Data": "https://test.com"
            }
        ]
    }
}

Human Readable Output

AlienVault OTX v2 - Related url list to queried indicator

Data
https://test.com

alienvault-get-related-hashes-by-indicator


Returns related hashes by indicator.

Base Command

alienvault-get-related-hashes-by-indicator

Input

Argument Name Description Required
indicator The indicator for which to search for related hashes. Optional
indicator_type The indicator type. Can be: “IPv4”, “IPv6”, “domain”, or “hostname”. Possible values are: IPv4, IPv6, domain, hostname. Optional

Context Output

Path Type Description
AlienVaultOTX.File.Hash Unknown The path of the url.

Command Example

!alienvault-get-related-hashes-by-indicator indicator=8.8.8.8 indicator_type=IPv4

Context Example

{
    "AlienVaultOTX": {
        "File": [
            {
                "Hash": "ffc2595aefa80b61621023252b5f0ccb22b6e31d7f1640913cd8ff74ddbd8b41"
            },
            {
                "Hash": "0b4d4a7c35a185680bc5102bdd98218297e2cdf0a552bde10e377345f3622c1c"
            },
            {
                "Hash": "d8b8a5c941b6a1c3cb58f7e59489b2554ed14e6c6655d1fbf6852e45404b7516"
            },
            {
                "Hash": "b3d8adc185834ab858ebf55082828cb9fc1170bbe8de222821d225a6056ff5dc"
            },
            {
                "Hash": "e43cf3f5fa5e14972ba3f159dee6e98330bd19dccc1267cfc91b1000aef975d9"
            },
            {
                "Hash": "9e11b1e769da3c8059345b36c62b4a857845bd7e14c7c14af2945ce26570d91f"
            },
            {
                "Hash": "ae695ce9b8ff4bb831721a8c60377c1757d6d4fe579640b54f3c7f62b175f506"
            },
            {
                "Hash": "093bde5d50daba59bfe68b31251cf2c39353bdfe8ad510284935ca027f269637"
            }
        ]
    }
}

Human Readable Output

AlienVault OTX v2 - Related malware list to queried indicator

No entries.

alienvault-get-passive-dns-data-by-indicator


Returns passive DNS records by indicator.

Base Command

alienvault-get-passive-dns-data-by-indicator

Input

Argument Name Description Required
indicator_type The indicator type. Can be: “IPv4”, “IPv6”, “domain”, or “hostname”. Possible values are: IPv4, IPv6, domain, hostname. Required
indicator The indicator for which to search URLs. Required

Context Output

Path Type Description
AlienVaultOTX.PassiveDNS.Hostname String The domain value.
AlienVaultOTX.PassiveDNS.IP String The IP passive DNS.
AlienVaultOTX.PassiveDNS.Domain String The domain value.
AlienVaultOTX.PassiveDNS.Type String The asset type.
AlienVaultOTX.PassiveDNS.FirstSeen Date The date first seen.
AlienVaultOTX.PassiveDNS.LastSeen Date The date last seen.

Command Example

!alienvault-get-passive-dns-data-by-indicator indicator=8.8.8.8 indicator_type=IPv4

Context Example

{
    "AlienVaultOTX": {
        "PassiveDNS": [
            {
                "FirstSeen": "2021-04-27T09:48:48",
                "Hostname": "www.heyheyitskateforay.com",
                "IP": "8.8.8.8",
                "LastSeen": "2021-04-27T09:49:05",
                "Type": "hostname"
            },
            {
                "FirstSeen": "2021-04-27T09:48:30",
                "Hostname": "www.djjimmykennedy.com",
                "IP": "8.8.8.8",
                "LastSeen": "2021-04-27T09:49:11",
                "Type": "hostname"
            }
        ]
    }
}

Human Readable Output

AlienVault OTX v2 - Related passive dns list to queried indicator

FirstSeen Hostname IP LastSeen Type
2021-04-27T09:48:48 www.heyheyitskateforay.com 8.8.8.8 2021-04-27T09:49:05 hostname
2021-04-27T09:48:30 www.djjimmykennedy.com 8.8.8.8 2021-04-27T09:49:11 hostname

alienvault-search-pulses


Searches for pulses in AlienVault OTX.

Base Command

alienvault-search-pulses

Input

Argument Name Description Required
page The page of the pulse to retrieve. Required

Context Output

Path Type Description
AlienVaultOTX.Pulses.ID String The ID of the pulse.
AlienVaultOTX.Pulses.Author.ID String The ID of the Author.
AlienVaultOTX.Pulses.Author.Username String The username of the Author.
AlienVaultOTX.Pulses.Count String The pulse count.
AlienVaultOTX.Pulses.Modified Date The date of the pulse modification.
AlienVaultOTX.Pulses.Name String The name of the pulse.
AlienVaultOTX.Pulses.Source String The source of the Pulse.
AlienVaultOTX.Pulses.SubscriberCount String The count of the pulse subscriber.
AlienVaultOTX.Pulses.Tags String The tags of the pulse.
AlienVaultOTX.Pulses.Description String The description of the pulse.

Command Example

!alienvault-search-pulses page=1

Context Example

{
    "AlienVaultOTX": {
        "Pulses": [
            {
                "Author": {
                    "ID": "2",
                    "Username": "AlienVault"
                },
                "Count": 28,
                "ID": "546ce8eb11d40838dc6e43f1",
                "Modified": "1273 days ago ",
                "Name": "PoS Scammers Toolbox",
                "Source": "web",
                "SubscriberCount": 141735
            },
            {
                "Author": {
                    "ID": "2",
                    "Username": "AlienVault"
                },
                "Count": 11,
                "ID": "546cf5ba11d40839ea8821ca",
                "Modified": "2098 days ago ",
                "Name": " RAZOR BLADES IN THE CANDY JAR",
                "Source": "web",
                "SubscriberCount": 141715
            },
            {
                "Author": {
                    "ID": "2",
                    "Username": "AlienVault"
                },
                "Count": 10,
                "ID": "546e2e4f11d4083bc021c37d",
                "Modified": "1342 days ago ",
                "Name": "Linking Asprox, Zemot, Rovix and  Rerdom Malware Families ",
                "Source": "web",
                "SubscriberCount": 141707,
                "Tags": [
                    "Asprox",
                    "Zemot",
                    "Rovix"
                ]
            },
            {
                "Author": {
                    "ID": "2",
                    "Username": "AlienVault"
                },
                "Count": 23,
                "ID": "546fc7bf11d4083bc021c37f",
                "Modified": "1341 days ago ",
                "Name": "Operation Double Tap",
                "Source": "web",
                "SubscriberCount": 141711
            },
            {
                "Author": {
                    "ID": "2",
                    "Username": "AlienVault"
                },
                "Count": 60,
                "Description": "Regin is a multi-purpose data collection tool which dates back several years. Symantec first began looking into this threat in the fall of 2013. Multiple versions of Regin were found in the wild, targeting several corporations, institutions, academics, and individuals.\nRegin has a wide range of standard capabilities, particularly around monitoring targets and stealing data. It also has the ability to load custom features tailored to individual targets. Some of Regin\u2019s custom payloads point to a high level of specialist knowledge in particular sectors, such as telecoms infrastructure software, on the part of\nthe developers.",
                "ID": "5473709d11d4083bc021c387",
                "Modified": "824 days ago ",
                "Name": "Regin",
                "Source": "web",
                "SubscriberCount": 141690
            }
        ]
    }
}

Human Readable Output

AlienVault OTX v2 - pulse page 1

Author Count ID Modified Name Source SubscriberCount
ID: 2
Username: AlienVault
28 546ce8eb11d40838dc6e43f1 1273 days ago PoS Scammers Toolbox web 141735
ID: 2
Username: AlienVault
11 546cf5ba11d40839ea8821ca 2098 days ago RAZOR BLADES IN THE CANDY JAR web 141715
ID: 2
Username: AlienVault
10 546e2e4f11d4083bc021c37d 1342 days ago Linking Asprox, Zemot, Rovix and Rerdom Malware Families web 141707
ID: 2
Username: AlienVault
23 546fc7bf11d4083bc021c37f 1341 days ago Operation Double Tap web 141711
ID: 2
Username: AlienVault
60 5473709d11d4083bc021c387 824 days ago Regin web 141690

alienvault-get-pulse-details


Returns pulse details.

Base Command

alienvault-get-pulse-details

Input

Argument Name Description Required
pulse_id The ID of the pulse. Required

Context Output

Path Type Description
AlienVaultOTX.Pulses.Created Date The date the pulse was created.
AlienVaultOTX.Pulses.Author.Username String The author username of the pulse.
AlienVaultOTX.Pulses.ID String The ID of the pulse.
AlienVaultOTX.Pulses.Name String The name of the pulse.
AlienVaultOTX.Pulses.Tags String The tags of the pulse.
AlienVaultOTX.Pulses.TargetedCountries String The targeted countries of the pulse.
AlienVaultOTX.Pulses.Description String The description of the pulse.

Command Example

!alienvault-get-pulse-details pulse_id=57204e9b3c4c3e015d93cb12

Context Example

{
    "AlienVaultOTX": {
        "Pulses": {
            "Author": {
                "Username": "AlienVault"
            },
            "Created": "2016-04-27T05:31:06.941000",
            "Description": "The infamous Remote Access Trojan (RAT) Poison Ivy (hereafter referred to as PIVY) has resurfaced recently, and exhibits some new behaviors. PIVY has been observed targeting a number of Asian countries for various purposes over the past year. Palo Alto Networks\u2019 Unit 42 recently blogged about a new Poison Ivy variant targeting Hong Kong activists dubbed SPIVY that uses DLL sideloading and operates quite differently from a variant recently observed by ASERT that has been active for at least the past 12 months.",
            "ID": "57204e9b3c4c3e015d93cb12",
            "Name": "Poison Ivy Activity Targeting Myanmar, Asian Countries",
            "Tags": [
                "rat",
                "remote access trojan",
                "poison ivy",
                "pivy",
                "Myanmar",
                "asia",
                "Hong Kong",
                "arbornetworks"
            ],
            "TargetedCountries": []
        }
    }
}

Human Readable Output

AlienVault OTX v2 - pulse id details

Author Created Description ID Name Tags TargetedCountries
Username: AlienVault 2016-04-27T05:31:06.941000 The infamous Remote Access Trojan (RAT) Poison Ivy (hereafter referred to as PIVY) has resurfaced recently, and exhibits some new behaviors. PIVY has been observed targeting a number of Asian countries for various purposes over the past year. Palo Alto Networks’ Unit 42 recently blogged about a new Poison Ivy variant targeting Hong Kong activists dubbed SPIVY that uses DLL sideloading and operates quite differently from a variant recently observed by ASERT that has been active for at least the past 12 months. 57204e9b3c4c3e015d93cb12 Poison Ivy Activity Targeting Myanmar, Asian Countries rat,
remote access trojan,
poison ivy,
pivy,
Myanmar,
asia,
Hong Kong,
arbornetworks
 

url


Queries a URL in AlienVault OTX.

Base Command

url

Input

Argument Name Description Required
url The URL to query. Required
threshold If the number of pulses is bigger than the threshold, the URL is considered as malicious. If threshold is not specified, the default indicator threshold is used, which is configured in the instance settings. Optional

Context Output

Path Type Description
URL.Data String The URL.
AlienVaultOTX.URL.Hostname String The host name of the URL.
AlienVaultOTX.URL.Domain String The domain of the URL.
AlienVaultOTX.URL.Alexa String The domain data for the Alexa URL.
AlienVaultOTX.URL.Url String Url
AlienVaultOTX.URL.Whois String The Whois URL for domain data.
DBotScore.Indicator String The indicator that was tested.
DBotScore.Score Number The actual score.
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.
URL.Relationships.EntityA string The source of the relationship.
URL.Relationships.EntityB string The destination of the relationship.
URL.Relationships.Relationship string The name of the relationship.
URL.Relationships.EntityAType string The type of the source of the relationship.
URL.Relationships.EntityBType string The type of the destination of the relationship.

Command Example

!url url="http://www.fotoidea.com/sport/4x4_san_ponso/slides/IMG_0068.html/url_list"

Context Example

{
    "AlienVaultOTX": {
        "URL": {
            "Alexa": "http://www.alexa.com/siteinfo/fotoidea.com",
            "Domain": "fotoidea.com",
            "Hostname": "www.fotoidea.com",
            "Url": "http://www.fotoidea.com/sport/4x4_san_ponso/slides/IMG_0068.html/url_list",
            "Whois": "http://whois.domaintools.com/fotoidea.com"
        }
    },
    "DBotScore": {
        "Indicator": "http://www.fotoidea.com/sport/4x4_san_ponso/slides/IMG_0068.html/url_list",
        "Reliability": "C - Fairly reliable",
        "Score": 0,
        "Type": "url",
        "Vendor": "AlienVault OTX v2"
    },
    "URL": {
        "Data": "http://www.fotoidea.com/sport/4x4_san_ponso/slides/IMG_0068.html/url_list",
        "Relationships": [
            {
                "EntityA": "http://www.fotoidea.com/sport/4x4_san_ponso/slides/IMG_0068.html/url_list",
                "EntityAType": "URL",
                "EntityB": "fotoidea.com",
                "EntityBType": "Domain",
                "Relationship": "hosted-on"
            }
        ]
    }
}

Human Readable Output

AlienVault OTX v2 - Results for url query

Alexa Domain Hostname Url Whois
http://www.alexa.com/siteinfo/fotoidea.com fotoidea.com www.fotoidea.com http://www.fotoidea.com/sport/4x4_san_ponso/slides/IMG_0068.html/url_list http://whois.domaintools.com/fotoidea.com

Additional Information

  • AlienVault considers non lowercased URL protocol as invalid, e.g, HTTP://www.google.com. Hence such submissions will be lowercased to ensure a seamless usage of the integration.

Dbot score calculation method

In case AlienVault OTX API response contains accepted under the false_positive.assessment key, the DbotScore will be set to Good.

Otherwise, if the response includes one validation, DbotScore will be set to SUSPICIOUS, if there’s no validation in the response then the DbotScore will be set by the pulse_info length in the following manner:

  • Bad - If the length of is greater or equal to the default threshold given by the user.
  • SUSPICIOUS - If the length is shorter than the default threshold.
  • None - If the length is zero.

In any other case, the DbotScore will be set to Good.

Configuration parameters

  • url — Server address (required)
  • credentials
  • api_token — API Token
  • default_threshold — Indicator Threshold. The minimum number of pulses to consider the indicator as malicious.
  • min_valid_sources — Minimum number of validated sources to consider the indicator as benign.
  • max_indicator_relationships — Maximum number of relationships for indicators
  • integrationReliability — Source Reliability (required)
  • create_relationships — Create relationships
  • should_error — Should Error When Not Found
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (12)

  • alienvault-get-passive-dns-data-by-indicator

    Returns passive DNS records by indicator.

  • alienvault-get-pulse-details

    Returns pulse details.

  • alienvault-get-related-hashes-by-indicator

    Returns related hashes by indicator.

  • alienvault-get-related-urls-by-indicator

    Returns related URLs by indicator.

  • alienvault-search-cve

    Query Common Vulnerabilities and Exposures (CVE) in AlienVault OTX.

  • alienvault-search-hostname

    Searches for a host name in AlienVault OTX.

  • alienvault-search-ipv6

    Queries IPv6 in AlienVault OTX.

  • alienvault-search-pulses

    Searches for pulses in AlienVault OTX.

  • domain

    Queries a domain in AlienVault OTX.

  • file

    Query a file in AlienVault OTX.

  • ip

    Queries an IP address in AlienVault OTX.

  • url

    Queries a URL in AlienVault OTX.

from CommonServerPython import *

""" IMPORTS """
import urllib3

# Disable insecure warnings
urllib3.disable_warnings()

"""GLOBALS/PARAMS
Attributes:
    INTEGRATION_NAME:
        Name of the integration as shown in the integration UI, for example: Microsoft Graph User.

    INTEGRATION_COMMAND_NAME:
        Command names should be written in all lower-case letters,
        and each word separated with a hyphen, for example: msgraph-user.

    INTEGRATION_CONTEXT_NAME:
        Context output names should be written in camel case, for example: MSGraphUser.
"""
INTEGRATION_NAME = "AlienVault OTX v2"
INTEGRATION_COMMAND_NAME = "alienvault"
INTEGRATION_CONTEXT_NAME = "AlienVaultOTX"


class Client(BaseClient):
    def __init__(
        self,
        base_url,
        headers,
        verify,
        proxy,
        default_threshold,
        min_valid_sources,
        max_indicator_relationships,
        reliability,
        create_relationships=True,
        should_error=True,
    ):
        BaseClient.__init__(
            self,
            base_url=base_url,
            headers=headers,
            verify=verify,
            proxy=proxy,
        )

        self.reliability = reliability
        self.create_relationships = create_relationships
        self.min_valid_sources = min_valid_sources
        self.default_threshold = default_threshold
        self.max_indicator_relationships = max_indicator_relationships if max_indicator_relationships else 0
        self.should_error = should_error

    def test_module(self) -> dict:
        """Performs basic GET request to check if the API is reachable and authentication is successful.

        Returns:
            Response json
        """
        return self.query(section="IPv4", argument="8.8.8.8")

    def query(self, section: str, argument: str = None, sub_section: str = "general", params: dict = None) -> dict:
        """Query the specified kwargs.

        Args:
            section: indicator type
            argument: indicator value
            sub_section: sub section of api
            params: params to send in http request

        Returns:
            Response JSON
        """
        # The service endpoint to request from
        demisto.debug(f"in query with section: {section}, sub section: {sub_section}, params: {params}")
        if section == "pulses":
            suffix = f"{section}/{argument}"
        elif argument and sub_section:
            suffix = f"indicators/{section}/{argument}/{sub_section}"
        else:
            suffix = f"{section}/{sub_section}"
        # Send a request using our http_request wrapper
        try:
            if sub_section == "passive_dns":
                return self._http_request("GET", url_suffix=suffix, params=params, timeout=30)
            result = self._http_request("GET", url_suffix=suffix, params=params, timeout=60)
        except DemistoException as e:
            demisto.debug("DemistoException was raised")
            if hasattr(e.res, "status_code"):
                res_status = e.res.status_code
                if res_status == 404:
                    demisto.debug("The status code is 404")
                    result = 404
                elif res_status == 400:
                    demisto.debug("The status code is 400")
                    demisto.debug(f"{e.res.text} response received from server when trying to get api:{e.res.url}")
                    if not self.should_error:
                        return_warning(f"The command could not be execute: {argument} is invalid.", exit=True)
                    raise Exception(f"The command could not be execute: {argument} is invalid.")
                elif res_status == 502:
                    demisto.debug("The status code is 502")
                    if self.should_error:
                        raise e
                    return_warning(f"{e.res.text} response received from server", exit=True)
                elif res_status in (500, 504):
                    demisto.debug(f"The status code is {res_status}")
                    if self.should_error:
                        raise e
                    result = {}
                else:
                    demisto.debug("A DemistoException was raised but no status code was caught.")
                    raise
            else:
                demisto.debug("A DemistoException was raised but there is no status code.")
                raise
        except requests.exceptions.ReadTimeout as e:
            demisto.debug(f"A ReadTimeout error was raised with suffix {suffix}. Error: {str(e)}.")
            if sub_section == "passive_dns":
                return {}
            return_warning(f"A ReadTimeout error was raised with suffix {suffix}.", exit=True)
        return result


""" HELPER FUNCTIONS """


def calculate_dbot_score(client: Client, raw_response: dict | None) -> float:
    """
    calculate DBot score for query

    Args:
        client: Client object with request
        raw_response: The response gotten from the server

    :returns:
        score - good (if 0), bad (if grater than default), suspicious if between
    """
    default_threshold = int(client.default_threshold)
    min_valid_sources = int(client.min_valid_sources)
    false_Positive = {}
    pulase_info_dict = {}
    validation = []
    if isinstance(raw_response, dict):
        false_Positive = raw_response.get("false_positive", {})
        validation = raw_response.get("validation", [])
        pulase_info_dict = raw_response.get("pulse_info", {})
    if false_Positive and false_Positive[0].get("assessment") == "accepted":
        return Common.DBotScore.GOOD
    else:
        if not validation and pulase_info_dict:
            count = int(pulase_info_dict.get("count", "0"))
            if count >= default_threshold:
                return Common.DBotScore.BAD
            elif 0 < count < default_threshold:
                return Common.DBotScore.SUSPICIOUS
            else:
                return Common.DBotScore.NONE
        elif validation and len(validation) < min_valid_sources:
            return Common.DBotScore.SUSPICIOUS
        else:
            return Common.DBotScore.GOOD


def create_list_by_ec(list_entries: list, list_type: str) -> list:
    def create_entry_by_ec(entry: dict) -> dict:
        if list_type == "passive_dns":
            return {
                "Hostname": entry.get("hostname"),
                "IP": entry.get("address"),
                "Type": entry.get("asset_type"),
                "FirstSeen": entry.get("first"),
                "LastSeen": entry.get("last"),
            }

        if list_type == "url_list":
            return assign_params(Data=entry.get("url"))

        if list_type == "hash_list":
            return assign_params(Hash=entry.get("hash"))

        # should not get here
        return {}

    return [create_entry_by_ec(entry) for entry in list_entries]


def create_pulse_by_ec(entry: dict) -> dict:
    pulse_by_ec = {
        "ID": entry.get("id"),
        "Author": {"ID": entry.get("author", {}).get("id"), "Username": entry.get("author", {}).get("username")},
        "Count": entry.get("indicator_count"),
        "Modified": entry.get("modified_text"),
        "Name": entry.get("name"),
        "Source": entry.get("pulse_source"),
        "SubscriberCount": entry.get("subscriber_count"),
        "Tags": entry.get("tags"),
        "Description": entry.get("description"),
    }
    return assign_params(**pulse_by_ec)


def extract_attack_ids(raw_response: dict):
    """
    extract the attack_ids field from the raw response if exists

    Args:
        raw_response: The response gotten from the server

    :returns:
        the attack_ids list of all the attack_ids that exists in the response.
    """
    pulses = dict_safe_get(raw_response, ["pulse_info", "pulses"]) or [{}]
    attack_ids = []
    for pulse in pulses:
        if pulse.get("attack_ids"):
            attack_ids.extend(pulse.get("attack_ids"))
    return attack_ids


def relationships_manager(
    client: Client,
    entity_a: str,
    entity_a_type: str,
    indicator_type: str,
    attack_ids: list,
    indicator: str,
    field_for_passive_dns_rs: str,
    feed_indicator_type_for_passive_dns_rs: str,
):
    """
    manage the relationships creation

    Args:
        client: Client object with request
        entity_a: str the first entity of the relationship
        entity_a_type: str the type of the first entity
        indicator_type: str the indicator type to get the related information by
        entity_b_type: str the indicator to get the related information by

    :returns:
        a list of the relationships that were created
    """
    relationships = create_relationships(
        client,
        attack_ids,
        entity_a,
        entity_a_type,
        "display_name",
        FeedIndicatorType.indicator_type_by_server_version("STIX Attack Pattern"),
    )
    if client.max_indicator_relationships > 0:
        limit = str(client.max_indicator_relationships)
        _, _, urls_raw_response = alienvault_get_related_urls_by_indicator_command(client, indicator_type, indicator, limit)
        urls_raw_response = delete_duplicated_entities(urls_raw_response.get("url_list", []), "url")
        relationships += create_relationships(client, urls_raw_response, entity_a, entity_a_type, "url", FeedIndicatorType.URL)

        _, _, hash_raw_response = alienvault_get_related_hashes_by_indicator_command(client, indicator_type, indicator, limit)
        hash_raw_response = delete_duplicated_entities(hash_raw_response.get("data", []), "hash")
        relationships += create_relationships(client, hash_raw_response, entity_a, entity_a_type, "hash", FeedIndicatorType.File)

        _, _, passive_dns_raw_response = alienvault_get_passive_dns_data_by_indicator_command(
            client, indicator_type, indicator, limit
        )
        passive_dns_raw_response = delete_duplicated_entities(
            passive_dns_raw_response.get("passive_dns", []), field_for_passive_dns_rs
        )
        passive_dns_raw_response = validate_string_is_not_url(passive_dns_raw_response, field_for_passive_dns_rs)
        passive_dns_raw_response = passive_dns_raw_response[0 : client.max_indicator_relationships]
        relationships += create_relationships(
            client,
            passive_dns_raw_response,
            entity_a,
            entity_a_type,
            field_for_passive_dns_rs,
            feed_indicator_type_for_passive_dns_rs,
        )

    return relationships


def create_relationships(
    client: Client, relevant_field: list, entity_a: str, entity_a_type: str, relevant_id: str, entity_b_type: str
):
    """
    create relationships list for the given fields

    Args:
        client: Client object with request
        relevant_field: the field that holds the relevant display name(entity_b) for the relationship
        entity_a: str the first entity of the relationship
        entity_a_type: str the type of the first entity
        relevant_id: str the exact key where the display name is located inside the relevant_field
        entity_b_type: str the type of the second entity

    :returns:
        a list of the relationships that were created
    """
    relationships: list = []
    if not client.create_relationships:
        return relationships

    if relevant_field and isinstance(relevant_field, list) and relevant_id in relevant_field[0]:
        display_names = [item.get(relevant_id) for item in relevant_field]
        if display_names:
            relationships = [
                EntityRelationship(
                    name=EntityRelationship.Relationships.INDICATOR_OF,
                    entity_a=entity_a,
                    entity_a_type=entity_a_type,
                    entity_b=display_name,
                    entity_b_type=entity_b_type,
                    source_reliability=client.reliability,
                    brand=INTEGRATION_NAME,
                )
                for display_name in display_names
            ]
    return relationships


def delete_duplicated_entities(entities_list: List[dict], field_name: str):
    """delete duplicated results from a response

    Args:
        entities_list: The list of the entities brought back from the query.
        field_name: The field to compare according to between the given entities.

    Returns:
        a list without duplicated entities.
    """
    unique_dict: dict = {}
    for entity_dict in entities_list:
        if isinstance(entity_dict, dict) and (ind_value := entity_dict.get(field_name)) not in unique_dict:
            unique_dict[ind_value] = entity_dict
    return list(unique_dict.values())


def validate_string_is_not_url(entities_list: List[dict], field_name: str):
    """delete url type entities from a given list.

    Args:
        entities_list: The list of the entities brought back from the query.
        field_name: The field to compare according to between the given entities.

    Returns:
        a list without url type entities.
    """
    return [dict for dict in entities_list if auto_detect_indicator_type(dict.get(field_name)) != "URL"]


def lowercase_protocol_callback(pattern: re.Match) -> str:
    return pattern.group(0).lower()


""" COMMANDS """


@logger
def test_module_command(client: Client, *_) -> tuple[None, None, str]:
    """Performs a basic GET request to check if the API is reachable and authentication is successful.

    Args:
        client: Client object with request
        *_: Usually demisto.args()

    Returns:
        'ok' if test successful.

    Raises:
        DemistoException: If test failed.
    """
    results = client.test_module()
    if "city" in results:
        return None, None, "ok"
    raise DemistoException(f"Test module failed, {results}")


@logger
def ip_command(client: Client, ip_address: str, ip_version: str) -> List[CommandResults]:
    """Enrichment for IPv4/IPv6

    Args:
        client: Client object with request
        ip_address: ip address
        ip_version: IPv4 or IPv6

    Returns:
        List of CommandResults
    """
    ips_list: list = argToList(ip_address)

    title = f"{INTEGRATION_NAME} - Results for ips query"
    command_results: List[CommandResults] = []

    for ip_ in ips_list:
        raw_response = client.query(section=ip_version, argument=ip_)

        if raw_response and raw_response != 404:
            ip_version = FeedIndicatorType.IP if ip_version == "IPv4" else FeedIndicatorType.IPv6
            relationships = relationships_manager(
                client,
                entity_a=ip_,
                entity_a_type=ip_version,
                indicator_type=ip_version,
                indicator=ip_,
                field_for_passive_dns_rs="hostname",
                feed_indicator_type_for_passive_dns_rs=FeedIndicatorType.Domain,
                attack_ids=extract_attack_ids(raw_response),
            )

            dbot_score = Common.DBotScore(
                indicator=ip_,
                indicator_type=DBotScoreType.IP,
                integration_name=INTEGRATION_NAME,
                score=calculate_dbot_score(client, raw_response),
                reliability=client.reliability,
            )

            ip_object = Common.IP(
                ip=ip_,
                dbot_score=dbot_score,
                asn=raw_response.get("asn"),
                geo_country=raw_response.get("country_code"),
                geo_latitude=raw_response.get("latitude"),
                geo_longitude=raw_response.get("longitude"),
                relationships=relationships,
            )

            context = {"Reputation": raw_response.get("reputation"), "IP": ip_}

            human_readable_context = {
                "Address": ip_object.to_context().get("IP(val.Address && val.Address == obj.Address)").get("Address"),
                "Geo": ip_object.to_context().get("IP(val.Address && val.Address == obj.Address)").get("Geo"),
            }

            human_readable = tableToMarkdown(name=title, t=human_readable_context)

            command_results.append(
                CommandResults(
                    readable_output=human_readable,
                    outputs_prefix=f"{INTEGRATION_CONTEXT_NAME}.IP(val.IP && val.IP === obj.IP)",
                    outputs={"IP": context},
                    indicator=ip_object,
                    raw_response=raw_response,
                    relationships=relationships,
                )
            )
        else:
            command_results.append(
                create_indicator_result_with_dbotscore_unknown(
                    indicator=ip_, indicator_type=DBotScoreType.IP, reliability=client.reliability
                )
            )
    if not command_results:
        return [CommandResults(f"{INTEGRATION_NAME} - Could not find any results for given query.")]
    return command_results


@logger
def domain_command(client: Client, domain: str) -> List[CommandResults]:
    """Enrichment for domain

    Args:
        client: Client object with request
        domain: domains to query

    Returns:
        List of CommandResults
    """
    domains_list: list = argToList(domain)

    title = f"{INTEGRATION_NAME} - Results for Domain query"
    command_results: List[CommandResults] = []

    for domain in domains_list:
        raw_response = client.query(section="domain", argument=domain)
        if raw_response and raw_response != 404:
            relationships = relationships_manager(
                client,
                entity_a=domain,
                indicator_type="domain",
                entity_a_type=FeedIndicatorType.Domain,
                indicator=domain,
                field_for_passive_dns_rs="address",
                feed_indicator_type_for_passive_dns_rs=FeedIndicatorType.IP,
                attack_ids=extract_attack_ids(raw_response),
            )

            dbot_score = Common.DBotScore(
                indicator=domain,
                indicator_type=DBotScoreType.DOMAIN,
                integration_name=INTEGRATION_NAME,
                score=calculate_dbot_score(client, raw_response),
                reliability=client.reliability,
            )
            domain_object = Common.Domain(domain=domain, dbot_score=dbot_score, relationships=relationships)

            context = {
                "Name": raw_response.get("indicator"),
                "Alexa": raw_response.get("alexa"),
                "Whois": raw_response.get("whois"),
            }

            human_readable = tableToMarkdown(t=context, name=title)

            command_results.append(
                CommandResults(
                    readable_output=human_readable,
                    outputs_prefix=f"{INTEGRATION_CONTEXT_NAME}.Domain(val.Alexa && val.Alexa === obj.Alexa &&"
                    f" val.Whois && val.Whois === obj.Whois)",
                    outputs=context,
                    indicator=domain_object,
                    raw_response=raw_response,
                    relationships=relationships,
                )
            )
        else:
            command_results.append(
                create_indicator_result_with_dbotscore_unknown(
                    indicator=domain, indicator_type=DBotScoreType.DOMAIN, reliability=client.reliability
                )
            )
    if not command_results:
        return [CommandResults(f"{INTEGRATION_NAME} - Could not find any results for given query")]

    return command_results


@logger
def file_command(client: Client, file: str) -> List[CommandResults]:
    """Enrichment for file hash MD5/SHA1/SHA256

    Args:
        client: Client object with request
        file: File hash MD5/SHA1/SHA256

    Returns:
        List of CommandResults
    """
    hashes_list: list = argToList(file)

    title = f"{INTEGRATION_NAME} - Results for File hash query"
    command_results: List[CommandResults] = []

    for hash_ in hashes_list:
        raw_response_analysis = client.query(section="file", argument=hash_, sub_section="analysis")
        raw_response_general = client.query(section="file", argument=hash_)

        if (
            raw_response_analysis
            and raw_response_general
            and (shortcut := dict_safe_get(raw_response_analysis, ["analysis", "info", "results"], {}))
            and raw_response_general != 404
            and raw_response_analysis != 404
        ):
            relationships = create_relationships(
                client,
                extract_attack_ids(raw_response_general),
                hash_,
                FeedIndicatorType.File,
                "display_name",
                FeedIndicatorType.indicator_type_by_server_version("STIX Attack Pattern"),
            )

            dbot_score = Common.DBotScore(
                indicator=hash_,
                indicator_type=DBotScoreType.FILE,
                integration_name=INTEGRATION_NAME,
                score=calculate_dbot_score(client, raw_response_general),
                malicious_description=raw_response_general.get("pulse_info", {}).get("pulses"),
                reliability=client.reliability,
            )

            file_object = Common.File(
                md5=shortcut.get("md5"),
                sha1=shortcut.get("sha1"),
                sha256=shortcut.get("sha256"),
                ssdeep=shortcut.get("ssdeep"),
                size=shortcut.get("filesize"),
                file_type=shortcut.get("file_type"),
                dbot_score=dbot_score,
                relationships=relationships,
            )

            context = {
                "MD5": shortcut.get("md5"),
                "SHA1": shortcut.get("sha1"),
                "SHA256": shortcut.get("sha256"),
                "SSDeep": shortcut.get("ssdeep"),
                "Size": shortcut.get("filesize"),
                "Type": shortcut.get("file_type"),
                "Malicious": {"PulseIDs": raw_response_general.get("pulse_info", {}).get("pulses")},
            }

            human_readable = tableToMarkdown(name=title, t=context)

            command_results.append(
                CommandResults(
                    readable_output=human_readable,
                    outputs_prefix=outputPaths.get("file"),
                    outputs=context,
                    indicator=file_object,
                    raw_response=raw_response_general,
                    relationships=relationships,
                )
            )
        else:
            command_results.append(
                create_indicator_result_with_dbotscore_unknown(
                    indicator=hash_, indicator_type=DBotScoreType.FILE, reliability=client.reliability
                )
            )
    if not command_results:
        return [CommandResults(f"{INTEGRATION_NAME} - Could not find any results for given query")]

    return command_results


@logger
def url_command(client: Client, url: str) -> List[CommandResults]:
    """Enrichment for url

    Args:
        client: Client object with request
        url:  url address

    Returns:
        List of CommandResults
    """
    urls_list: list = argToList(url)

    title = f"{INTEGRATION_NAME} - Results for url query"
    command_results: List[CommandResults] = []

    for url in urls_list:
        url = re.sub(r"(\w+)://", lowercase_protocol_callback, url)
        raw_response = client.query(section="url", argument=url)

        if raw_response:
            if raw_response == 404:
                command_results.append(
                    create_indicator_result_with_dbotscore_unknown(
                        indicator=url, indicator_type=DBotScoreType.URL, reliability=client.reliability
                    )
                )
            else:
                relationships = []
                if client.create_relationships:
                    indicator = FeedIndicatorType.indicator_type_by_server_version("STIX Attack Pattern")
                    relationships = create_relationships(
                        client, extract_attack_ids(raw_response), url, FeedIndicatorType.URL, "display_name", indicator
                    )

                    domain = raw_response.get("domain")
                    if domain:
                        relationships.extend(
                            [
                                EntityRelationship(
                                    name=EntityRelationship.Relationships.HOSTED_ON,
                                    entity_a=url,
                                    entity_a_type=FeedIndicatorType.URL,
                                    entity_b=domain,
                                    entity_b_type=FeedIndicatorType.Domain,
                                    source_reliability=client.reliability,
                                    brand=INTEGRATION_NAME,
                                )
                            ]
                        )

                dbot_score = Common.DBotScore(
                    indicator=url,
                    indicator_type=DBotScoreType.URL,
                    integration_name=INTEGRATION_NAME,
                    score=calculate_dbot_score(client, raw_response),
                    reliability=client.reliability,
                )

                url_object = Common.URL(url=url, dbot_score=dbot_score, relationships=relationships)

                context = {
                    "Url": url,
                    "Hostname": raw_response.get("hostname"),
                    "Domain": raw_response.get("domain"),
                    "Alexa": raw_response.get("alexa"),
                    "Whois": raw_response.get("whois"),
                }

                human_readable = tableToMarkdown(name=title, t=context)

                command_results.append(
                    CommandResults(
                        readable_output=human_readable,
                        outputs_prefix=f"{INTEGRATION_CONTEXT_NAME}.URL(val.Url && val.Url === obj.Url)",
                        outputs=context,
                        indicator=url_object,
                        raw_response=raw_response,
                        relationships=relationships,
                    )
                )

    if not command_results:
        command_results.append(CommandResults(f"{INTEGRATION_NAME} - Could not find any results for given query"))
    return command_results


@logger
def alienvault_search_hostname_command(client: Client, hostname: str) -> tuple[str, dict, dict]:
    """Search for hostname details

    Args:
        client: Client object with request
        hostname: hostname address

    Returns:
        Outputs
    """
    raw_response = client.query(section="hostname", argument=hostname)
    if raw_response and raw_response != 404:
        title = f"{INTEGRATION_NAME} - Results for Hostname query"
        context_entry: dict = {
            "Endpoint(val.Hostname && val.Hostname === obj.Hostname)": {"Hostname": raw_response.get("indicator")},
            "AlienVaultOTX.Endpoint(val.Alexa && val.Alexa === obj.Alexa &&val.Whois && val.Whois === obj.Whois)": {
                "Hostname": raw_response.get("indicator"),
                "Alexa": raw_response.get("alexa"),
                "Whois": raw_response.get("whois"),
            },
            outputPaths.get("dbotscore"): {
                "Indicator": raw_response.get("indicator"),
                "Score": calculate_dbot_score(client, raw_response),
                "Type": "hostname",
                "Vendor": "AlienVault OTX v2",
                "Reliability": client.reliability,
            },
        }
        human_readable = tableToMarkdown(
            name=title,
            t=context_entry.get(
                "AlienVaultOTX.Endpoint(val.Alexa && val.Alexa === obj.Alexa &&val.Whois && val.Whois === obj.Whois)"
            ),
        )

        return human_readable, context_entry, raw_response
    else:
        return f"{INTEGRATION_NAME} - Could not find any results for given query", {}, {}


@logger
def alienvault_search_cve_command(client: Client, cve_id: str) -> tuple[str, dict, dict]:
    """Get Common Vulnerabilities and Exposures by id

    Args:
        client: Client object with request
        cve_id: CVE id

    Returns:
        Outputs
    """
    raw_response = client.query(section="cve", argument=cve_id)
    if raw_response and raw_response != 404:
        title = f"{INTEGRATION_NAME} - Results for Hostname query"
        context_entry: dict = {
            outputPaths.get("cve"): {
                "ID": raw_response.get("indicator"),
                "CVSS": raw_response.get("cvss", {}).get("Score"),
                "Published": raw_response.get("date_created"),
                "Modified": raw_response.get("date_modified"),
                "Description": raw_response.get("description"),
            },
            outputPaths.get("dbotscore"): {
                "Indicator": raw_response.get("indicator"),
                "Score": calculate_dbot_score(client, raw_response),
                "Type": "cve",
                "Vendor": "AlienVault OTX v2",
                "Reliability": client.reliability,
            },
        }
        human_readable = tableToMarkdown(t=context_entry.get(outputPaths.get("cve")), name=title)

        return human_readable, context_entry, raw_response
    else:
        return f"{INTEGRATION_NAME} - Could not find any results for given query", {}, {}


@logger
def alienvault_get_related_urls_by_indicator_command(
    client: Client, indicator_type: str, indicator: str, limit: str = ""
) -> tuple[str, dict, dict]:
    """Get related urls by indicator (IPv4,IPv6,domain,hostname,url)

    Args:
        client: Client object with request
        indicator_type: IPv4,IPv6,domain,hostname,url
        indicator: indicator its self (google.com)
        limit: the maximum number of indicators to fetch

    Returns:
        Outputs
    """
    if indicator_type == "IP":
        indicator_type = "IPv4"
    params = {}
    if limit:
        params["limit"] = limit
    raw_response = client.query(section=indicator_type, argument=indicator, sub_section="url_list", params=params)
    if raw_response and raw_response != 404:
        title = f"{INTEGRATION_NAME} - Related url list to queried indicator"
        context_entry: list = create_list_by_ec(list_entries=raw_response.get("url_list", {}), list_type="url_list")
        context: dict = {"AlienVaultOTX.URL(val.URL.Data && val.URL.Data == obj.URL.Data)": context_entry}
        human_readable = tableToMarkdown(t=context_entry, name=title)

        return human_readable, context, raw_response
    else:
        return f"{INTEGRATION_NAME} - Could not find any results for given query", {}, {}


@logger
def alienvault_get_related_hashes_by_indicator_command(
    client: Client, indicator_type: str, indicator: str, limit: str = ""
) -> tuple[str, dict, dict]:
    """Get related file hashes by indicator (IPv4,IPv6,domain,hostname)

    Args:
        client: Client object with request
        indicator_type: IPv4,IPv6,domain,hostname
        indicator: indicator its self (google.com)
        limit: the maximum number of indicators to fetch

    Returns:
        Outputs
    """
    if indicator_type == "IP":
        indicator_type = "IPv4"
    params = {}
    if limit:
        params["limit"] = limit
    raw_response = client.query(section=indicator_type, argument=indicator, sub_section="malware", params=params)
    if raw_response and raw_response != 404:
        title = f"{INTEGRATION_NAME} - Related malware list to queried indicator"
        context_entry: dict = {
            "AlienVaultOTX.File(val.File.Hash && val.File.Hash == obj.File.Hash)": create_list_by_ec(
                list_entries=raw_response.get("data", {}), list_type="hash_list"
            )
        }
        human_readable = tableToMarkdown(
            t=context_entry.get(
                "AlienVaultOTX.File(val.File.Hash && val.File.Hash \
                                            == obj.File.Hash)"
            ),
            name=title,
        )

        return human_readable, context_entry, raw_response
    else:
        return f"{INTEGRATION_NAME} - Could not find any results for given query", {}, {}


@logger
def alienvault_get_passive_dns_data_by_indicator_command(
    client: Client, indicator_type: str, indicator: str, limit: str = ""
) -> tuple[str, dict, dict]:
    """Get related file hashes by indicator (IPv4,IPv6,domain,hostname)

    Args:
        client: Client object with request
        indicator_type: IPv4,IPv6,domain,hostname
        indicator: indicator its self (google.com)
        limit: the maximum number of indicators to fetch
    Returns:
        Outputs
    """
    if indicator_type == "IP":
        indicator_type = "IPv4"
    params = {}
    if limit:
        params["limit"] = limit
    raw_response = client.query(section=indicator_type, argument=indicator, sub_section="passive_dns", params=params)
    if raw_response and raw_response != 404:
        title = f"{INTEGRATION_NAME} - Related passive dns list to queried indicator"
        context_entry: dict = {
            "AlienVaultOTX.PassiveDNS(val.PassiveDNS.Hostname && val.PassiveDNS.Hostname == obj.PassiveDNS.Hostname &&"
            "val.PassiveDNS.LastSeen && val.PassiveDNS.LastSeen == obj.PassiveDNS.LastSeen &&"
            "val.PassiveDNS.IP && val.PassiveDNS.IP == obj.PassiveDNS.IP)": create_list_by_ec(
                list_entries=raw_response.get("passive_dns", {}), list_type="passive_dns"
            )
        }
        human_readable = tableToMarkdown(
            t=context_entry.get(
                "AlienVaultOTX.PassiveDNS(val.PassiveDNS.Hostname && val.PassiveDNS.Hostname == obj.PassiveDNS.Hostname &&"
                "val.PassiveDNS.LastSeen && val.PassiveDNS.LastSeen == obj.PassiveDNS.LastSeen &&"
                "val.PassiveDNS.IP && val.PassiveDNS.IP == obj.PassiveDNS.IP)"
            ),
            name=title,
        )
        return human_readable, context_entry, raw_response
    else:
        return f"{INTEGRATION_NAME} - Could not find any results for given query", {}, {}


@logger
def alienvault_search_pulses_command(client: Client, page: str) -> tuple[str, dict, dict]:
    """Get pulse page by number of the page

    Args:
        client: Client object with request
        page: pulse page number

    Returns:
        Outputs
    """
    raw_response = client.query(section="search", sub_section="pulses", params={"page": page})
    if raw_response and raw_response != 404:
        title = f"{INTEGRATION_NAME} - pulse page {page}"
        context_entry: dict = {
            "AlienVaultOTX.Pulses(val.ID && val.ID == obj.ID && val.Modified && val.Modified == obj.Modified)": [
                create_pulse_by_ec(entry) for entry in raw_response.get("results", {})
            ]
        }
        human_readable = tableToMarkdown(
            t=context_entry.get(
                "AlienVaultOTX.Pulses(val.ID && val.ID == obj.ID && val.Modified && val.Modified == obj.Modified)"
            ),
            name=title,
        )

        return human_readable, context_entry, raw_response
    else:
        return f"{INTEGRATION_NAME} - Could not find any results for given query", {}, {}


@logger
def alienvault_get_pulse_details_command(client: Client, pulse_id: str) -> tuple[str, dict, dict]:
    """Get pulse by ID

    Args:
        client: Client object with request
        pulse_id: pulse ID

    Returns:
        Outputs
    """
    raw_response = client.query(section="pulses", argument=pulse_id)
    if raw_response and raw_response != 404:
        title = f"{INTEGRATION_NAME} - pulse id details"
        context_entry: dict = {
            "AlienVaultOTX.Pulses(val.ID && val.ID == obj.ID)": {
                "Description": raw_response.get("description"),
                "Created": raw_response.get("created"),
                "Author": {"Username": raw_response.get("author", {}).get("username")},
                "ID": raw_response.get("id"),
                "Name": raw_response.get("name"),
                "Tags": raw_response.get("tags"),
                "TargetedCountries": raw_response.get("targeted_countries"),
            }
        }
        human_readable = tableToMarkdown(t=context_entry.get("AlienVaultOTX.Pulses(val.ID && val.ID == obj.ID)"), name=title)

        return human_readable, context_entry, raw_response
    else:
        return f"{INTEGRATION_NAME} - Could not find any results for given query", {}, {}


""" COMMANDS MANAGER / SWITCH PANEL """


def main():
    params = demisto.params()

    base_url = urljoin(params.get("url"), "/api/v1/")
    verify_ssl = not params.get("insecure", False)
    proxy = params.get("proxy")
    default_threshold = int(params.get("default_threshold", 2))
    min_valid_sources = int(params.get("min_valid_sources", 2))
    max_indicator_relationships = arg_to_number(params.get("max_indicator_relationships", 0))
    token = params.get("credentials", {}).get("password", "") or params.get("api_token", "")
    reliability = params.get("integrationReliability")
    reliability = reliability if reliability else DBotScoreReliability.C
    if DBotScoreReliability.is_valid_type(reliability):
        reliability = DBotScoreReliability.get_dbot_score_reliability_from_str(reliability)
    else:
        Exception("Please provide a valid value for the Source Reliability parameter.")
    should_error = argToBoolean(params.get("should_error", True))

    client = Client(
        base_url=base_url,
        headers={"X-OTX-API-KEY": token},
        verify=verify_ssl,
        proxy=proxy,
        default_threshold=default_threshold,
        min_valid_sources=min_valid_sources,
        reliability=reliability,
        create_relationships=argToBoolean(params.get("create_relationships")),
        max_indicator_relationships=max_indicator_relationships,
        should_error=should_error,
    )

    command = demisto.command()
    demisto.debug(f"Command being called is {command}")
    commands = {
        "test-module": test_module_command,
        "domain": domain_command,
        "file": file_command,
        "url": url_command,
        f"{INTEGRATION_COMMAND_NAME}-search-hostname": alienvault_search_hostname_command,
        f"{INTEGRATION_COMMAND_NAME}-search-cve": alienvault_search_cve_command,
        f"{INTEGRATION_COMMAND_NAME}-get-related-urls-by-indicator": alienvault_get_related_urls_by_indicator_command,
        f"{INTEGRATION_COMMAND_NAME}-get-related-hashes-by-indicator": alienvault_get_related_hashes_by_indicator_command,
        f"{INTEGRATION_COMMAND_NAME}-get-passive-dns-data-by-indicator": alienvault_get_passive_dns_data_by_indicator_command,
        f"{INTEGRATION_COMMAND_NAME}-search-pulses": alienvault_search_pulses_command,
        f"{INTEGRATION_COMMAND_NAME}-get-pulse-details": alienvault_get_pulse_details_command,
    }
    try:
        if command == f"{INTEGRATION_COMMAND_NAME}-search-ipv6":
            return_results(ip_command(client=client, ip_address=demisto.args().get("ip"), ip_version="IPv6"))
        elif command == "ip":
            return_results(ip_command(client=client, ip_address=demisto.args().get("ip"), ip_version="IPv4"))
        elif command in ["file", "domain", "url"]:
            return_results(commands[command](client=client, **demisto.args()))
        else:
            readable_output, outputs, raw_response = commands[command](client=client, **demisto.args())
            return_outputs(readable_output, outputs, raw_response)
    # Log exceptions
    except Exception as e:
        err_msg = f"Error in {INTEGRATION_NAME} Integration [{e}]"
        return_error(err_msg, error=e)


if __name__ in ("__main__", "__builtin__", "builtins"):
    main()