ThreatExchange v2

Receive threat intelligence about applications, IP addresses, URLs, and hashes. A service by Facebook.

Data Enrichment & Threat Intelligence · ThreatExchange

Details

IDThreatExchange v2
ProviderMeta
CategoryData Enrichment & Threat Intelligence
From Version5.5.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Receive threat intelligence about applications, IP addresses, URLs, and hashes. A service by Facebook
This integration was integrated and tested with API version v3.2 of ThreatExchange

Authentication

The ThreatExchange APIs perform authentication via access tokens consisting of App ID and App Secret.

In order to get your App ID and App Secret, Facebook must first confirm your App’s access to ThreatExchange.

After Facebook notifies you that your App can access ThreatExchange, go to the App’s Settings - Basic - and copy your App ID and App Secret.

When configuring ThreatExchange v2 on Cortex XSOAR, set the copied values in the App ID and App Secret fields.

For more information see the ThreatExchange API Overview

For Cortex XSOAR versions 6.0 and below, the App Secret should be set in the password field.

Configure ThreatExchange v2 in Cortex

Parameter Description Required
App ID   True
App Secret   True
Source Reliability Reliability of the source providing the intelligence data True
Share Level Type A designation of how the indicator may be shared based on the US-CERT’s Traffic Light Protocol False
Use system proxy settings   False
Trust any certificate (not secure)   False
Malicious Threshold If the percentage of ‘Malicious’ reported statuses is above this threshold the indicator will be defined as malicious, otherwise suspicious. False
Suspicious Threshold If the number of ‘Suspicious’ reported statuses is above this threshold the indicator will be defined as suspicious. False
Non Malicious Threshold If the percentage of ‘Non Malicious’ reported statuses is above this threshold the indicator will be defined as good, otherwise unknown. False

Changes compared to previous version

  1. Dbot score calculation is different. See DBot Score / Reputation scores for details.

  2. The context output of the threatexchange-query command appears under ThreatExchange - Query instead of under queryResult.

  3. The output of reputation commands which was executed on an invalid input does not raise an exception, but provides an output that says no information was found for the given input. In addition a description of the error that occurred is added to the Cortex XSOAR server log.

DBot Score / Reputation scores

The following information describes a DBot Score calculation logic which is new for this version:

If the percentage of ‘Malicious’ reported statuses is above the Malicious Threshold (50% by default),
the indicator will be defined as malicious.

If the percentage of ‘Malicious’ reported statuses is below the Malicious Threshold, but there exists at least one
‘Malicious’ status, the indicator will be defined as suspicious.

If there are no ‘Malicious’ statuses, but the number of ‘Suspicious’ statuses is above the Suspicious Threshold (1 by default),
the indicator will be defined as suspicious.

If there are no ‘Malicious’ statuses and the number of ‘Suspicious’ statuses is below the Suspicious Threshold,
and the percentage of ‘Non Malicious’ reported statuses is above the Non Malicious Threshold (50% by default),
the indicator will be defined as good.

Otherwise, the indicator will be defined as unknown.

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.

file


Checks the file reputation of the given hash.

Base Command

file

Input

Argument Name Description Required
file Hash of the file to query. Supports MD5, SHA1, and SHA256 hashes. Required
limit The maximum number of results per page. The maximum is 1000. Default is 20. Optional
headers A comma-separated list of headers to display in human-readable format. For example: header1,header2,header3. Optional
since The start timestamp for collecting malware. Supported time formats: epoch time (e.g., 1619870400), ISO 8601 (e.g., 2021-05-01T12:00:00), and free text (e.g., 24 hours ago). Optional
until The end timestamp for collecting malware. Supported time formats: epoch time (e.g., 1619870400), ISO 8601 (e.g., 2021-05-01T12:00:00), and free text (e.g., 24 hours ago). 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.
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.
File.Malicious.Vendor String For malicious files, the vendor that made the decision.
File.Malicious.Description String For malicious files, the reason that the vendor made the decision.
File.Malicious.Score Number For malicious files, the score from the vendor.
ThreatExchange.File.share_level String A designation of how the indicator may be shared, based on the US-CERT’s Traffic Light Protocol.
ThreatExchange.File.privacy_type String The level of privacy applied to the descriptor. Also known as “visibility”.
ThreatExchange.File.status String If the indicator is known to be malicious.
ThreatExchange.File.review_status String Describes how the indicator was vetted.
ThreatExchange.File.id String Unique identifier of the threat descriptor. Automatically assigned at create time, and non-editable.
ThreatExchange.File.description String A short summary of the indicator and threat.
ThreatExchange.File.added_on Date The datetime this descriptor was first uploaded. Automatically computed; not directly editable.
ThreatExchange.File.sha1 String The SHA1 hash of the file.
ThreatExchange.File.sha256 String The SHA256 hash of the file.
ThreatExchange.File.sample_size_compressed Number The size of the compressed sample.
ThreatExchange.File.ssdeep String The SSDeep hash of the file.
ThreatExchange.File.sample_type String The MIME type of the sample.
ThreatExchange.File.sample_size Number The size of the sample.
ThreatExchange.File.sha3_384 String The SHA3-384 hash of the file.
ThreatExchange.File.victim_count Number A count of known victims infected and/or spreading the malware.
ThreatExchange.File.password String The password required to decompress the sample.
ThreatExchange.File.md5 String The MD5 hash of the file.

Command Example

!file file=cb57e263ab51f8e9b40d6f292bb17512cec0aa701bde14df33dfc06c815be54c

Context Example

{
    "DBotScore": {
        "Indicator": "cb57e263ab51f8e9b40d6f292bb17512cec0aa701bde14df33dfc06c815be54c",
        "Reliability": "C - Fairly reliable",
        "Score": 0,
        "Type": "file",
        "Vendor": "ThreatExchange v2"
    },
    "File": {
        "MD5": "f5c3281ed489772c840a137011c76b58",
        "SHA1": "2517620f427f0019e2eee3b36e206567b6e7a74a",
        "SHA256": "cb57e263ab51f8e9b40d6f292bb17512cec0aa701bde14df33dfc06c815be54c",
        "SSDeep": "3:N8RdNcvALtGTmAS3gG9HV6qVJNerWl/DKKIFjnD0SrrVKmTQXQN/:27NFGi79es2TFjnDXrP0i/",
        "Size": 142,
        "Type": "application/octet-stream"
    },
    "ThreatExchange": {
        "File": {
            "added_on": "2014-02-08T10:45:09+0000",
            "description": "New Kilim spam template",
            "id": "760220740669930",
            "md5": "f5c3281ed489772c840a137011c76b58",
            "password": "infected",
            "privacy_type": "VISIBLE",
            "review_status": "REVIEWED_AUTOMATICALLY",
            "sample_size": 142,
            "sample_size_compressed": 142,
            "sample_type": "application/octet-stream",
            "sha1": "2517620f427f0019e2eee3b36e206567b6e7a74a",
            "sha256": "cb57e263ab51f8e9b40d6f292bb17512cec0aa701bde14df33dfc06c815be54c",
            "sha3_384": "bc1ed0a4e634aaa784255bc50fa54fe41839c8763e797d083cefb87b87f7c743bc989c2c80bd6d72239fe86c489e802f",
            "share_level": "GREEN",
            "ssdeep": "3:N8RdNcvALtGTmAS3gG9HV6qVJNerWl/DKKIFjnD0SrrVKmTQXQN/:27NFGi79es2TFjnDXrP0i/",
            "status": "UNKNOWN",
            "victim_count": 0
        }
    }
}

Human Readable Output

ThreatExchange Result for file hash cb57e263ab51f8e9b40d6f292bb17512cec0aa701bde14df33dfc06c815be54c

added_on description id md5 password privacy_type review_status sample_size sample_size_compressed sample_type sha1 sha256 sha3_384 share_level ssdeep status victim_count
2014-02-08T10:45:09+0000 New Kilim spam template 760220740669930 f5c3281ed489772c840a137011c76b58 infected VISIBLE REVIEWED_AUTOMATICALLY 142 142 application/octet-stream 2517620f427f0019e2eee3b36e206567b6e7a74a cb57e263ab51f8e9b40d6f292bb17512cec0aa701bde14df33dfc06c815be54c bc1ed0a4e634aaa784255bc50fa54fe41839c8763e797d083cefb87b87f7c743bc989c2c80bd6d72239fe86c489e802f GREEN 3:N8RdNcvALtGTmAS3gG9HV6qVJNerWl/DKKIFjnD0SrrVKmTQXQN/:27NFGi79es2TFjnDXrP0i/ UNKNOWN 0

ip


Checks the reputation of the given IP address.

Base Command

ip

Input

Argument Name Description Required
ip IP address to check. Required
headers A comma-separated list of headers to display in human-readable format. For example: header1,header2,header3. Optional
since The start timestamp for collecting malware. Supported time formats: epoch time (e.g., 1619870400), ISO 8601 (e.g., 2021-05-01T12:00:00), and free text (e.g., 24 hours ago). Optional
until The end timestamp for collecting malware. Supported time formats: epoch time (e.g., 1619870400), ISO 8601 (e.g., 2021-05-01T12:00:00), and free text (e.g., 24 hours ago). Optional
limit The maximum number of results per page. The maximum is 1000. Optional

