CreateNewIndicatorsOnly

Create indicators to the Threat Intel database only if they are not registered. All submitted indicators will be associated with the parent incident. When using the script with many indicators, or when the Threat Intel Management database is highly populated, this script may have low performance issue.

python · Common Scripts

Source

import time
from typing import Any

import demistomock as demisto  # noqa: F401
from CommonServerPython import *  # noqa: F401

STATUS_NEW = "new"
STATUS_EXISTING = "existing"
STATUS_UNAVAILABLE = "unavailable"

KEY_CREATION_STATUS = "CreationStatus"
KEY_VALUE = "value"

MAX_FIND_INDICATOR_RETRIES = 10
SLEEP_TIME = 2


def find_existing_indicators_by_value(indicator_values: list[str]) -> dict[str, dict[str, Any]]:
    """
    Searches for existing indicators by their values using a single batched query.

    Args:
        indicator_values: List of indicator values to search for.

    Returns:
        A case-insensitive dictionary mapping indicator values to their indicator objects.
    """
    escaped_normalized_indicators = {indicator_value.replace('"', r"\"") for indicator_value in indicator_values}
    if not escaped_normalized_indicators:
        return {}

    quoted_values = " ".join(f'"{indicator_value}"' for indicator_value in escaped_normalized_indicators)
    query = f"value:({quoted_values})"
    demisto.debug(f"Searching for existing indicators with query: {query}")

    searcher = IndicatorsSearcher(query=query)
    existing_indicators_by_value: dict[str, dict[str, Any]] = {}

    for page_result in searcher:
        for indicator in page_result.get("iocs") or []:
            indicator_value = indicator.get(KEY_VALUE)
            if indicator_value:
                existing_indicators_by_value.setdefault(str(indicator_value).casefold(), indicator)

    demisto.debug(f"Found {len(existing_indicators_by_value)} existing indicators")
    return existing_indicators_by_value


def associate_indicator_to_incident(indicator_value: Any) -> None:
    """
    Associate an indicator to this incident. Raise an exception if an error occurs.
    """

    incident_id = demisto.incidents()[0].get("id")
    demisto.debug(f"The incident id is {incident_id}")

    cmd_args = {
        "incidentId": incident_id,
        "value": f"{indicator_value}",  # Force an error
    }

    retry_num = 1
    res = ""
    while res != "done" and retry_num <= MAX_FIND_INDICATOR_RETRIES:
        try:
            demisto.debug(f"Executing associateIndicatorToIncident command with retry {retry_num}/{MAX_FIND_INDICATOR_RETRIES}")
            res = execute_command("associateIndicatorToIncident", cmd_args)
            demisto.debug(f"The associateIndicatorToIncident response is: {res}")

        except Exception as err:
            if "For associateIndicatorToIncident found no indicator" not in str(err):
                raise err
            # else log and continue with retries after sleeping
            demisto.debug(f"Failed to find indicator {indicator_value} in the system.")
            if retry_num != MAX_FIND_INDICATOR_RETRIES:
                time.sleep(SLEEP_TIME)  # pylint: disable=E9003
            retry_num += 1

    if res != "done":
        raise Exception(f"Failed to associate {indicator_value} with incident {incident_id}")


def normalize_indicator_value(indicator_value: Any) -> str:
    if isinstance(indicator_value, int):
        return str(indicator_value)
    elif isinstance(indicator_value, str) and indicator_value:
        return indicator_value
    else:
        raise DemistoException(f"Invalid indicator value: {indicator_value!s}")


