Google Threat Intelligence IoC Stream Feed

Use this feed integration to fetch Google Threat Intelligence IoC Stream notifications as indicators.

Data Enrichment & Threat Intelligence · GoogleThreatIntelligence · Feed

Details

IDGoogle Threat Intelligence IoC Stream Feed
ProviderGoogle
CategoryData Enrichment & Threat Intelligence
From Version5.5.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Use the Google Threat Intelligence IoC Stream Feed integration to fetch indicators from IoC Stream rules or rulesets.

Configure Google Threat Intelligence IoC Stream Feed on Cortex XSOAR

  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for Google Threat Intelligence IoC Stream Feed.
  3. Click Add instance to create and configure a new integration instance.
Parameter Description Required
feed The fetch indicators. False
credentials API Key. True
limit The maximum number of indicators to fetch from IoC Stream. The default value is 200. False
filter Exact name of the rule or ruleset you want to filter on. Leave empty to receive all. False
feedReputation The indicator reputation. False
feedReliability The source’s reliability. True
tlp_color The Traffic Light Protocol (TLP) designation to apply to indicators fetched from the feed. More information about the protocol can be found at https://us-cert.cisa.gov/tlp False
feedExpirationPolicy The feed’s expiration policy. False
feedFetchInterval The feed fetch interval. False
feedMinimumGTIScore The minimum GTI score to import as part of the feed. True
feedBypassExclusionList Whether to bypass exclusion list. False
  1. Click Test to validate the Google Threat Intelligence API Key, and connection.

IoC Stream Feed info

By default the IoC Stream Feed retrieve all indicators on IoC Stream. You have the option to get files, domains, IP addresses or URLs only from LiveHunt, RetroHunt, Collections, Threat Actors, etc., using the filter parameter.

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.

Get Indicators


Gets the indicators from Google Threat Intelligence IoC Stream.

Base Command

gti-iocstream-get-indicators

Input
Argument Name Description Required
limit The maximum number of results to return. The default value is 200. Optional
filter Filter your IoC Stream (e.g., “source_type:hunting_ruleset” for LiveHunt, “source_type:retrohunt_job” for RetroHunt). Leave empty to receive all. Optional
Context Output

There is no context output for this command.

Command Example

!gti-iocstream-get-indicators limit=1 filter=source_type:hunting_ruleset

Human Readable Output

Indicators from Google Threat Intelligence IoC Stream

Id Detections Origin Sources Gti Threat Score Gti Severity Gti Verdict Malware Families Threat Actors
f221425286c9073cbb2168f73120b6… 59/69 hunting [hunting_ruleset] YARA ruleset 80 SEVERITY_LOW VERDICT_MALICIOUS beacon SWEED

Configuration parameters

  • credentials — API Key (leave empty. Fill in the API key in the password field.) (required)
  • filter — Filter
  • feed — Fetch indicators
  • limit — Limit
  • feedReputation — Indicator Reputation
  • feedReliability — Source Reliability (required)
  • feedExpirationPolicy
  • feedExpirationInterval
  • feedFetchInterval — Feed Fetch Interval
  • feedMinimumGTIScore — Feed Minimum Google Threat Intelligence Score (required)
  • feedBypassExclusionList — Bypass exclusion list
  • feedTags — Tags
  • tlp_color — Traffic Light Protocol Color

Commands (1)

  • gti-iocstream-get-indicators

    Gets the indicators from Google Threat Intelligence IoC Stream.

"""Google Threat Intelligence IoC Stream Feed integration."""

import re
from datetime import datetime

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

# Disable insecure warnings
urllib3.disable_warnings()


class DetectionRatio:
    """Class for detections."""

    malicious = 0
    total = 0

    def __init__(self, last_analysis_stats: dict):
        self.malicious = last_analysis_stats.get("malicious", 0)
        self.total = (
            last_analysis_stats.get("harmless", 0)
            + last_analysis_stats.get("suspicious", 0)
            + last_analysis_stats.get("undetected", 0)
            + last_analysis_stats.get("malicious", 0)
        )

    def __repr__(self):
        return f"{self.malicious}/{self.total}"


