MITRE ATT&CK v2

Use the MITRE ATT&CK® feed to fetch MITRE’s Adversarial Tactics, Techniques, and Common Knowledge (ATT&CK®) content. MITRE ATT&CK is a globally-accessible knowledge base of adversary tactics and techniques based on real-world observations. The ATT&CK knowledge base is used as a foundation for the development of specific threat models and methodologies in the private sector, in government, and in the cybersecurity product and service community.

Data Enrichment & Threat Intelligence · MITRE ATT&CK · Feed

Details

IDMITRE ATT&CK v2
ProviderMITRE Corporation
CategoryData Enrichment & Threat Intelligence
From Version5.5.0
Docker Imagedemisto/taxii2:1.0.0.7704170
Supported ModulesAgentix XSIAM EDR Cortex Cloud Cloud Runtime Security Attack Surface Management Cloud Posture Security Exposure Management

README

Use the MITRE ATT&CK Feed integration to fetch indicators from MITRE ATT&CK.
For more information click here.

Note: When upgrading from v1 (MITRE IDs Feed) to v2 (MITRE ATT&CK) - disabling the MITRE IDs Feed indicator type, and instance are important for the smooth flow of the upgrade.

Configure MITRE ATT&CK Feed in Cortex

Parameter Description Required
includeAPT This option will also create indicators using APT / actor name references if they are part of a MITRE Intrusion Set False
feedReputation The indicator reputation (defaults to ‘None’). 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
feedExpirationInterval The interval after which the feed expires. False
feedFetchInterval The feed fetch interval. False
feedBypassExclusionList Whether to bypass exclusion list. False
insecure Whether to trust any certificate (not secure). False
proxy Whether to use the system proxy settings. False
Create relationships Create relationships between indicators as part of Enrichment. False

Feed timeouts

MITRE enforce a rate limit for connecting to their taxii server. Ensure that your fetch interval is reasonable, otherwise you will receive connection errors.

Commands

You can execute these commands from the 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 MITRE ATT&CK.

Note: This command does not create indicators within Cortex XSOAR.

Base Command

mitre-get-indicators

Input
Argument Name Description Required
limit The maximum number of indicators to return. The default value is 10. Optional
raw Enabling raw will also output the raw content of each indicator Optional
Context Output

The context is output as:

  • MITRE (dict)
    • ATT&CK (list)

Each item in the “ATT&CK” list contains the following keys:

  • fields (any fields that the indicator will attempt to map into the indicator)
  • rawJSON (the raw JSON of the indicator)
  • score (the indicator score)
  • type (the type of indicator - will always be “MITRE ATT&CK”)
  • value (the indicator value, for example “T1134”)
Command Example

!mitre-get-indicators limit=2

Human Readable Output

MITRE ATT&CK Indicators

Value Score Type
T1531 0 MITRE ATT&CK
T1506 0 MITRE ATT&CK
Argument Name Description Required
indicator Indicator to lookup Required
Context Output

The context is output as:

  • DBotScore
  • MITRE (dict)
    • ATT&CK (list)

Each item in the “ATT&CK” list contains the customFields that are mapped into the indicator (each beginning with ‘mitre’)

MITRE Show Feeds


Displays the available feeds from the MITRE taxii service.

Base Command

mitre-show-feeds

Input

There are no inputs

Context Output

There is no context output

Command Example

!mitre-showfeeds

Human Readable Output

MITRE ATT&CK Feeds

Name ID
Enterprise ATT&CK 95ecc380-afe9-11e4-9b6c-751b66dd541e
PRE-ATT&CK 062767bd-02d2-4b72-84ba-56caef0f8658
Mobile ATT&CK 2f669986-b40b-4423-b720-4396ca6a462b

MITRE Get Indicator Name


Gets the Attack Pattern value from the Attack Pattern ID in the Enterprise collection only.

Base Command

mitre-get-indicator-name

Input
Argument Name Description Required
attack_ids The Attack Pattern IDs list True
Context Output
Path Type Description
MITREATTACK.id String MITRE ATTACK Attack Pattern ID.
MITREATTACK.value String MITRE ATTACK Attack Pattern value.
Command Example

!mitre-get-indicator-name attack_id=T1111

Human Readable Output

MITRE ATTACK Attack Patterns values

Attack ID Attack Value
T1111 Some Attack Value