Context Output

Path Type Description
IP.Address String The IP address found.
IP.Malicious.Vendor String For malicious IP addresses, the vendor that made the decision.
IP.Malicious.Description String For malicious IP addresses, the reason that the vendor made the decision.
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.
IP.Malicious.Score Number For malicious IP addresses, the score from the vendor.
ThreatExchange.IP.share_level String A designation of how the indicator may be shared, based on the US-CERT’s Traffic Light Protocol.
ThreatExchange.IP.confidence Number A rating, from 0-100, on how confident the publisher is of the threat indicator’s status. 0 is the least confident. 100 is the most confident.
ThreatExchange.IP.indicator.id String The ID of the threat indicator described by the descriptor.
ThreatExchange.IP.indicator.indicator String The threat indicator described by the descriptor.
ThreatExchange.IP.indicator.type String The type of the threat indicator described by the descriptor.
ThreatExchange.IP.privacy_type String The level of privacy applied to the descriptor. Also known as “visibility”.
ThreatExchange.IP.last_updated Date Datetime the threat descriptor was last updated. Automatically computed; not directly editable.
ThreatExchange.IP.status String If the indicator is known to be malicious.
ThreatExchange.IP.owner.email String The email of the ThreatExchange member that submitted the descriptor. Non-editable.
ThreatExchange.IP.owner.id String The ID of the ThreatExchange member that submitted the descriptor. Non-editable.
ThreatExchange.IP.owner.name String The name of the ThreatExchange member that submitted the descriptor. Non-editable
ThreatExchange.IP.raw_indicator String A raw, unsanitized string of the indicator being described.
ThreatExchange.IP.review_status String Describes how the indicator was vetted.
ThreatExchange.IP.type String The type of indicator.
ThreatExchange.IP.id String Unique identifier of the threat descriptor. Automatically assigned at create time, and non-editable.
ThreatExchange.IP.description String A short summary of the indicator and threat.
ThreatExchange.IP.severity String Severity of the threat associated with the indicator.
ThreatExchange.IP.added_on Date The datetime this descriptor was first uploaded. Automatically computed; not directly editable.

Command Example

!ip ip=8.8.8.8

Context Example

{
    "DBotScore": {
        "Indicator": "8.8.8.8",
        "Reliability": "C - Fairly reliable",
        "Score": 1,
        "Type": "ip",
        "Vendor": "ThreatExchange v2"
    },
    "IP": {
        "Address": "8.8.8.8",
        "DetectionEngines": 2
    },
    "ThreatExchange": {
        "IP": [
            {
                "added_on": "2015-07-07T22:36:04+0000",
                "confidence": 50,
                "description": "Known DNS server",
                "id": "881335228606937",
                "indicator": {
                    "id": "501655576609539",
                    "indicator": "8.8.8.8",
                    "type": "IP_ADDRESS"
                },
                "last_updated": "2020-07-24T05:25:48+0000",
                "owner": {
                    "email": "threatexchange@support.facebook.com",
                    "id": "588498724619612",
                    "name": "Facebook CERT ThreatExchange"
                },
                "privacy_type": "VISIBLE",
                "raw_indicator": "8.8.8.8",
                "review_status": "REVIEWED_AUTOMATICALLY",
                "severity": "INFO",
                "share_level": "GREEN",
                "status": "NON_MALICIOUS",
                "type": "IP_ADDRESS"
            }
        ]
    }
}

Human Readable Output

ThreatExchange Result for IP 8.8.8.8

added_on confidence description id indicator last_updated owner privacy_type raw_indicator review_status severity share_level status type
2015-07-07T22:36:04+0000 50 Known DNS server 881335228606937 id: 501655576609539
indicator: 8.8.8.8
type: IP_ADDRESS
2020-07-24T05:25:48+0000 id: 588498724619612
email: threatexchange@support.facebook.com
name: Facebook CERT ThreatExchange
VISIBLE 8.8.8.8 REVIEWED_AUTOMATICALLY INFO GREEN NON_MALICIOUS IP_ADDRESS

url


Checks URL Reputation

Base Command

url

Input

Argument Name Description Required
url URL to be checked. Required
limit The maximum number of results per page. The maximum is 1000. Default is 20. Optional
headers A comma-separated list of headers to display in human-readable format. For example: header1,header2,header3. Optional
since The start timestamp for collecting malware. Supported time formats: epoch time (e.g., 1619870400), ISO 8601 (e.g., 2021-05-01T12:00:00), and free text (e.g., 24 hours ago). Optional
until The end timestamp for collecting malware. Supported time formats: epoch time (e.g., 1619870400), ISO 8601 (e.g., 2021-05-01T12:00:00), and free text (e.g., 24 hours ago). Optional
share_level A designation of how the indicator may be shared, based on the US-CERT’s Traffic Light Protocol. Default is RED. Optional

Context Output

Path Type Description
URL.Data String The URL found.
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.
URL.Malicious.Vendor String For malicious URLs, the vendor that made the decision.
URL.Malicious.Description String For malicious URLs, the reason for the vendor to make the decision.
URL.Malicious.Score Number For malicious URLs, the score from the vendor.
ThreatExchange.URL.share_level String A designation of how the indicator may be shared, based on the US-CERT’s Traffic Light Protocol.
ThreatExchange.URL.confidence Number A rating, from 0-100, on how confident the publisher is of the threat indicator’s status. 0 is the least confident. 100 is the most confident.
ThreatExchange.URL.indicator.id String The ID of the threat indicator described by the descriptor.
ThreatExchange.URL.indicator.indicator String The threat indicator described by the descriptor.
ThreatExchange.URL.indicator.type String The type of the threat indicator described by the descriptor.
ThreatExchange.URL.privacy_type String The level of privacy applied to the descriptor. Also known as “visibility”.
ThreatExchange.URL.last_updated Date Datetime the threat descriptor was last updated. Automatically computed; not directly editable.
ThreatExchange.URL.status String If the indicator is known to be malicious.
ThreatExchange.URL.owner.email String The email of the ThreatExchange member that submitted the descriptor. Non-editable.
ThreatExchange.URL.owner.id String The ID of the ThreatExchange member that submitted the descriptor. Non-editable.
ThreatExchange.URL.owner.name String The name of the ThreatExchange member that submitted the descriptor. Non-editable
ThreatExchange.URL.raw_indicator String A raw, unsanitized string of the indicator being described.
ThreatExchange.URL.review_status String Describes how the indicator was vetted.
ThreatExchange.URL.type String The type of indicator.
ThreatExchange.URL.id String Unique identifier of the threat descriptor. Automatically assigned at create time, and non-editable.
ThreatExchange.URL.description String A short summary of the indicator and threat.
ThreatExchange.URL.severity String Severity of the threat associated with the indicator.
ThreatExchange.URL.added_on Date The datetime this descriptor was first uploaded. Automatically computed; not directly editable.

Command Example

!url url=https://www.test.com/

Context Example

{
    "DBotScore": {
        "Indicator": "https://www.test.com/",
        "Reliability": "C - Fairly reliable",
        "Score": 0,
        "Type": "url",
        "Vendor": "ThreatExchange v2"
    },
    "ThreatExchange": {
        "URL": [
            {
                "added_on": "2018-07-11T09:50:34+0000",
                "confidence": 25,
                "id": "1904903709602326",
                "indicator": {
                    "id": "838258172933557",
                    "indicator": "https://www.test.com/",
                    "type": "URI"
                },
                "last_updated": "2020-07-24T19:24:35+0000",
                "owner": {
                    "email": "threatexchange@support.facebook.com",
                    "id": "210126779388350",
                    "name": "URLQueryThreatData Feed"
                },
                "privacy_type": "VISIBLE",
                "raw_indicator": "https://www.test.com/",
                "review_status": "REVIEWED_AUTOMATICALLY",
                "severity": "WARNING",
                "share_level": "WHITE",
                "status": "UNKNOWN",
                "type": "URI"
            },
            {
                "added_on": "2015-07-09T03:04:19+0000",
                "confidence": 1,
                "id": "835880593160550",
                "indicator": {
                    "id": "838258172933557",
                    "indicator": "https://www.test.com/",
                    "type": "URI"
                },
                "last_updated": "2020-07-24T03:37:14+0000",
                "owner": {
                    "email": "threatexchange@support.facebook.com",
                    "id": "820763734618599",
                    "name": "Facebook Administrator"
                },
                "privacy_type": "HAS_PRIVACY_GROUP",
                "raw_indicator": "https://www.test.com/",
                "review_status": "REVIEWED_AUTOMATICALLY",
                "severity": "INFO",
                "share_level": "RED",
                "status": "UNKNOWN",
                "type": "URI"
            }
        ]
    },
    "URL": {
        "Data": "https://www.test.com/",
        "DetectionEngines": 2
    }
}

Human Readable Output

ThreatExchange Result for URL https://www.test.com/