class Client(BaseClient):
    """Class for GTI client."""

    def get_api_indicators(self, filter_query: str = None, limit: int = 200) -> list:
        """Get indicators from GTI API."""
        stop = False
        iocs = []
        cursor = None
        while not stop:
            response = self._http_request(
                "GET",
                "ioc_stream",
                params=assign_params(
                    filter=filter_query,
                    limit=min(limit, 40),
                    cursor=cursor,
                ),
            )
            cursor = response.get("meta", {}).get("cursor")
            current_iocs = response.get("data", [])
            limit -= len(current_iocs)
            iocs.extend(current_iocs)
            stop = not (limit and cursor)
        return iocs

    def fetch_indicators(self, limit: int = 200, filter_query: str = None, fetch_command: bool = False) -> list:
        """Retrieves all entries from the feed.
        Returns:
            A list of objects, containing the indicators.
        """
        filter_query = filter_query or ""

        if fetch_command and (last_run := self.get_last_run()):
            filter_query += f" {last_run}"

        iocs = self.get_api_indicators(filter_query.strip(), limit)

        if fetch_command:
            self.set_last_run()

        return iocs

    @staticmethod
    def set_last_run():
        """
        Returns: Current timestamp
        """
        current_time = datetime.now()
        current_timestamp = datetime.timestamp(current_time)
        timestamp = str(int(current_timestamp))
        demisto.setLastRun({"last_run": timestamp})

    @staticmethod
    def get_last_run() -> str:
        """Gets last run time in timestamp
        Returns:
            last run in timestamp, or '' if no last run
        """
        if last_run := (demisto.getLastRun() or {}).get("last_run"):
            demisto.info(f"get last_run: {last_run}")
            params = f"date:{last_run}+"
        else:
            params = ""
        return params


def test_module(client: Client, args: dict) -> str:
    """Tests module."""
    try:
        client.fetch_indicators()
    except Exception:
        raise Exception(
            "Could not fetch Google Threat Intelligence IoC Stream Feed\n"
            "\nCheck your API key and your connection to Google Threat Intelligence."
        )
    return "ok"


def _gti_verdict_to_dbot_score(gti_verdict: str):
    """Parses GTI verdict to DBotScore."""
    return {
        "VERDICT_BENIGN": 1,
        "VERDICT_SUSPICIOUS": 2,
        "VERDICT_MALICIOUS": 3,
    }.get(gti_verdict, 0)


def _add_gti_attributes(indicator_obj: dict, attributes: dict):
    """Addes GTI attributes."""

    # GTI assessment
    gti_assessment = attributes.get("gti_assessment", {})
    gti_threat_score = gti_assessment.get("threat_score", {}).get("value")
    gti_severity = gti_assessment.get("severity", {}).get("value")
    gti_verdict = gti_assessment.get("verdict", {}).get("value")

    # Attribution
    attribution = attributes.get("attribution", {})
    malware_families = [x["family"] for x in attribution.get("malware_families", [])]
    malware_families = list(set(malware_families))
    threat_actors = attribution.get("threat_actors", [])
    threat_actors = list(set(threat_actors))

    indicator_obj["fields"].update(
        {
            "gtithreatscore": gti_threat_score,
            "gtiseverity": gti_severity,
            "gtiverdict": gti_verdict,
            "malwarefamily": malware_families,
            "actor": threat_actors,
        }
    )
    indicator_obj.update(
        {
            "score": _gti_verdict_to_dbot_score(gti_verdict),
            "gti_threat_score": gti_threat_score,
            "gti_severity": gti_severity,
            "gti_verdict": gti_verdict,
            "malware_families": malware_families,
            "threat_actors": threat_actors,
            "relationships": [
                EntityRelationship(
                    name=EntityRelationship.Relationships.PART_OF,
                    entity_a=indicator_obj["value"],
                    entity_a_type=indicator_obj["type"],
                    entity_b=malware_family.title(),
                    entity_b_type=ThreatIntel.ObjectsNames.MALWARE,
                    reverse_name=EntityRelationship.Relationships.CONTAINS,
                ).to_indicator()
                for malware_family in malware_families
            ]
            + [
                EntityRelationship(
                    name=EntityRelationship.Relationships.ATTRIBUTED_BY,
                    entity_a=indicator_obj["value"],
                    entity_a_type=indicator_obj["type"],
                    entity_b=threat_actor.title(),
                    entity_b_type=ThreatIntel.ObjectsNames.THREAT_ACTOR,
                    reverse_name=EntityRelationship.Relationships.ATTRIBUTED_TO,
                ).to_indicator()
                for threat_actor in threat_actors
            ],
        }
    )

    return indicator_obj


