ThreatConnect Feed

This integration fetches indicators from ThreatConnect.

Data Enrichment & Threat Intelligence · ThreatConnect Feed · Feed

Details

IDThreatConnect Feed
ProviderDataminr
CategoryData Enrichment & Threat Intelligence
From Version5.5.0
Docker Imagedemisto/python3:3.12.13.10399583
Supported ModulesAgentix XSIAM

README

This integration fetches indicators from ThreatConnect.
This integration was integrated and tested with version 3 of ThreatConnect Feed.

ThreatConnect Feed HMAC credentials

  1. On the top navigation bar, hover the cursor over the Settings icon and select Org Settings from the dropdown menu.
  2. Click the Create API User button on the Membership tab of the Organization Settings screen, and the API User Administration window will be displayed.
  3. Fill up the following parts of the form:
    • First Name: Enter the API user’s first name.
    • Last Name: Enter the API user’s last name.
    • Organization Role: Use the dropdown menu to select an Organization role for the user.
    • Include in Observations and False Positives: Check this box to allow data provided by the API user to be included in observation and false-positive counts.
    • Disabled: Click the checkbox to disable an API user’s account in the event that the Administrator wishes to retain log integrity when the API user no longer requires ThreatConnect access.
  4. Record the Secret Key, as it will not be accessible after the window is closed.
  5. Click the SAVE button to create the API user account.

For more information - click here (Section - Creating an API User).

Configure ThreatConnect Feed on Cortex XSOAR

  1. Navigate to one of the following:
    Cortex XSOAR 8: Settings & Info > Settings > Integrations > Instances.
    Cortex XSOAR 6: Settings > Integrations.
    Cortex XSIAM: Settings > Configurations > Automation & Feed Integrations.
  2. Search for ThreatConnect Feed.
  3. Click Add instance to create and configure a new integration instance.

    Parameter Description Required
    Base URL The API URL. True
    Access ID The API ID key True
    Secret key The secret key for the API. True
    Indicators to get per fetch The number of indicators to get per fetch. False
    Tags to filter results by A comma-separated list of tags to filter by. False
    Owners A comma-separated list of owners to fetch indicators from. False
    Fetch indicators   False
    Indicator Reputation Indicators from this integration instance will be marked with this reputation False
    Source Reliability Reliability of the source providing the intelligence data. True
    Traffic Light Protocol Color The Traffic Light Protocol (TLP) designation to apply to indicators fetched from the feed. False
    Feed Fetch Interval The time interval we send request to fetch indicators. False
    Trust any certificate (not secure) Whether to trust any certificate. False
    Bypass exclusion list When selected, the exclusion list is ignored for indicators from this feed. This means that if an indicator from this feed is on the exclusion list, the indicator might still be added to the system. False
    Indicator types Fetch specific ThreatConnect indicator types. Default value is “All”. False
    Group types Fetch specific ThreatConnect group types. Default value is “All”. False
    Active Indicators Only Fetch active only indicators when true. Default is “True”. False
    Create Relationships Fetch related indicators. Default is “False”. False
    Indicator Query Filter results using ThreatConnect Query Language (TQL). For more information, see the ThreatConnect documentation https://training.threatconnect.com/learn/article/using-threatconnect-query-language-tql-kb-article False
    Use Indicator Query For Group Use the ThreatConnect Indicator Query for group filtering. Default is “True”. False
    Group Query Filter results using ThreatConnect Query Language (TQL). For more information, see the ThreatConnect documentation https://training.threatconnect.com/learn/article/using-threatconnect-query-language-tql-kb-article False
    Confidence Threshold Minimal confidence value to fetch indicators by (an integer between 0 to 100). Note: this parameter is not relevant for groups. False
    Threat Assess Score Threshold Minimal threat assess score value to fetch indicators by (an integer between 0 to 1000). Note: this parameter is not relevant for groups. False
  4. Click Test to validate the URLs, token, and connection.

Commands

You can execute these commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

tc-get-indicators


Gets indicators from ThreatConnect.

Base Command

tc-get-indicators

Input