added_on confidence id indicator last_updated owner privacy_type raw_indicator review_status severity share_level status type
2018-07-11T09:50:34+0000 25 1904903709602326 id: 838258172933557
indicator: https://www.test.com/
type: URI
2020-07-24T19:24:35+0000 id: 210126779388350
email: threatexchange@support.facebook.com
name: URLQueryThreatData Feed
VISIBLE https://www.test.com/ REVIEWED_AUTOMATICALLY WARNING WHITE UNKNOWN URI
2015-07-09T03:04:19+0000 1 835880593160550 id: 838258172933557
indicator: https://www.test.com/
type: URI
2020-07-24T03:37:14+0000 id: 820763734618599
email: threatexchange@support.facebook.com
name: Facebook Administrator
HAS_PRIVACY_GROUP https://www.test.com/ REVIEWED_AUTOMATICALLY INFO RED UNKNOWN URI

domain


Checks domain reputation.

Base Command

domain

Input

Argument Name Description Required
domain Domain name to check reputation. Required
limit The maximum number of results per page. The maximum is 1000. Default is 20. Optional
headers A comma-separated list of headers to display in human-readable format. For example: header1,header2,header3. Optional
since The start timestamp for collecting malware. Supported time formats: epoch time (e.g., 1619870400), ISO 8601 (e.g., 2021-05-01T12:00:00), and free text (e.g., 24 hours ago). Optional
until The end timestamp for collecting malware. Supported time formats: epoch time (e.g., 1619870400), ISO 8601 (e.g., 2021-05-01T12:00:00), and free text (e.g., 24 hours ago). Optional
share_level A designation of how the indicator may be shared, based on the US-CERT’s Traffic Light Protocol. Default is RED. Optional

Context Output

Path Type Description
Domain.Name String The domain found.
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.Malicious.Vendor String For malicious domains, the vendor that made the decision.
Domain.Malicious.Description String For malicious domains, the reason that the vendor made the decision.
ThreatExchange.Domain.share_level String A designation of how the indicator may be shared, based on the US-CERT’s Traffic Light Protocol.
ThreatExchange.Domain.confidence Number A rating, from 0-100, on how confident the publisher is of the threat indicator’s status. 0 is the least confident. 100 is the most confident.
ThreatExchange.Domain.indicator.id String The ID of the threat indicator described by the descriptor.
ThreatExchange.Domain.indicator.indicator String The threat indicator described by the descriptor.
ThreatExchange.Domain.indicator.type String The type of the threat indicator described by the descriptor.
ThreatExchange.Domain.privacy_type String The level of privacy applied to the descriptor. Also known as “visibility”.
ThreatExchange.Domain.last_updated Date Datetime the threat descriptor was last updated. Automatically computed; not directly editable.
ThreatExchange.Domain.status String If the indicator is known to be malicious.
ThreatExchange.Domain.owner.email String The email of the ThreatExchange member that submitted the descriptor. Non-editable.
ThreatExchange.Domain.owner.id String The ID of the ThreatExchange member that submitted the descriptor. Non-editable.
ThreatExchange.Domain.owner.name String The name of the ThreatExchange member that submitted the descriptor. Non-editable
ThreatExchange.Domain.raw_indicator String A raw, unsanitized string of the indicator being described.
ThreatExchange.Domain.review_status String Describes how the indicator was vetted.
ThreatExchange.Domain.type String The type of indicator.
ThreatExchange.Domain.id String Unique identifier of the threat descriptor. Automatically assigned at create time, and non-editable.
ThreatExchange.Domain.description String A short summary of the indicator and threat.
ThreatExchange.Domain.severity String Severity of the threat associated with the indicator.
ThreatExchange.Domain.added_on Date Datetime the analysis was created.

Command Example

!domain domain=google.com

Context Example

{
    "DBotScore": {
        "Indicator": "google.com",
        "Reliability": "C - Fairly reliable",
        "Score": 0,
        "Type": "domain",
        "Vendor": "ThreatExchange v2"
    },
    "Domain": {
        "DetectionEngines": 2,
        "Name": "google.com"
    },
    "ThreatExchange": {
        "Domain": [
            {
                "added_on": "2018-05-02T18:05:33+0000",
                "confidence": 75,
                "id": "1688788781168786",
                "indicator": {
                    "id": "826838047363868",
                    "indicator": "google.com",
                    "type": "DOMAIN"
                },
                "last_updated": "2020-07-24T21:13:36+0000",
                "owner": {
                    "email": "threatexchange@support.facebook.com",
                    "id": "1656584897716085",
                    "name": "JoeSandbox Analysis"
                },
                "privacy_type": "HAS_PRIVACY_GROUP",
                "raw_indicator": "google.com",
                "review_status": "UNREVIEWED",
                "severity": "INFO",
                "share_level": "RED",
                "status": "UNKNOWN",
                "type": "DOMAIN"
            }
        ]
    }
}

Human Readable Output

ThreatExchange Result for domain google.com

added_on confidence id indicator last_updated owner privacy_type raw_indicator review_status severity share_level status type
2018-05-02T18:05:33+0000 75 1688788781168786 id: 826838047363868
indicator: google.com
type: DOMAIN
2020-07-24T21:13:36+0000 id: 1656584897716085
email: threatexchange@support.facebook.com
name: JoeSandbox Analysis
HAS_PRIVACY_GROUP google.com UNREVIEWED INFO RED UNKNOWN DOMAIN

threatexchange-query


Searches for subjective opinions on indicators of compromise stored in ThreatExchange.

Base Command

threatexchange-query

Input

Argument Name Description Required
text Free-form text field with a value to search for. This can be a file hash or a string found in other fields of the objects. Required
type The type of descriptor to search for. Possible values are: ADJUST_TOKEN, API_KEY, AS_NUMBER, BANNER, CMD_LINE, COOKIE_NAME, CRX, DEBUG_STRING, DEST_PORT, DIRECTORY_QUERIED, DOMAIN, EMAIL_ADDRESS, FILE_CREATED, FILE_DELETED, FILE_MOVED, FILE_NAME, FILE_OPENED, FILE_READ, FILE_WRITTEN, GET_PARAM, HASH_IMPHASH, HASH_MD5, HASH_PDQ, HASH_TMK, HASH_SHA1, HASH_SHA256, HASH_SSDEEP, HASH_VIDEO_MD5, HTML_ID, HTTP_REQUEST, IP_ADDRESS, IP_SUBNET, ISP, LATITUDE, LATITUDE, LAUNCH_AGENT, LOCATION, LONGITUDE, MALWARE_NAME, MEMORY_ALLOC, MEMORY_PROTECT, MEMORY_WRITTEN, MUTANT_CREATED, MUTEX, NAME_SERVER, OTHER_FILE_OP, PASSWORD, PASSWORD_SALT, PAYLOAD_DATA, PAYLOAD_TYPE, POST_DATA, PROTOCOL, REFERER, REGISTRAR, REGISTRY_KEY, REG_KEY_CREATED, REG_KEY_DELETED, REG_KEY_ENUMERATED, REG_KEY_MONITORED, REG_KEY_OPENED, REG_KEY_VALUE_CREATED, REG_KEY_VALUE_DELETED, REG_KEY_VALUE_MODIFIED, REG_KEY_VALUE_QUERIED, SIGNATURE, SOURCE_PORT, TELEPHONE, TEXT_STRING, TREND_QUERY, URI, USER_AGENT, VOLUME_QUERIED, WEBSTORAGE_KEY, WEB_PAYLOAD, WHOIS_NAME, WHOIS_ADDR1, WHOIS_ADDR2, XPI. Required
limit The maximum number of results per page. The maximum is 1000. Default is 20. Optional
headers A comma-separated list of headers to display in human-readable format. For example: header1,header2,header3. Optional
since The start timestamp for collecting malware. Supported time formats: epoch time (e.g., 1619870400), ISO 8601 (e.g., 2021-05-01T12:00:00). and free text (e.g., 24 hours ago). Optional
until The end timestamp for collecting malware. Supported time formats: epoch time (e.g., 1619870400), ISO 8601 (e.g., 2021-05-01T12:00:00). and free text (e.g., 24 hours ago). Optional
strict_text When set to ‘true’, the API will not do approximate matching on the value in the text. Default is false. Optional
before Returns results collected before this cursor. Optional
after Returns results collected after this cursor. Optional

Context Output

Path Type Description
ThreatExchange.Query.data.share_level String A designation of how the indicator may be shared, based on the US-CERT’s Traffic Light Protocol.
ThreatExchange.Query.data.last_updated Date Datetime the threat descriptor was last updated. Automatically computed; not directly editable.
ThreatExchange.Query.data.owner.email String The email of the ThreatExchange member that submitted the descriptor. Non-editable.
ThreatExchange.Query.data.owner.id String The ID of the ThreatExchange member that submitted the descriptor. Non-editable.
ThreatExchange.Query.data.owner.name String The name of the ThreatExchange member that submitted the descriptor. Non-editable.
ThreatExchange.Query.data.raw_indicator String A raw, unsanitized string of the indicator being described.
ThreatExchange.Query.data.type String The type of indicator.
ThreatExchange.Query.data.id String Unique identifier of the threat descriptor. Automatically assigned at create time, and non-editable.
ThreatExchange.Query.data.added_on Date The datetime this descriptor was first uploaded. Automatically computed; not directly editable.
ThreatExchange.Query.paging.before String Paging before cursor.
ThreatExchange.Query.paging.after String Paging after cursor.

Command Example

!threatexchange-query text=geektime type=URI limit=3

Context Example

