FeedMISPThreatActors

Fetches the MISP threat actor galaxy and builds it into Threat Actor indicators in Cortex Threat Intel Management (TIM).

Data Enrichment & Threat Intelligence · MISP Threat Actors · Feed

Details

IDFeedMISPThreatActors
ProviderOpen Source
CategoryData Enrichment & Threat Intelligence
From Version6.10.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

MISP Threat Actors Feed Integration

Overview

This integration fetches threat actor information from the MISP Threat Actors Galaxy and creates indicators in Cortex TIM. It provides valuable threat intelligence about various threat actors, including their aliases, targets, origin countries, and related information.

Use Cases

  • Fetch and update threat actor information regularly.
  • Enrich your threat intelligence with detailed information about known threat actors.
  • Create relationships between threat actors and their targets or aliases.

Configuration

  1. Navigate to Settings > Integrations > Instances.
  2. Search for MISP Threat Actors Feed.
  3. Click Add instance to create and configure a new integration instance.
    • Name: A meaningful name for the integration instance.
    • URL: The URL to fetch the MISP Threat Actors Galaxy file (default: https://raw.githubusercontent.com/MISP/misp-galaxy/main/galaxies/threat-actor.json)
    • Feed Fetch Interval: How often the feed should be fetched and indicators created or updated.
    • Reliability: Reliability of the feed source.
    • TLP Color: Traffic Light Protocol color for the indicators.
    • Feed Tags: Tags to be added to each indicator fetched from the feed.
    • Bypass exclusion list: Whether to bypass the exclusion list when creating indicators.
  4. Click Test to validate the URLs and connection.
  5. Save and exit the integration instance.

Commands

This integration works in the background to fetch indicators and does not have any specific commands to execute manually.

fetch-indicators

This command runs in the background at the specified feed fetch interval to create and update threat actor indicators.

Additional Information

  • The integration fetches the latest version of the MISP Threat Actors Galaxy file and only processes new updates.
  • Indicators are created with rich metadata, including descriptions, aliases, targeted sectors and countries, and origin information when available.
  • The integration creates relationships between threat actors and their aliases, targets, and attributed countries.

Troubleshooting

  • If the integration fails to fetch data, ensure the provided URL is accessible and the network settings (including proxy if used) are correctly configured.
  • Check the integration logs for any error messages or debugging information.

Configuration parameters

  • feed — Fetch indicators
  • url — MISP Threat actor galaxy raw address (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • feedReputation — Indicator Reputation
  • feedReliability — Source Reliability (required)
  • feedExpirationPolicy
  • feedExpirationInterval
  • feedFetchInterval — Feed Fetch Interval
  • feedBypassExclusionList — Bypass exclusion list
  • tlp_color — Traffic Light Protocol Color
  • feedTags — Tags

Commands (1)

  • mispthreatactors-get-indicators

    Retrieves indicators from Threat Vault.

from typing import Any

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

from CommonServerUserPython import *  # noqa

# Disable insecure warnings
urllib3.disable_warnings()


""" CONSTANTS """

SINGLE_WORD = 1
DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"  # ISO8601 format with UTC, default in XSOAR
LOG_LINE = "FeedMISPThreatActors -"
COUNTRIES = {
    "AF": "Afghanistan",
    "AX": "Aland Islands",
    "AL": "Albania",
    "AS": "American Samoa",
    "AD": "Andorra",
    "AO": "Angola",
    "AI": "Anguilla",
    "AQ": "Antarctica",
    "AG": "Antigua and Barbuda",
    "AR": "Argentina",
    "AM": "Armenia",
    "AW": "Aruba",
    "AU": "Australia",
    "AT": "Austria",
    "AZ": "Azerbaijan",
    "BS": "Bahamas",
    "BH": "Bahrain",
    "BD": "Bangladesh",
    "BB": "Barbados",
    "BY": "Belarus",
    "BE": "Belgium",
    "BZ": "Belize",
    "BJ": "Benin",
    "BM": "Bermuda",
    "BT": "Bhutan",
    "BO": "Bolivia, Plurinational State of",
    "BQ": "Bonaire, Sint Eustatius and Saba",
    "BA": "Bosnia and Herzegovina",
    "BW": "Botswana",
    "BV": "Bouvet Island",
    "BR": "Brazil",
    "IO": "British Indian Ocean Territory",
    "BN": "Brunei Darussalam",
    "BG": "Bulgaria",
    "BF": "Burkina Faso",
    "BI": "Burundi",
    "KH": "Cambodia",
    "CM": "Cameroon",
    "CA": "Canada",
    "CV": "Cape Verde",
    "KY": "Cayman Islands",
    "CF": "Central African Republic",
    "TD": "Chad",
    "CL": "Chile",
    "CN": "China",
    "CX": "Christmas Island",
    "CC": "Cocos (Keeling) Islands",
    "CO": "Colombia",
    "KM": "Comoros",
    "CG": "Congo",
    "CD": "Congo, The Democratic Republic of the",
    "CK": "Cook Islands",
    "CR": "Costa Rica",
    "CI": "Côte d'Ivoire",
    "HR": "Croatia",
    "CU": "Cuba",
    "CW": "Curaçao",
    "CY": "Cyprus",
    "CZ": "Czech Republic",
    "DK": "Denmark",
    "DJ": "Djibouti",
    "DM": "Dominica",
    "DO": "Dominican Republic",
    "EC": "Ecuador",
    "EG": "Egypt",
    "SV": "El Salvador",
    "GQ": "Equatorial Guinea",
    "ER": "Eritrea",
    "EE": "Estonia",
    "ET": "Ethiopia",
    "FK": "Falkland Islands (Malvinas)",
    "FO": "Faroe Islands",
    "FJ": "Fiji",
    "FI": "Finland",
    "FR": "France",
    "GF": "French Guiana",
    "PF": "French Polynesia",
    "TF": "French Southern Territories",
    "GA": "Gabon",
    "GM": "Gambia",
    "GE": "Georgia",
    "DE": "Germany",
    "GH": "Ghana",
    "GI": "Gibraltar",
    "GR": "Greece",
    "GL": "Greenland",
    "GD": "Grenada",
    "GP": "Guadeloupe",
    "GU": "Guam",
    "GT": "Guatemala",
    "GG": "Guernsey",
    "GN": "Guinea",
    "GW": "Guinea-Bissau",
    "GY": "Guyana",
    "HT": "Haiti",
    "HM": "Heard Island and McDonald Islands",
    "VA": "Holy See (Vatican City State)",
    "HN": "Honduras",
    "HK": "Hong Kong",
    "HU": "Hungary",
    "IS": "Iceland",
    "IN": "India",
    "ID": "Indonesia",
    "IR": "Iran, Islamic Republic of",
    "IQ": "Iraq",
    "IE": "Ireland",
    "IM": "Isle of Man",
    "IL": "Israel",
    "IT": "Italy",
    "JM": "Jamaica",
    "JP": "Japan",
    "JE": "Jersey",
    "JO": "Jordan",
    "KZ": "Kazakhstan",
    "KE": "Kenya",
    "KI": "Kiribati",
    "KP": "Korea, Democratic People's Republic of",
    "KR": "Korea, Republic of",
    "KW": "Kuwait",
    "KG": "Kyrgyzstan",
    "LA": "Lao People's Democratic Republic",
    "LV": "Latvia",
    "LB": "Lebanon",
    "LS": "Lesotho",
    "LR": "Liberia",
    "LY": "Libya",
    "LI": "Liechtenstein",
    "LT": "Lithuania",
    "LU": "Luxembourg",
    "MO": "Macao",
    "MK": "Macedonia, Republic of",
    "MG": "Madagascar",
    "MW": "Malawi",
    "MY": "Malaysia",
    "MV": "Maldives",
    "ML": "Mali",
    "MT": "Malta",
    "MH": "Marshall Islands",
    "MQ": "Martinique",
    "MR": "Mauritania",
    "MU": "Mauritius",
    "YT": "Mayotte",
    "MX": "Mexico",
    "FM": "Micronesia, Federated States of",
    "MD": "Moldova, Republic of",
    "MC": "Monaco",
    "MN": "Mongolia",
    "ME": "Montenegro",
    "MS": "Montserrat",
    "MA": "Morocco",
    "MZ": "Mozambique",
    "MM": "Myanmar",
    "NA": "Namibia",
    "NR": "Nauru",
    "NP": "Nepal",
    "NL": "Netherlands",
    "NC": "New Caledonia",
    "NZ": "New Zealand",
    "NI": "Nicaragua",
    "NE": "Niger",
    "NG": "Nigeria",
    "NU": "Niue",
    "NF": "Norfolk Island",
    "MP": "Northern Mariana Islands",
    "NO": "Norway",
    "OM": "Oman",
    "PK": "Pakistan",
    "PW": "Palau",
    "PS": "Palestinian Territory, Occupied",
    "PA": "Panama",
    "PG": "Papua New Guinea",
    "PY": "Paraguay",
    "PE": "Peru",
    "PH": "Philippines",
    "PN": "Pitcairn",
    "PL": "Poland",
    "PT": "Portugal",
    "PR": "Puerto Rico",
    "QA": "Qatar",
    "RE": "Réunion",
    "RO": "Romania",
    "RU": "Russian Federation",
    "RW": "Rwanda",
    "BL": "Saint Barthélemy",
    "SH": "Saint Helena, Ascension and Tristan da Cunha",
    "KN": "Saint Kitts and Nevis",
    "LC": "Saint Lucia",
    "MF": "Saint Martin (French part)",
    "PM": "Saint Pierre and Miquelon",
    "VC": "Saint Vincent and the Grenadines",
    "WS": "Samoa",
    "SM": "San Marino",
    "ST": "Sao Tome and Principe",
    "SA": "Saudi Arabia",
    "SN": "Senegal",
    "RS": "Serbia",
    "SC": "Seychelles",
    "SL": "Sierra Leone",
    "SG": "Singapore",
    "SX": "Sint Maarten (Dutch part)",
    "SK": "Slovakia",
    "SI": "Slovenia",
    "SB": "Solomon Islands",
    "SO": "Somalia",
    "ZA": "South Africa",
    "GS": "South Georgia and the South Sandwich Islands",
    "ES": "Spain",
    "LK": "Sri Lanka",
    "SD": "Sudan",
    "SR": "Suriname",
    "SS": "South Sudan",
    "SJ": "Svalbard and Jan Mayen",
    "SZ": "Swaziland",
    "SE": "Sweden",
    "CH": "Switzerland",
    "SY": "Syrian Arab Republic",
    "TW": "Taiwan, Province of China",
    "TJ": "Tajikistan",
    "TZ": "Tanzania, United Republic of",
    "TH": "Thailand",
    "TL": "Timor-Leste",
    "TG": "Togo",
    "TK": "Tokelau",
    "TO": "Tonga",
    "TT": "Trinidad and Tobago",
    "TN": "Tunisia",
    "TR": "Turkey",
    "TM": "Turkmenistan",
    "TC": "Turks and Caicos Islands",
    "TV": "Tuvalu",
    "UG": "Uganda",
    "UA": "Ukraine",
    "AE": "United Arab Emirates",
    "GB": "United Kingdom",
    "US": "United States",
    "UM": "United States Minor Outlying Islands",
    "UY": "Uruguay",
    "UZ": "Uzbekistan",
    "VU": "Vanuatu",
    "VE": "Venezuela, Bolivarian Republic of",
    "VN": "Viet Nam",
    "VG": "Virgin Islands, British",
    "VI": "Virgin Islands, U.S.",
    "WF": "Wallis and Futuna",
    "EH": "Western Sahara",
    "YE": "Yemen",
    "ZM": "Zambia",
    "ZW": "Zimbabwe",
}


""" CLIENT CLASS """


class Client(BaseClient):
    """
    Client to use in the Threat Vault integration. Overrides BaseClient.
    """

    def __init__(self, base_url: str, verify: bool, proxy: bool, reliability: str):
        super().__init__(
            base_url=base_url,
            verify=verify,
            proxy=proxy,
        )

        self.name = "MISPThreatActors"
        self.reliability = reliability

    def get_threat_actors_galaxy_file(self) -> dict[str, Any]:  # pragma: no cover
        demisto.debug(f"{LOG_LINE} - Trying to fetch Threat Actor Galaxy from Github")

        try:
            demisto.debug(f'{LOG_LINE} - Calling "{self._base_url}"')
            data = self._http_request(method="GET", raise_on_status=True, full_url=self._base_url)

        except Exception:
            raise

        return data

    def test_module(self) -> str:  # pragma: no cover
        """
        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.
        :type client: ``Client``
        :param Client: client to use
        :return: 'ok' if test passed, anything else will fail the test.
        :rtype: ``str``
        """

        try:
            demisto.debug(f"{LOG_LINE} - Running test module.")
            self.get_threat_actors_galaxy_file()

        except DemistoException:
            raise

        return "ok"


""" HELPER FUNCTIONS """


def build_relationships(
    original_ioc: str, related_iocs: list[str], related_iocs_type: str, relationship_name: str
) -> list[dict[str, Any]]:
    """
    Builds a list of EntityRelationship objects based on the provided original IOC and related IOCs.

    Args:
        original_ioc (str): The original IOC value.
        related_iocs (list[str]): A list of related IOC values.
        related_iocs_type (str): The type of the related IOCs.

    Returns:
        list[EntityRelationship]: A list of EntityRelationship objects.
    """
    relationships = []

    for related_ioc in related_iocs:
        if len(related_ioc.split(" ")) >= 2:
            parsed_ioc = related_ioc.title()
        else:
            parsed_ioc = related_ioc

        relationships.append(
            EntityRelationship(
                name=relationship_name,
                entity_a=original_ioc,
                entity_a_type="Threat Actor",
                entity_b=parsed_ioc,
                entity_b_type=related_iocs_type,
            ).to_indicator()
        )

    return relationships


def parse_refs(original_ioc: str, refs: list[str]) -> list[dict[str, str]]:
    """
    Parses the references for a given original IOC and builds the correct format to be used in the indicator Publications.

    Args:
        original_ioc (str): The value of the original threat actor.
        refs (list[str]): A list of URLs relevant to the threat actor.

    Returns:
        list[dict[str, str]]: A list of dictionaries containing the parsed references for the original IOC.
    """

    parsed_refs = []

    for ref in refs:
        parsed_refs.append(
            {
                "title": original_ioc,
                "source": "MISP Threat Actors Galaxy",
                "link": ref,
                "timestamp": datetime.now().strftime(DATE_FORMAT),
            }
        )

    return parsed_refs


""" COMMAND FUNCTIONS """


def get_indicators_command(client: Client, args: dict[str, str]) -> CommandResults:
    """
    Retrieve indicators from the MISP Threat Actors Galaxy feed.

    This function fetches threat actor indicators from the MISP Threat Actors Galaxy,
    processes them according to the provided arguments, and returns the results.

    Args:
        client (Client): An instance of the Client class used to interact with the MISP Threat Actors Galaxy feed.
        args (dict[str, str]): A dictionary containing command arguments, which may include:
            - limit: The maximum number of indicators to retrieve.

    Returns:
        CommandResults: An object containing the processed indicators and associated metadata,
        formatted for display in Cortex XSOAR.
    """

    def build_results(actors: list[dict[str, Any]]) -> CommandResults:
        return CommandResults(
            outputs_prefix="FeedMISPThreatActors.ThreatActor",
            outputs_key_field="",
            outputs=actors,
            readable_output=tableToMarkdown("Threat Actors", actors, headers=["Name", "Aliases", "Country", "Description"]),
            raw_response=data,
        )

    limit = int(args.get("limit", 10))
    data = client.get_threat_actors_galaxy_file()
    threat_actors_data = data.get("values", [])
    actors = []

    for counter, threat_actor in enumerate(threat_actors_data):
        if counter >= limit:
            break

        actor = {"Name": threat_actor["value"], "Description": threat_actor.get("description", "")}

        if synonyms := threat_actor["meta"].get("synonyms", []):
            actor["Aliases"] = ", ".join(synonyms)

        if origin_country := threat_actor["meta"].get("country", ""):
            full_country_name = COUNTRIES.get(origin_country, origin_country)
            actor["Country"] = full_country_name

        actors.append(actor)

    return build_results(actors)


def fetch_indicators_command(client: Client, feed_tags: str, tlp_color: str) -> tuple[str, list[dict[str, Any]]]:
    """
    Fetch threat actor indicators from the MISP Threat Actors Galaxy via Github.

    This function retrieves the latest threat actor data from the MISP Threat Actors Galaxy,
    processes it, and returns a list of indicators along with the current version.

    Args:
        client (Client): The client object used to make API requests.
        feed_tags (str): Comma-separated string of tags to be added to each indicator.
        tlp_color (str): The TLP color to be assigned to the indicators.

    Returns:
        tuple[str, list[dict[str, Any]]]: A tuple containing the current version as a string
        and a list of dictionaries representing the processed indicators.
    """
    indicators = []
    data = client.get_threat_actors_galaxy_file()

    version = data["version"]
    demisto.debug(f'{LOG_LINE} - Fetched MISP threat actor galaxy version "{version}"')
    latest_version = demisto.getLastRun().get("version", 0)

    demisto.debug(f'{LOG_LINE} - Latest saved version is "{latest_version}"')

    if int(version) <= int(latest_version):
        demisto.debug(f"{LOG_LINE} Detected same or older version, No new updates - Exiting")
        return version, []

    demisto.debug(f'{LOG_LINE} - Fetched {len(data["values"])} objects.')

    for threat_actor in data["values"]:
        relationships = []
        meta = threat_actor.get("meta", {})
        value = threat_actor["value"]

        if len(value.split(" ")) > SINGLE_WORD:
            value = value.title()
        indicator = {
            "value": value,
            "type": "Threat Actor",
            "rawJSON": threat_actor,
            "fields": {
                "description": threat_actor.get("description", ""),
                "trafficlightprotocol": tlp_color,
                "tags": [tag for tag in feed_tags.split(",") + [f'MISP_ID: {(threat_actor.get("uuid"))}'] if tag],
            },
        }

        if refs := meta.get("refs", []):
            indicator["fields"]["publications"] = parse_refs(threat_actor["value"], refs)

        if synonyms := meta.get("synonyms", []):
            indicator["fields"]["aliases"] = synonyms
            relationships.extend(build_relationships(indicator["value"], synonyms, "Threat Actor", "is-also"))

        if targets := meta.get("cfr-suspected-victims", []):
            relationships.extend(build_relationships(indicator["value"], targets, "Location", "targets"))

        if sectors := meta.get("cfr-target-category", []):
            relationships.extend(build_relationships(indicator["value"], sectors, "Identity", "targets"))

        if origin_country := meta.get("country", ""):
            full_country_name = COUNTRIES.get(origin_country, origin_country)
            indicator["fields"]["geocountry"] = full_country_name
            relationships.extend(build_relationships(indicator["value"], [full_country_name], "Location", "attributed-to"))

        if goals := meta.get("cfr-type-of-incident", ""):
            indicator["fields"]["goals"] = goals

        indicator["relationships"] = relationships

        indicators.append(indicator)

    return version, indicators


""" MAIN FUNCTION """


def main():
    params = demisto.params()

    """PARAMS"""
    base_url = params["url"]
    verify = not params.get("insecure", False)
    proxy = params.get("proxy", "")
    reliability = params.get("integrationReliability", "B - Usually reliable")
    tlp_color = params.get("tlp_color") or "WHITE"
    feed_tags = params.get("feedTags", "")

    if not DBotScoreReliability.is_valid_type(reliability):
        raise Exception("Please provide a valid value for the Source Reliability parameter.")

    try:
        command = demisto.command()
        client = Client(
            base_url=base_url,
            verify=verify,
            proxy=proxy,
            reliability=reliability,
        )

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

        elif command == "fetch-indicators":
            version, res = fetch_indicators_command(
                client=client,
                feed_tags=feed_tags,
                tlp_color=tlp_color,
            )

            for iter_ in batch(res, batch_size=2000):
                demisto.debug(f"{LOG_LINE} - Processing {len(iter_)} new indicators.")
                demisto.createIndicators(iter_)

            demisto.setLastRun({"version": f"{version}"})

        elif command == "mispthreatactors-get-indicators":
            return_results(get_indicators_command(client, demisto.args()))

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

    except NotImplementedError:
        demisto.error(traceback.format_exc())  # print the traceback
        return_error(f"Failed to execute {command} command. The command not implemented")

    except Exception as err:
        demisto.error(traceback.format_exc())  # print the traceback
        return_error(f"Error running integration - {err}")


""" ENTRY POINT """

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