def _get_indicator_type(item: dict):
    """Gets indicator type."""
    if item["type"] == "file":
        return FeedIndicatorType.File
    if item["type"] == "domain":
        return FeedIndicatorType.Domain
    if item["type"] == "url":
        return FeedIndicatorType.URL
    if item["type"] == "ip_address":
        return FeedIndicatorType.IP
    raise ValueError(f'Unknown type: {item["type"]}. ID: {item["id"]}')


def _get_indicator_id(item: dict):
    """Gets indicator ID."""
    if item["type"] == "url":
        return item.get("attributes", {}).get("url") or item["id"]
    return item["id"]


def _add_file_attributes(indicator_obj: dict, attributes: dict) -> dict:
    """Adds file attributes."""
    indicator_obj["fields"].update(
        {
            "md5": attributes.get("md5"),
            "sha1": attributes.get("sha1"),
            "sha256": attributes.get("sha256"),
            "ssdeep": attributes.get("ssdeep"),
            "fileextension": attributes.get("type_extension"),
            "filetype": attributes.get("type_tag"),
            "imphash": attributes.get("pe_info", {}).get("imphash"),
            "displayname": attributes.get("meaningful_name"),
            "name": attributes.get("meaningful_name"),
            "size": attributes.get("size"),
            "creationdate": attributes.get("creation_date"),
            "firstseenbysource": attributes.get("first_submission_date"),
            "lastseenbysource": attributes.get("last_submission_date"),
        }
    )

    return indicator_obj


def _add_domain_attributes(indicator_obj: dict, attributes: dict) -> dict:
    """Adds domain attributes."""
    whois: str = attributes.get("whois", "")

    admin_country = re.search(r"Admin Country:\s*([^\n]+)", whois)
    admin_email = re.search(r"Admin Email:\s*([^\n]+)", whois)
    admin_name = re.search(r"Admin Name:\s*([^\n]+)", whois)
    admin_phone = re.search(r"Admin Phone:\s*([^\n]+)", whois)

    registrant_country = re.search(r"Registrant Country:\s*([^\n]+)", whois)
    registrant_email = re.search(r"Registrant Email:\s*([^\n]+)", whois)
    registrant_name = re.search(r"Registrant Name:\s*([^\n]+)", whois)
    registrant_phone = re.search(r"Registrant Phone:\s*([^\n]+)", whois)

    registrar_abuse_email = re.search(r"Registrar Abuse Contact Email:\s*([^\n]+)", whois)
    registrar_abuse_phone = re.search(r"Registrar Abuse Contact Phone:\s*([^\n]+)", whois)

    indicator_obj["fields"].update(
        {
            "creationdate": attributes.get("creation_date"),
            "admincountry": admin_country.group(1) if admin_country else None,
            "adminemail": admin_email.group(1) if admin_email else None,
            "adminname": admin_name.group(1) if admin_name else None,
            "adminphone": admin_phone.group(1) if admin_phone else None,
            "registrantcountry": registrant_country.group(1) if registrant_country else None,
            "registrantemail": registrant_email.group(1) if registrant_email else None,
            "registrantname": registrant_name.group(1) if registrant_name else None,
            "registrantphone": registrant_phone.group(1) if registrant_phone else None,
            "registrarabuseemail": registrar_abuse_email.group(1) if registrar_abuse_email else None,
            "registrarabusephone": registrar_abuse_phone.group(1) if registrar_abuse_phone else None,
            "registrarname": attributes.get("registrar"),
            "firstseenbysource": attributes.get("first_seen_itw_date"),
            "lastseenbysource": attributes.get("last_seen_itw_date"),
        }
    )

    return indicator_obj