{
    "ThreatExchange": {
        "Query": {
            "data": [
                {
                    "added_on": "2018-08-30T07:12:28+0000",
                    "confidence": 50,
                    "id": "2036544083043163",
                    "indicator": {
                        "id": "2036543926376512",
                        "indicator": "http://www.geektime.co.il/wp-content/uploads/2016/09/",
                        "type": "URI"
                    },
                    "last_updated": "2021-03-03T02:41:06+0000",
                    "owner": {
                        "email": "threatexchange@support.facebook.com",
                        "id": "820763734618599",
                        "name": "Facebook Administrator"
                    },
                    "privacy_type": "VISIBLE",
                    "raw_indicator": "http://www.geektime.co.il/wp-content/uploads/2016/09/",
                    "review_status": "REVIEWED_AUTOMATICALLY",
                    "severity": "INFO",
                    "share_level": "GREEN",
                    "status": "UNKNOWN",
                    "type": "URI"
                },
                {
                    "added_on": "2018-08-28T14:59:24+0000",
                    "confidence": 50,
                    "id": "1799344580151062",
                    "indicator": {
                        "id": "1799344400151080",
                        "indicator": "http://www.geektime.co.il/wp-content/uploads/2016/05/",
                        "type": "URI"
                    },
                    "last_updated": "2020-07-24T20:12:26+0000",
                    "owner": {
                        "email": "threatexchange@support.facebook.com",
                        "id": "820763734618599",
                        "name": "Facebook Administrator"
                    },
                    "privacy_type": "VISIBLE",
                    "raw_indicator": "http://www.geektime.co.il/wp-content/uploads/2016/05/",
                    "review_status": "REVIEWED_AUTOMATICALLY",
                    "severity": "INFO",
                    "share_level": "GREEN",
                    "status": "UNKNOWN",
                    "type": "URI"
                },
                {
                    "added_on": "2018-08-24T20:16:16+0000",
                    "confidence": 50,
                    "id": "2265237266824665",
                    "indicator": {
                        "id": "2265236920158033",
                        "indicator": "http://www.geektime.co.il/wp-content/uploads/2016/07/",
                        "type": "URI"
                    },
                    "last_updated": "2020-07-24T18:45:09+0000",
                    "owner": {
                        "email": "threatexchange@support.facebook.com",
                        "id": "820763734618599",
                        "name": "Facebook Administrator"
                    },
                    "privacy_type": "VISIBLE",
                    "raw_indicator": "http://www.geektime.co.il/wp-content/uploads/2016/07/",
                    "review_status": "REVIEWED_AUTOMATICALLY",
                    "severity": "INFO",
                    "share_level": "GREEN",
                    "status": "UNKNOWN",
                    "type": "URI"
                }
            ],
            "paging": {
                "after": "AcGbapTFY3H6ZCEZBYp5gdlibpIrqCJhOm4uk1YgoxkT8nJFgNZCDzzXF04S89kT5ZCPiUUZD",
                "before": "AcFjybJa7Ba5DZBti3wUtysfdqtcOc6lezkjjhRJAMgvCok7nBQUB40uKU5K2xyZBYnF4ZD"
            },
            "text": "geektime",
            "type": "URI"
        }
    }
}

Human Readable Output

ThreatExchange Query Result

added_on confidence id indicator last_updated owner privacy_type raw_indicator review_status severity share_level status type
2018-08-30T07:12:28+0000 50 2036544083043163 id: 2036543926376512
indicator: http://www.geektime.co.il/wp-content/uploads/2016/09/
type: URI
2021-03-03T02:41:06+0000 id: 820763734618599
email: threatexchange@support.facebook.com
name: Facebook Administrator
VISIBLE http://www.geektime.co.il/wp-content/uploads/2016/09/ REVIEWED_AUTOMATICALLY INFO GREEN UNKNOWN URI
2018-08-28T14:59:24+0000 50 1799344580151062 id: 1799344400151080
indicator: http://www.geektime.co.il/wp-content/uploads/2016/05/
type: URI
2020-07-24T20:12:26+0000 id: 820763734618599
email: threatexchange@support.facebook.com
name: Facebook Administrator
VISIBLE http://www.geektime.co.il/wp-content/uploads/2016/05/ REVIEWED_AUTOMATICALLY INFO GREEN UNKNOWN URI
2018-08-24T20:16:16+0000 50 2265237266824665 id: 2265236920158033
indicator: http://www.geektime.co.il/wp-content/uploads/2016/07/
type: URI
2020-07-24T18:45:09+0000 id: 820763734618599
email: threatexchange@support.facebook.com
name: Facebook Administrator
VISIBLE http://www.geektime.co.il/wp-content/uploads/2016/07/ REVIEWED_AUTOMATICALLY INFO GREEN UNKNOWN URI

Pagination

after before
AcGbapTFY3H6ZCEZBYp5gdlibpIrqCJhOm4uk1YgoxkT8nJFgNZCDzzXF04S89kT5ZCPiUUZD AcFjybJa7Ba5DZBti3wUtysfdqtcOc6lezkjjhRJAMgvCok7nBQUB40uKU5K2xyZBYnF4ZD

threatexchange-members


Returns a list of current members of the ThreatExchange, alphabetized by application name. Each application may also include an optional contact email address. You can set this address, if desired, under the settings panel for your application.

Base Command

threatexchange-members

Input

There are no input arguments for this command.

Context Output

Path Type Description
ThreatExchange.Member.id String Member’s ID.
ThreatExchange.Member.email String Member’s email.
ThreatExchange.Member.name String Member’s name.

Command Example


#### Context Example