Argument Name Description Required
owners Comma-separated list of owners to fetch indicators from. (If not specified will retrieve only indicators owned by the account. If you supply this argument, it overwrites the “Owners” parameter.). Optional
limit The maximum number of indicators to retrieve. Default is 50. Optional
offset The index of the first indicator to fetch. Possible values are: . Default is 0. Optional
tql_query Filter results using ThreatConnect Query Language (TQL), will override all other arguments. For more information, see the ThreatConnect documentation https://training.threatconnect.com/learn/article/using-threatconnect-query-language-tql-kb-article. Optional
indicator_type Comma-separated list that will allow filtering of the retrieved indicators. Possible values are: All, EmailAddress, File, Host, URL, ASN, CIDR, Mutex, Registry Key, Address. Optional
active_indicators If true, fetches only active indicators. Possible values are: true, false. Default is true. Optional
confidence This will fetch indicators with confidence of “greater than” the (integer) input. Optional
threat_assess_score An integer that will determine the threshold (an integer between 0 to 1000). Optional

Context Output

There is no context output for this command.

Command example

!tc-get-indicators limit=1 offset=0 indicator_type=URL active_indicators=true

Human Readable Output

ThreatConnect Feed - Indicators

fields rawJSON relationships score type value
firstseenbysource: 2022-09-27T17:20:19Z
updateddate: 2022-09-27T17:20:19Z
description: This indicator appears in a post from VirIT.
name: name
address: address
reportedby: Technical Blogs and Reports
id: 98590287
ownerName: Technical Blogs and Reports
dateAdded: 2022-09-27T17:20:19Z
webLink: link
type: URL
lastModified: 2022-09-27T17:20:19Z
rating: 3.0
confidence: 70
source: source
description: This indicator appears in a post from VirIT.
summary: address/
privateFlag: false
active: true
activeLocked: false
text: address
  0 URL address

Command Example

!tc-get-indicators limit=2 offset=0

Human Readable Output

rawJSON score type value
id: Indicator01 2 IP 8.8.8.8
id: Indicator02 3 IP 8.8.4.4

tc-get-owners


Gets available indicators owners.

Base Command

tc-get-owners

Input

There are no input arguments for this command.

Context Output

There is no context output for this command.

Human Readable Output

ThreatConnect Feed - Owners

id name type
10303 NAME-01 Organization
10666 NAME-02 Source

Configuration parameters

  • tc_api_path — Base URL (required)
  • api_credentials — Access ID (required)
  • api_access_id — Access ID
  • api_secret_key — Secret key
  • fetch_limit — Indicators to get per fetch
  • tags — Tags to filter results by
  • owners — Owners
  • feedIncremental — Incremental feed
  • feed — Fetch indicators
  • feedReputation — Indicator Reputation
  • feedReliability — Source Reliability (required)
  • tlp_color — Traffic Light Protocol Color
  • indicator_type — Indicator types
  • group_type — Group types
  • indicator_active — Active Indicators Only
  • createRelationships — Create Relationships
  • indicator_query — Indicator Query
  • use_indicator_query_for_group — Use Indicator Query For Group
  • group_query — Group Query
  • confidence — Confidence Threshold
  • threat_assess_score — Threat Assess Score Threshold
  • feedExpirationPolicy
  • feedExpirationInterval
  • feedFetchInterval — Feed Fetch Interval
  • insecure — Trust any certificate (not secure)
  • feedBypassExclusionList — Bypass exclusion list
  • feedTags — Tags

Commands (2)

  • tc-get-indicators

    Gets indicators from ThreatConnect.

  • tc-get-owners

    Gets available indicators owners.

import json

import demistomock as demisto
import pytest
from CommonServerPython import *
from FeedThreatConnect import (
    build_url_with_query_params,
    create_indicator_fields,
    create_or_query,
    create_types_query,
    get_indicator_value,
    get_updated_last_run,
    parse_indicator,
    set_fields_query,
    set_tql_query,
    should_send_request,
)
from pytest_mock import MockerFixture


def load_json_file(path):
    with open(path) as _json_file:
        return json.load(_json_file)


@pytest.mark.parametrize(
    argnames="threatconnect_score, dbot_score", argvalues=[(1000, 3), (830, 3), (664, 2), (498, 2), (332, 1), (166, 1), (0, 0)]
)
def test_calculate_dbot_score(threatconnect_score, dbot_score):
    from FeedThreatConnect import calculate_dbot_score

    assert calculate_dbot_score(threatconnect_score) == dbot_score


def test_parse_indicator(mocker):
    mocker.patch.object(demisto, "params", return_value={"createRelationships": True, "tlpcolor": None})
    data_dir = {
        "parsed_indicator.json": "./test_data/parsed_indicator.json",  # type: ignore # noqa
        "indicators.json": "./test_data/indicators.json",
    }  # type: ignore # noqa
    indicator = parse_indicator(load_json_file(data_dir["indicators.json"]))
    assert load_json_file(data_dir["parsed_indicator.json"]) == indicator