attack-pattern


Looks up the reputation of the indicator in the Enterprise collection only.

Base Command

attack-pattern

Input

Argument Name Description Required
attack_pattern Indicator to look up. Required

Context Output

Path Type Description
DBotScore.Indicator string The indicator that was tested.
DBotScore.Score number The actual score.
DBotScore.Type string The indicator type.
DBotScore.Vendor string The vendor used to calculate the score.
AttackPattern.STIXID string The STIX ID of the Attack Pattern.
AttackPattern.KillChainPhases string The kill chain phases of the Attack Pattern.
AttackPattern.FirstSeenBySource string The first seen by source of the Attack Pattern.
AttackPattern.Description string The description of the Attack Pattern.
AttackPattern.OperatingSystemRefs string The operating system references of the Attack Pattern.
AttackPattern.Publications string The publications of the Attack Pattern.
AttackPattern.MITREID string The MITRE ID of the Attack Pattern.
AttackPattern.Tags string The tags of the Attack Pattern.

Command Example

!attack-pattern attack_pattern="Search Threat Vendor Data"

Human Readable Output

MITRE ATTACK
Name: Search Threat Vendor Data - ID: T1681

Threat actors may seek information/indicators from closed or open threat intelligence sources gathered about their own campaigns, as well as those conducted by other adversaries that may align with their target industries, capabilities/objectives, or other operational concerns. These reports may include descriptions of behavior, detailed breakdowns of attacks, atomic indicators such as malware hashes or IP addresses, timelines of a group’s activity, and more. Adversaries may change their behavior when planning their future operations.

Adversaries have been observed replacing atomic indicators mentioned in blog posts in under a week. Adversaries have also been seen searching for their own domain names in threat vendor data and then taking them down, likely to avoid seizure or further investigation.

This technique is distinct from Threat Intel Vendors in that it describes threat actors performing reconnaissance on their own activity, not in search of victim information.

Troubleshooting

Rate-Limiting or IP Blocking Issues

Problem: You may encounter a “Too Many Requests” error for the URL https://attack-taxii.mitre.org/taxii2/ when accessing the MITRE ATT&CK API, because the MITRE ATT&CK API enforces rate limits and may block requests based on the IP address. This often occurs when multiple users or systems are making requests from the same IP address.

Solution: To resolve this issue, it is recommended to use a dedicated engine for your MITRE ATT&CK Feed integration. This will provide a unique IP address for the integration instance, preventing it from being blocked due to shared usage. Additionally, ensure that your fetch interval is set to a reasonable value to avoid exceeding the rate limits.

Configuration parameters

  • feed — Fetch indicators
  • feedReputation — Indicator Reputation
  • feedReliability — Source Reliability
  • tlp_color — Traffic Light Protocol Color
  • feedExpirationPolicy
  • feedExpirationInterval
  • feedFetchInterval — Feed Fetch Interval
  • feedTags — Tags
  • create_relationships — Create relationships
  • switch_intrusion_set_to_threat_actor — Save intrusion sets as threat actor indicator types
  • feedBypassExclusionList — Bypass exclusion list
  • proxy — Use system proxy settings
  • insecure — Trust any certificate (not secure)

Commands (4)

  • attack-pattern

    Looks up the reputation of the indicator in the Enterprise collection only.

  • mitre-get-indicator-name

    Gets the Attack Pattern value from the Attack Pattern ID in the Enterprise collection only.

  • mitre-get-indicators

    Retrieves a limited number of indicators.

  • mitre-show-feeds

    Shows the feed names and IDs from TAXII.

import json

import demistomock as demisto  # noqa: F401
import pytest
from stix2 import TAXIICollectionSource, parse
from test_data.mitre_test_data import (
    ATTACK_PATTERN,
    ATTACK_PATTERNS,
    CAMPAIGN,
    COURSE_OF_ACTION,
    ID_TO_NAME,
    INDICATORS_LIST,
    INTRUSION_SET,
    MALWARE,
    MALWARE_LIST_WITH_PREFIX,
    MALWARE_LIST_WITHOUT_PREFIX,
    MITRE_ID_TO_MITRE_NAME,
    NEW_ID_TO_NAME,
    NEW_INDICATORS_LIST,
    OLD_ID_TO_NAME,
    RELATION,
    RELATIONSHIP_ENTITY,
    TOOL,
)