def add_new_indicator(
    indicator_value: Any,
    create_new_indicator_args: dict[str, Any],
    associate_to_incident: bool = False,
    existing_indicators_by_value: dict[str, dict[str, Any]] | None = None,
) -> dict[str, Any]:
    indicator_value = normalize_indicator_value(indicator_value)
    existing_indicators_by_value = existing_indicators_by_value or {}

    if indicator := existing_indicators_by_value.get(indicator_value.casefold()):
        indicator[KEY_CREATION_STATUS] = STATUS_EXISTING

        # findIndicators might find an indicator with different letter case.
        # Unfortunately associate_indicator_to_incident does not ignore case
        # and as a result in some cases is unable to associate indicator.
        indicator_value = indicator[KEY_VALUE]

    else:
        args = dict(create_new_indicator_args, value=indicator_value)
        indicator = execute_command("createNewIndicator", args)

        if isinstance(indicator, dict):
            indicator[KEY_CREATION_STATUS] = STATUS_NEW

        elif isinstance(indicator, str):
            # createNewIndicator has been successfully done, but the indicator
            # wasn't created for some reasons.
            if "done - Indicator was not created" in indicator:
                demisto.debug(f'Indicator was not created. Make sure "{indicator_value}" is not excluded.')
            else:
                demisto.debug(indicator)

            indicator = {
                "value": indicator_value,
                "indicator_type": args.get("type", "Unknown"),
                KEY_CREATION_STATUS: STATUS_UNAVAILABLE,
            }

        else:
            raise DemistoException(f"Unknown response from createNewIndicator: {indicator!r}")

    if indicator[KEY_CREATION_STATUS] != STATUS_UNAVAILABLE and associate_to_incident:
        demisto.debug(f"Associating {indicator_value} to incident.")
        associate_indicator_to_incident(indicator_value)

    return indicator


def add_new_indicators(
    indicator_values: list[Any] | None,
    create_new_indicator_args: dict[str, Any],
    associate_to_incident: bool = False,
) -> list[dict[str, Any]]:
    normalized_indicator_values = [normalize_indicator_value(indicator_value) for indicator_value in indicator_values or []]

    existing_indicators_by_value = find_existing_indicators_by_value(normalized_indicator_values)

    return [
        add_new_indicator(
            indicator_value,
            create_new_indicator_args,
            associate_to_incident,
            existing_indicators_by_value,
        )
        for indicator_value in normalized_indicator_values
    ]


def main():
    try:
        args = assign_params(**demisto.args())

        # Don't use argToList to make a list in order to accept an indicator including commas.
        # The `indicator_values` parameter doesn't support a comma separated list.
        if (indicator_values := args.get("indicator_values", [])) and not isinstance(indicator_values, list):
            indicator_values = [indicator_values]

        create_new_indicator_args = dict(args)
        create_new_indicator_args.pop("indicator_values", None)
        create_new_indicator_args.pop("verbose", None)
        associate_to_incident = argToBoolean(create_new_indicator_args.pop("associate_to_current", "false"))
        ents = add_new_indicators(indicator_values, create_new_indicator_args, associate_to_incident)

        outputs = [
            assign_params(
                ID=ent.get("id"),
                Score=ent.get("score"),
                CreationStatus=ent.get(KEY_CREATION_STATUS),
                Type=ent.get("indicator_type"),
                Value=ent.get("value"),
            )
            for ent in ents
        ]

        count_new = sum(1 for ent in ents if ent.get(KEY_CREATION_STATUS) == STATUS_NEW)
        readable_output = f"{count_new} new indicators have been added."
        if argToBoolean(args.get("verbose", "false")):
            readable_output += "\n" + tblToMd(
                "New Indicator Created", outputs, ["ID", "Score", "CreationStatus", "Type", "Value"]
            )

        return_results(
            CommandResults(
                outputs_prefix="CreateNewIndicatorsOnly",
                outputs_key_field=["Value", "Type"],
                outputs=outputs,
                raw_response=ents,
                readable_output=readable_output,
            )
        )
    except Exception as e:
        return_error(f"Failed to execute CreateNewIndicatorsOnly.\nError:\n{e!s}")


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

README

Create indicators to the Threat Intel database only if they are not registered. When using the script with many indicators, or when the Threat Intel Management database is highly populated, this script may have low performance issue.

Script Data


Name Description
Script Type python3
Tags Utility
Cortex XSOAR Version 6.5.0

Inputs


Argument Name Description
indicator_values The indicator values
type The indicator type of the indicators.
source The indicator source.
verdict The indicator reputation.
tags The tags to add to the new indicators.
verbose Whether to output additional messages as readable output.

Outputs


Path Description Type
CreateNewIndicatorsOnly.ID The indicator ID. string
CreateNewIndicatorsOnly.Value The indicator value. string
CreateNewIndicatorsOnly.Type The indicator type. string
CreateNewIndicatorsOnly.Score The indicator score. number
CreateNewIndicatorsOnly.CreationStatus The status of the indicator requested to add. Value is either new, existing, or unavailable. string

Notice


When using the script with many indicators, or when the Threat Intel Management database is highly populated, this script may have low performance issue.