diff --git a/docs/mitre_attack_data/custom_objects.rst b/docs/mitre_attack_data/custom_objects.rst index fcc8b75c..bbb43470 100644 --- a/docs/mitre_attack_data/custom_objects.rst +++ b/docs/mitre_attack_data/custom_objects.rst @@ -41,6 +41,13 @@ information about the mapping of ATT&CK concepts to STIX 2.0 objects can be foun * **x_mitre_data_source_ref** (*str*) - The STIX ID of the data source this component is a part of. +.. autoclass:: mitreattack.stix20.Asset + + **Custom Properties:** + + * **x_mitre_platforms** (*list[str]*) - The list of platforms that apply to the asset. + * **x_mitre_related_assets** (*list[dict]*) - The list of related assets. + STIX Object Factory ------------------- diff --git a/docs/mitre_attack_data/examples.rst b/docs/mitre_attack_data/examples.rst index f905e2d5..81c0060d 100644 --- a/docs/mitre_attack_data/examples.rst +++ b/docs/mitre_attack_data/examples.rst @@ -31,6 +31,7 @@ Getting ATT&CK Objects by Type * `get_all_groups.py `_ * `get_all_software.py `_ * `get_all_campaigns.py `_ +* `get_all_assets.py `_ * `get_all_datasources.py `_ * `get_all_datacomponents.py `_ @@ -111,3 +112,10 @@ Campaign:Group Relationships * `get_groups_attributing_to_campaign.py `_ * `get_all_campaigns_attributed_to_all_groups.py `_ * `get_campaigns_attributed_to_group.py `_ + +Technique:Asset Relationships + +* `get_all_assets_targeted_by_all_techniques.py `_ +* `get_assets_targeted_by_technique.py `_ +* `get_all_techniques_targeting_all_assets.py `_ +* `get_techniques_targeting_asset.py `_ diff --git a/examples/get_all_assets.py b/examples/get_all_assets.py new file mode 100644 index 00000000..24c0feba --- /dev/null +++ b/examples/get_all_assets.py @@ -0,0 +1,13 @@ +from mitreattack.stix20 import MitreAttackData + + +def main(): + mitre_attack_data = MitreAttackData("ics-attack.json") + + assets = mitre_attack_data.get_assets(remove_revoked_deprecated=True) + + print(f"Retrieved {len(assets)} ICS assets.") + + +if __name__ == "__main__": + main() diff --git a/examples/get_all_assets_targeted_by_all_techniques.py b/examples/get_all_assets_targeted_by_all_techniques.py new file mode 100644 index 00000000..daf84029 --- /dev/null +++ b/examples/get_all_assets_targeted_by_all_techniques.py @@ -0,0 +1,16 @@ +from mitreattack.stix20 import MitreAttackData + + +def main(): + mitre_attack_data = MitreAttackData("ics-attack.json") + + # get all assets targeted by techniques + assets_targeted_by_techniques = mitre_attack_data.get_all_assets_targeted_by_all_techniques() + + print(f"Assets targeted by techniques ({len(assets_targeted_by_techniques.keys())} techniques):") + for id, techniques in assets_targeted_by_techniques.items(): + print(f"* {id} - targets {len(techniques)} asset(s)") + + +if __name__ == "__main__": + main() diff --git a/examples/get_all_techniques_targeting_all_assets.py b/examples/get_all_techniques_targeting_all_assets.py new file mode 100644 index 00000000..376ea21a --- /dev/null +++ b/examples/get_all_techniques_targeting_all_assets.py @@ -0,0 +1,15 @@ +from mitreattack.stix20 import MitreAttackData + + +def main(): + mitre_attack_data = MitreAttackData("ics-attack.json") + + techniques_targeting_assets = mitre_attack_data.get_all_techniques_targeting_all_assets() + + print(f"Techniques targeting assets ({len(techniques_targeting_assets.keys())} assets):") + for id, techniques in techniques_targeting_assets.items(): + print(f"* {id} - targeted by {len(techniques)} {'technique' if len(techniques) == 1 else 'techniques'}") + + +if __name__ == "__main__": + main() diff --git a/examples/get_assets_targeted_by_technique.py b/examples/get_assets_targeted_by_technique.py new file mode 100644 index 00000000..a16634f8 --- /dev/null +++ b/examples/get_assets_targeted_by_technique.py @@ -0,0 +1,18 @@ +from mitreattack.stix20 import MitreAttackData + + +def main(): + mitre_attack_data = MitreAttackData("ics-attack.json") + + # get assets targeted by T0806 + technique_stix_id = "attack-pattern--8e7089d3-fba2-44f8-94a8-9a79c53920c4" + assets_targeted = mitre_attack_data.get_assets_targeted_by_technique(technique_stix_id) + + print(f"Assets targeted by {technique_stix_id} ({len(assets_targeted)}):") + for a in assets_targeted: + asset = a["object"] + print(f"* {asset.name} ({mitre_attack_data.get_attack_id(asset.id)})") + + +if __name__ == "__main__": + main() diff --git a/examples/get_techniques_targeting_asset.py b/examples/get_techniques_targeting_asset.py new file mode 100644 index 00000000..2886016e --- /dev/null +++ b/examples/get_techniques_targeting_asset.py @@ -0,0 +1,18 @@ +from mitreattack.stix20 import MitreAttackData + + +def main(): + mitre_attack_data = MitreAttackData("ics-attack.json") + + # get techniques targeting A0004 + asset_stix_id = "x-mitre-asset--1769c499-55e5-462f-bab2-c39b8cd5ae32" + techniques_targeting_asset = mitre_attack_data.get_techniques_targeting_asset(asset_stix_id) + + print(f"Techniques targeting {asset_stix_id} ({len(techniques_targeting_asset)}):") + for t in techniques_targeting_asset: + technique = t["object"] + print(f"* {technique.name} ({mitre_attack_data.get_attack_id(technique.id)})") + + +if __name__ == "__main__": + main() diff --git a/mitreattack/attackToExcel/attackToExcel.py b/mitreattack/attackToExcel/attackToExcel.py index ffd525d1..eed0ea37 100644 --- a/mitreattack/attackToExcel/attackToExcel.py +++ b/mitreattack/attackToExcel/attackToExcel.py @@ -101,6 +101,7 @@ def build_dataframes(src: MemoryStore, domain: str) -> Dict: "software": stixToDf.softwareToDf(src), "groups": stixToDf.groupsToDf(src), "campaigns": stixToDf.campaignsToDf(src), + "assets": stixToDf.assetsToDf(src), "mitigations": stixToDf.mitigationsToDf(src), "matrices": stixToDf.matricesToDf(src, domain), "relationships": stixToDf.relationshipsToDf(src), diff --git a/mitreattack/attackToExcel/stixToDf.py b/mitreattack/attackToExcel/stixToDf.py index 0975a643..fdab23cb 100644 --- a/mitreattack/attackToExcel/stixToDf.py +++ b/mitreattack/attackToExcel/stixToDf.py @@ -522,6 +522,65 @@ def campaignsToDf(src): return dataframes +def assetsToDf(src): + """Parse STIX assets from the given data and return corresponding pandas dataframes. + + :param src: MemoryStore or other stix2 DataSource object holding the domain data + :returns: a lookup of labels (descriptors/names) to dataframes + """ + assets = src.query([Filter("type", "=", "x-mitre-asset")]) + assets = remove_revoked_deprecated(assets) + + dataframes = {} + if assets: + asset_rows = [] + for asset in tqdm(assets, desc="parsing assets"): + row = parseBaseStix(asset) + # add asset-specific fields + if "x_mitre_platforms" in asset: + row["platforms"] = ", ".join(sorted(asset["x_mitre_platforms"])) + if "x_mitre_sectors" in asset: + row["sectors"] = ", ".join(sorted(asset["x_mitre_sectors"])) + if "x_mitre_related_assets" in asset: + related_assets = [] + related_assets_sectors = [] + related_assets_descriptions = [] + + for related_asset in asset["x_mitre_related_assets"]: + related_assets.append(related_asset["name"]) + related_assets_sectors.append(", ".join(related_asset["related_asset_sectors"])) + related_assets_descriptions.append(related_asset["description"]) + + row["related assets"] = "; ".join(related_assets) + row["related assets sectors"] = "; ".join(related_assets_sectors) + row["related assets description"] = "; ".join(related_assets_descriptions) + + asset_rows.append(row) + + citations = get_citations(assets) + dataframes = { + "assets": pd.DataFrame(asset_rows).sort_values("name"), + } + # add relationships + codex = relationshipsToDf(src, relatedType="asset") + dataframes.update(codex) + # add relationship references + dataframes["assets"]["relationship citations"] = _get_relationship_citations(dataframes["assets"], codex) + # add/merge citations + if not citations.empty: + # append to existing citations from references + if "citations" in dataframes: + dataframes["citations"] = pd.concat([dataframes["citations"], citations]) + else: # add citations + dataframes["citations"] = citations + + dataframes["citations"].sort_values("reference") + else: + logger.warning("No assets found - nothing to parse") + + return dataframes + + def mitigationsToDf(src): """Parse STIX mitigations from the given data and return corresponding pandas dataframes. @@ -859,6 +918,7 @@ def relationshipsToDf(src, relatedType=None): "software": ["tool", "malware"], "group": ["intrusion-set"], "campaign": ["campaign"], + "asset": ["x-mitre-asset"], "mitigation": ["course-of-action"], "matrix": ["x-mitre-matrix"], "datasource": ["x-mitre-data-component"], @@ -874,6 +934,7 @@ def relationshipsToDf(src, relatedType=None): "x-mitre-data-component": "datacomponent", "x-mitre-data-source": "datasource", "campaign": "campaign", + "x-mitre-asset": "asset", } mitre_attack_data = MitreAttackData(src=src) @@ -983,6 +1044,7 @@ def relationshipsToDf(src, relatedType=None): procedureExamples = relationships.query("`mapping type` == 'uses' and `target type` == 'technique'") attributedCampaignGroup = relationships.query("`mapping type` == 'attributed-to' and `target type` == 'group'") relatedMitigations = relationships.query("`mapping type` == 'mitigates'") + targetedAssets = relationships.query("`mapping type` == 'targets' and `target type` == 'asset'") if not relatedGroupSoftware.empty: if relatedType == "group": @@ -1021,6 +1083,13 @@ def relationshipsToDf(src, relatedType=None): sheet_name = "techniques addressed" dataframes[sheet_name] = relatedMitigations + if not targetedAssets.empty: + if relatedType == "technique": + sheet_name = "targeted assets" + else: + sheet_name = "associated techniques" + dataframes[sheet_name] = targetedAssets + if not citations.empty: # filter citations by ones actually used # build master list of used citations diff --git a/mitreattack/diffStix/README.md b/mitreattack/diffStix/README.md index 80453812..292fb4a6 100644 --- a/mitreattack/diffStix/README.md +++ b/mitreattack/diffStix/README.md @@ -73,6 +73,7 @@ A brief explanation of key pieces can be found below. "software": {}, "groups": {}, "campaigns": {}, + "assets": {}, "mitigations": {}, "datasources": {}, "datacomponents": {} diff --git a/mitreattack/diffStix/changelog_helper.py b/mitreattack/diffStix/changelog_helper.py index bd298f9b..7b25e419 100644 --- a/mitreattack/diffStix/changelog_helper.py +++ b/mitreattack/diffStix/changelog_helper.py @@ -108,7 +108,7 @@ def __init__( self.new = new self.show_key = show_key self.site_prefix = site_prefix - self.types = ["techniques", "software", "groups", "campaigns", "mitigations", "datasources", "datacomponents"] + self.types = ["techniques", "software", "groups", "campaigns", "assets", "mitigations", "datasources", "datacomponents"] self.use_mitre_cti = use_mitre_cti self.verbose = verbose self.include_contributors = include_contributors @@ -123,6 +123,7 @@ def __init__( "software": "Software", "groups": "Groups", "campaigns": "Campaigns", + "assets": "Assets", "mitigations": "Mitigations", "datasources": "Data Sources", "datacomponents": "Data Components", @@ -582,6 +583,7 @@ def parse_extra_data(self, data_store: stix2.MemoryStore, domain: str, datastore "software": [Filter("type", "=", "malware"), Filter("type", "=", "tool")], "groups": [Filter("type", "=", "intrusion-set")], "campaigns": [Filter("type", "=", "campaign")], + "assets": [Filter("type", "=", "x-mitre-asset")], "mitigations": [Filter("type", "=", "course-of-action")], "datasources": [Filter("type", "=", "x-mitre-data-source")], "datacomponents": [Filter("type", "=", "x-mitre-data-component")], diff --git a/mitreattack/navlayers/generators/overview_generator.py b/mitreattack/navlayers/generators/overview_generator.py index a3fd959c..5591e3e7 100644 --- a/mitreattack/navlayers/generators/overview_generator.py +++ b/mitreattack/navlayers/generators/overview_generator.py @@ -52,6 +52,8 @@ def __init__(self, source, domain="enterprise", resource=None): self.sources = self.source_handle.query([Filter("type", "=", "x-mitre-data-source")]) self.components = self.source_handle.query([Filter("type", "=", "x-mitre-data-component")]) + self.campaigns = self.source_handle.query([Filter("type", "=", "campaign")]) + self.assets = self.source_handle.query([Filter("type", "=", "x-mitre-asset")]) self.source_mapping = build_data_strings(self.sources, self.components) # Contains relationship mapping [stix id] -> [relationships associated with that stix id for each type] self.simplifier = { @@ -61,6 +63,7 @@ def __init__(self, source, domain="enterprise", resource=None): "intrusion-set": dict(), "x-mitre-data-component": dict(), "campaign": dict(), + "asset": dict(), } # Scan through all relationships to identify ones that target attack techniques (attack-pattern). Then, sort @@ -139,6 +142,24 @@ def get_datasources(self, relationships): """ names = [self.source_mapping[x.source_ref] for x in relationships] return len(names), names + + def get_campaigns(self, relationships): + """Sort campaigns out of relationships. + + :param relationships: List of all related relationships to a given technique + :return: length of matched campaigns, list of campaign names + """ + names = [x.name for x in self.campaigns if x.id in relationships] + return len(names), names + + def get_assets(self, relationships): + """Sort assets out of relationships. + + :param relationships: List of all related relationships to a given technique + :return: length of matched assets, list of asset names + """ + names = [x.name for x in self.assets if x.id in relationships] + return len(names), names def get_matrix_template(self): """Build the raw dictionary form matrix layer object. @@ -211,6 +232,18 @@ def update_template(self, obj_type, complete_tech_listing): score, listing = self.get_datasources(related) except KeyError: pass + elif obj_type == "campaign": + try: + related = self.simplifier["campaign"][tech.id] + score, listing = self.get_campaigns(related) + except KeyError: + pass + elif obj_type == "asset": + try: + related = self.simplifier["asset"][tech.id] + score, listing = self.get_assets(related) + except KeyError: + pass entry["score"] = score entry["comment"] = ", ".join(listing) return temp @@ -222,7 +255,7 @@ def generate_layer(self, obj_type): :return: layer object with annotated techniques """ typeChecker(type(self).__name__, obj_type, str, "type") - categoryChecker(type(self).__name__, obj_type, ["group", "software", "mitigation", "datasource"], "type") + categoryChecker(type(self).__name__, obj_type, ["group", "software", "mitigation", "datasource", "campaign", "asset"], "type") initial_list = self.get_matrix_template() updated_list = self.update_template(obj_type, initial_list) if obj_type == "group": @@ -234,6 +267,12 @@ def generate_layer(self, obj_type): elif obj_type == "datasource": p_name = "data sources" r_type = "detecting" + elif obj_type == "campaign": + p_name = "campaigns" + r_type = "using" + elif obj_type == "asset": + p_name = "assets" + r_type = "targeted by" else: # mitigation case p_name = "mitigations" r_type = "mitigating" diff --git a/mitreattack/navlayers/layerGenerator_cli.py b/mitreattack/navlayers/layerGenerator_cli.py index 2c3adaea..6b257419 100644 --- a/mitreattack/navlayers/layerGenerator_cli.py +++ b/mitreattack/navlayers/layerGenerator_cli.py @@ -14,18 +14,18 @@ def main(argv=None): group = parser.add_mutually_exclusive_group(required=True) group.add_argument( "--overview-type", - choices=["group", "software", "mitigation", "datasource"], + choices=["group", "software", "mitigation", "datasource", "campaign", "asset"], help="Output a layer file where the target type is summarized across the entire dataset.", ) group.add_argument( "--mapped-to", help="Output layer file with techniques mapped to the given group, software, " - "mitigation, or data component. Argument can be name, associated " + "mitigation, data component, campaign, or asset. Argument can be name, associated " "group/software, or ATT&CK ID.", ) group.add_argument( "--batch-type", - choices=["group", "software", "mitigation", "datasource"], + choices=["group", "software", "mitigation", "datasource", "campaign", "asset"], help="Output a collection of layer files to the specified folder, each one representing a " "different instance of the target type.", ) diff --git a/mitreattack/stix20/MitreAttackData.py b/mitreattack/stix20/MitreAttackData.py index 982ecd1e..f04901fd 100644 --- a/mitreattack/stix20/MitreAttackData.py +++ b/mitreattack/stix20/MitreAttackData.py @@ -23,6 +23,7 @@ class MitreAttackData: "x-mitre-tactic", "x-mitre-data-source", "x-mitre-data-component", + "x-mitre-asset", ] # software:group @@ -52,6 +53,9 @@ class MitreAttackData: # technique:data-component all_techniques_detected_by_all_datacomponents = None all_datacomponents_detecting_all_techniques = None + # technique:asset + all_techniques_targeting_all_assets = None + all_assets_targeted_by_all_techniques = None def __init__(self, stix_filepath: str = None, src: stix2.MemoryStore = None): """Initialize a MitreAttackData object. @@ -264,6 +268,21 @@ def get_campaigns(self, remove_revoked_deprecated=False) -> list: """ return self.get_objects_by_type("campaign", remove_revoked_deprecated) + def get_assets(self, remove_revoked_deprecated=False) -> list: + """Retrieve all asset objects. + + Parameters + ---------- + remove_revoked_deprecated : bool, optional + remove revoked or deprecated objects from the query, by default False + + Returns + ------- + list + a list of Asset objects + """ + return self.get_objects_by_type("x-mitre-asset", remove_revoked_deprecated) + def get_datasources(self, remove_revoked_deprecated=False) -> list: """Retrieve all data source objects. @@ -334,7 +353,7 @@ def get_objects_by_content(self, content: str, object_type: str = None, remove_r object_type : str, optional the STIX object type (must be 'attack-pattern', 'malware', 'tool', 'intrusion-set', 'campaign', 'course-of-action', 'x-mitre-matrix', 'x-mitre-tactic', - 'x-mitre-data-source', or 'x-mitre-data-component') + 'x-mitre-data-source', 'x-mitre-data-component', or 'x-mitre-asset') remove_revoked_deprecated : bool, optional remove revoked or deprecated objects from the query, by default False @@ -589,7 +608,7 @@ def get_object_by_attack_id(self, attack_id: str, stix_type: str) -> object: stix_type : str the object STIX type (must be 'attack-pattern', 'malware', 'tool', 'intrusion-set', 'campaign', 'course-of-action', 'x-mitre-matrix', 'x-mitre-tactic', - 'x-mitre-data-source', or 'x-mitre-data-component') + 'x-mitre-data-source', 'x-mitre-data-component', or 'x-mitre-asset') Returns ------- @@ -624,7 +643,7 @@ def get_objects_by_name(self, name: str, stix_type: str) -> list: stix_type : str the STIX object type (must be 'attack-pattern', 'malware', 'tool', 'intrusion-set', 'campaign', 'course-of-action', 'x-mitre-matrix', 'x-mitre-tactic', - 'x-mitre-data-source', or 'x-mitre-data-component') + 'x-mitre-data-source', 'x-mitre-data-component', or 'x-mitre-asset') Returns ------- @@ -1679,3 +1698,77 @@ def get_revoking_object(self, revoked_stix_id: str = "") -> object: return None return revoked_by[0] + + ################################### + # Technique/Asset Relationships + ################################### + + def get_all_techniques_targeting_all_assets(self) -> dict: + """Get all techniques targeting all assets. + + Returns + ------- + dict + a mapping of asset_stix_id => [{'object': AttackPattern, 'relationships': Relationship[]}] for each technique targeting the asset + """ + # return data if it has already been fetched + if self.all_techniques_targeting_all_assets: + return self.all_techniques_targeting_all_assets + + self.all_techniques_targeting_all_assets = self.get_related( + "attack-pattern", "targets", "x-mitre-asset", reverse=True + ) + + return self.all_techniques_targeting_all_assets + + def get_techniques_targeting_asset(self, asset_stix_id: str) -> list: + """Get all techniques targeting an asset. + + Parameters + ---------- + asset_stix_id : str + the STIX ID of the asset + + Returns + ------- + list + a list of {"object": AttackPattern, "relationships": Relationship[]} for each technique targeting the asset + """ + techniques_targeting_assets = self.get_all_techniques_targeting_all_assets() + return techniques_targeting_assets[asset_stix_id] if asset_stix_id in techniques_targeting_assets else [] + + def get_all_assets_targeted_by_all_techniques(self) -> dict: + """Get all assets targeted by all techniques. + + Returns + ------- + dict + a mapping of technique_stix_id => [{'object': Asset, 'relationships': Relationship[]}] for each asset targeted by the technique + """ + # return data if it has already been fetched + if self.all_assets_targeted_by_all_techniques: + return self.all_assets_targeted_by_all_techniques + + self.all_assets_targeted_by_all_techniques = self.get_related("attack-pattern", "targets", "x-mitre-asset") + + return self.all_assets_targeted_by_all_techniques + + def get_assets_targeted_by_technique(self, technique_stix_id: str) -> list: + """Get all assets targeted by a technique. + + Parameters + ---------- + technique_stix_id : str + the STIX ID of the technique + + Returns + ------- + list + a list of {"object": Asset, "relationships": Relationship[]} for each asset targeted by the technique + """ + assets_targeted_by_techniques = self.get_all_assets_targeted_by_all_techniques() + return ( + assets_targeted_by_techniques[technique_stix_id] + if technique_stix_id in assets_targeted_by_techniques + else [] + ) diff --git a/mitreattack/stix20/__init__.py b/mitreattack/stix20/__init__.py index 84e47c1f..ab20152b 100644 --- a/mitreattack/stix20/__init__.py +++ b/mitreattack/stix20/__init__.py @@ -1,2 +1,2 @@ from .MitreAttackData import MitreAttackData -from .custom_attack_objects import StixObjectFactory, Matrix, Tactic, DataSource, DataComponent +from .custom_attack_objects import StixObjectFactory, Matrix, Tactic, DataSource, DataComponent, Asset diff --git a/mitreattack/stix20/custom_attack_objects.py b/mitreattack/stix20/custom_attack_objects.py index 3bdb7e0a..27bba253 100644 --- a/mitreattack/stix20/custom_attack_objects.py +++ b/mitreattack/stix20/custom_attack_objects.py @@ -9,6 +9,7 @@ ReferenceProperty, TimestampProperty, BooleanProperty, + DictionaryProperty, ) @@ -45,6 +46,7 @@ def StixObjectFactory(data: dict) -> object: "x-mitre-tactic": Tactic, "x-mitre-data-source": DataSource, "x-mitre-data-component": DataComponent, + "x-mitre-asset": Asset, } stix_type = data.get("type") @@ -193,3 +195,40 @@ class DataComponent(CustomStixObject, object): """ pass + + +@CustomObject( + "x-mitre-asset", + [ + # SDO Common Properties + ("id", IDProperty("x-mitre-asset", spec_version="2.0")), + ("type", TypeProperty("x-mitre-asset", spec_version="2.0")), + ("created_by_ref", ReferenceProperty(valid_types="identity", spec_version="2.0")), + ("created", TimestampProperty(precision="millisecond")), + ("modified", TimestampProperty(precision="millisecond")), + ("revoked", BooleanProperty(default=lambda: False)), + ("external_references", ListProperty(ExternalReference)), + ("object_marking_refs", ListProperty(ReferenceProperty(valid_types="marking-definition", spec_version="2.0"))), + ("name", StringProperty(required=True)), + ("description", StringProperty()), + ("x_mitre_modified_by_ref", ReferenceProperty(valid_types="identity", spec_version="2.0")), + ("x_mitre_version", StringProperty()), + ("x_mitre_attack_spec_version", StringProperty()), + ("x_mitre_domains", ListProperty(StringProperty())), + ("x_mitre_contributors", ListProperty(StringProperty())), + # Asset Properties + ("sectors", ListProperty(StringProperty())), + ("x_mitre_related_assets", ListProperty(DictionaryProperty())), + ("x_mitre_platforms", ListProperty(StringProperty())), + ], +) +class Asset(CustomStixObject, object): + """Custom Asset object of type stix2.CustomObject. + + Custom Properties + ----------------- + x_mitre_platforms: list[str] + x_mitre_related_assets: list[object] + """ + + pass diff --git a/tests/test_stix20.py b/tests/test_stix20.py index c62dffc8..58d75d1a 100644 --- a/tests/test_stix20.py +++ b/tests/test_stix20.py @@ -1,4 +1,4 @@ -from mitreattack.stix20.custom_attack_objects import DataComponent, DataSource, Matrix, StixObjectFactory, Tactic +from mitreattack.stix20.custom_attack_objects import DataComponent, DataSource, Matrix, StixObjectFactory, Tactic, Asset class TestCustomAttackObjects: @@ -29,6 +29,7 @@ def test_stix_object_factory(self): "x-mitre-data-source": DataSource, "x-mitre-matrix": Matrix, "x-mitre-tactic": Tactic, + "x-mitre-asset": Asset, } object_name = "Object name" @@ -52,3 +53,10 @@ def test_tactic(self): assert tactic.type == "x-mitre-tactic" assert tactic.get_shortname() == shortname assert tactic.get_version() == version + + def test_asset(self): + name = "Asset" + asset = Asset(name=name) + + assert asset.name == name + assert asset.type == "x-mitre-asset" diff --git a/tests/test_to_excel.py b/tests/test_to_excel.py index ef64aa48..bfd219e7 100644 --- a/tests/test_to_excel.py +++ b/tests/test_to_excel.py @@ -10,6 +10,7 @@ def check_excel_files_exist(excel_folder: Path, domain: str): assert (excel_folder / f"{domain}.xlsx").exists() + assert (excel_folder / f"{domain}-assets.xlsx").exists() assert (excel_folder / f"{domain}-campaigns.xlsx").exists() if domain != "mobile-attack": # Mobile domain does not have datasources