ENTERPRISE_COLLECTION_ID = "	x-mitre-collection–1f5f1533-f617-4ca8-9ab4-6a02367fa019"  # noqa: RUF001
NON_ENTERPRISE_COLLECTION_ID = "101010101010101010101010101010101"


class MockCollection:
    def __init__(self, id_, title):
        self.id = id_
        self.title = title


def mock_create_relations(original):
    def mock(item_json, id_to_name):
        return original(item_json, ID_TO_NAME)

    return mock


@pytest.mark.parametrize(
    "indicator, expected_result",
    [
        ([ATTACK_PATTERN.get("response")], ATTACK_PATTERN.get("indicator")),
        ([COURSE_OF_ACTION.get("response")], COURSE_OF_ACTION.get("indicator")),
        ([INTRUSION_SET.get("response")], INTRUSION_SET.get("indicator")),
        ([MALWARE.get("response")], MALWARE.get("indicator")),
        ([TOOL.get("response")], TOOL.get("indicator")),
    ],
)
def test_fetch_indicators(mocker, indicator, expected_result):
    """
    Given
    - fetch incidents command
    - command args
    - command raw response
    When
    - mock the Client's get_stix_objects.
    Then
    - run the fetch incidents command using the Client
    Validate that all the indicators extracted successfully
    """
    import FeedMitreAttackv2 as fm
    from FeedMitreAttackv2 import Client, create_relationship

    client = Client(url="https://test.org", proxies=False, verify=False, tags=[], tlp_color=None)
    client.tactic_name_to_mitre_id = {
        "Defense Evasion": "TA0005",
        "Privilege Escalation": "TA0004",
        "Resource Development": "TA0042",
    }

    default_id = ENTERPRISE_COLLECTION_ID
    nondefault_id = 2
    client.collections = [MockCollection(default_id, "enterprise att&ck"), MockCollection(nondefault_id, "not_default")]
    mocker.patch.object(client, "initialise")

    mocker.patch.object(TAXIICollectionSource, "__init__", return_value=None)
    mocker.patch.object(TAXIICollectionSource, "query", return_value=indicator)
    # mocker.patch.object(json, 'loads', return_value=indicator[0])
    mocker.patch.object(fm, "create_relationship", wraps=mock_create_relations(create_relationship))

    indicators = client.build_iterator(create_relationships=True, limit=7)
    assert indicators == expected_result

    default_id = NON_ENTERPRISE_COLLECTION_ID
    nondefault_id = 2
    client.collections = [MockCollection(default_id, "default"), MockCollection(nondefault_id, "not_default")]
    mocker.patch.object(client, "initialise")

    mocker.patch.object(TAXIICollectionSource, "__init__", return_value=None)
    mocker.patch.object(TAXIICollectionSource, "query", return_value=indicator)
    mocker.patch.object(json, "loads", return_value=indicator[0])
    mocker.patch.object(fm, "create_relationship", wraps=mock_create_relations(create_relationship))

    indicators = client.build_iterator(create_relationships=True, limit=7)
    assert indicators == ([], [], {}, {})


@pytest.mark.parametrize(
    "field_name, field_value, expected_result",
    [
        ("created", "2017-05-31T21:31:43.540Z", "2017-05-31T21:31:43.540Z"),
        ("created", "2019-04-25T20:53:07.719Z\n2019-04-25T20:53:07.814Z", "2019-04-25T20:53:07.719Z"),
        ("modified", "2017-05-31T21:31:43.540Z", "2017-05-31T21:31:43.540Z"),
        ("modified", "2020-03-16T15:38:37.650Z\n2020-01-17T16:45:24.252Z", "2020-03-16T15:38:37.650Z"),
    ],
)
def test_handle_multiple_dates_in_one_field(field_name, field_value, expected_result):
    """
    Given
    - created / modified indicator field
    When
    - this field contains two dates
    Then
    - run the handle_multiple_dates_in_one_field
    Validate The field contain one specific date.
    """
    from FeedMitreAttackv2 import handle_multiple_dates_in_one_field

    assert handle_multiple_dates_in_one_field(field_name, field_value) == expected_result