def _add_url_attributes(indicator_obj: dict, attributes: dict) -> dict:
    """Adds URL attributes."""
    indicator_obj["fields"].update(
        {
            "firstseenbysource": attributes.get("first_submission_date"),
            "lastseenbysource": attributes.get("last_submission_date"),
        }
    )

    return indicator_obj


def _add_ip_attributes(indicator_obj: dict, attributes: dict) -> dict:
    """Adds IP attributes."""
    indicator_obj["fields"].update(
        {
            "countrycode": attributes.get("country"),
            "firstseenbysource": attributes.get("first_seen_itw_date"),
            "lastseenbysource": attributes.get("last_seen_itw_date"),
        }
    )

    return indicator_obj


def _add_dedicated_attributes(indicator_obj: dict, attributes: dict) -> dict:
    """Adds dedicated attributes to indicator object."""
    if indicator_obj["type"] == FeedIndicatorType.File:
        return _add_file_attributes(indicator_obj, attributes)
    if indicator_obj["type"] == FeedIndicatorType.Domain:
        return _add_domain_attributes(indicator_obj, attributes)
    if indicator_obj["type"] == FeedIndicatorType.URL:
        return _add_url_attributes(indicator_obj, attributes)
    if indicator_obj["type"] == FeedIndicatorType.IP:
        return _add_ip_attributes(indicator_obj, attributes)
    raise ValueError(f'Unknown type: {indicator_obj["type"]}. ID: {indicator_obj["id"]}')


def _create_indicator(item: dict) -> dict:
    """Creates indicator object."""
    indicator_type = _get_indicator_type(item)
    indicator_id = _get_indicator_id(item)

    attributes = item.get("attributes", {})
    context_attributes = item.get("context_attributes", {})

    detection_ratio = DetectionRatio(attributes.get("last_analysis_stats", {}))

    indicator_obj = {
        "type": indicator_type,
        "value": indicator_id,
        "service": "Google Threat Intelligence",
        "fields": {
            "tags": attributes.get("tags") or None,
            "updateddate": attributes.get("last_modification_date"),
            "detectionengines": detection_ratio.total,
            "positivedetections": detection_ratio.malicious,
        },
        "rawJSON": {
            "type": indicator_type,
            "value": indicator_id,
            "attributes": attributes,
        },
        "id": indicator_id,
        "detections": str(detection_ratio),
        "origin": context_attributes.get("origin"),
        "sources": ", ".join([f'[{x["type"]}] {x["label"]}' for x in context_attributes.get("sources", [])]) or None,
    }

    indicator_obj = _add_gti_attributes(indicator_obj, attributes)
    indicator_obj = _add_dedicated_attributes(indicator_obj, attributes)

    return indicator_obj