def test_create_or_query():
    assert create_or_query("test", "1,2,3,4,5") == 'test="1" OR test="2" OR test="3" OR test="4" OR test="5" '


@pytest.mark.parametrize(
    "params, expected_result, endpoint",
    [
        (
            {
                "indicator_active": False,
                "indicator_type": ["All"],
                "createRelationships": False,
                "confidence": 0,
                "threat_assess_score": 0,
            },
            'typeName IN ("EmailAddress","File","Host","URL","ASN","CIDR","Hashtag","Mutex","Registry Key",'
            '"User Agent","Address")',
            "indicators",
        ),  # noqa: E501
        (
            {
                "indicator_active": True,
                "group_type": ["File"],
                "createRelationships": False,
                "confidence": 0,
                "threat_assess_score": 0,
            },
            'typeName IN ("File")',
            "groups",
        ),
        (
            {
                "indicator_active": False,
                "group_type": ["Tool"],
                "createRelationships": False,
                "confidence": 50,
                "threat_assess_score": 80,
            },
            'typeName IN ("Tool")',
            "groups",
        ),
    ],
)
def test_set_tql_query(params, expected_result, endpoint):
    """
    Given:
        - an empty from_date value and demisto params
        Case 1: expecting no tql query
        Case 2: expecting a specific group type, and only active indicators

    When:
        - running set_tql_query command

    Then:
        - validate the tql output
    """
    from_date = ""
    output = set_tql_query(from_date, params, endpoint)

    assert output == expected_result


@pytest.mark.parametrize(
    "params, expected_result, endpoint",
    [
        (
            {"group_type": ["All"], "indicator_type": []},
            'typeName IN ("Attack Pattern","Campaign",'
            '"Course of Action","Intrusion Set","Malware","Report","Tool","Vulnerability")',
            "groups",
        ),
        ({"group_type": ["File"], "indicator_type": []}, 'typeName IN ("File")', "groups"),
        ({"group_type": ["File"], "indicator_type": ["All"]}, 'typeName IN ("File")', "groups"),
    ],
)
def test_create_types_query(params, expected_result, endpoint):
    """
    Given:
        - demisto params and an endpoint
    When:
        - running create_types_query command
    Then:
        - validate the output
    """
    output = create_types_query(params, endpoint)

    assert output == expected_result


@pytest.mark.parametrize(
    "params, expected_result, endpoint",
    [
        ({"group_type": ["All"], "indicator_type": []}, False, "indicators"),
        ({"group_type": [], "indicator_type": ["All"]}, True, "indicators"),
    ],
)
def test_should_send_request(params, expected_result, endpoint):
    """
    Given:
        - demisto params and an endpoint
    When:
        - running should_send_request command
    Then:
        - validate the result
    """
    output = should_send_request(params, endpoint)

    assert output == expected_result


@pytest.mark.parametrize(
    "params, expected_result, endpoint",
    [
        pytest.param(
            {
                "indicator_type": ["All"],
                "indicator_query": "",
                "group_query": "",
                "use_indicator_query_for_group": True,
                "createRelationships": False,
            },
            "/api/v3/indicators?tql=indicatorActive%20EQ%20True&fields=tags&fields=threatAssess&resultStart"
            "=0&resultLimit=100&sorting=dateAdded%20ASC",
            "indicators",
            id="indicators with indicator query",
        ),
        pytest.param(
            {
                "group_type": ["All"],
                "indicator_query": "indicatorActive EQ False",
                "group_query": "",
                "use_indicator_query_for_group": True,
                "createRelationships": True,
            },
            "/api/v3/groups?tql=indicatorActive%20EQ%20False&fields=tags&fields=associatedGroups"
            "&fields=associatedIndicators&resultStart=0&resultLimit=100&sorting=dateAdded%20ASC",
            "groups",
            id="groups with indicator query",
        ),
        pytest.param(
            {
                "group_type": ["All"],
                "indicator_query": "",
                "group_query": "Test",
                "use_indicator_query_for_group": True,
                "createRelationships": False,
            },
            "/api/v3/groups?tql=indicatorActive%20EQ%20True&fields=tags&resultStart=0&resultLimit=100&sorting=dateAdded%20ASC",
            "groups",
            id="groups with default query",
        ),
        pytest.param(
            {
                "group_type": ["All"],
                "indicator_query": "indicatorActive EQ False",
                "group_query": "Test",
                "use_indicator_query_for_group": False,
                "createRelationships": True,
            },
            "/api/v3/groups?tql=Test&fields=tags&fields=associatedGroups"
            "&fields=associatedIndicators&resultStart=0&resultLimit=100&sorting=dateAdded%20ASC",
            "groups",
            id="groups with group query",
        ),
    ],
)
def test_build_url_with_query_params(mocker: MockerFixture, params: dict, expected_result: str, endpoint: str):
    """
    Given:
        - demisto params and an endpoint
    When:
        - running build_url_with_query_params command
    Then:
        - validate the result
    """
    mocker.patch("FeedThreatConnect.set_tql_query", return_value="indicatorActive EQ True")
    # Determine the tql parameter based on endpoint and params
    if endpoint == "indicators":
        tql = params.get("indicator_query") or None
    else:  # groups
        tql = params.get("group_query") if not params.get("use_indicator_query_for_group") else params.get("indicator_query")

    output = build_url_with_query_params(params, endpoint, {}, tql)

    assert output == expected_result