@pytest.mark.parametrize(
    "indicator, expected_result", [({"x_mitre_deprecated": True}, True), ({"revoked": True}, True), ({}, False)]
)
def test_is_indicator_deprecated_or_revoked(indicator, expected_result):
    """
    Given
    - indicator in STIX format.
    When
    - we cheed
    Then
    - run the create_list_relationships
    Validate The relationships list extracted successfully.
    """
    from FeedMitreAttackv2 import is_indicator_deprecated_or_revoked

    assert is_indicator_deprecated_or_revoked(indicator) == expected_result


@pytest.mark.parametrize(
    "indicator_type, indicator_json, expected_result",
    [
        ("Attack Pattern", ATTACK_PATTERN.get("response"), ATTACK_PATTERN.get("map_result")),
        ("Course of Action", COURSE_OF_ACTION.get("response"), COURSE_OF_ACTION.get("map_result")),
        ("Intrusion Set", INTRUSION_SET.get("response"), INTRUSION_SET.get("map_result")),
        ("Malware", MALWARE.get("response"), MALWARE.get("map_result")),
        ("Tool", TOOL.get("response"), TOOL.get("map_result")),
        ("Campaign", CAMPAIGN.get("response"), CAMPAIGN.get("map_result")),
    ],
)
def test_map_fields_by_type(indicator_type, indicator_json, expected_result):
    from FeedMitreAttackv2 import map_fields_by_type

    assert map_fields_by_type(indicator_type, indicator_json) == expected_result


def test_create_relationship():
    """
    Given
    - relationship obj in STIX format.
    When
    - we extract this relationship to Demisto format
    Then
    - run the create_relationship
    Validate The relationship extracted successfully.
    """
    from FeedMitreAttackv2 import create_relationship

    relation = create_relationship(RELATION.get("response"), ID_TO_NAME)
    relation._entity_a = "entity a"
    relation._entity_a_type = "STIX Malware"
    relation._entity_b = "entity b"
    relation._entity_b_type = "STIX Attack Pattern"
    relation._name = "uses"
    relation._relation_type = "IndicatorToIndicator"
    relation._reverse_name = "used-by"


def test_get_item_type():
    from FeedMitreAttackv2 import get_item_type

    assert get_item_type("malware", True) == "Malware"
    assert get_item_type("malware", False) == "STIX Malware"
    assert get_item_type("intrusion-set", True) == "Intrusion Set"
    assert get_item_type("intrusion-set", False) == "Intrusion Set"


def test_create_relationship_list():
    from FeedMitreAttackv2 import create_relationship_list

    assert create_relationship_list([RELATION.get("response")], ID_TO_NAME) == RELATION.get("indicator")


def test_add_malware_prefix_to_dup_with_intrusion_set():
    from FeedMitreAttackv2 import add_malware_prefix_to_dup_with_intrusion_set

    malware_list = MALWARE_LIST_WITHOUT_PREFIX
    add_malware_prefix_to_dup_with_intrusion_set(MALWARE_LIST_WITHOUT_PREFIX, ID_TO_NAME)
    assert malware_list == MALWARE_LIST_WITH_PREFIX


def test_add_obj_to_mitre_id_to_mitre_name():
    from FeedMitreAttackv2 import add_obj_to_mitre_id_to_mitre_name

    mitre_id_to_mitre_name = {}
    add_obj_to_mitre_id_to_mitre_name(mitre_id_to_mitre_name, ATTACK_PATTERN["response"])
    assert mitre_id_to_mitre_name == {"T1047": "ATTACK_PATTERN 1"}


def test_add_technique_prefix_to_sub_technique():
    from FeedMitreAttackv2 import add_technique_prefix_to_sub_technique

    indicators = INDICATORS_LIST
    mitre_id_to_mitre_name = MITRE_ID_TO_MITRE_NAME
    id_to_name = OLD_ID_TO_NAME

    add_technique_prefix_to_sub_technique(indicators, id_to_name, mitre_id_to_mitre_name)
    assert indicators == NEW_INDICATORS_LIST
    assert id_to_name == NEW_ID_TO_NAME


def test_publication_link_not_none():
    from FeedMitreAttackv2 import map_fields_by_type

    indicator = {"created": "2022-01-05T14:27:46.612705Z", "modified": "2022-01-05T14:27:46.612705Z", "external_references": [{}]}

    res = map_fields_by_type("Malware", indicator)
    assert res["publications"][0]["link"] is not None