def fetch_indicators_command(
    client: Client,
    tlp_color: str = None,
    feed_tags: list = [],
    limit: int = 200,
    filter_query: str = None,
    minimum_score: int = 0,
    fetch_command: bool = False,
) -> list[dict]:
    """Retrieves indicators from the feed
    Args:
        client (Client): Client object with request
        tlp_color (str): Traffic Light Protocol color
        feed_tags (list): tags to assign fetched indicators
        limit (int): limit the results
        filter_query (string): filter query
    Returns:
        Indicators.
    """
    indicators = []

    raw_indicators = client.fetch_indicators(limit, filter_query, fetch_command=fetch_command)

    # extract values from iterator
    for item in raw_indicators:
        try:
            indicator_obj = _create_indicator(item)
        except ValueError as exc:
            demisto.info(str(exc))
            continue

        if feed_tags:
            indicator_obj["fields"]["tags"] = feed_tags

        if tlp_color:
            indicator_obj["fields"]["trafficlightprotocol"] = tlp_color

        if int(indicator_obj["fields"].get("gtithreatscore") or 0) >= minimum_score:
            indicators.append(indicator_obj)
        else:
            try:
                existing_indicators = list(IndicatorsSearcher(value=indicator_obj["value"]))
            except SystemExit as exc:
                demisto.debug(exc)
                existing_indicators = []
            if len(existing_indicators) > 0 and int(existing_indicators[0].get("total", 0)) > 0:
                indicators.append(indicator_obj)

    return indicators


def get_indicators_command(client: Client, params: dict[str, str], args: dict[str, str]) -> CommandResults:
    """Wrapper for retrieving indicators from the feed to the war-room.
    Args:
        client: Client object with request
        params: demisto.params()
        args: demisto.args()
    Returns:
        Outputs.
    """
    tlp_color = params.get("tlp_color")
    feed_tags = argToList(params.get("feedTags", ""))
    limit = int(args.get("limit", 200))
    filter_query = args.get("filter")
    minimum_score = int(params.get("feedMinimumGTIScore", 80))

    indicators = fetch_indicators_command(
        client,
        tlp_color,
        feed_tags,
        limit,
        filter_query,
        minimum_score,
    )

    human_readable = tableToMarkdown(
        "Indicators from Google Threat Intelligence IoC Stream Feed:",
        indicators,
        headers=[
            "id",
            "detections",
            "origin",
            "sources",
            "gti_threat_score",
            "gti_severity",
            "gti_verdict",
            "malware_families",
            "threat_actors",
        ],
        headerTransform=string_to_table_header,
        removeNull=True,
    )

    return CommandResults(
        readable_output=human_readable,
        outputs_prefix="",
        outputs_key_field="",
        raw_response=indicators,
        outputs={},
    )


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

    tlp_color = params.get("tlp_color")
    feed_tags = argToList(params.get("feedTags", ""))
    limit = int(params.get("limit", 200))
    filter_query = params.get("filter")
    minimum_score = int(params.get("feedMinimumGTIScore", 80))

    # If your Client class inherits from BaseClient, SSL verification is
    # handled out of the box by it, just pass ``verify_certificate`` to
    # the Client constructor
    insecure = not params.get("insecure", False)

    # If your Client class inherits from BaseClient, system proxy is handled
    # out of the box by it, just pass ``proxy`` to the Client constructor
    proxy = params.get("proxy", False)

    command = demisto.command()

    demisto.debug(f"Command being called is {command}")

    try:
        client = Client(
            base_url="https://www.virustotal.com/api/v3/",
            verify=insecure,
            proxy=proxy,
            headers={
                "x-apikey": params["credentials"]["password"],
                "x-tool": "CortexGTIIoCStreamFeed",
            },
        )

        if command == "test-module":
            # This is the call made when pressing the integration Test button.
            return_results(test_module(client, {}))

        elif command == "gti-iocstream-get-indicators":
            # This is the command that fetches a limited number of indicators
            # from the feed source and displays them in the war room.
            return_results(get_indicators_command(client, params, demisto.args()))

        elif command == "fetch-indicators":
            # This is the command that initiates a request to the feed endpoint
            # and create new indicators objects from the data fetched. If the
            # integration instance is configured to fetch indicators, then this
            # is the command that will be executed at the specified feed fetch
            # interval.
            indicators = fetch_indicators_command(
                client, tlp_color, feed_tags, limit, filter_query, minimum_score, fetch_command=True
            )
            for iter_ in batch(indicators, batch_size=2000):
                demisto.createIndicators(iter_)

        else:
            raise NotImplementedError(f"Command {command} is not implemented.")

    # 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()