TAXII 2 Feed

Ingests indicator feeds from TAXII 2.0 and 2.1 servers.

Data Enrichment & Threat Intelligence · TAXII Feed · Feed

Details

IDTAXII 2 Feed
ProviderOpen Source
CategoryData Enrichment & Threat Intelligence
From Version5.5.0
Docker Imagedemisto/taxii2:1.0.0.11040999
Supported ModulesAgentix XSIAM

README

Ingest indicator feeds from TAXII 2.0 and 2.1 servers.

Configure TAXII 2 Feed in Cortex

Parameter Description Required
Name   True
Discovery Service URL (e.g. https://example.net/taxii)   True
Username / API Key   False
Password   False
API Root to Use The API root to use (for example default or public). If left empty, the server default API root is used. If the server has no default root, the first available API root is used instead. False
Collection Name To Fetch Indicators From Indicators will be fetched from this collection. Run “taxii2-get-collections” command to get a valid value. If left empty, the instance will try to fetch from all the collections in the given discovery service. False
Certificate File as Text Add a certificate file as text to connect to the TAXII server. False
Key File as Text Add a key file as text to connect to the TAXII server False
Run on Single engine   False
Trust any certificate (not secure) Located under Advanced Settings. False
Use system proxy settings Located under Advanced Settings. False
Log Level Debug/Verbose logging is recommended only during troubleshooting. Logging can affect integration performance. Recommended usage is to turn logging on during setup and troubleshooting, and then turn it off in production. These settings only affect the integration log. The server log is not affected. False
Do not use in CLI by default   False
Fetch indicators   False
Classifier Determines the type of incident that is created for events ingested from this integration instance. False
Mapper (incoming) Determines how event fields are mapped to the Cortex XSOAR incident fields. 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
Max Indicators Per Fetch (disabled for Full Feed Fetch) The maximum number of indicators that can be fetched per fetch. If this field is left empty, there will be no limit on the number of indicators fetched. False
First Fetch Time The time interval for the first fetch (retroactive). <number> <time unit> of type minute/hour/day/year. For example, 1 minute, 12 hour False
STIX Objects To Fetch Which STIX objects to fetch from the TAXII server. If left empty, all available object types will be fetched. False
Max STIX Objects Per Poll Set the number of stix object that will be requested with each TAXII poll (http request). A single fetch is made of several taxii polls. Changing this setting can help speed up fetches, or fix issues on slower networks. Please note server restrictions may apply, overriding and limiting the “requested limit”. False
Enrichment Excluded Select this option to exclude the fetched indicators from the enrichment process. False
Indicator Expiration Method   False
Feed Fetch Interval   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
Incremental Feed Incremental feeds pull only new or modified indicators that have been sent from the integration. As the determination if the indicator is new or modified happens on the 3rd-party vendor’s side, and only indicators that are new or modified are sent to Cortex XSOAR, all indicators coming from these feeds are labeled new or modified. False
Full Feed Fetch When enabled, fetch-indicators will try to fetch the entire feed for every fetch. When disabled, fetch-indicators will try to fetch just the latest entries (since the last fetch). False
Complex Observation Mode Choose how to handle complex observations. Two or more observation expressions can be combined using a complex observation operator such as “AND”, “OR”. e.g. `[ IP = ‘b’ ] AND [ URL = ‘d’ ]` False
Update custom fields Choose whether to import the XSOAR custom fields. Note: this might overwrite the data pulled from other sources. False
Tags Supports CSV values. False

Using API token authentication

To use the integration with an API token you first need to change the Username / API Key (see '?') field to _api_token_key. You can then enter the API Token into the Password field - this value will be used as an API key.

Using a custom authentication header

If the TAXII 2 server you are trying to connect to requires a custom authentication header, you first need to change the Username / API Key (see '?') field to _header: and the custom header name, e.g. _header:custom_auth. You can then enter the custom auth header value into the Password field - this value will be used as a custom auth header.

Complex Observation Mode consideration

You can combine two or more observation expressions using a complex observation operator such as “AND”, “OR”, and “FOLLOWEDBY”, for example [ IP = 'b' ] AND [ URL = 'd' ]. These relationships are not represented in CORTEX XSOAR TIM indicators. You can create them while ignoring these relations, or you can ignore these expressions. If you choose to ignore these expressions, then no indicator will be created for complex observations.

Enrichment Excluded consideration

Setting the Traffic Light Protocol Color to red automatically excludes enrichment, even if Enrichment Excluded is unchecked.

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.

taxii2-get-indicators


Allows you to test your feed and to make sure you can fetch indicators successfuly.

Base Command

taxii2-get-indicators

Input

Argument Name Description Required
raw Will return only the rawJSON of the indicator object. Optional
limit Maximum number of indicators to fetch. Optional
added_after Fetch only indicators that were added to the server after the given time. Please provide a <number> and <time unit> of type minute/hour/day. For example, 1 minute, 12 hour, 24 days. Optional

Context Output

Path Type Description
TAXII2.Indicators.type String Indicator type.
TAXII2.Indicators.value String Indicator value.
TAXII2.Indicators.rawJSON String Indicator rawJSON.

Command Example

!taxii2-get-indicators limit=3

Human Readable Output

value type
coronashop.jp Domain
e6ecb146f469d243945ad8a5451ba1129c5b190f7d50c64580dbad4b8246f88e File
2014[.]zzux[.]com Domain

taxii2-get-collections


Gets the list of collections from the discovery service.

Base Command

taxii2-get-collections

Input

There are no input arguments for this command.

Context Output

Path Type Description
TAXII2.Collections.ID String Collection ID.
TAXII2.Collections.Name String Collection Name.

Command Example

!taxii2-get-collections

Human Readable Output

Name ID
Phish Tank 107
Abuse.ch Ransomware IPs 135
Abuse.ch Ransomware Domains 136
DShield Scanning IPs 150
Malware Domain List - Hotlist 200
Blutmagie TOR Nodes 209
Emerging Threats C&C Server 31
DT COVID-19 313
Lehigh Malwaredomains 33
CyberCrime 41
Emerging Threats - Compromised 68

taxii2-reset-fetch-indicators


WARNING: This command will reset your fetch history.

Base Command

taxii2-reset-fetch-indicators

Input

There are no input arguments for this command.

Context Output

There is no context output for this command.

Command Example

!taxii2-reset-fetch-indicators

Human Readable Output

Fetch was reset successfully. Your next indicator fetch will collect indicators from the configured “First Fetch Time”

Troubleshooting

When the feed is set to “Incremental Feed”, we recommend specifying a value for the Max Indicators Per Fetch parameter to prevent potential timeout issues.

Configuration parameters

  • feed — Fetch indicators
  • feedReputation — Indicator Reputation
  • feedReliability — Source Reliability (required)
  • tlp_color — Traffic Light Protocol Color
  • feedExpirationPolicy
  • feedExpirationInterval
  • feedFetchInterval — Feed Fetch Interval
  • feedBypassExclusionList — Bypass exclusion list
  • url — Discovery Service URL (e.g. https://example.net/taxii2) (required)
  • credentials — Username / API Key
  • default_api_root — API Root to Use
  • collection_to_fetch — Collection Name To Fetch Indicators From
  • feedIncremental — Incremental Feed
  • fetch_full_feed — Full Feed Fetch
  • limit — Max Indicators Per Fetch (disabled for Full Feed Fetch)
  • initial_interval — First Fetch Time
  • objects_to_fetch — STIX Objects To Fetch
  • creds_certificate — Certificate File as Text
  • certificate — Certificate File as Text
  • key — Key File as Text
  • limit_per_request — Max STIX Objects Per Poll
  • observation_operator_mode — Complex Observation Mode
  • update_custom_fields — Update custom fields
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • feedTags — Tags
  • enrichmentExcluded — Enrichment Excluded

Commands (3)

  • taxii2-get-collections

    Gets the list of collections from the discovery service.

  • taxii2-get-indicators

    Allows you to test your feed and to make sure you can fetch indicators successfuly.

  • taxii2-reset-fetch-indicators Deprecated

    WARNING: This command will reset your fetch history.

from typing import Any
from urllib.parse import urlparse

import demistomock as demisto
from CommonServerPython import *

from CommonServerUserPython import *

""" CONSTANT VARIABLES """


CONTEXT_PREFIX = "TAXII2"
COMPLEX_OBSERVATION_MODE_SKIP = "Skip indicators with more than a single observation"
COMPLEX_OBSERVATION_MODE_CREATE_ALL = "Create indicator for each observation"

""" HELPER FUNCTIONS """


def try_parse_integer(int_to_parse: Any, err_msg: str = "Please provide a valid limit (positive integer)") -> int:
    """
    Tries to parse an integer, and if fails will throw DemistoException with given err_msg
    """
    if not int_to_parse:
        return int_to_parse
    try:
        res = int(int_to_parse)
    except (TypeError, ValueError):
        raise DemistoException(err_msg)
    return res


def assert_incremental_feed_params(fetch_full_feed, is_incremental_feed):
    if fetch_full_feed == is_incremental_feed:
        toggle_value = "enabled" if fetch_full_feed else "disabled"
        raise DemistoException(f"'Full Feed Fetch' cannot be {toggle_value} when 'Incremental Feed' is {toggle_value}.")


""" COMMAND FUNCTIONS """


def module_test_command(client, limit, fetch_full_feed):
    if client.collections:
        if fetch_full_feed and limit and limit != -1:
            return_error("Configuration Error - Max Indicators Per Fetch is disabled when Full Feed Fetch is enabled")
        demisto.results("ok")
    else:
        return_error("Could not connect to server")


def filter_previously_fetched_indicators(indicators: list, last_run: dict) -> list:
    """
    Filter the indicators returned from the taxii server, by taking out indicators that we're ingested in the previous fetch
    call and their modified date was not updated,
    """
    last_indicators = last_run.get("latest_indicators")  # indicators from prev fetch
    new_indicators: list = []
    skipped_indicators: list = []
    if not last_indicators:  # first fetch
        last_run["latest_indicators"] = [
            {obj.get("rawJSON", {}).get("id"): obj.get("rawJSON", {}).get("modified")}
            if obj.get("value") != "$$DummyIndicator$$"
            else obj
            for obj in indicators
        ]
        demisto.debug("with first fetch, updated the latest_indicators")
        return indicators
    for indicator in indicators:
        indicator_id = indicator.get("rawJSON", {}).get("id")

        # check if the indicator is stored in latest_indicators
        saved_indicator = list(filter(lambda ind: indicator_id in ind, last_indicators))

        # if the indicator is stored in latest_indicators -> check if it was modified
        if saved_indicator:
            saved_modified_date = saved_indicator[0].get(indicator_id)
            new_modified_date = indicator.get("rawJSON", {}).get("modified")

            # if indicator stored in saved indicators but does not have modified field -> add to new_indicators
            if not saved_modified_date or not new_modified_date:
                demisto.debug(
                    f"saved indicator's modified value: {saved_modified_date}, "
                    f"new indicator's modified value is: {new_modified_date}"
                )
                new_indicators.append(indicator)
            # the indicator is stored in latest_indicators, but got modified -> add to new_indicators
            elif new_modified_date > saved_modified_date:
                new_indicators.append(indicator)
            else:
                skipped_indicators.append(indicator_id)

        # the indicator is not stored in latest_indicators -> add to new_indicators
        else:
            new_indicators.append(indicator)

    if skipped_indicators:
        demisto.info(f"{len(skipped_indicators)} indicators were already ingested in the previous fetch...skipping.")
        demisto.debug(f"Skipped indicators: {skipped_indicators}")

    demisto.debug(f"found {len(new_indicators)} new indicators from {len(indicators)} fetched indicators")

    # updated lastrun with the indicators fetched in the current round
    last_run["latest_indicators"] = [
        {obj.get("rawJSON", {}).get("id"): obj.get("rawJSON", {}).get("modified")}
        if obj.get("value") != "$$DummyIndicator$$"
        else obj
        for obj in indicators
    ]

    return new_indicators


def fetch_indicators_command(
    client,
    initial_interval,
    limit,
    last_run_ctx,
    fetch_full_feed: bool = False,
) -> tuple[list, dict]:
    """
    Fetch indicators from TAXII 2 server
    :param client: Taxii2FeedClient
    :param initial_interval: initial interval in parse_date_range format
    :param limit: upper limit of indicators to fetch
    :param last_run_ctx: last run dict with {collection_id: last_run_time string}
    :param fetch_full_feed: when set to true, will ignore last run, and try to fetch the entire feed
    :return: indicators in cortex TIM format
    """
    if initial_interval:
        initial_interval, _ = parse_date_range(initial_interval, date_format=TAXII_TIME_FORMAT)

    last_fetch_time = last_run_ctx.get(client.collection_to_fetch.id) if client.collection_to_fetch else None

    if not client.collection_to_fetch:
        # fetch all collections
        if client.collections is None:
            raise DemistoException(ERR_NO_COLL)
        indicators: list = []
        pass_collection, fail_collection = 0, 0
        for collection in client.collections:
            try:
                client.collection_to_fetch = collection
                added_after = get_added_after(fetch_full_feed, initial_interval, last_run_ctx.get(collection.id))
                fetched_iocs = client.build_iterator(limit, added_after=added_after)
                demisto.debug(f"fetched {len(fetched_iocs)} iocs from {collection} collection")
                indicators.extend(fetched_iocs)
                last_run_ctx[collection.id] = (
                    client.last_fetched_indicator__modified if client.last_fetched_indicator__modified else added_after
                )
                if limit >= 0:
                    limit -= len(fetched_iocs)
                    if limit <= 0:
                        break
                pass_collection += 1
            except Exception as e:
                demisto.updateModuleHealth({"message": f"Error fetching collection {collection.id}: {str(e)}"}, is_error=True)
                demisto.error(f"Failed to fetch IOCs from collection {collection.id}: {str(e)}")
                fail_collection += 1
                continue
            demisto.debug(f"{pass_collection} collections successfully fetched. {fail_collection} collections failed.")
    else:
        # fetch from a single collection
        added_after = get_added_after(fetch_full_feed, initial_interval, last_fetch_time)
        indicators = client.build_iterator(limit, added_after=added_after)
        demisto.debug(f"fetched {len(indicators)} iocs")
        last_run_ctx[client.collection_to_fetch.id] = (
            client.last_fetched_indicator__modified if client.last_fetched_indicator__modified else added_after
        )

    indicators = filter_previously_fetched_indicators(indicators, last_run_ctx)
    demisto.debug(f"{indicators=}")
    return indicators, last_run_ctx


def get_added_after(fetch_full_feed, initial_interval, last_fetch_time=None):
    """
    Creates the added_after param, or extracts it from the filter_args
    :param fetch_full_feed: when set to true, will limit added_after
    :param initial_interval: initial_interval if no
    :param last_fetch_time: last_fetch time value (str)
    :return: added_after
    """
    if fetch_full_feed:
        return initial_interval

    return last_fetch_time or initial_interval


def get_indicators_command(client, raw="false", limit=10, added_after=None):
    """
    Fetch indicators from TAXII 2 server
    :param client: Taxii2FeedClient
    :param raw: When set to "true" will return only rawJSON
    :param limit: upper limit of indicators to fetch
    :param (Optional) added_after: added after time string in parse_date_range format
    :return: indicators in cortex TIM format
    """

    limit = try_parse_integer(limit)
    if added_after:
        added_after, _ = parse_date_range(added_after, date_format=TAXII_TIME_FORMAT)
    raw = argToBoolean(raw)

    if not client.collection_to_fetch:
        # fetch all collections
        if client.collections is None:
            raise DemistoException(ERR_NO_COLL)
        indicators: list = []
        for collection in client.collections:
            client.collection_to_fetch = collection
            fetched_iocs = client.build_iterator(limit, added_after=added_after)
            indicators.extend(fetched_iocs)
            if limit >= 0:
                limit -= len(fetched_iocs)
                if limit <= 0:
                    break

    else:
        indicators = client.build_iterator(limit=limit, added_after=added_after)
    relationships_list: list = []
    parsed_relationships: str = ""
    if (
        indicators
        and indicators[-1].get(
            "value",
        )
        == "$$DummyIndicator$$"
    ):
        relationships_list = indicators[-1].get(
            "relationships",
        )
        parsed_relationships = f"\n\n\nRelations ships:\n{tableToMarkdown('', relationships_list)}"
        md = (
            f"Found {len(indicators) - 1} results:\n"
            f"{tableToMarkdown('', indicators[:-1], ['value', 'type'])}{parsed_relationships}"
        )
    else:
        md = f"Found {len(indicators)} results:\n{tableToMarkdown('', indicators, ['value', 'type'])}{parsed_relationships}"

    if raw:
        demisto.results({"indicators": [x.get("rawJSON") for x in indicators]})
        return None

    if indicators:
        return CommandResults(
            outputs_prefix=CONTEXT_PREFIX + ".Indicators",
            outputs_key_field="value",
            outputs=indicators,
            readable_output=md,
        )
    return md


def get_collections_command(client):
    """
    Get the available collections in the TAXII server
    :param client: FeedClient
    :return: available collections
    """
    collections = []
    for collection in client.collections:
        collections.append({"Name": collection.title, "ID": collection.id})
    md = tableToMarkdown("TAXII2 Server Collections:", collections, ["Name", "ID"])
    return CommandResults(
        outputs_prefix=CONTEXT_PREFIX + ".Collections",
        outputs_key_field="ID",
        outputs=collections,
        readable_output=md,
    )


def reset_fetch_command(client):
    """
    Reset the last fetch from the integration context
    """
    demisto.setIntegrationContext({})
    return (
        "Fetch was reset successfully. Your next indicator fetch will collect indicators from "
        'the configured "First Fetch Time"'
    )


def is_valid_taxii_url(url: Optional[str]):
    """
    Checks the correctness of the url.
    :param url: str
    :return: boolean whether the url valid or not.
    """
    if url and (parse_result := urlparse(url)):
        path = parse_result.path
        if path.endswith(("taxii", "taxii2", "taxii2/", "taxii/")):
            return True
    return False


def main():  # pragma: no cover
    params = demisto.params()
    args = demisto.args()
    url = params.get("url")
    if not is_valid_taxii_url(url):
        demisto.debug("ERROR: Discovery Service URL is NOT VALID, The URL suffix should be taxii or taxii2.")
    collection_to_fetch = params.get("collection_to_fetch")
    credentials = params.get("credentials") or {}
    username = credentials.get("identifier")
    password = credentials.get("password")
    proxies = handle_proxy()
    verify_certificate = not params.get("insecure", False)
    skip_complex_mode = params.get("observation_operator_mode") == COMPLEX_OBSERVATION_MODE_SKIP
    feed_tags = argToList(params.get("feedTags"))
    tlp_color = params.get("tlp_color", "")

    initial_interval = params.get("initial_interval")
    fetch_full_feed = params.get("fetch_full_feed") or False
    is_incremental_feed = params.get("feedIncremental") or False
    limit = try_parse_integer(params.get("limit") or -1)
    limit_per_request = try_parse_integer(params.get("limit_per_request"))
    certificate = replace_spaces_in_credential(params.get("creds_certificate", {}).get("identifier")) or params.get(
        "certificate", None
    )
    key = params.get("creds_certificate", {}).get("password") or params.get("key", None)
    objects_to_fetch = argToList(params.get("objects_to_fetch") or [])
    default_api_root = params.get("default_api_root")
    update_custom_fields = params.get("update_custom_fields") or False
    enrichment_excluded = argToBoolean(params.get("enrichmentExcluded", False)) or (
        params.get("tlp_color") == "RED" and is_xsiam_or_xsoar_saas()
    )

    demisto.info(f"{objects_to_fetch=}")

    command = demisto.command()
    demisto.info(f"Command being called in {CONTEXT_PREFIX} is {command}")
    try:
        assert_incremental_feed_params(fetch_full_feed, is_incremental_feed)
        client = Taxii2FeedClient(
            url=url,
            collection_to_fetch=collection_to_fetch,
            proxies=proxies,
            verify=verify_certificate,
            objects_to_fetch=objects_to_fetch,
            skip_complex_mode=skip_complex_mode,
            username=username,
            password=password,
            tags=feed_tags,
            limit_per_request=limit_per_request,
            tlp_color=tlp_color,
            certificate=certificate,
            key=key,
            default_api_root=default_api_root,
            update_custom_fields=update_custom_fields,
            enrichment_excluded=enrichment_excluded,
        )
        client.initialise()
        commands = {
            "taxii2-reset-fetch-indicators": reset_fetch_command,
            "taxii2-get-indicators": get_indicators_command,
            "taxii2-get-collections": get_collections_command,
        }

        if demisto.command() == "test-module":
            # This is the call made when pressing the integration Test button.
            module_test_command(client, limit, fetch_full_feed)

        elif demisto.command() == "fetch-indicators":
            if fetch_full_feed:
                limit = -1

            last_run_indicators = get_feed_last_run()
            demisto.debug(f"Before fetch command last run: {last_run_indicators}")
            (indicators, last_run_indicators) = fetch_indicators_command(
                client,
                initial_interval,
                limit,
                last_run_indicators,
                fetch_full_feed,
            )
            demisto.debug(f"After fetch command last run: {last_run_indicators}")
            demisto.debug(f"returning {len(indicators)} indicators")
            for iter_ in batch(indicators, batch_size=2000):
                demisto.createIndicators(iter_)

            set_feed_last_run(last_run_indicators)
        else:
            return_results(commands[command](client, **args))  # type: ignore[operator]

    except Exception as e:
        err_msg = f"Failed to execute {command} command. Error: {e!s}\n\ntraceback: {traceback.format_exc()}"
        if isinstance(e, requests.exceptions.SSLError):
            LOG(err_msg)
            err_msg = (
                "Encountered an HTTPS certificate error. This error can be ignored by enabling "
                '"Trust any certificate (not secure)" in the instance configuration.'
            )
        return_error(err_msg)


from TAXII2ApiModule import *  # noqa: E402

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