def test_create_relationships_invalid():
    from FeedMitreAttackv2 import create_relationship

    item_json = {"source_ref": "", "target_ref": ""}
    assert create_relationship(item_json, {}) is None


def test_create_relationship_with_unknown_relationship_name():
    from FeedMitreAttackv2 import create_relationship

    item_json = {"source_ref--source_ref": "source_ref", "target_ref--target_ref": "target_ref"}
    output = create_relationship(RELATIONSHIP_ENTITY, item_json)
    assert output is not None


@pytest.mark.parametrize(
    "attack_id, attack_pattern_obj, expected_result",
    [
        ("T1111", {"external_references": [{"external_id": "T1111"}]}, True),
        ("T1098", {"external_references": [{"external_id": "T1111"}]}, False),
    ],
)
def test_filter_attack_pattern_object_by_attack_id(attack_id, attack_pattern_obj, expected_result):
    from FeedMitreAttackv2 import filter_attack_pattern_object_by_attack_id

    output = filter_attack_pattern_object_by_attack_id(attack_id, attack_pattern_obj)
    assert output == expected_result


@pytest.mark.parametrize(
    "description, expected_result",
    [
        ("Test (23)", ""),
        ("Test (2020, Mar)", "2020-03-01T00:00:00"),
        ("Test (Test) (2020, Mar)", "2020-03-01T00:00:00"),
        ("Test 2033)", ""),
        ("Test ()", ""),
        ("Test (Test)", ""),
        ("Gross, J. (2016, February 23). Operation Dust Storm. Retrieved December 22, 2021.", "2016-02-23T00:00:00"),
        ("Cisco. (n.d.). Cisco IOS Software Integrity Assurance - Command History. Retrieved October 21, 2020.", ""),
        ("Citation: Security Affairs Elderwood Sept 2012)", ""),
        ("Insikt Group (Recorded Future). (2017, May 17).", "2017-05-17T00:00:00"),
        ("Insikt Group (Recorded Future). (2017, May17).", "2017-05-17T00:00:00"),
        ("Insikt Group (Recorded Future). (2017,May17).", "2017-05-17T00:00:00"),
        ("Insikt Group (Recorded Future). (2017,March17).", "2017-03-17T00:00:00"),
        ("Insikt Group (Recorded Future). (2017, March 17).", "2017-03-17T00:00:00"),
    ],
)
def test_extract_date_time_from_description(description, expected_result):
    from FeedMitreAttackv2 import extract_date_time_from_description

    output = extract_date_time_from_description(description)
    assert output == expected_result


def test_attack_pattern_reputation_command(mocker):
    """
    Given:
        Some attack patterns to retrieve, with and without sub-technique

    When:
        Running attack-pattern reputation command

    Then:
        Returns the wanted attack patterns
    """
    from FeedMitreAttackv2 import attack_pattern_reputation_command

    stix_objs = [parse(stix_obj_dict, allow_custom=True) for stix_obj_dict in ATTACK_PATTERNS]
    mocker.patch("FeedMitreAttackv2.get_mitre_data_by_filter", return_value=stix_objs)

    args = {"attack_pattern": "Abuse Elevation Control Mechanism, Active Scanning: Wordlist Scanning"}
    command_results = attack_pattern_reputation_command("", args)

    assert command_results[0].indicator.value == "Abuse Elevation Control Mechanism"
    assert command_results[1].indicator.value == "Active Scanning: Wordlist Scanning"


def test_attack_pattern_reputation_without_answer_command(mocker):
    """
    Given:
        One attach pattern to retrive data on, that is not found in the collection

    When:
        Running attack-pattern reputation command

    Then:
        Ensures the command_results is not empty and readable_output is as expected
    """
    from FeedMitreAttackv2 import attack_pattern_reputation_command

    stix_objs = [parse(stix_obj_dict, allow_custom=True) for stix_obj_dict in ATTACK_PATTERNS]
    mocker.patch("FeedMitreAttackv2.get_mitre_data_by_filter", return_value=stix_objs)

    args = {"attack_pattern": "dummy attack pattern"}
    command_results = attack_pattern_reputation_command("", args)

    assert command_results
    assert (
        command_results.readable_output == "MITRE ATTACK Attack Patterns values: No Attack "
        "Patterns found for ['dummy attack pattern'] in the Enterprise collection."
    )


