diff --git a/mitreattack/attackToExcel/stixToDf.py b/mitreattack/attackToExcel/stixToDf.py index c6bdf6c..5d2906a 100644 --- a/mitreattack/attackToExcel/stixToDf.py +++ b/mitreattack/attackToExcel/stixToDf.py @@ -72,6 +72,13 @@ def get_citations(objects): return pd.DataFrame(citations).drop_duplicates(subset="reference", ignore_index=True) +def _get_mapping_descriptions(dataframe): + """Return non-null mapping descriptions from a relationship dataframe.""" + if "mapping description" not in dataframe.columns: + return [] + return filter(lambda x: x == x, dataframe["mapping description"].tolist()) + + def parseBaseStix(sdo): """Given an SDO, return a dict of field names:values that are common across all ATT&CK STIX types.""" row = {} @@ -1311,8 +1318,7 @@ def relationshipsToDf(src, relatedType=None): usedCitations = set() for dfname in dataframes: df = dataframes[dfname] - # filter out missing descriptions which for whatever reason - for description in filter(lambda x: x == x, df["mapping description"].tolist()): + for description in _get_mapping_descriptions(df): # in pandas don't equal themselves [usedCitations.add(x) for x in re.findall(r"\(Citation: (.*?)\)", description)] @@ -1342,7 +1348,7 @@ def _get_relationship_citations(object_dataframe, relationship_df): mask = relationship_df[z].values == y filtered = relationship_df[z].loc[mask] temp = set() - for description in filter(lambda x: x == x, filtered["mapping description"].tolist()): + for description in _get_mapping_descriptions(filtered): [temp.add(x) for x in re.findall(r"\(Citation: (.*?)\)", description)] subset.append(",".join([f"(Citation: {z})" for z in temp])) if not new_citations: diff --git a/tests/test_stix_to_df.py b/tests/test_stix_to_df.py index 2655505..dcb8dc6 100644 --- a/tests/test_stix_to_df.py +++ b/tests/test_stix_to_df.py @@ -43,3 +43,77 @@ def test_techniques_to_df_handles_missing_tactic_definition(monkeypatch): assert len(techniques_df) == 1 assert techniques_df.iloc[0]["tactics"] == "Defense Evasion" + + +def test_techniques_to_df_handles_targets_relationship_without_description(): + """TechniquesToDf should tolerate asset targets relationships with no description.""" + mem_store = stix2.MemoryStore( + stix_data=[ + { + "type": "attack-pattern", + "spec_version": "2.1", + "id": "attack-pattern--11111111-1111-4111-8111-111111111111", + "created": "2020-01-01T00:00:00.000Z", + "modified": "2020-01-01T00:00:00.000Z", + "name": "Test Technique", + "description": "Test technique", + "kill_chain_phases": [ + { + "kill_chain_name": "mitre-attack", + "phase_name": "inhibit-response-function", + } + ], + "external_references": [ + { + "source_name": "mitre-attack", + "external_id": "T0001", + "url": "https://example.com/technique", + } + ], + "x_mitre_domains": ["ics-attack"], + }, + { + "type": "x-mitre-asset", + "spec_version": "2.1", + "id": "x-mitre-asset--22222222-2222-4222-8222-222222222222", + "created": "2020-01-01T00:00:00.000Z", + "modified": "2020-01-01T00:00:00.000Z", + "name": "Test Asset", + "description": "Test asset", + "external_references": [ + { + "source_name": "mitre-attack", + "external_id": "A0001", + "url": "https://example.com/asset", + } + ], + "x_mitre_domains": ["ics-attack"], + }, + { + "type": "relationship", + "spec_version": "2.1", + "id": "relationship--33333333-3333-4333-8333-333333333333", + "created": "2020-01-01T00:00:00.000Z", + "modified": "2020-01-01T00:00:00.000Z", + "relationship_type": "targets", + "source_ref": "attack-pattern--11111111-1111-4111-8111-111111111111", + "target_ref": "x-mitre-asset--22222222-2222-4222-8222-222222222222", + "external_references": [ + { + "source_name": "Test Reference", + "description": "Test citation", + "url": "https://example.com/reference", + } + ], + }, + ] + ) + + dataframes = stixToDf.techniquesToDf(mem_store, "ics-attack") + + assert "targeted assets" in dataframes + assert len(dataframes["targeted assets"]) == 1 + assert dataframes["targeted assets"].iloc[0]["target name"] == "Test Asset" + assert dataframes["techniques"].iloc[0]["relationship citations"] == "" + if "citations" in dataframes: + assert dataframes["citations"].empty