@pytest.mark.parametrize(
    "params, expected_result, endpoint",
    [
        ({"createRelationships": False}, "&fields=tags&fields=threatAssess", "indicators"),
        ({"createRelationships": True}, "&fields=tags&fields=associatedGroups&fields=associatedIndicators", "groups"),
    ],
)
def test_set_fields_query(params, expected_result, endpoint):
    """
    Given:
        - demisto params and an endpoint
    When:
        - running set_fields_query command
    Then:
        - validate the result
    """
    output = set_fields_query(params, endpoint)

    assert output == expected_result


@pytest.mark.parametrize(
    "indicators, groups, previous_run, expected_result",
    [
        (
            [{"dateAdded": "dateAdded"}],
            [{"dateAdded": "dateAdded"}],
            {},
            {"indicators": {"from_date": "dateAdded"}, "groups": {"from_date": "dateAdded"}},
        ),
        (
            (
                [{"dateAdded": "dateAdded"}],
                [],
                {"groups": {"from_date": "from_date"}},
                {"indicators": {"from_date": "dateAdded"}, "groups": {"from_date": "from_date"}},
            )
        ),
    ],
)
def test_get_updated_last_run(indicators, groups, previous_run, expected_result):
    """
    Given:
        - list of indicators, list of groups, and a previouse run
    When:
        - running get_updated_last_run command
    Then:
        - validate the result
    """
    output = get_updated_last_run(indicators, groups, previous_run)

    assert output == expected_result


def test_create_indicator_fields_registry_key():
    """
    Given:
        - lindicator from type Registry Key
    When:
        - running create_indicator_fields command
    Then:
        - validate the result contains the 'Key Value' key and the expected data
    """
    indicator = {
        "Key Name": "key name",
        "Value Name": "value name",
        "Key Type": "key type",
        "dateAdded": "firstseenbysource",
        "lastModified": "updateddate",
        "threatAssessRating": "verdict",
        "threatAssessConfidence": "confidence",
        "description": "description",
        "summary": "name",
    }

    result = create_indicator_fields(indicator, "Registry Key")

    assert "Key Value" in result
    assert "name" in result.get("Key Value")[0]
    assert result.get("Key Value")[0].get("name") == "key name"


def test_get_indicator_value_for_file():
    """
    Given:
        An indicator dictionary with file hashes.
    When:
        The indicator type is 'File'.
    Then:
        It should return the sha256 hash if present, else sha1, else md5.
    """
    indicator = {"sha256": "sha256_hash", "sha1": "sha1_hash", "md5": "md5_hash"}
    indicator_type = FeedIndicatorType.File
    indicator_value = get_indicator_value(indicator, indicator_type)
    assert indicator_value == "sha256_hash"

    # Test when sha256 is not present
    del indicator["sha256"]
    indicator_value = get_indicator_value(indicator, indicator_type)
    assert indicator_value == "sha1_hash"

    # Test when sha256 and sha1 are not present
    del indicator["sha1"]
    indicator_value = get_indicator_value(indicator, indicator_type)
    assert indicator_value == "md5_hash"


def test_get_indicator_value_for_non_file(mocker: MockerFixture):
    """
    Given:
        An indicator dictionary without file hashes.
    When:
        The indicator type is not 'File'.
    Then:
        It should return the summary if present, else name.
    """
    indicator = {"summary": "indicator_summary", "name": "indicator_name"}
    indicator_type = "IP"
    indicator_value = get_indicator_value(indicator, indicator_type)
    assert indicator_value == "indicator_summary"

    # Test when summary is not present
    mocker.patch.dict(indicator, {"summary": None})
    indicator_value = get_indicator_value(indicator, indicator_type)
    assert indicator_value == "indicator_name"