def test_get_mitre_value_from_id_without_answer_command(mocker):
    """
    Given:
        One attach pattern to retrive data on, that is not found in the collection

    When:
        Running attack-pattern reputation command

    Then:
        Ensures the command_results is not empty and readable_output is as expected
    """
    from FeedMitreAttackv2 import get_mitre_value_from_id

    stix_objs = [parse(stix_obj_dict, allow_custom=True) for stix_obj_dict in ATTACK_PATTERNS]
    mocker.patch("FeedMitreAttackv2.get_mitre_data_by_filter", return_value=stix_objs)

    args = {"attack_ids": ["dummy attack pattern id"]}
    command_results = get_mitre_value_from_id("", args)

    assert command_results
    assert (
        command_results.readable_output == "MITRE ATTACK Attack Patterns values: "
        "No Attack Patterns found for ['dummy attack pattern id'] in the "
        "Enterprise collection."
    )


@pytest.mark.parametrize(
    "description, expected_result",
    [
        (
            "Waterbear is modular malware attributed to BlackTech ...(Citation: Trend Micro Waterbear December 2019)",
            "Waterbear is modular malware attributed to BlackTech ...",
        ),
        (
            "Adversaries may employ various means to detect and avoid debuggers.(Citation: ProcessHacker Github)\
(assuming a present debugger would “swallow” or handle the potential error).\
(Citation: hasherezade debug)(Citation: AlKhaser Debug)(Citation: vxunderground debug)\
<code>OutputDebugStringW()</code>.(Citation: wardle evilquest partii)(Citation: Checkpoint Dridex Jan 2021)",
            "Adversaries may employ various means to detect and avoid debuggers.\
(assuming a present debugger would “swallow” or handle the potential error).\
<code>OutputDebugStringW()</code>.",
        ),
    ],
)
def test_remove_citations(description, expected_result):
    """
    Given:
        A description with Citation.
    When:
        Calling remove_citation method.
    Then:
        Output description will not contain Citation parts.
    """
    from FeedMitreAttackv2 import remove_citations

    actual_result = remove_citations(description)
    assert "Citation" not in actual_result
    assert actual_result == expected_result


def test_create_indicator_with_unknown_tactic_name(mocker):
    """
    Given:
        An Attack Pattern whose kill_chain_phases contains a tactic phase name ('stealth')
        that is NOT present in the client's tactic_name_to_mitre_id mapping.
    When:
        Calling client.create_indicator().
    Then:
        The indicator is created successfully without raising a KeyError.
        Both relationships are created: the known tactic uses the full "ID - Name" format,
        and the unknown tactic falls back to using just its name.
    """
    from FeedMitreAttackv2 import Client

    client = Client(url="https://test.org", proxies=False, verify=False, tags=[], tlp_color=None)
    # Only known tactics are pre-populated; 'Stealth' is intentionally absent
    client.tactic_name_to_mitre_id = {
        "Defense Evasion": "TA0005",
    }

    mitre_item_json = {
        "id": "attack-pattern--test-stealth-1234",
        "name": "Some Technique",
        "type": "attack-pattern",
        "created": "2024-01-01T00:00:00.000Z",
        "modified": "2024-01-01T00:00:00.000Z",
        "description": "A technique that uses stealth.",
        "external_references": [
            {"source_name": "mitre-attack", "external_id": "T9999", "url": "https://attack.mitre.org/techniques/T9999"},
        ],
        "kill_chain_phases": [
            {"kill_chain_name": "mitre-attack", "phase_name": "defense-evasion"},
            {"kill_chain_name": "mitre-attack", "phase_name": "stealth"},  # new unknown tactic
        ],
        "x_mitre_platforms": ["Windows"],
    }

    mocker.patch("FeedMitreAttackv2.demisto.debug")

    # Should not raise KeyError
    indicator = client.create_indicator("Attack Pattern", "Some Technique", mitre_item_json)

    assert indicator["value"] == "Some Technique"
    # Both tactics should appear in relationships:
    # - known tactic uses full "ID - Name" format
    # - unknown tactic falls back to name-only (no MITRE ID prefix)
    assert len(indicator["relationships"]) == 2
    entity_b_values = {rel["entityB"] for rel in indicator["relationships"]}
    assert "TA0005 - Defense Evasion" in entity_b_values
    assert "Stealth" in entity_b_values