```json
{
    "ThreatExchange": {
        "Member": [
            {
                "email": "user@example.com",
                "id": "906975333085907",
                "name": "2U ThreatExchange App"
            }
        ]
    }
}

Human Readable Output

ThreatExchange Members

id name email
906975333085907 2U ThreatExchange App user@example.com

threatexchange-tags-search


Enables searching for tags in ThreatExchange.
With this call you can search for ThreatTag objects by text.

Base Command

threatexchange-tags-search

Input

Argument Name Description Required
text Freeform text field with a value to search for.
This value should describe a broader type or class of attack you are interested in.
Required
before Returns results collected before this cursor. Optional
after Returns results collected after this cursor. Optional

Context Output

Path Type Description
ThreatExchange.Tag.data.id String The tag’s ID.
ThreatExchange.Tag.data.text String The tag’s text.
ThreatExchange.Tag.paging.before Unknown Paging before cursor.
ThreatExchange.Tag.paging.after String Paging after cursor.

Command Example

!threatexchange-tags-search text=malware

Context Example

{
    "ThreatExchange": {
        "Tag": {
            "data": [
                {
                    "id": "1318516441499594",
                    "text": "malware"
                }
            ],
            "paging": {
                "after": "MAZDZD",
                "before": "MAZDZD"
            },
            "text": "malware"
        }
    }
}

Human Readable Output

ThreatExchange Tags

id text
1318516441499594 malware

Pagination

after before
MAZDZD MAZDZD

threatexchange-tagged-objects-list


Gets a list of tagged objects for a specific ThreatTag.

Base Command

threatexchange-tagged-objects-list

Input

Argument Name Description Required
tag_id ThreatTag ID to get it’s related tagged objects. ThreatTag ID can be retrieved by the threatexchange-tags-search command. Required
tagged_since Fetches all objects that have been tagged since this time (inclusive). Optional
tagged_until Fetches all objects that have been tagged until this time (inclusive). Optional
before Returns results collected before this cursor. Optional
after Returns results collected after this cursor. Optional

Context Output

Path Type Description
ThreatExchange.TaggedObject.data.id String The ID of the tagged object.
ThreatExchange.TaggedObject.data.type String The type of the tagged object.
ThreatExchange.TaggedObject.data.name String The name of the tagged object.
ThreatExchange.TaggedObject.paging.before String Paging before cursor.
ThreatExchange.TaggedObject.paging.after String Paging after cursor.

Command Example

!threatexchange-tagged-objects-list tag_id=1318516441499594

Context Example

{
    "ThreatExchange": {
        "TaggedObject": {
            "data": [
                {
                    "id": "1460089820713228",
                    "name": "cafece4c21572473fed821bb64381d0a",
                    "type": "MALWARE_DESCRIPTOR"
                }
            ],
            "paging": {
                "after": "QVFIUmFFOERJZATZAmMW9wRnJwbjFiY2tTdFpHRk9PTVlIYm80bVREdXlIS1pWWmRrSU4zSHpYT2dXUTR0QW1HTkVWal9oalU5dGhyRlZA6U2ZAKWC04T0R0NXVR",
                "before": "QVFIUlhyUENfX2U1UUkyOWxySlpEWVFveEJiM0twRVpGWkc2LWdLME1CU0hYS3hfVzFibjltSUdTYi1LdWlBNF8zenZADaUlZAWm1vQ1RkVm1zc3NnSllza2lB"
            },
            "tag_id": "1318516441499594"
        }
    }
}

Human Readable Output

ThreatExchange Tagged Objects for ThreatTag: 1318516441499594

id name type
1460089820713228 cafece4c21572473fed821bb64381d0a MALWARE_DESCRIPTOR

Pagination

after before
QVFIUmFFOERJZATZAmMW9wRnJwbjFiY2tTdFpHRk9PTVlIYm80bVREdXlIS1pWWmRrSU4zSHpYT2dXUTR0QW1HTkVWal9oalU5dGhyRlZA6U2ZAKWC04T0R0NXVR QVFIUlhyUENfX2U1UUkyOWxySlpEWVFveEJiM0twRVpGWkc2LWdLME1CU0hYS3hfVzFibjltSUdTYi1LdWlBNF8zenZADaUlZAWm1vQ1RkVm1zc3NnSllza2lB

threatexchange-object-get-by-id


Gets ThreatExchange object by ID.

Base Command

threatexchange-object-get-by-id

Input

Argument Name Description Required
object_id ID of a ThreatExchange object. Can be retrieved by ThreatExchange reputation commands and threatexchange-tagged-objects-list command. Required

Context Output

Path Type Description
ThreatExchange.Object.id String ID of a ThreatExchange object.

Command Example

!threatexchange-object-get-by-id object_id=1318516441499594

Context Example

{
    "ThreatExchange": {
        "Object": {
            "id": "1318516441499594",
            "text": "malware"
        }
    }
}

Human Readable Output

ThreatExchange Object 1318516441499594

id text
1318516441499594 malware

Configuration parameters

  • app_id — App ID (required)
  • malicious_threshold — Malicious Threshold
  • suspicious_threshold — Suspicious Threshold
  • non_malicious_threshold — Non Malicious Threshold
  • feedReliability — Source Reliability (required)
  • share_level — Share Level Type
  • proxy — Use system proxy settings
  • insecure — Trust any certificate (not secure)

Commands (9)

  • domain

    Checks a domain reputation.

  • file

    Checks the file reputation of the given hash.

  • ip

    Checks the reputation of the given IP address.

  • threatexchange-members

    Returns a list of current members of ThreatExchange, alphabetized by application name. Each application may also include an optional contact email address. You can set this address, if desired, under the settings panel for your application.

  • threatexchange-object-get-by-id

    Gets a ThreatExchange object by ID.

  • threatexchange-query

    Searches for subjective opinions on indicators of compromise stored in ThreatExchange.

  • threatexchange-tagged-objects-list

    Gets a list of tagged objects for a specific ThreatTag.

  • threatexchange-tags-search

    Enables searching for tags in ThreatExchange. With this call, you can search for ThreatTag objects by text.

  • url

    Checks the URL reputation.

"""
An integration module for the ThreatExchange V2 API.
API Documentation:
    https://developers.facebook.com/docs/threat-exchange/reference/apis
"""

import collections

import urllib3
from CommonServerPython import *  # noqa # pylint: disable=unused-wildcard-import

from CommonServerUserPython import *  # noqa

# Disable insecure warnings #
urllib3.disable_warnings()

DEFAULT_LIMIT = 20
COMMAND_PREFIX = "threatexchange"
VENDOR_NAME = "ThreatExchange v2"
CONTEXT_PREFIX = "ThreatExchange"
THREAT_DESCRIPTORS_SUFFIX = "threat_descriptors"
MALWARE_ANALYSES_SUFFIX = "malware_analyses"
THREAT_TAGS_SUFFIX = "threat_tags"
TAGGED_OBJECTS_SUFFIX = "tagged_objects"
THREAT_EXCHANGE_MEMBERS_SUFFIX = "threat_exchange_members"
TIMEOUT_FOR_LIST_CALLS = 30
DEFAULT_DESCRIPTION_FOR_MALICIOUS_INDICATOR = "Indicator was classified as malicious by more than {}% of detection engines."


class ThreatExchangeV2Status:
    UNKNOWN = "UNKNOWN"
    NON_MALICIOUS = "NON_MALICIOUS"
    SUSPICIOUS = "SUSPICIOUS"
    MALICIOUS = "MALICIOUS"


class Client(BaseClient):
    """Client class to interact with the service API

    This Client implements API calls, and does not contain any XSOAR logic.
    Should only do requests and return data.
    It inherits from BaseClient defined in CommonServer Python.
    Most calls use _http_request() that handles proxy, SSL verification, etc.
    For this implementation, no special attributes defined
    """

    def __init__(self, base_url, access_token, verify=True, proxy=False):
        super().__init__(base_url, verify, proxy)
        self.access_token = access_token

    def ip(self, ip: str, since: Optional[int], until: Optional[int], limit: Optional[int] = DEFAULT_LIMIT) -> Dict:
        """
        See Also:
            https://developers.facebook.com/docs/threat-exchange/reference/apis/threat-descriptors
        Args:
            ip: ip address
            since: Returns malware collected after a timestamp
            until: Returns malware collected before a timestamp
            limit: Defines the maximum size of a page of results. The maximum is 1,000

        Returns: The API call response
        """
        response = self._http_request(
            "GET",
            THREAT_DESCRIPTORS_SUFFIX,
            params={
                "access_token": self.access_token,
                "type": "IP_ADDRESS",
                "text": ip,
                "strict_text": True,
                "since": since,
                "until": until,
                "limit": limit,
            },
        )
        return response

    def file(self, file: str, since: Optional[int], until: Optional[int], limit: Optional[int] = DEFAULT_LIMIT) -> Dict:
        """
        See Also:
            https://developers.facebook.com/docs/threat-exchange/reference/apis/malware-analyses/v10.0
        Args:
            file: Hash of a file
            since: Returns malware collected after a timestamp
            until: Returns malware collected before a timestamp
            limit: Defines the maximum size of a page of results. The maximum is 1,000

        Returns: The API call response
        """

        response = self._http_request(
            "GET",
            MALWARE_ANALYSES_SUFFIX,
            params=assign_params(
                access_token=self.access_token, text=file, strict_text=True, since=since, until=until, limit=limit
            ),
        )
        return response

    def domain(
        self, domain: str, since: Optional[int], until: Optional[int], share_level: str, limit: Optional[int] = DEFAULT_LIMIT
    ) -> Dict:
        """
        See Also:
            https://developers.facebook.com/docs/threat-exchange/reference/apis/threat-descriptors
        Args:
            domain: Domain
            since: Returns malware collected after a timestamp
            until: Returns malware collected before a timestamp
            share_level: A designation of how the indicator may be shared, based on the US-CERT's Traffic Light Protocol.
            limit: Defines the maximum size of a page of results. The maximum is 1,000

        Returns: The API call response
        """

        response = self._http_request(
            "GET",
            THREAT_DESCRIPTORS_SUFFIX,
            params=assign_params(
                access_token=self.access_token,
                type="DOMAIN",
                text=domain,
                strict_text=True,
                since=since,
                until=until,
                limit=limit,
                share_level=share_level,
            ),
        )
        return response

    def url(
        self, url: str, since: Optional[int], until: Optional[int], share_level: str, limit: Optional[int] = DEFAULT_LIMIT
    ) -> Dict:
        """
        See Also:
            https://developers.facebook.com/docs/threat-exchange/reference/apis/threat-descriptors
        Args:
            url: URL
            since: Returns malware collected after a timestamp
            until: Returns malware collected before a timestamp
            share_level: A designation of how the indicator may be shared, based on the US-CERT's Traffic Light Protocol.
            limit: Defines the maximum size of a page of results. The maximum is 1,000

        Returns: The API call response
        """

        response = self._http_request(
            "GET",
            THREAT_DESCRIPTORS_SUFFIX,
            params=assign_params(
                access_token=self.access_token,
                type="URI",
                text=url,
                strict_text=True,
                since=since,
                until=until,
                limit=limit,
                share_level=share_level,
            ),
        )
        return response

    def members(self) -> Dict:
        """
        See Also:
            https://developers.facebook.com/docs/threat-exchange/reference/apis/threat-exchange-members/v10.0

        Returns: The API call response
        """

        response = self._http_request(
            "GET", THREAT_EXCHANGE_MEMBERS_SUFFIX, params={"access_token": self.access_token}, timeout=TIMEOUT_FOR_LIST_CALLS
        )
        return response

    def query(
        self,
        text: str,
        descriptor_type: str,
        since: Optional[int],
        until: Optional[int],
        limit: Optional[int] = DEFAULT_LIMIT,
        strict_text: Optional[bool] = False,
        before: Optional[str] = None,
        after: Optional[str] = None,
    ) -> Dict:
        """
        See Also:
            https://developers.facebook.com/docs/threat-exchange/reference/apis/threat-descriptors
        Args:
            text: Freeform text field with a value to search for
            descriptor_type: The type of descriptor to search for
            since: Returns malware collected after a timestamp
            until: Returns malware collected before a timestamp
            limit: Defines the maximum size of a page of results. The maximum is 1,000
            strict_text: When set to 'true', the API will not do approximate matching on the value in text
            before: Returns results collected before this cursor
            after: Returns results collected after this cursor

        Returns: The API call response
        """

        response = self._http_request(
            "GET",
            THREAT_DESCRIPTORS_SUFFIX,
            params=assign_params(
                access_token=self.access_token,
                type=descriptor_type,
                text=text,
                strict_text=strict_text,
                since=since,
                until=until,
                limit=limit,
                before=before,
                after=after,
            ),
            timeout=TIMEOUT_FOR_LIST_CALLS,
        )
        return response

    def tags_search(self, text: str, before: Optional[str] = None, after: Optional[str] = None) -> Dict:
        """
        See also:
            https://developers.facebook.com/docs/threat-exchange/reference/apis/threat-tags/v10.0
        Args:
            text: Freeform text field with a value to search for.
                  This value should describe a broader type or class of attack you are interested in.
            before: Returns results collected before this cursor
            after:  Returns results collected after this cursor

        Returns: The API call response
        """
        response = self._http_request(
            "GET",
            THREAT_TAGS_SUFFIX,
            params=assign_params(access_token=self.access_token, text=text, before=before, after=after),
            timeout=TIMEOUT_FOR_LIST_CALLS,
        )
        return response

    def tagged_objects_list(
        self,
        tag_id: str,
        tagged_since: Optional[int],
        tagged_until: Optional[int],
        before: Optional[str] = None,
        after: Optional[str] = None,
    ) -> Dict:
        """
        See also:
            https://developers.facebook.com/docs/threat-exchange/reference/apis/threattags/v10.0
        Args:
            tag_id: ThreatTag ID to get it's related tagged objects
            tagged_since:  Fetches all objects that have been tagged since this time (inclusive)
            tagged_until: Fetches all objects that have been tagged until this time (inclusive)
            before: Returns results collected before this cursor
            after:  Returns results collected after this cursor

        Returns: The API call response
        """
        url_suffix = f"{tag_id}/{TAGGED_OBJECTS_SUFFIX}"
        response = self._http_request(
            "GET",
            url_suffix,
            params=assign_params(
                access_token=self.access_token, tagged_since=tagged_since, tagged_until=tagged_until, before=before, after=after
            ),
            timeout=TIMEOUT_FOR_LIST_CALLS,
        )
        return response

    def object_get_by_id(self, object_id: str) -> Dict:
        """
        Gets ThreatExchange object by ID
        Args:
            object_id: ID of a ThreatExchange object

        Returns: The API call response
        """
        response = self._http_request("GET", object_id, params={"access_token": self.access_token})
        return response


def get_reputation_data_statuses(reputation_data: List) -> List[str]:
    """
    collects reported statuses of reputation data
    Args:
        reputation_data: returned data list of a certain reputation command

    Returns: a list of reported statuses
    """
    reputation_statuses = [status for data_entry in reputation_data if (status := data_entry.get("status"))]
    return reputation_statuses


def calculate_dbot_score(reputation_data: List, params: Dict[str, Any]) -> int:
    """
    Calculates the Dbot score of the given reputation command data, by the following logic:
    MALICIOUS > malicious threshold (50%) = Malicious
    MALICIOUS <= malicious threshold (50%) = Suspicious
    SUSPICIOUS > suspicious threshold (1) = Suspicious
    NON_MALICIOUS > non malicious threshold (50%) = Good
    else Unknown
    Args:
        reputation_data: returned data list of a certain reputation command
        params: parameters of the integration
    Returns: the calculated Dbot score
    """
    # get user's thresholds:
    malicious_threshold = arg_to_number(params.get("malicious_threshold", 50), arg_name="malicious_threshold")
    if malicious_threshold is None:
        malicious_threshold = 50
    suspicious_threshold = arg_to_number(params.get("suspicious_threshold", 1), arg_name="suspicious_threshold")
    if suspicious_threshold is None:
        suspicious_threshold = 1
    non_malicious_threshold = arg_to_number(params.get("non_malicious_threshold", 50), arg_name="non_malicious_threshold")
    if non_malicious_threshold is None:
        non_malicious_threshold = 50

    # collect and count reported statuses:
    reputation_statuses = get_reputation_data_statuses(reputation_data)
    num_of_statuses = len(reputation_statuses)
    occurrences = collections.Counter(reputation_statuses)

    # calculate Dbot score:
    num_of_malicious = occurrences.get(ThreatExchangeV2Status.MALICIOUS, 0)
    num_of_suspicious = occurrences.get(ThreatExchangeV2Status.SUSPICIOUS, 0)
    num_of_non_malicious = occurrences.get(ThreatExchangeV2Status.NON_MALICIOUS, 0)
    if num_of_statuses == 0:  # no reported statuses
        score = Common.DBotScore.NONE
    elif num_of_malicious >= 1:  # at least one malicious status was reported
        if ((num_of_malicious / num_of_statuses) * 100) > malicious_threshold:
            score = Common.DBotScore.BAD
        else:  # num_of_malicious <= malicious_threshold
            score = Common.DBotScore.SUSPICIOUS
    elif num_of_suspicious > suspicious_threshold:  # number of suspicious statuses is above threshold
        score = Common.DBotScore.SUSPICIOUS
    elif ((num_of_non_malicious / num_of_statuses) * 100) > non_malicious_threshold:
        # number of non malicious statuses is above threshold
        score = Common.DBotScore.GOOD
    else:  # there isn't enough information - Dbot score is defined as unknown
        score = Common.DBotScore.NONE

    return score


def calculate_engines(reputation_data: List) -> tuple[int, int]:
    """
    Calculates the number of engines that scanned the indicator, and how many of them are positive
     - i.e returned malicious status.
    Args:
        reputation_data: returned data list of a certain reputation command

    Returns: number of engines, number of positive engines
    """
    num_of_engines = len(reputation_data)
    reputation_statuses = get_reputation_data_statuses(reputation_data)
    occurrences = collections.Counter(reputation_statuses)
    num_of_positive_engines = occurrences.get(ThreatExchangeV2Status.MALICIOUS, 0)

    return num_of_engines, num_of_positive_engines


def flatten_outputs_paging(raw_response: Dict) -> Dict:
    """
    flatten the paging section of the raw_response - i.e removes 'cursors' key.
    Args:
        raw_response: response of an API call

    Returns: outputs dict

    """
    paging: Dict
    paging = raw_response.get("paging", {})
    outputs = raw_response.copy()
    cursor_before = paging.get("cursors", {}).get("before")
    cursor_after = paging.get("cursors", {}).get("after")
    outputs.pop("paging", None)
    outputs["paging"] = {
        "before": cursor_before,
        "after": cursor_after,
    }
    return outputs


def get_malicious_description(score: int, reputation_data: List[Dict], params: Dict[str, Any]) -> Optional[str]:
    """
    Gets the malicious description of certain indicator.
    If the indicator was classified as malicious, description is defined as default malicious description.
    If the indicator wasn't classified as malicious, description will be None (and won't be added to context).
    Args:
        score: calculated dbot score of the indicator
        reputation_data: returned data of a certain reputation command
        params: integration's parameters

    Returns: malicious description

    """
    malicious_description: Union[str, None]
    if score == Common.DBotScore.BAD:
        malicious_threshold = arg_to_number(params.get("malicious_threshold", 50))
        default_description = DEFAULT_DESCRIPTION_FOR_MALICIOUS_INDICATOR.format(malicious_threshold)
        malicious_description = default_description

    else:  # dbot-score isn't malicious
        malicious_description = None

    return malicious_description


def convert_string_to_epoch_time(date: Optional[str], arg_name: Optional[str] = None) -> Optional[int]:
    """
    Converts a string representing a date into epoch time format
    Args:
        date: date string
        arg_name: name of the date argument

    Returns: date in epoch time format (if an error occurred, or date in None returns None)
    """
    if date:
        if date.isdigit():  # date is an epoch time format string
            return int(date)

        date_obj = dateparser.parse(date)  # date is a string in a time format such as: iso 8601, free text, etc
        if isinstance(date_obj, datetime):
            epoch_time = date_obj.timestamp()
            return int(epoch_time)
        else:  # date was given in a wrong format
            if arg_name:
                raise ValueError(f'Invalid date: "{arg_name}"="{date}"')

    return None


def test_module(client: Client) -> str:
    """
    Tests API connectivity and authentication.

    Returning 'ok' indicates that the integration works like it is supposed to.
    Connection to the service is successful.
    Raises exceptions if something goes wrong.

    Args:
        client: client to use

    Returns: 'ok' if test passed, anything else will fail the test
    """

    client.ip(ip="8.8.8.8", since=None, until=None)
    return "ok"


def ip_command(client: Client, args: Dict[str, Any], params: Dict[str, Any]) -> List[CommandResults]:
    """
    Returns IP's reputation
    """
    ips = argToList(args.get("ip"))
    since = convert_string_to_epoch_time(args.get("since"), arg_name="since")
    until = convert_string_to_epoch_time(args.get("until"), arg_name="until")
    limit = arg_to_number(args.get("limit"), arg_name="limit")
    headers = argToList(args.get("headers"))
    reliability = params.get("feedReliability")
    results: List[CommandResults] = []

    for ip in ips:
        if not is_ip_valid(ip, accept_v6_ips=True):  # check IP's validity
            raise ValueError(f'IP "{ip}" is not valid')
        try:
            raw_response = client.ip(ip, since, until, limit)
        except Exception as exception:
            # If anything happens, handle like there are no results
            err_msg = f'Could not process IP: "{ip}"\n {exception!s}'
            demisto.debug(err_msg)
            raw_response = {}
        if data := raw_response.get("data"):
            score = calculate_dbot_score(reputation_data=data, params=params)
            num_of_engines, num_of_positive_engines = calculate_engines(reputation_data=data)
            malicious_description = get_malicious_description(score, data, params)
            dbot_score = Common.DBotScore(
                indicator=ip,
                indicator_type=DBotScoreType.IP,
                integration_name=VENDOR_NAME,
                score=score,
                reliability=reliability,
                malicious_description=malicious_description,
            )
            if not headers:
                headers = [
                    "description",
                    "owner",
                    "status",
                    "type",
                    "raw_indicator",
                    "share_level",
                    "confidence",
                    "severity",
                    "added_on",
                    "last_updated",
                    "review_status",
                    "id",
                ]
            readable_output = tableToMarkdown(f"{CONTEXT_PREFIX} Result for IP {ip}", data, headers=headers)
            ip_indicator = Common.IP(
                ip=ip, dbot_score=dbot_score, detection_engines=num_of_engines, positive_engines=num_of_positive_engines
            )

        else:  # no data
            dbot_score = Common.DBotScore(
                indicator=ip,
                indicator_type=DBotScoreType.IP,
                integration_name=VENDOR_NAME,
                score=Common.DBotScore.NONE,
                reliability=reliability,
            )
            readable_output = f"{CONTEXT_PREFIX} does not have details about IP: {ip} \n"
            ip_indicator = Common.IP(
                ip=ip,
                dbot_score=dbot_score,
            )

        result = CommandResults(
            outputs_prefix=f"{CONTEXT_PREFIX}.IP",
            outputs_key_field="id",
            outputs=data,
            indicator=ip_indicator,
            readable_output=readable_output,
            raw_response=raw_response,
        )
        results.append(result)

    return results


def file_command(client: Client, args: Dict[str, Any], params: Dict[str, Any]) -> List[CommandResults]:
    """
    Returns file's reputation
    """
    files = argToList(args.get("file"))
    since = convert_string_to_epoch_time(args.get("since"), arg_name="since")
    until = convert_string_to_epoch_time(args.get("until"), arg_name="until")
    limit = arg_to_number(args.get("limit"), arg_name="limit")
    headers = argToList(args.get("headers"))
    reliability = params.get("feedReliability")
    results: List[CommandResults] = []

    for file in files:
        if get_hash_type(file) not in ("sha256", "sha1", "md5"):  # check file's validity
            raise ValueError(f'Hash "{file}" is not of type SHA-256, SHA-1 or MD5')
        try:
            raw_response = client.file(file, since, until, limit)
        except Exception as exception:
            # If anything happens, handle like there are no results
            err_msg = f'Could not process file: "{file}"\n {exception!s}'
            demisto.debug(err_msg)
            raw_response = {}
        if data := raw_response.get("data"):
            score = calculate_dbot_score(reputation_data=data, params=params)
            malicious_description = get_malicious_description(score, data, params)
            dbot_score = Common.DBotScore(
                indicator=file,
                indicator_type=DBotScoreType.FILE,
                integration_name=VENDOR_NAME,
                score=score,
                reliability=reliability,
                malicious_description=malicious_description,
            )
            if not headers:
                headers = [
                    "description",
                    "status",
                    "share_level",
                    "added_on",
                    "review_status",
                    "id",
                    "password",
                    "sample_size",
                    "sample_size_compressed",
                    "sample_type",
                    "victim_count",
                    "md5",
                    "sha1",
                    "sha256",
                    "sha3_384",
                    "ssdeep",
                ]
            readable_output = tableToMarkdown(f"{CONTEXT_PREFIX} Result for file hash {file}", data, headers=headers)
            data_entry = data[0]
            file_indicator = Common.File(
                dbot_score=dbot_score,
                file_type=data_entry.get("sample_type"),
                size=data_entry.get("sample_size"),
                md5=data_entry.get("md5"),
                sha1=data_entry.get("sha1"),
                sha256=data_entry.get("sha256"),
                ssdeep=data_entry.get("ssdeep"),
                tags=data_entry.get("tags"),
            )

        else:  # no data
            dbot_score = Common.DBotScore(
                indicator=file,
                indicator_type=DBotScoreType.FILE,
                integration_name=VENDOR_NAME,
                score=Common.DBotScore.NONE,
                reliability=reliability,
            )
            readable_output = f"{CONTEXT_PREFIX} does not have details about file: {file} \n"
            file_indicator = Common.File(dbot_score=dbot_score)

        result = CommandResults(
            outputs_prefix=f"{CONTEXT_PREFIX}.File",
            outputs_key_field="id",
            outputs=data,
            indicator=file_indicator,
            readable_output=readable_output,
            raw_response=raw_response,
        )
        results.append(result)

    return results


def domain_command(client: Client, args: Dict[str, Any], params: Dict[str, Any]) -> List[CommandResults]:
    """
    Returns domain's reputation
    """
    domains = argToList(args.get("domain"))
    since = convert_string_to_epoch_time(args.get("since"), arg_name="since")
    until = convert_string_to_epoch_time(args.get("until"), arg_name="until")
    limit = arg_to_number(args.get("limit"), arg_name="limit")
    headers = argToList(args.get("headers"))
    reliability = params.get("feedReliability")
    share_level = args.get("share_level", params.get("share_level", "RED"))
    demisto.debug(f"Setting share level to {share_level}")
    results: List[CommandResults] = []

    for domain in domains:
        try:
            raw_response = client.domain(domain, since, until, share_level, limit)
        except Exception as exception:
            # If anything happens, handle like there are no results
            err_msg = f'Could not process domain: "{domain}"\n {exception!s}'
            demisto.debug(err_msg)
            raw_response = {}
            readable_output = f'Processing domain "{domain}" resulted in an exception. See logs for the exact error.'
            result = CommandResults(outputs={}, readable_output=readable_output, raw_response=raw_response)
            results.append(result)
            continue
        if data := raw_response.get("data"):
            score = calculate_dbot_score(reputation_data=data, params=params)
            num_of_engines, num_of_positive_engines = calculate_engines(reputation_data=data)
            malicious_description = get_malicious_description(score, data, params)
            dbot_score = Common.DBotScore(
                indicator=domain,
                indicator_type=DBotScoreType.DOMAIN,
                integration_name=VENDOR_NAME,
                score=score,
                reliability=reliability,
                malicious_description=malicious_description,
            )
            if not headers:
                headers = [
                    "description",
                    "owner",
                    "status",
                    "type",
                    "raw_indicator",
                    "share_level",
                    "confidence",
                    "severity",
                    "added_on",
                    "last_updated",
                    "review_status",
                    "id",
                ]
            readable_output = tableToMarkdown(f"{CONTEXT_PREFIX} Result for domain {domain}", data, headers=headers)
            domain_indicator = Common.Domain(
                domain=domain,
                dbot_score=dbot_score,
                detection_engines=num_of_engines,
                positive_detections=num_of_positive_engines,
            )
        else:  # no data
            dbot_score = Common.DBotScore(
                indicator=domain,
                indicator_type=DBotScoreType.DOMAIN,
                integration_name=VENDOR_NAME,
                score=Common.DBotScore.NONE,
                reliability=reliability,
            )
            readable_output = f"{CONTEXT_PREFIX} does not have details about domain: {domain} \n"
            domain_indicator = Common.Domain(domain=domain, dbot_score=dbot_score)

        result = CommandResults(
            outputs_prefix=f"{CONTEXT_PREFIX}.Domain",
            outputs_key_field="id",
            outputs=data,
            indicator=domain_indicator,
            readable_output=readable_output,
            raw_response=raw_response,
        )
        results.append(result)

    return results


def url_command(client: Client, args: Dict[str, Any], params: Dict[str, Any]) -> List[CommandResults]:
    """
    Returns URL's reputation
    """
    urls = argToList(args.get("url"))
    since = convert_string_to_epoch_time(args.get("since"), arg_name="since")
    until = convert_string_to_epoch_time(args.get("until"), arg_name="until")
    limit = arg_to_number(args.get("limit"), arg_name="limit")
    headers = argToList(args.get("headers"))
    reliability = params.get("feedReliability")
    share_level = args.get("share_level", params.get("share_level", "RED"))
    demisto.debug(f"Setting share level to {share_level}")
    results: List[CommandResults] = []
    for url in urls:
        try:
            raw_response = client.url(url, since, until, share_level, limit)
        except Exception as exception:
            # If anything happens, handle like there are no results
            err_msg = f'Could not process URL: "{url}"\n {exception!s}'
            demisto.debug(err_msg)
            raw_response = {}
            readable_output = f'Processing URL "{url}" resulted in an exception. See logs for the exact error.'
            result = CommandResults(outputs={}, readable_output=readable_output, raw_response=raw_response)
            results.append(result)
            continue
        if data := raw_response.get("data"):
            score = calculate_dbot_score(reputation_data=data, params=params)
            num_of_engines, num_of_positive_engines = calculate_engines(reputation_data=data)
            malicious_description = get_malicious_description(score, data, params)
            dbot_score = Common.DBotScore(
                indicator=url,
                indicator_type=DBotScoreType.URL,
                integration_name=VENDOR_NAME,
                score=score,
                reliability=reliability,
                malicious_description=malicious_description,
            )
            if not headers:
                headers = [
                    "description",
                    "owner",
                    "status",
                    "type",
                    "raw_indicator",
                    "share_level",
                    "confidence",
                    "severity",
                    "added_on",
                    "last_updated",
                    "review_status",
                    "id",
                ]
            readable_output = tableToMarkdown(f"{CONTEXT_PREFIX} Result for URL {url}", data, headers=headers)
            url_indicator = Common.URL(
                url=url, dbot_score=dbot_score, detection_engines=num_of_engines, positive_detections=num_of_positive_engines
            )
        else:  # no data
            dbot_score = Common.DBotScore(
                indicator=url,
                indicator_type=DBotScoreType.URL,
                integration_name=VENDOR_NAME,
                score=Common.DBotScore.NONE,
                reliability=reliability,
            )
            readable_output = f"{CONTEXT_PREFIX} does not have details about URL: {url} \n"
            url_indicator = Common.URL(url=url, dbot_score=dbot_score)

        result = CommandResults(
            outputs_prefix=f"{CONTEXT_PREFIX}.URL",
            outputs_key_field="id",
            outputs=data,
            indicator=url_indicator,
            readable_output=readable_output,
            raw_response=raw_response,
        )
        results.append(result)

    return results


def members_command(client: Client) -> CommandResults:
    """
    Returns a list of current members of the ThreatExchange, alphabetized by application name.
    Each application may also include an optional contact email address.
    See Also:
          https://developers.facebook.com/docs/threat-exchange/reference/apis/threat-exchange-members/v10.0

    """
    raw_response = client.members()
    if data := raw_response.get("data"):
        headers = ["id", "name", "email"]
        readable_output = tableToMarkdown(f"{CONTEXT_PREFIX} Members: ", data, headers=headers, removeNull=True)
    else:  # no data
        readable_output = f"{CONTEXT_PREFIX} does not have any members \n"

    result = CommandResults(
        outputs_prefix=f"{CONTEXT_PREFIX}.Member",
        outputs_key_field="id",
        outputs=data,
        readable_output=readable_output,
        raw_response=raw_response,
    )
    return result


def query_command(client: Client, args: Dict[str, Any]) -> CommandResults:
    """
    Searches for subjective opinions on indicators of compromise stored in ThreatExchange.
    """
    text = str(args.get("text"))
    descriptor_type = str(args.get("type"))
    since = convert_string_to_epoch_time(args.get("since"), arg_name="since")
    until = convert_string_to_epoch_time(args.get("until"), arg_name="until")
    limit = arg_to_number(args.get("limit"), arg_name="limit")
    strict_text = argToBoolean(args.get("strict_text", False))
    headers = argToList(args.get("headers"))
    before = args.get("before")
    after = args.get("after")

    raw_response = client.query(text, descriptor_type, since, until, limit, strict_text, before, after)
    try:  # removes 'next' field to prevent access token uncovering
        del raw_response["paging"]["next"]
    except KeyError:  # for no paging cases
        pass

    if data := raw_response.get("data"):
        readable_output = tableToMarkdown(f"{CONTEXT_PREFIX} Query Result:", data, headers=headers)
        if raw_response.get("paging"):  # if paging exist - flatten the output
            outputs = flatten_outputs_paging(raw_response)
            readable_output += tableToMarkdown("Pagination:", outputs.get("paging"))
        else:  # no paging
            outputs = raw_response

    else:  # no data
        readable_output = f"{CONTEXT_PREFIX} does not have details about {descriptor_type}: {text} \n"
        outputs = raw_response

    outputs["text"] = text
    outputs["type"] = descriptor_type

    result = CommandResults(
        outputs_prefix=f"{CONTEXT_PREFIX}.Query",
        outputs_key_field=["text", "type"],
        outputs=outputs,
        readable_output=readable_output,
        raw_response=raw_response,
    )

    return result


def tags_search_command(client: Client, args: Dict[str, Any]) -> CommandResults:
    """
    Enables searching for tags in ThreatExchange.
    With this call you can search for ThreatTag objects by text.
    See Also:
        https://developers.facebook.com/docs/threat-exchange/reference/apis/threattags/v10.0
    """
    text = str(args.get("text"))
    before = args.get("before")
    after = args.get("after")

    raw_response = client.tags_search(text, before, after)
    try:  # removes 'next' field to prevent access token uncovering
        del raw_response["paging"]["next"]
    except KeyError:  # for no paging cases
        pass

    if data := raw_response.get("data"):
        readable_output = tableToMarkdown(f"{CONTEXT_PREFIX} Tags: ", data, removeNull=True)
        if raw_response.get("paging"):  # if paging exist - flatten the output
            outputs = flatten_outputs_paging(raw_response)
            readable_output += tableToMarkdown("Pagination:", outputs.get("paging"))
        else:  # no paging
            outputs = raw_response

    else:  # no data
        readable_output = f"{CONTEXT_PREFIX} does not have any tags for text: {text} \n"
        outputs = raw_response

    outputs["text"] = text

    result = CommandResults(
        outputs_prefix=f"{CONTEXT_PREFIX}.Tag",
        outputs_key_field="text",
        outputs=outputs,
        readable_output=readable_output,
        raw_response=raw_response,
    )
    return result


def tagged_objects_list_command(client: Client, args: Dict[str, Any]) -> CommandResults:
    """
    Gets a list of tagged objects for a specific ThreatTag.
    See Also:
        https://developers.facebook.com/docs/threat-exchange/reference/apis/threat-tags/v10.0
    """
    tag_id = str(args.get("tag_id"))
    tagged_since = arg_to_number(args.get("tagged_since"), arg_name="tagged_since")
    tagged_until = arg_to_number(args.get("tagged_until"), arg_name="tagged_until")
    before = args.get("before")
    after = args.get("after")

    raw_response = client.tagged_objects_list(tag_id, tagged_since, tagged_until, before, after)
    try:  # removes 'next' field to prevent access token uncovering
        del raw_response["paging"]["next"]
    except KeyError:  # for no paging cases
        pass

    if data := raw_response.get("data"):
        readable_output = tableToMarkdown(f"{CONTEXT_PREFIX} Tagged Objects for ThreatTag: {tag_id}", data, removeNull=True)
        if raw_response.get("paging"):  # if paging exist - flatten the output
            outputs = flatten_outputs_paging(raw_response)
            readable_output += tableToMarkdown("Pagination:", outputs.get("paging"))
        else:  # no paging
            outputs = raw_response

    else:  # no data
        readable_output = f"{CONTEXT_PREFIX} does not have any tagged objects for ThreatTag: {tag_id} \n"
        outputs = raw_response

    outputs["tag_id"] = tag_id

    result = CommandResults(
        outputs_prefix=f"{CONTEXT_PREFIX}.TaggedObject",
        outputs_key_field="tag_id",
        outputs=outputs,
        readable_output=readable_output,
        raw_response=raw_response,
    )
    return result


def object_get_by_id_command(client: Client, args: Dict[str, Any]) -> CommandResults:
    """
    Gets ThreatExchange object by ID.
    """
    object_id = str(args.get("object_id"))

    raw_response = client.object_get_by_id(object_id)
    if raw_response:
        readable_output = tableToMarkdown(f"{CONTEXT_PREFIX} Object {object_id}:", raw_response, removeNull=True)
    else:  # no data
        readable_output = f"{CONTEXT_PREFIX} does not have any object with ID: {object_id} \n"

    result = CommandResults(
        outputs_prefix=f"{CONTEXT_PREFIX}.Object",
        outputs_key_field="id",
        outputs=raw_response,
        readable_output=readable_output,
        raw_response=raw_response,
    )
    return result


def main():
    """
    main function, parses params and runs command functions
    """
    command = demisto.command()
    params = demisto.params()
    args = demisto.args()

    app_id_obj = params.get("app_id")
    app_id = app_id_obj["identifier"]
    app_secret = app_id_obj["password"]
    access_token = f"{app_id}|{app_secret}"
    base_url = "https://graph.facebook.com/v3.2"
    verify_certificate = not argToBoolean(params.get("insecure", False))
    proxy = argToBoolean(params.get("proxy", False))
    handle_proxy()

    demisto.debug(f"Command being called is {command}")
    try:
        client = Client(base_url=base_url, access_token=access_token, verify=verify_certificate, proxy=proxy)
        result: Union[str, CommandResults, List[CommandResults]]
        if command == "test-module":
            result = test_module(client)
        elif command == "ip":
            result = ip_command(client, args, params)
        elif command == "file":
            result = file_command(client, args, params)
        elif command == "domain":
            result = domain_command(client, args, params)
        elif command == "url":
            result = url_command(client, args, params)
        elif command == f"{COMMAND_PREFIX}-members":
            result = members_command(client)
        elif command == f"{COMMAND_PREFIX}-query":
            result = query_command(client, args)
        elif command == f"{COMMAND_PREFIX}-tags-search":
            result = tags_search_command(client, args)
        elif command == f"{COMMAND_PREFIX}-tagged-objects-list":
            result = tagged_objects_list_command(client, args)
        elif command == f"{COMMAND_PREFIX}-object-get-by-id":
            result = object_get_by_id_command(client, args)
        else:
            raise NotImplementedError(f"Command {command} is not implemented")
        return_results(result)
    # Log exceptions and return errors
    except Exception as e:
        demisto.error(traceback.format_exc())  # print the traceback
        return_error(f"Failed to execute {command} command.\nError:\n{e!s}")


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