def test_get_mitre_value_from_id_with_valid_ids(mocker):
    """
    Given:
        Valid MITRE technique IDs (T1111 and T1111.001) that exist in the collection.

    When:
        Running mitre-get-indicator-name command.

    Then:
        Returns the correct attack pattern names, including parent: sub-technique format.
    """
    from FeedMitreAttackv2 import get_mitre_value_from_id

    stix_objs = [parse(stix_obj_dict, allow_custom=True) for stix_obj_dict in ATTACK_PATTERNS]
    mocker.patch("FeedMitreAttackv2.get_mitre_data_by_filter", return_value=stix_objs)

    # T1111 is a top-level technique ("Active Scanning")
    args = {"attack_ids": ["T1111"]}
    command_results = get_mitre_value_from_id("", args)

    assert command_results
    assert command_results.outputs
    assert command_results.outputs[0]["id"] == "T1111"
    assert command_results.outputs[0]["value"] == "Active Scanning"


def test_get_mitre_value_from_id_sub_technique_with_parent(mocker):
    """
    Given:
        A valid MITRE sub-technique ID (T1111.001) whose parent (T1111) exists in the collection.

    When:
        Running mitre-get-indicator-name command.

    Then:
        Returns the attack pattern name in "Parent: Sub-technique" format.
    """
    from FeedMitreAttackv2 import get_mitre_value_from_id

    stix_objs = [parse(stix_obj_dict, allow_custom=True) for stix_obj_dict in ATTACK_PATTERNS]
    mocker.patch("FeedMitreAttackv2.get_mitre_data_by_filter", return_value=stix_objs)

    # T1111.001 is "Wordlist Scanning", parent T1111 is "Active Scanning"
    args = {"attack_ids": ["T1111.001"]}
    command_results = get_mitre_value_from_id("", args)

    assert command_results
    assert command_results.outputs
    assert command_results.outputs[0]["id"] == "T1111.001"
    assert command_results.outputs[0]["value"] == "Active Scanning: Wordlist Scanning"


def test_get_mitre_value_from_id_sub_technique_missing_parent(mocker):
    """
    Given:
        A MITRE sub-technique ID whose parent technique is NOT in the collection.

    When:
        Running mitre-get-indicator-name command.

    Then:
        Does not raise an IndexError; returns the sub-technique name without parent prefix.
    """
    from FeedMitreAttackv2 import get_mitre_value_from_id

    # Only include the sub-technique, not the parent
    sub_technique_only = [ATTACK_PATTERNS[0]]  # T1111.001 "Wordlist Scanning", parent T1111 not included
    stix_objs = [parse(stix_obj_dict, allow_custom=True) for stix_obj_dict in sub_technique_only]
    mocker.patch("FeedMitreAttackv2.get_mitre_data_by_filter", return_value=stix_objs)

    args = {"attack_ids": ["T1111.001"]}
    # Should not raise IndexError
    command_results = get_mitre_value_from_id("", args)

    assert command_results
    assert command_results.outputs
    assert command_results.outputs[0]["id"] == "T1111.001"
    # Without parent, name is just the sub-technique name
    assert command_results.outputs[0]["value"] == "Wordlist Scanning"


def test_show_feeds_command(mocker):
    """
    Given:
        A Client.
    When:
        Calling show_feeds_command method.
    Then:
        Validate the output extracted successfully.
    """
    from FeedMitreAttackv2 import Client, show_feeds_command

    client = Client(url="https://test.org", proxies=False, verify=False, tags=[], tlp_color=None)
    default_id = NON_ENTERPRISE_COLLECTION_ID
    nondefault_id = 2
    client.collections = [MockCollection(default_id, "default"), MockCollection(nondefault_id, "not_default")]
    mocker.patch.object(demisto, "results")
    show_feeds_command(client)
    assert demisto.results.call_count == 1
    assert demisto.results.call_args[0][0] == {
        "Type": 1,
        "Contents": [{"Name": "default", "ID": "101010101010101010101010101010101"}, {"Name": "not_default", "ID": 2}],
        "ContentsFormat": "json",
        "HumanReadable": "### MITRE ATT&CK Feeds:\n|Name|ID|\n|---|---|\n| default |\
 101010101010101010101010101010101 |\n| not_default | 2 |\n",
        "ReadableContentsFormat": "markdown",
    }