From 1b718cb90c860b54ed3892013ec8cdd09da48d69 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Tue, 12 Apr 2022 21:15:46 -0500 Subject: [PATCH 1/3] Add diffStix functionality --- .gitignore | 3 + mitreattack/diffStix/__init__.py | 0 mitreattack/diffStix/changelog_helper.py | 1368 ++++++++++++++++++++++ requirements-dev.txt | 16 +- setup.py | 33 +- 5 files changed, 1398 insertions(+), 22 deletions(-) create mode 100644 mitreattack/diffStix/__init__.py create mode 100644 mitreattack/diffStix/changelog_helper.py diff --git a/.gitignore b/.gitignore index 83c08086..dac45318 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# mitreattack-python specific +output/ + # Created by https://www.toptal.com/developers/gitignore/api/python,macos # Edit at https://www.toptal.com/developers/gitignore?templates=python,macos diff --git a/mitreattack/diffStix/__init__.py b/mitreattack/diffStix/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mitreattack/diffStix/changelog_helper.py b/mitreattack/diffStix/changelog_helper.py new file mode 100644 index 00000000..250762f7 --- /dev/null +++ b/mitreattack/diffStix/changelog_helper.py @@ -0,0 +1,1368 @@ +import argparse +import datetime +import json +import os +from itertools import chain +from pathlib import Path + +import markdown +import requests +import urllib3 +from dateutil import parser as dateparser +from loguru import logger +from stix2 import Filter, MemoryStore, TAXIICollectionSource +from taxii2client.v20 import Collection +from tqdm import tqdm + +# helper maps +domainToDomainLabel = {"enterprise-attack": "Enterprise", "mobile-attack": "Mobile"} +domainToTaxiiCollectionId = { + "enterprise-attack": "95ecc380-afe9-11e4-9b6c-751b66dd541e", + "mobile-attack": "2f669986-b40b-4423-b720-4396ca6a462b", +} +# stix filters for querying for each type of data +attackTypeToStixFilter = { + "technique": [Filter("type", "=", "attack-pattern")], + "software": [Filter("type", "=", "malware"), Filter("type", "=", "tool")], + "group": [Filter("type", "=", "intrusion-set")], + "mitigation": [Filter("type", "=", "course-of-action")], + "datasource": [ + Filter("type", "=", "x-mitre-data-source"), + Filter("type", "=", "x-mitre-data-component"), + ], + "datasource-only": [Filter("type", "=", "x-mitre-data-source")], +} +# ATT&CK type to Title +attackTypeToTitle = { + "technique": "Techniques", + "malware": "Malware", + "software": "Software", + "group": "Groups", + "mitigation": "Mitigations", + "datasource": "Data Sources and/or Components", +} +# ATT&CK type to section name +attackTypeToSectionName = { + "technique": "Technique", + "malware": "Malware", + "software": "Software", + "group": "Group", + "mitigation": "Mitigation", + "datasource": "Data Source and/or Component", +} +# how we want to format headers for each section +sectionNameToSectionHeaders = { + "additions": "New {obj_type}", + "changes": "{obj_type} changes", + "minor_changes": "Minor {obj_type} changes", + "deprecations": "{obj_type} deprecations", + "revocations": "{obj_type} revocations", + "deletions": "{obj_type} deletions", + "unchanged": "Unchanged {obj_type}", +} +# color key for layers +statusToColor = { + "additions": "#a1d99b", + "changes": "#fcf3a2", + "minor_changes": "#c7c4e0", + "deletions": "#ff00e1", # this will probably never show up but just in case + "revocations": "#ff9000", + "deprecations": "#ff6363", + "unchanged": "#ffffff", +} +# explanation of modification types to data objects for legend in layer files +statusDescriptions = { + "additions": "objects which are present in the new data and not the old", + "changes": "objects which have a newer version number in the new data compared to the old", + "minor_changes": "objects which have a newer last edit date in the new data than in the old, but the same version number", + "revocations": "objects which are revoked in the new data but not in the old", + "deprecations": "objects which are deprecated in the new data but not in the old", + "deletions": "objects which are present in the old data but not the new", + "unchanged": "objects which did not change between the two versions", +} +date = datetime.datetime.today() +this_month = date.strftime("%B_%Y") +layer_defaults = [ + os.path.join("output", f"{this_month}_Updates_Enterprise.json"), + os.path.join("output", f"{this_month}_Updates_Mobile.json"), + os.path.join("output", f"{this_month}_Updates_Pre.json"), +] +md_default = os.path.join("output", f"updates-{this_month.lower()}.md") +json_default = os.path.join("output", f"updates-{this_month.lower()}.json") + + +class DiffStix(object): + """Utilities for detecting and summarizing differences between two versions of the ATT&CK content.""" + + def __init__( + self, + domains=["enterprise-attack", "mobile-attack"], + layers=None, + markdown=None, + minor_changes=False, + unchanged=False, + new="new", + old="old", + show_key=False, + site_prefix="", + types=["technique", "software", "group", "mitigation", "datasource"], + use_taxii=False, + use_mitre_cti=False, + verbose=False, + include_contributors=False, + release_contributors={}, + ): + """Construct a new DiffStix object. + + params: + domains: list of domains to parse, e.g. enterprise-attack, mobile-attack + layers: array of output filenames for layer files, e.g. ['enterprise.json', 'mobile.json', 'pre.json'] + markdown: output filename for markdown content to be written to + minor_changes: if true, also report minor changes section (changes which didn't increment version) + new: directory to load for new stix version + old: directory to load for old stix version + show_key: if true, output key to markdown file + site_prefix: prefix links in markdown output + types: which types of objects to report on, e.g technique, software + verbose: if true, print progress bar and status messages to stdout + """ + self.domains = domains + self.layers = layers + self.markdown = markdown + self.minor_changes = minor_changes + self.unchanged = unchanged + self.new = new + self.old = old + self.show_key = show_key + self.site_prefix = site_prefix + self.types = types + self.use_taxii = use_taxii + self.use_mitre_cti = use_mitre_cti + self.verbose = verbose + self.include_contributors = include_contributors + # will hold information of contributors of the new release {... {"contributor_credit/name_as_key": counter]} ...} + self.release_contributors = {} + + # data gets load into here in the load() function. All other functionalities rely on this data structure + self.data = { + # { + # technique: { + # enterprise-attack: { + # additions: [], + # deletions: [], + # changes: [], + # minor_changes: [], + # revocations: [], + # deprecations: [], + # unchanged: [], + # }, + # mobile-attack: {...}, + # }, + # software: {...}, + # } + } + + # stixID to object name + self.stixIDToName = {} + + # all subtechnique-of relationships in the new/old data + self.new_subtechnique_of_rels = [] + self.old_subtechnique_of_rels = [] + + # all data components in the new/old data + self.new_datacomponents = [] + self.old_datacomponents = [] + + # stixID => technique for every technique in the new/old data + self.new_id_to_technique = {} + self.old_id_to_technique = {} + + # stixID => data source for every data source in the new/old data + self.new_id_to_datasource = {} + self.old_id_to_datasource = {} + + # build the bove data structures + self.load_data() + + logger.info("removing duplicate relationships") + self.new_subtechnique_of_rels = [ + i + for n, i in enumerate(self.new_subtechnique_of_rels) + if i not in self.new_subtechnique_of_rels[n + 1 :] + ] + self.old_subtechnique_of_rels = [ + i + for n, i in enumerate(self.old_subtechnique_of_rels) + if i not in self.old_subtechnique_of_rels[n + 1 :] + ] + + logger.info("removing duplicate data components") + self.new_datacomponents = [ + i + for n, i in enumerate(self.new_datacomponents) + if i not in self.new_datacomponents[n + 1 :] + ] + self.old_datacomponents = [ + i + for n, i in enumerate(self.old_datacomponents) + if i not in self.old_datacomponents[n + 1 :] + ] + + def getUrlFromStix(self, datum, is_subtechnique=False): + """ + Parse the website url from a stix object. + """ + if datum.get("external_references"): + url = datum["external_references"][0]["url"] + split_url = url.split("/") + splitfrom = -3 if is_subtechnique else -2 + link = "/".join(split_url[splitfrom:]) + return link + return None + + def getDataComponentUrl(self, datasource, datacomponent): + """Create url of data component with parent data source""" + return f"{self.getUrlFromStix(datasource)}/#{'%20'.join(datacomponent['name'].split(' '))}" + + def deep_copy_stix(self, objects): + """Transform stix to dict and deep copy the dict.""" + result = [] + for obj in objects: + obj = dict(obj) + if "external_references" in obj: + for i in range(len(obj["external_references"])): + obj["external_references"][i] = dict(obj["external_references"][i]) + if "kill_chain_phases" in obj: + for i in range(len(obj["kill_chain_phases"])): + obj["kill_chain_phases"][i] = dict(obj["kill_chain_phases"][i]) + if "modified" in obj: + obj["modified"] = str(obj["modified"]) + if "definition" in obj: + obj["definition"] = dict(obj["definition"]) + obj["created"] = str(obj["created"]) + result.append(obj) + return result + + # load data into data structure + def load_data(self): + """Load data from files into data dict.""" + pbar = tqdm( + total=len(self.types) * len(self.domains), + desc="loading data", + bar_format="{l_bar}{bar}| [{elapsed}<{remaining}, {rate_fmt}{postfix}]", + ) + for obj_type in self.types: + for domain in self.domains: + + def load_datastore(data_store): + """Handle data loaded from either a directory or the TAXII server""" + raw_data = list( + chain.from_iterable( + data_store.query(f) + for f in attackTypeToStixFilter[obj_type] + ) + ) + raw_data = self.deep_copy_stix(raw_data) + id_to_obj = {item["id"]: item for item in raw_data} + + return { + "id_to_obj": id_to_obj, + "keys": set(id_to_obj.keys()), + "data_store": data_store, + } + + def parse_subtechniques(data_store, new=False): + """Parse dataStore sub-technique-of relationships""" + if new: + for technique in list( + data_store.query(attackTypeToStixFilter["technique"]) + ): + self.new_id_to_technique[technique["id"]] = technique + self.new_subtechnique_of_rels += list( + data_store.query( + [ + Filter("type", "=", "relationship"), + Filter("relationship_type", "=", "subtechnique-of"), + ] + ) + ) + else: + for technique in list( + data_store.query(attackTypeToStixFilter["technique"]) + ): + self.old_id_to_technique[technique["id"]] = technique + self.old_subtechnique_of_rels += list( + data_store.query( + [ + Filter("type", "=", "relationship"), + Filter("relationship_type", "=", "subtechnique-of"), + ] + ) + ) + + def parse_datacomponents(data_store, new=False): + """Parse dataStore x-mitre-data-components""" + if new: + for datasource in list( + data_store.query(attackTypeToStixFilter["datasource-only"]) + ): + self.new_id_to_datasource[datasource["id"]] = datasource + self.new_datacomponents += list( + data_store.query( + [Filter("type", "=", "x-mitre-data-component")] + ) + ) + else: + for datasource in list( + data_store.query(attackTypeToStixFilter["datasource-only"]) + ): + self.old_id_to_datasource[datasource["id"]] = datasource + self.old_datacomponents += list( + data_store.query( + [Filter("type", "=", "x-mitre-data-component")] + ) + ) + + def update_contributors(old_object, new_object): + """Update contributors list if new object has contributors""" + if new_object.get("x_mitre_contributors"): + new_object_contributors = set( + new_object["x_mitre_contributors"] + ) + + # Check if old objects had contributors + if old_object is None or not old_object.get( + "x_mitre_contributors" + ): + old_object_contributors = set() + else: + old_object_contributors = set( + old_object["x_mitre_contributors"] + ) + + # Remove old contributors from showing up + # if contributors are the same the result will be empty + new_contributors = ( + new_object_contributors - old_object_contributors + ) + + # Update counter of contributor to track contributions + for new_contributor in new_contributors: + if self.release_contributors.get(new_contributor): + self.release_contributors[new_contributor] += 1 + else: + self.release_contributors[new_contributor] = 1 + + def load_dir(dir, new=False): + """Load data from directory according to domain""" + data_store = MemoryStore() + datafile = os.path.join(dir, domain + ".json") + data_store.load_from_file(datafile) + parse_subtechniques(data_store, new) + parse_datacomponents(data_store, new) + return load_datastore(data_store) + + def load_taxii(new=False): + """Load data from TAXII server according to domain""" + collection = Collection( + "https://cti-taxii.mitre.org/stix/collections/" + + domainToTaxiiCollectionId[domain] + ) + data_store = TAXIICollectionSource(collection) + parse_subtechniques(data_store, new) + parse_datacomponents(data_store, new) + return load_datastore(data_store) + + def load_mitre_cti(new=False): + """Load data from MITRE CTI repo according to domain""" + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + stix_json = requests.get( + f"https://raw.githubusercontent.com/mitre/cti/master/{domain}/{domain}.json", + verify=False, + ) + if stix_json.status_code == 200: + stix_json = stix_json.json() + data_store = MemoryStore(stix_data=stix_json["objects"]) + parse_subtechniques(data_store, new) + parse_datacomponents(data_store, new) + return load_datastore(data_store) + exit(f"\n{domain} stix bundle download was unsuccessful") + + logger.debug(f"Loading: [{domain:17}]/{obj_type}") + + if self.use_taxii: + old = load_taxii(False) + elif self.use_mitre_cti: + old = load_mitre_cti(False) + else: + old = load_dir(self.old, False) + new = load_dir(self.new, True) + + intersection = old["keys"] & new["keys"] + additions = new["keys"] - old["keys"] + deletions = old["keys"] - new["keys"] + + # sets to store the ids of objects for each section + changes = set() + minor_changes = set() + revocations = set() + deprecations = set() + unchanged = set() + + # find changes, revocations and deprecations + for key in intersection: + + # find revoked objects + if ( + "revoked" in new["id_to_obj"][key] + and new["id_to_obj"][key]["revoked"] + ): + # if it was previously revoked, it's not a change + if ( + not "revoked" in old["id_to_obj"][key] + or not old["id_to_obj"][key]["revoked"] + ): + # store the revoking object + revoked_by_key = new["data_store"].query( + [ + Filter("type", "=", "relationship"), + Filter("relationship_type", "=", "revoked-by"), + Filter("source_ref", "=", key), + ] + ) + if len(revoked_by_key) == 0: + logger.error( + f"[{key}] revoked object has no revoked-by relationship" + ) + continue + else: + revoked_by_key = revoked_by_key[0]["target_ref"] + + new["id_to_obj"][key]["revoked_by"] = new["id_to_obj"][ + revoked_by_key + ] + + revocations.add(key) + # else it was already revoked, and not a change; do nothing with it + + # find deprecated objects + elif ( + "x_mitre_deprecated" in new["id_to_obj"][key] + and new["id_to_obj"][key]["x_mitre_deprecated"] + ): + # if previously deprecated, not a change + if not "x_mitre_deprecated" in old["id_to_obj"][key]: + deprecations.add(key) + + # find all other changed objects + else: + # try getting version numbers; should only lack version numbers if something has gone + # horribly wrong or a revoked object has slipped through + try: + old_version = float( + old["id_to_obj"][key]["x_mitre_version"] + ) + except ValueError: + logger.error( + f"ERROR: cannot get old version for object: {key}" + ) + + try: + new_version = float( + new["id_to_obj"][key]["x_mitre_version"] + ) + except ValueError: + logger.error( + f"ERROR: cannot get new version for object: {key}" + ) + + # Verify if there are new contributors on the object + update_contributors( + old["id_to_obj"][key], new["id_to_obj"][key] + ) + + # check for changes + if new_version > old_version: + # an update has occurred to this object + changes.add(key) + else: + # check for minor change; modification date increased but not version + old_date = dateparser.parse( + old["id_to_obj"][key]["modified"] + ) + new_date = dateparser.parse( + new["id_to_obj"][key]["modified"] + ) + if new_date > old_date: + minor_changes.add(key) + else: + unchanged.add(key) + + # Add contributions from additions + for key in additions: + update_contributors(None, new["id_to_obj"][key]) + + # set data + if obj_type not in self.data: + self.data[obj_type] = {} + self.data[obj_type][domain] = { + "additions": [new["id_to_obj"][key] for key in additions], + "changes": [new["id_to_obj"][key] for key in changes], + } + # only create minor_changes data if we want to display it later + if self.minor_changes: + self.data[obj_type][domain]["minor_changes"] = [ + new["id_to_obj"][key] for key in minor_changes + ] + + # ditto for unchanged + if self.unchanged: + self.data[obj_type][domain]["unchanged"] = [ + new["id_to_obj"][key] for key in unchanged + ] + + self.data[obj_type][domain]["revocations"] = [ + new["id_to_obj"][key] for key in revocations + ] + self.data[obj_type][domain]["deprecations"] = [ + new["id_to_obj"][key] for key in deprecations + ] + + # only show deletions if objects were deleted + if len(deletions) > 0: + self.data[obj_type][domain]["deletions"] = [ + old["id_to_obj"][key] for key in deletions + ] + + logger.debug(f"Loaded: [{domain:17}]/{obj_type}") + pbar.update(1) + pbar.close() + + def get_md_key(self): + """Create string describing each type of difference (change, addition, etc). + + Used in get_markdown_string. + + Includes minor changes if the DiffStix instance was instantiated with the minor_changes argument. + + Includes deletions if the changes include deletions. + """ + + have_deletions = False + for types in self.data.keys(): + for domain in self.data[types].keys(): + if "deletions" in self.data[types][domain].keys(): + have_deletions = True + + key = "#### Key\n\n" + key += ( + "* New objects: " + statusDescriptions["additions"] + "\n" + "* Object changes: " + statusDescriptions["changes"] + "\n" + ) + if self.minor_changes: + key += ( + "* Minor object changes: " + statusDescriptions["minor_changes"] + "\n" + ) + if self.unchanged: + key += "* Unchanged objects: " + statusDescriptions["unchanged"] + "\n" + key += ( + "* Object revocations: " + statusDescriptions["revocations"] + "\n" + "* Object deprecations: " + statusDescriptions["deprecations"] + ) + if have_deletions: + key += "\n" + "* Object deletions: " + statusDescriptions["deletions"] + return key + + def has_subtechniques(self, sdo, new=False): + """Return true or false depending on whether the SDO has sub-techniques. + + new determines whether to parse from the new or old data""" + if new: + return ( + len( + list( + filter( + lambda rel: rel["target_ref"] == sdo["id"], + self.new_subtechnique_of_rels, + ) + ) + ) + > 0 + ) + else: + return ( + len( + list( + filter( + lambda rel: rel["target_ref"] == sdo["id"], + self.old_subtechnique_of_rels, + ) + ) + ) + > 0 + ) + + def get_groupings( + self, + obj_type, + items, + subtechnique_of_rels, + id_to_technique, + datacomponents, + id_to_datasource, + ): + # get parents which have children + if obj_type != "datasource": + childless = list( + filter( + lambda item: not self.has_subtechniques(item, True) + and not ( + "x_mitre_is_subtechnique" in item + and item["x_mitre_is_subtechnique"] + ), + items, + ) + ) + parents = list( + filter( + lambda item: self.has_subtechniques(item, True) + and not ( + "x_mitre_is_subtechnique" in item + and item["x_mitre_is_subtechnique"] + ), + items, + ) + ) + children = { + item["id"]: item + for item in filter( + lambda item: ("x_mitre_is_subtechnique") in item + and (item["x_mitre_is_subtechnique"]), + items, + ) + } + else: + childless = ( + [] + ) # all data sources should have data components, i.e., should have children + parents = list( + filter( + lambda item: not ( + "x_mitre_data_source_ref" in item + and item["x_mitre_data_source_ref"] + ), + items, + ) + ) + children = { + item["id"]: item + for item in filter( + lambda item: ("x_mitre_data_source_ref") in item + and (item["x_mitre_data_source_ref"]), + items, + ) + } + + # stixID => [ children ] + parentToChildren = {} + for relationship in subtechnique_of_rels: + if relationship["target_ref"] in parentToChildren: + if relationship["source_ref"] in children: + parentToChildren[relationship["target_ref"]].append( + children[relationship["source_ref"]] + ) + else: + if relationship["source_ref"] in children: + parentToChildren[relationship["target_ref"]] = [ + children[relationship["source_ref"]] + ] + + for datacomponent in datacomponents: + if datacomponent["x_mitre_data_source_ref"] in parentToChildren: + if datacomponent["id"] in children: + parentToChildren[datacomponent["x_mitre_data_source_ref"]].append( + children[datacomponent["id"]] + ) + else: + if datacomponent["id"] in children: + parentToChildren[datacomponent["x_mitre_data_source_ref"]] = [ + children[datacomponent["id"]] + ] + + # now group parents and children + groupings = [] + for parent in childless + parents: + parent_children = ( + parentToChildren.pop(parent["id"]) + if parent["id"] in parentToChildren + else [] + ) + groupings.append( + { + "parent": parent, + "parentInSection": True, + "children": parent_children, + } + ) + + for parentID in parentToChildren: + if id_to_technique.get(parentID): + parentObj = id_to_technique[parentID] + elif id_to_datasource.get(parentID): + parentObj = id_to_datasource[parentID] + + if parentObj: + groupings.append( + { + "parent": parentObj, + "parentInSection": False, + "children": parentToChildren[parentID], + } + ) + + groupings = sorted(groupings, key=lambda grouping: grouping["parent"]["name"]) + return groupings + + def get_markdown_string(self): + """Return a markdown string summarizing detected differences.""" + + def getSectionList(items, obj_type, section): + """Parse a list of items in a section and return a string for the items.""" + logger.debug(f"getting section list for {obj_type}/{section}") + + if section == "deletions": + subtechnique_of_rels = self.old_subtechnique_of_rels + id_to_technique = self.old_id_to_technique + datacomponents = self.old_datacomponents + id_to_datasource = self.old_id_to_datasource + else: + subtechnique_of_rels = self.new_subtechnique_of_rels + id_to_technique = self.new_id_to_technique + datacomponents = self.new_datacomponents + id_to_datasource = self.new_id_to_datasource + + def placard(item): + """Get a section list item for the given SDO according to section type""" + if section == "revocations": + revoker = item["revoked_by"] + if ( + "x_mitre_is_subtechnique" in revoker + and revoker["x_mitre_is_subtechnique"] + ): + # get revoking technique's parent for display + parentID = list( + filter( + lambda rel: rel["source_ref"] == revoker["id"], + subtechnique_of_rels, + ) + )[0]["target_ref"] + parentName = ( + id_to_technique[parentID]["name"] + if parentID in id_to_technique + else "ERROR NO PARENT" + ) + return f"{item['name']} (revoked by { parentName}: [{revoker['name']}]({self.site_prefix}/{self.getUrlFromStix(revoker, True)}))" + elif ( + "x_mitre_data_source_ref" in revoker + and revoker["x_mitre_data_source_ref"] + ): + # get revoking technique's parent for display + parentID = list( + filter( + lambda rel: rel["id"] == revoker["id"], datacomponents + ) + )[0]["x_mitre_data_source_ref"] + parentName = ( + id_to_datasource[parentID]["name"] + if parentID in id_to_datasource + else "ERROR NO PARENT" + ) + return f"{item['name']} (revoked by { parentName}: [{revoker['name']}]({self.site_prefix}/{self.getDataComponentUrl(id_to_datasource[parentID], item)}))" + else: + return f"{item['name']} (revoked by [{revoker['name']}]({self.site_prefix}/{self.getUrlFromStix(revoker)}))" + elif section == "deletions": + return f"{item['name']}" + else: + is_subtechnique = ( + item["type"] == "attack-pattern" + and "x_mitre_is_subtechnique" in item + and item["x_mitre_is_subtechnique"] + ) + if item["type"] == "x-mitre-data-component": + parentID = item["x_mitre_data_source_ref"] + if id_to_datasource.get(parentID): + return f"[{item['name']}]({self.site_prefix}/{self.getDataComponentUrl(id_to_datasource[parentID], item)})" + return f"[{item['name']}]({self.site_prefix}/{self.getUrlFromStix(item, is_subtechnique)})" + + groupings = self.get_groupings( + obj_type=obj_type, + items=items, + subtechnique_of_rels=subtechnique_of_rels, + id_to_technique=id_to_technique, + datacomponents=datacomponents, + id_to_datasource=id_to_datasource, + ) + + # build sectionList string + sectionString = "" + for grouping in groupings: + if grouping["parentInSection"]: + sectionString += f"* { placard(grouping['parent']) }\n" + + for child in sorted( + grouping["children"], key=lambda child: child["name"] + ): + if grouping["parentInSection"]: + sectionString += f" * {placard(child) }\n" + else: + sectionString += ( + f"* {grouping['parent']['name']}: { placard(child) }\n" + ) + + logger.debug(f"finished getting section list for {obj_type}/{section}") + # logger.debug(sectionString) + return sectionString + + def getContributorSection(): + # Get contributors markdown + contribSection = "### Contributors to this release\n\n" + sorted_contributors = sorted( + self.release_contributors, key=lambda v: v.lower() + ) + + for contributor in sorted_contributors: + if contributor == "ATT&CK": + continue # do not include ATT&CK as contributor + contribSection += f"* {contributor}\n" + + return contribSection + + logger.info("generating markdown string") + content = "" + for obj_type in self.data.keys(): + domains = "" + for domain in self.data[obj_type]: + logger.debug( + f"==== Generating markdown for domain: {domainToDomainLabel[domain]} --- {obj_type} ====" + ) + domains += f"#### {domainToDomainLabel[domain]}\n\n" # e.g "Enterprise" + # Skip mobile sections for data sources + if domain == "mobile-attack" and obj_type == "datasource": + logger.debug( + f"Skipping - ATT&CK for Mobile does not support data sources" + ) + domains += "ATT&CK for Mobile does not support data sources\n\n" + continue + domain_sections = "" + for section, values in self.data[obj_type][domain].items(): + logger.debug(f"{section}: {len(values)}") + + if values: # if there are items in the section + section_items = getSectionList( + items=values, obj_type=obj_type, section=section + ) + else: # no items in section + section_items = "* No changes\n" + + header = sectionNameToSectionHeaders[section] + ":" + + if "{obj_type}" in header: + if section == "additions": + header = header.replace( + "{obj_type}", attackTypeToTitle[obj_type] + ) + else: + header = header.replace( + "{obj_type}", attackTypeToSectionName[obj_type] + ) + + # e.g "added techniques:" + domain_sections += f"{header}\n\n{section_items}\n" + + # add domain sections + domains += f"{domain_sections}" + + # e.g "techniques" + content += f"### {attackTypeToTitle[obj_type]}\n\n{domains}" + + if self.show_key: + key_content = self.get_md_key() + content = f"{key_content}\n\n{content}" + + # Add contributors if requested by argument + if self.include_contributors: + content += getContributorSection() + + logger.info("finished generating markdown string") + + return content + + def get_layers_dict(self): + """Return ATT&CK Navigator layers in dict format summarizing detected differences. + + Returns a dict mapping domain to its layer dict. + """ + logger.info("generating layers dict") + + layers = {} + thedate = datetime.datetime.today().strftime("%B %Y") + # for each layer file in the domains mapping + for domain in self.domains: + logger.debug(f"===== Generating layer for domain: {domain} =====") + # build techniques list + techniques = [] + used_statuses = set() + for status in self.data["technique"][domain]: + logger.debug(f"Parsing: {status}") + if status == "revocations" or status == "deprecations": + continue + for technique in self.data["technique"][domain][status]: + problem_detected = False + if "kill_chain_phases" not in technique: + logger.error( + f"{technique['id']}: technique missing a tactic!! {technique['name']}" + ) + problem_detected = True + if "external_references" not in technique: + logger.error( + f"{technique['id']}: technique missing external references!! {technique['name']}" + ) + problem_detected = True + + if problem_detected: + continue + + for phase in technique["kill_chain_phases"]: + techniques.append( + { + "techniqueID": technique["external_references"][0][ + "external_id" + ], + "tactic": phase["phase_name"], + "enabled": True, + "color": statusToColor[status], + "comment": status[:-1] + if status != "unchanged" + else status, # trim s off end of word + } + ) + used_statuses.add(status) + + # build legend based off used_statuses + legendItems = list( + map( + lambda status: { + "color": statusToColor[status], + "label": status + ": " + statusDescriptions[status], + }, + used_statuses, + ) + ) + + # build layer structure + layer_json = { + "versions": {"layer": "4.1", "navigator": "4.1"}, + "name": f"{thedate} {domainToDomainLabel[domain]} Updates", + "description": f"{domainToDomainLabel[domain]} updates for the {thedate} release of ATT&CK", + "domain": domain, + "techniques": techniques, + "sorting": 0, + "hideDisabled": False, + "legendItems": legendItems, + "showTacticRowBackground": True, + "tacticRowBackground": "#205b8f", + "selectTechniquesAcrossTactics": True, + } + layers[domain] = layer_json + + logger.info("finished generating layers dict") + return layers + + def get_changes_dict(self): + """Return dict format summarizing detected differences.""" + logger.info("generating changes dict") + + def cleanup_values(items, obj_type, section): + if section == "deletions": + subtechnique_of_rels = self.old_subtechnique_of_rels + id_to_technique = self.old_id_to_technique + datacomponents = self.old_datacomponents + id_to_datasource = self.old_id_to_datasource + else: + subtechnique_of_rels = self.new_subtechnique_of_rels + id_to_technique = self.new_id_to_technique + datacomponents = self.new_datacomponents + id_to_datasource = self.new_id_to_datasource + + groupings = self.get_groupings( + obj_type=obj_type, + items=items, + subtechnique_of_rels=subtechnique_of_rels, + id_to_technique=id_to_technique, + datacomponents=datacomponents, + id_to_datasource=id_to_datasource, + ) + + new_values = [] + for grouping in groupings: + if grouping["parentInSection"]: + new_values.append(grouping["parent"]) + + for child in sorted( + grouping["children"], key=lambda child: child["name"] + ): + new_values.append(child) + + return new_values + + changes_dict = {} + for domain in self.domains: + changes_dict[domain] = {} + + for obj_type, domains in self.data.items(): + for domain, sections in domains.items(): + logger.debug( + f"===== Generating domain: {domainToDomainLabel[domain]} --- {obj_type} =====" + ) + changes_dict[domain][obj_type] = {} + + for section, values in sections.items(): + # new_values includes parents & children mixed (e.g. techniques/sub-techniques, data sources/components) + new_values = cleanup_values( + items=values, obj_type=obj_type, section=section + ) + changes_dict[domain][obj_type][section] = new_values + + # always add contributors + changes_dict["new-contributors"] = [] + sorted_contributors = sorted(self.release_contributors, key=lambda v: v.lower()) + for contributor in sorted_contributors: + if contributor == "ATT&CK": + continue # do not include ATT&CK as contributor + changes_dict["new-contributors"].append(contributor) + + logger.info("finished generating changes dict") + return changes_dict + + +def markdown_to_index_html(markdown_outfile, content): + """Convert the markdown string passed in to HTML and store in index.html + of indicated output file path""" + logger.info("writing HTML to file") + + # get output file path + outputfile_path = os.path.split(markdown_outfile)[0] + outfile = os.path.join(outputfile_path, "index.html") + + # Center content + html_string = """
""" + html_string += "" + html_string += ( + "

Changes between ATT&CK STIX bundles

" + ) + html_string += markdown.markdown(content) + html_string += "
" + + outfile = open(outfile, "w", encoding="utf-8") + outfile.write(html_string) + outfile.close() + + logger.info("finished writing HTML to file") + + +def layers_dict_to_files(outfiles, layers): + """Print the layers dict passed in to layer files.""" + logger.info("writing layers dict to layer files") + + # write each layer to separate files + if "enterprise-attack" in layers: + enterprise_attack_layer_file = outfiles[0] + Path(enterprise_attack_layer_file).parent.mkdir(parents=True, exist_ok=True) + json.dump(layers["enterprise-attack"], open(enterprise_attack_layer_file, "w"), indent=4) + + if "mobile-attack" in layers: + mobile_attack_layer_file = outfiles[1] + Path(mobile_attack_layer_file).parent.mkdir(parents=True, exist_ok=True) + json.dump(layers["mobile-attack"], open(mobile_attack_layer_file, "w"), indent=4) + + logger.info("finished writing layers dict to layer files") + + +def get_parsed_args(): + """Create argument parser and parse arguments""" + old_dir_default = "old" + + parser = argparse.ArgumentParser( + description=( + "Create -markdown and/or -layers reporting on the changes between two versions of the ATT&CK content. " + "Takes STIX bundles as input. For default operation, put enterprise-attack.json and mobile-attack.json bundles " + "in 'old' and 'new' folders for the script to compare." + ) + ) + + parser.add_argument( + "-old", + type=str, + metavar="OLD_DIR", + help=f"the directory of the old content. Default is '{old_dir_default}'", + ) + + parser.add_argument( + "-new", + type=str, + metavar="NEW_DIR", + default="new", + help="the directory of the new content. Default is '%(default)s'", + ) + + parser.add_argument( + "-types", + type=str, + nargs="+", + metavar=("OBJ_TYPE", "OBJ_TYPE"), + choices=["technique", "software", "group", "mitigation", "datasource"], + default=["technique", "software", "group", "mitigation", "datasource"], + help="which types of objects to report on. Choices (and defaults) are %(choices)s", + ) + + parser.add_argument( + "-domains", + type=str, + nargs="+", + metavar="DOMAIN", + choices=["enterprise-attack", "mobile-attack"], + default=["enterprise-attack", "mobile-attack"], + help="which domains to report on. Choices (and defaults) are %(choices)s", + ) + + parser.add_argument( + "-markdown", + type=str, + nargs="?", + metavar="MARKDOWN_FILE", + const=md_default, + help="create a markdown file reporting changes. If value is unspecified, defaults to %(const)s", + ) + + parser.add_argument( + "-json-output", + type=str, + nargs="?", + metavar="JSON_FILE", + const=json_default, + help="create a JSON file reporting changes. If value is unspecified, defaults to %(const)s", + ) + + parser.add_argument( + "--create-html", + action="store_true", + help="create index.html page of markdown file that reported changes. Does not do anything unless -markdown is provided", + ) + + parser.add_argument( + "-layers", + type=str, + nargs="*", + # metavar=("ENTERPRISE", "MOBILE", "PRE"), + help=f""" + create layer files showing changes in each domain + expected order of filenames is 'enterprise', 'mobile', 'pre attack'. + If values are unspecified, defaults to {", ".join(layer_defaults)} + """, + ) + + parser.add_argument( + "-site_prefix", + type=str, + default="", + help="prefix links in markdown output, e.g. [prefix]/techniques/T1484", + ) + + parser.add_argument( + "-v", + "--verbose", + action="store_true", + help="print status messages", + ) + + parser.add_argument( + "--minor-changes", + action="store_true", + help="show changes to objects which didn't increment the version number", + ) + + parser.add_argument( + "--unchanged", + action="store_true", + help="show objects without changes in the markdown output", + ) + + parser.add_argument( + "--use-taxii", + action="store_true", + help="Use content from the ATT&CK TAXII server for the -old data", + ) + + parser.add_argument( + "--use-mitre-cti", + action="store_true", + help="Use content from the MITRE CTI repo for the -old data", + ) + + parser.add_argument( + "--show-key", + action="store_true", + help="Add a key explaining the change types to the markdown", + ) + + parser.add_argument( + "--contributors", + action="store_true", + help="show new contributors between releases", + ) + + args = parser.parse_args() + + # the default loguru logger logs up to Debug by default + logger.remove() + if args.verbose: + logger.add(lambda msg: tqdm.write(msg, end=""), colorize=True) + else: + logger.add(lambda msg: tqdm.write(msg, end=""), colorize=True, level="INFO") + + if args.use_taxii and args.old is not None: + parser.error("--use-taxii and -old cannot be used together") + + if args.use_mitre_cti and args.old is not None: + parser.error("--use-mitre-cti and -old cannot be used together") + + if not args.markdown and args.layers is None: + logger.error( + "Script doesn't output anything unless -markdown and/or -layers are specified." + ) + logger.error("Run 'python3 diff_stix.py -h' for usage instructions") + exit() + + if args.old is None: + args.old = old_dir_default + + if args.layers is not None: + if len(args.layers) not in [0, 3]: + parser.error( + "-layers requires exactly three files to be specified or none at all" + ) + + return args + + +# Used by attack-website script to generate changelog +def get_new_changelog_md( + domains: list[str] = ["enterprise-attack", "mobile-attack"], + layers: list[str] = layer_defaults, + markdown_file: str = md_default, + minor_changes: bool = False, + unchanged: bool = False, + new: str = "new", + old: str = None, + show_key: bool = False, + site_prefix: str = "", + types: list[str] = ["technique", "software", "group", "mitigation", "datasource"], + use_taxii: bool = False, + use_mitre_cti: bool = False, + verbose: bool = False, + include_contributors: bool = False, + create_html: bool = False, + json_output: str = json_default, +): + # the default loguru logger logs up to Debug by default + logger.remove() + if verbose: + logger.add(lambda msg: tqdm.write(msg, end=""), colorize=True) + else: + logger.add(lambda msg: tqdm.write(msg, end=""), colorize=True, level="INFO") + + # if old: + # if use_mitre_cti or use_taxii: + # logger.error("Multiple sources selected as base STIX to compare against.") + # logger.error("When calling get_new_changelog_md(), 'old' is mutually exclusive with 'use_taxii' and 'use_mitre_cti'") + # return "" + + diffStix = DiffStix( + domains=domains, + layers=layers, + markdown=markdown_file, + minor_changes=minor_changes, + unchanged=unchanged, + new=new, + old=old, + show_key=show_key, + site_prefix=site_prefix, + types=types, + use_taxii=use_taxii, + use_mitre_cti=use_mitre_cti, + verbose=verbose, + include_contributors=include_contributors, + ) + + md_string = None + if markdown_file: + md_string = diffStix.get_markdown_string() + + logger.info("writing markdown to file") + Path(markdown_file).parent.mkdir(parents=True, exist_ok=True) + with open(markdown_file, "w") as file: + file.write(md_string) + logger.info("finished writing markdown to file") + + if create_html: + markdown_to_index_html(markdown_file, md_string) + + if layers: + if len(layers) == 0: + # no files specified, e.g. '-layers', use defaults + diffStix.layers = layer_defaults + layers = layer_defaults + elif len(layers) == 3: + # files specified, e.g. '-layers file.json file2.json file3.json', use specified + # assumes order of files is enterprise, mobile, pre attack (same order as defaults) + diffStix.layers = layers + + layers_dict = diffStix.get_layers_dict() + layers_dict_to_files(outfiles=layers, layers=layers_dict) + + if json_output: + changes_dict = diffStix.get_changes_dict() + + logger.info("writing JSON updates to file") + Path(json_output).parent.mkdir(parents=True, exist_ok=True) + json.dump(changes_dict, open(json_output, "w"), indent=4) + logger.info("finished writing JSON updates to file") + + return md_string + + +def main(): + args = get_parsed_args() + + get_new_changelog_md( + domains=args.domains, + layers=args.layers, + markdown_file=args.markdown, + minor_changes=args.minor_changes, + unchanged=args.unchanged, + new=args.new, + old=args.old, + show_key=args.show_key, + site_prefix=args.site_prefix, + types=args.types, + use_taxii=args.use_taxii, + use_mitre_cti=args.use_mitre_cti, + verbose=args.verbose, + include_contributors=args.contributors, + create_html=args.create_html, + json_output=args.json_output, + ) + + +if __name__ == "__main__": + main() diff --git a/requirements-dev.txt b/requirements-dev.txt index b7fa4065..206880fe 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,13 +1,15 @@ colour>=0.1.5 +drawSvg>=1.6.0 +loguru>=0.6.0 +Markdown>=3.3.6 +numpy>=1.16.0 openpyxl>=3.0.3 +pandas>=1.1.5 +Pillow>=7.1.2 +requests>=2.21.0 stix2>=3.0.1 +stix2-elevator>=4.0.1 +tabulate>=0.8.9 taxii2-client>=2.3.0 -numpy>=1.16.0 -drawSvg>=1.6.0 -Pillow>=7.1.2 -pandas>=1.1.5 tqdm>=4.31.1 -requests>=2.21.0 xlsxwriter>=1.3.7 -tabulate>=0.8.9 -stix2-elevator>=4.0.1 diff --git a/setup.py b/setup.py index 16c091f8..f3b4dd26 100644 --- a/setup.py +++ b/setup.py @@ -18,12 +18,13 @@ include_package_data=True, entry_points={ 'console_scripts': [ - 'layerExporter_cli=mitreattack.navlayers.layerExporter_cli:main', 'attackToExcel_cli=mitreattack.attackToExcel.attackToExcel:main', + 'layerExporter_cli=mitreattack.navlayers.layerExporter_cli:main', 'layerGenerator_cli=mitreattack.navlayers.layerGenerator_cli:main', 'indexToMarkdown_cli=mitreattack.collections.index_to_markdown:main', 'collectionToIndex_cli=mitreattack.collections.collection_to_index:main', - 'stixToCollection_cli=mitreattack.collections.stix_to_collection:main' + 'stixToCollection_cli=mitreattack.collections.stix_to_collection:main', + 'diff_stix=mitreattack.diffStix.changelog_helper:main' ] }, packages=setuptools.find_packages(), @@ -36,18 +37,20 @@ ], python_requires='>=3.6', install_requires=[ - 'colour>=0.1.5', - 'openpyxl>=3.0.3', - 'stix2>=3.0.1', - 'taxii2-client>=2.3.0', - 'numpy>=1.16.0', - 'drawSvg>=1.6.0', - 'Pillow>=7.1.2', - 'pandas>=1.1.5', - 'tqdm>=4.31.1', - 'requests>=2.21.0', - 'xlsxwriter>=1.3.7', - 'tabulate>=0.8.9', - 'stix2-elevator>=4.0.1', + 'colour', + 'drawSvg', + 'loguru', + 'Markdown', + 'numpy', + 'openpyxl', + 'pandas', + 'Pillow', + 'requests', + 'stix2', + 'stix2-elevator', + 'tabulate', + 'taxii2-client', + 'tqdm', + 'xlsxwriter', ] ) From 9eacfd888db46f4c0c399aae56d92285bdd43cc4 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Tue, 12 Apr 2022 22:18:28 -0500 Subject: [PATCH 2/3] Add DiffStix documentation and clean up Markdown --- README.md | 118 ++++++++++---- mitreattack/attackToExcel/README.md | 43 +++-- mitreattack/collections/README.md | 47 ++++-- mitreattack/diffStix/README.md | 21 +++ mitreattack/navlayers/README.md | 245 ++++++++++++++++++++-------- 5 files changed, 345 insertions(+), 129 deletions(-) create mode 100644 mitreattack/diffStix/README.md diff --git a/README.md b/README.md index abc653ad..0ea58ffb 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,49 @@ # mitreattack-python This repository contains a library of Python-based tools and utilities for working with ATT&CK content. -- the [navlayers](https://github.com/mitre-attack/mitreattack-python/tree/master/mitreattack/navlayers) module contains a collection of utilities for working with [ATT&CK Navigator](https://github.com/mitre-attack/attack-navigator) layers. -- the [attackToExcel](https://github.com/mitre-attack/mitreattack-python/tree/master/mitreattack/attackToExcel) module provides utilities for converting [ATT&CK STIX data](https://github.com/mitre/cti) to Excel spreadsheets. It also provides access to [Pandas](https://pandas.pydata.org/) DataFrames representing the dataset for use in data analysis. -- the [collections](https://github.com/mitre-attack/mitreattack-python/tree/master/mitreattack/collections) module contains a set of utilities for working with [ATT&CK Collections and Collection Indexes](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md). + +- the [navlayers](https://github.com/mitre-attack/mitreattack-python/tree/master/mitreattack/navlayers) module + contains a collection of utilities for working with [ATT&CK Navigator](https://github.com/mitre-attack/attack-navigator) layers. +- the [attackToExcel](https://github.com/mitre-attack/mitreattack-python/tree/master/mitreattack/attackToExcel) module + provides utilities for converting [ATT&CK STIX data](https://github.com/mitre/cti) to Excel spreadsheets. + It also provides access to [Pandas](https://pandas.pydata.org/) DataFrames representing the dataset for use in data analysis. +- the [collections](https://github.com/mitre-attack/mitreattack-python/tree/master/mitreattack/collections) module + contains a set of utilities for working with [ATT&CK Collections and Collection Indexes](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md). ## Requirements + - [python3](https://www.python.org/) ## Installation -To use this package, simply install the mitreattack-python library: -``` + +To use this package, simply install the mitreattack-python library: + +```shell pip install mitreattack-python -``` +``` ## Contributing -To contribute to this project, either through a bug report, feature request, or merge request, please see the [Contributors guide](https://github.com/mitre-attack/mitreattack-python/docs/CONTRIBUTING.MD). + +To contribute to this project, either through a bug report, feature request, or merge request, +please see the [Contributors guide](https://github.com/mitre-attack/mitreattack-python/docs/CONTRIBUTING.MD). ## Usage -Some simple examples are provided here to get you started on using this library. More detailed information about the specific usage of the modules in this package, with examples, can be found in the individual README files for each module. + +Some simple examples are provided here to get you started on using this library. +More detailed information about the specific usage of the modules in this package, +with examples, can be found in the individual README files for each module. | module name | description | documentation | |:------------|:------------|:--------------| | navlayers | Provides a means by which to import, export, and manipulate [ATT&CK Navigator](https://github.com/mitre-attack/attack-navigator) layers. These layers can be read in from the filesystem or python dictionaries, combined and edited, and then exported to excel or SVG images as users desire. | Further documentation for the navlayers module can be found [here](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/navlayers/README.md).| | attackToExcel | Provides functionalities for exporting the ATT&CK dataset into Excel Spreadsheets. It also provides programmatic access to the dataset as [Pandas](https://pandas.pydata.org/) DataFrames to enable data analysis using that library. | Further documentation for the attackToExcel module can be found [here](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/attackToExcel/README.md).| -| collections | Provides functionalities for converting and summarizing data in collections and collection indexes. It also provides a means by which to generate a collection from a raw stix bundle input. | Further documentation for the collections module can be found [here](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/collections/README.md).| +| collections | Provides functionalities for converting and summarizing data in collections and collection indexes. It also provides a means by which to generate a collection from a raw stix bundle input. | Further documentation for the collections module can be found [here](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/collections/README.md).| +| diffStix | Create markdown, HTML, JSON and/or ATT&CK Navigator layers reporting on the changes between two versions of the STIX2 bundles representing the ATT&CK content. Run `diff_stix -h` for full usage instructions. | Further documentation for the diffStix module can be found [here](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/diffStix/README.md).| + ### Usage Examples + #### navlayers + ```python from mitreattack.navlayers import Layer @@ -58,6 +75,7 @@ t.to_svg(lay, filepath="example.svg") # render the layer to an SVG fil Further documentation for the navlayers module can be found [here](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/navlayers/README.md). #### attackToExcel + ```python import mitreattack.attackToExcel.attackToExcel as attackToExcel @@ -78,16 +96,22 @@ techniques_df = techniques_data["techniques"] print(techniques_df[techniques_df["ID"].str.contains("T1102")]["name"]) ``` -Further documentation for the attackToExcel module can be found [here](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/attackToExcel/README.md). +Further documentation for the attackToExcel module can be found +[here](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/attackToExcel/README.md). #### Command Line Tools -Two command line tools have been included in this package as part of the `navlayers` and `attackToExcel` modules. This can be run immediately after installing the package, using the syntax described below. - +Several command line tools have been included in this package. +They can be run immediately after installing the package, using the syntax described below. + ##### layerExporter_cli + This command line tool allows users to convert a [navigator](https://github.com/mitre-attack/attack-navigator) - layer file to either an svg image or excel file using the functionality provided by the navlayers module. - Details about the SVG configuration json mentioned below can be found in the [SVGConfig](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/navlayers/README.md#svgconfig) entry within the navlayers module documentation. +layer file to either an svg image or excel file using the functionality provided by the navlayers module. +Details about the SVG configuration json mentioned below can be found in the +[SVGConfig](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/navlayers/README.md#svgconfig) +entry within the navlayers module documentation. + ```commandline C:\Users\attack>layerExporter_cli -h usage: layerExporter_cli [-h] -m {svg,excel} [-s {taxii,local,remote}] @@ -121,7 +145,9 @@ C:\Users\attack>layerExporter_cli -m svg -s taxii -l settings/config.json -o out ``` ##### attackToExcel_cli + This command line tool allows users to generate excel spreadsheets representing the ATT&CK dataset. + ```commandline C:\Users\attack>attackToExcel_cli -h usage: attackToExcel_cli [-h] @@ -143,11 +169,12 @@ optional arguments: C:\Users\attack>attackToExcel_cli -domain ics-attack -version v8.1 -output exported_data ``` +##### layerGenerator_cli -##### layerGenerator_cli.py -This command line tool allows users to generate [ATT&CK Navigator](https://github.com/mitre-attack/attack-navigator) +This command line tool allows users to generate [ATT&CK Navigator](https://github.com/mitre-attack/attack-navigator) layer files from either a specific group, software, or mitigation. Alternatively, users can generate a layer file with a -mapping to all associated groups, software, or mitigations across the techniques within ATT&CK. +mapping to all associated groups, software, or mitigations across the techniques within ATT&CK. + ```commandline C:\Users\attack>layerGenerator_cli -h usage: layerGenerator_cli [-h] @@ -187,10 +214,12 @@ C:\Users\attack>layerGenerator_cli --domain enterprise --source taxii --overview ``` ##### IndexToMarkdown_cli -This command line tool allows users to transform a -[ATT&CK collection index file](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md#collection-indexes) -into a [human-readable markdown file](https://github.com/mitre-attack/attack-stix-data/blob/master/index.md) that + +This command line tool allows users to transform an +[ATT&CK collection index file](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md#collection-indexes) +into a [human-readable markdown file](https://github.com/mitre-attack/attack-stix-data/blob/master/index.md) that documents the contents of said collections. + ```commandline C:\Users\attack>indexToMarkdown_cli -h usage: index_to_markdown.py [-h] [-index INDEX] [-output OUTPUT] @@ -203,10 +232,14 @@ optional arguments: -o output, --output OUTPUT markdown output file C:\Users\attack>indexToMarkdown_cli --index C:\Users\attack\examples\index.json --output example.md ``` + ##### CollectionToIndex_cli -This command line tool allows users to transform [ATT&CK collections](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md#collections) -into an [ATT&CK collection index](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md#collection-indexes) + +This command line tool allows users to transform +[ATT&CK collections](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md#collections) +into an [ATT&CK collection index](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md#collection-indexes) that summarizes the contents of the linked collections. + ```commandline C:\Users\attack>collectionToIndex_cli -h usage: collection_to_index.py [-h] [--output OUTPUT] @@ -233,9 +266,13 @@ optional arguments: folder of JSON files to treat as collections C:\Users\attack>collectionToIndex_cli test_index "a layer created as a demo" www.example.com --files C:\Users\attack\examples\collection.json --output C:\Users\attack\examples\index.json ``` + ##### StixToCollection_cli -This command line tool allows users to transform raw stix bundle files into versions featuring [collection](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md#collections) objects. + +This command line tool allows users to transform raw stix bundle files into versions featuring +[collection](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md#collections) objects. It is compatible with both STIX 2.0 and STIX 2.1 bundles. + ```commandline C:\Users\attack>stixToCollection_cli -h usage: stix_to_collection.py [-h] [--input INPUT] [--output OUTPUT] @@ -259,28 +296,41 @@ optional arguments: C:\Users\attack>stixToCollection "2.0 demo bundle" 9.1 --input C:\Users\bundles\enterprise-bundle-2_0.json C:\Users\attack>stixToCollection "2.1 demo bundle" 9.1 --input C:\Users\bundles\enterprise-bundle-2_1.json ``` + ## Related MITRE Work -#### CTI -[Cyber Threat Intelligence repository](https://github.com/mitre/cti) of the ATT&CK catalog expressed in STIX 2.0 JSON. This repository also contains [our USAGE document](https://github.com/mitre/cti/blob/master/USAGE.md) which includes additional examples of accessing and parsing our dataset in Python. -#### ATT&CK -ATT&CK® is a curated knowledge base and model for cyber adversary behavior, reflecting the various phases of an adversary’s lifecycle, and the platforms they are known to target. ATT&CK is useful for understanding security risk against known adversary behavior, for planning security improvements, and verifying defenses work as expected. +### CTI + +[Cyber Threat Intelligence repository](https://github.com/mitre/cti) of the ATT&CK catalog expressed in STIX 2.0 JSON. +This repository also contains [our USAGE document](https://github.com/mitre/cti/blob/master/USAGE.md) which includes +additional examples of accessing and parsing our dataset in Python. -https://attack.mitre.org +### ATT&CK + +ATT&CK® is a curated knowledge base and model for cyber adversary behavior, reflecting the various phases of +an adversary’s lifecycle, and the platforms they are known to target. +ATT&CK is useful for understanding security risk against known adversary behavior, +for planning security improvements, and verifying defenses work as expected. + + + +### STIX -#### STIX Structured Threat Information Expression (STIX) is a language and serialization format used to exchange cyber threat intelligence (CTI). -STIX enables organizations to share CTI with one another in a consistent and machine-readable manner, allowing security communities to better understand what computer-based attacks they are most likely to see and to anticipate and/or respond to those attacks faster and more effectively. +STIX enables organizations to share CTI with one another in a consistent and machine-readable manner, +allowing security communities to better understand what computer-based attacks they are most likely to +see and to anticipate and/or respond to those attacks faster and more effectively. STIX is designed to improve many capabilities, such as collaborative threat analysis, automated threat exchange, automated detection and response, and more. -https://oasis-open.github.io/cti-documentation/ + + +### ATT&CK scripts -#### ATT&CK scripts One-off scripts and code examples you can use as inspiration for how to work with ATT&CK programmatically. Many of the functionalities found in the mitreattack-python package were originally posted on attack-scripts. -https://github.com/mitre-attack/attack-scripts + ## Notice @@ -292,7 +342,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, diff --git a/mitreattack/attackToExcel/README.md b/mitreattack/attackToExcel/README.md index 7f21550b..d69349e3 100644 --- a/mitreattack/attackToExcel/README.md +++ b/mitreattack/attackToExcel/README.md @@ -1,27 +1,34 @@ # ATT&CK To Excel -This folder contains a module for converting [ATT&CK STIX data](https://github.com/mitre/cti) to Excel spreadsheets. It also provides a means to access ATT&CK data as [Pandas](https://pandas.pydata.org/) DataFrames for data analysis. +This folder contains a module for converting [ATT&CK STIX data](https://github.com/mitre/cti) to Excel spreadsheets. +It also provides a means to access ATT&CK data as [Pandas](https://pandas.pydata.org/) DataFrames for data analysis. ## Usage + ### Command Line + Print full usage instructions: -``` + +```shell python3 attackToExcel.py -h ``` Example execution: -``` + +```shell python3 attackToExcel.py ``` Build a excel files corresponding to a specific domain and version of ATT&CK: -``` + +```shell python3 attackToExcel -domain mobile-attack -version v5.0 ``` -### Module +### Module + +Example execution targeting a specific domain and version: -Example execution targeting a specific domain and version: ```python import mitreattack.attackToExcel.attackToExcel as attackToExcel @@ -31,7 +38,8 @@ attackToExcel.export("mobile-attack", "v5.0", "/path/to/export/folder") ## Interfaces ### attackToExcel -attackToExcel provides the means by which to convert/extract the ATT&CK STIX data to Excel spreadsheets. A brief + +attackToExcel provides the means by which to convert/extract the ATT&CK STIX data to Excel spreadsheets. A brief overview of the available methods follows. | method name | arguments | usage | @@ -39,10 +47,11 @@ overview of the available methods follows. |get_stix_data|`domain`: the domain of ATT&CK to fetch data from
`version`: optional parameter indicating which version to fetch data from (such as "v8.1"). If omitted retrieves the most recent version of ATT&CK.
`remote`: optional parameter that provides a URL of a remote ATT&CK Workbench instance to grab data from.| Retrieves the ATT&CK STIX data for the specified version and returns it as a MemoryStore object| |build_dataframes| `src`: MemoryStore or other stix2 DataSource object holding domain data
`domain`: domain of ATT&CK that `src` corresponds to| Builds a Pandas DataFrame collection as a dictionary, with keys for each type, based on the ATT&CK data provided| |write_excel| `dataframes`: pandas DataFrame dictionary (generated by build_dataframes)
`domain`: domain of ATT&CK that `dataframes` corresponds to
`version`: optional parameter indicating which version of ATT&CK is in use
`outputDir`: optional parameter specifying output directory| Writes out DataFrame based ATT&CK data to excel files| -|export| `domain`: the domain of ATT&CK to download
`version`: optional parameter specifying which version of ATT&CK to download
`outputDir`: optional parameter specifying output directory| Downloads ATT&CK data from MITRE/CTI and exports it to Excel spreadsheets | +|export| `domain`: the domain of ATT&CK to download
`version`: optional parameter specifying which version of ATT&CK to download
`outputDir`: optional parameter specifying output directory| Downloads ATT&CK data from MITRE/CTI and exports it to Excel spreadsheets | ### stixToDf -stixToDf provides various methods to process and manipulate the STIX data in order to create [Pandas](https://pandas.pydata.org/) DataFrames for + +stixToDf provides various methods to process and manipulate the STIX data in order to create [Pandas](https://pandas.pydata.org/) DataFrames for processing. A brief overview of these methods follows. | method name | arguments | usage | @@ -57,15 +66,23 @@ processing. A brief overview of these methods follows. ## Spreadsheet format -The Excel representation of the ATT&CK dataset includes both master spreadsheets, containing all object types, and individual spreadsheets for each object type. The individual type spreadsheets break out relationships (e.g procedure examples connecting groups to techniques) into separate sheets by relationship type, while the master spreadsheet includes all relationship types in a single sheet. Otherwise, the representation is identical. +The Excel representation of the ATT&CK dataset includes both master spreadsheets, +containing all object types, and individual spreadsheets for each object type. +The individual type spreadsheets break out relationships (e.g procedure examples connecting groups to techniques) +into separate sheets by relationship type, while the master spreadsheet includes all relationship types in a single sheet. +Otherwise, the representation is identical. -A citations sheet can be used to look up the in-text citations which appear in some fields. For domains that include multiple matrices, such as Mobile ATT&CK, each matrix gets its own named sheet. Unlike the STIX dataset, objects that have been revoked or deprecated are not included in the spreadsheets. +A citations sheet can be used to look up the in-text citations which appear in some fields. +For domains that include multiple matrices, such as Mobile ATT&CK, each matrix gets its own named sheet. +Unlike the STIX dataset, objects that have been revoked or deprecated are not included in the spreadsheets. ## Accessing the Pandas DataFrames -Internally, attackToExcel stores the parsed STIX data as [Pandas](https://pandas.pydata.org/) DataFrames. These can be retrieved for use in data analysis. +Internally, attackToExcel stores the parsed STIX data as [Pandas](https://pandas.pydata.org/) DataFrames. +These can be retrieved for use in data analysis. Example of accessing [Pandas](https://pandas.pydata.org/) DataFrames: + ```python import mitreattack.attackToExcel.attackToExcel as attackToExcel import mitreattack.attackToExcel.stixToDf as stixToDf @@ -88,4 +105,4 @@ citations_df = techniques_data["citations"] print(citations_df[citations_df["reference"].str.contains("LOLBAS Wmic")]) # reference citation url # 1010 LOLBAS Wmic LOLBAS. (n.d.). Wmic.exe. Retrieved July 31, 2... https://lolbas-project.github.io/lolbas/Binari... -``` \ No newline at end of file +``` diff --git a/mitreattack/collections/README.md b/mitreattack/collections/README.md index 512a0a03..412aa48d 100644 --- a/mitreattack/collections/README.md +++ b/mitreattack/collections/README.md @@ -1,21 +1,29 @@ # collections -This folder contains modules and scripts for working with ATT&CK collections. Collections are sets of ATT&CK STIX objects, grouped for user convienence. For more information about ATT&CK collections, see the corresponding [ATT&CK documentation](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md#collections). +This folder contains modules and scripts for working with ATT&CK collections. +Collections are sets of ATT&CK STIX objects, grouped for user convienence. +For more information about ATT&CK collections, see the corresponding +[ATT&CK documentation](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md#collections). + +## Collections Scripts -##### Collections Scripts | script | description | |:-------|:------------| |[index_to_markdown](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/collections/index_to_markdown.py)| Provides a means by which to convert a [collection index](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md#collection-indexes) into a human-readable markdown file. More information can be found in the corresponding [section](#index_to_markdown.py) below.| |[collection_to_index](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/collections/collection_to_index.py)| Provides a means by which to convert a [collection](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md#collections) into a easy-to-share [index file](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md#collection-indexes). More information can be found in the corresponding [section](#collection_to_index.py) below.| |[stix_to_collection](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/collections/stix_to_collection.py)| Provides a means by which to convert raw stix (in the form of [bundles](https://docs.oasis-open.org/cti/stix/v2.1/cs01/stix-v2.1-cs01.html#_gms872kuzdmg)) into a [collection](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md#collections). More information can be found in the corresponding [section](#stix_to_collection.py) below.| -## index_to_markdown.py -index_to_markdown.py provides the IndexToMarkdown class, which provides a way to transform an existing [collection index file](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md#collection-indexes) -into a markdown file for easy of use and reference. The IndexToMarkdown class is very simple, and provides a -single method, `index_to_markdown`, which in turn only requires a single parameter - a dictionary representation of the -desired index file to convert to markdown. An example of how to use the class, and method, can be found below. +### index_to_markdown.py + +`index_to_markdown.py` provides the `IndexToMarkdown` class, which provides a way to transform an existing +[collection index file](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md#collection-indexes) +into a markdown file for easy of use and reference. +The `IndexToMarkdown` class is very simple, and provides a single method, `index_to_markdown`, +which in turn only requires a single parameter - a dictionary representation of the desired index file to convert to markdown. +An example of how to use the class, and method, can be found below. #### Example Usage + ```python import json from mitreattack.collections import IndexToMarkdown @@ -28,12 +36,16 @@ with open('collection_index.json', 'r') as input_file: print(generated_md) ``` -## collection_to_index.py -collection_to_index.py provides the CollectionToIndex class, which proves a means by which to summarize existing [collections](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md#collections) +### collection_to_index.py + +`collection_to_index.py` provides the `CollectionToIndex` class, which proves a means by which to summarize existing +[collections](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md#collections) into a single [collection index](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md#collection-indexes) file. -The CollectionToIndex class contains the generate_index function, which when provided with a name, description, root url (pointing to where the raw collections are stored), +The `CollectionToIndex` class contains the `generate_index` function, which when provided with a name, description, root url (pointing to where the raw collections are stored), and a list of either files, folders, or already loaded bundles in the form of dictionaries, will create a summarizing index. + #### Example Usage + ```python import json from mitreattack.collections import CollectionToIndex @@ -54,10 +66,17 @@ print(output_indexA) print(output_indexB) print(output_indexC) ``` -## stix_to_collection.py -stix_to_collection.py provides the STIXToCollection class, which proves a means by which to convert existing stix bundles into ones containing a [collection](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md#collections) object. -The STIXToCollection class contains the stix_to_collection function, which when provided with a starter bundle, a name, a version, and an optional description, will output a modified bundle that contains a summary collection object. + +### stix_to_collection.py + +`stix_to_collection.py` provides the `STIXToCollection` class, which proves a means by which to convert +existing stix bundles into ones containing a +[collection](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/blob/master/docs/collections.md#collections) object. +The `STIXToCollection` class contains the `stix_to_collection` function, which when provided with a starter bundle, +a name, a version, and an optional description, will output a modified bundle that contains a summary collection object. + #### Example Usage + ```python import json from mitreattack.collections import STIXToCollection @@ -72,4 +91,4 @@ output_bundleB = STIXToCollection.stix_to_collection(bundle=data, name='collecti print(output_bundleA) print(output_bundleB) -``` \ No newline at end of file +``` diff --git a/mitreattack/diffStix/README.md b/mitreattack/diffStix/README.md new file mode 100644 index 00000000..5e38baaf --- /dev/null +++ b/mitreattack/diffStix/README.md @@ -0,0 +1,21 @@ +# Diff Stix + +This folder contains a module for creating markdown, HTML, JSON and/or ATT&CK Navigator layers +reporting on the changes between two versions of the STIX2 bundles representing the ATT&CK content. +Run `diff_stix -h` for full usage instructions. + +## Usage + +### Command Line + +Print full usage instructions: + +```shell +python3 changelog_helper.py -h +``` + +Example execution: + +```shell +python3 changelog_helper.py -v --use-mitre-cti -new path/to/new/stix/ --minor-changes --show-key --create-html --contributors -markdown output/changelog.md -json-output output/changelog.json -layers output/layer-enterprise.json output/layer-mobile.json +``` diff --git a/mitreattack/navlayers/README.md b/mitreattack/navlayers/README.md index 22528bbf..e95058e1 100644 --- a/mitreattack/navlayers/README.md +++ b/mitreattack/navlayers/README.md @@ -1,8 +1,17 @@ # navlayers -This folder contains modules and scripts for working with ATT&CK Navigator layers. ATT&CK Navigator Layers are a set of annotations overlaid on top of the ATT&CK Matrix. For more about ATT&CK Navigator layers, visit the ATT&CK Navigator repository. The core module allows users to load, validate, manipulate, and save ATT&CK layers. A brief overview of the components can be found below. All scripts adhere to the MITRE ATT&CK Navigator Layer file format, [version 4.3](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv4_3.md), but will accept legacy [version 3.0](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv3.md) and version 4.X layers, upgrading them to version 4.3. +This folder contains modules and scripts for working with ATT&CK Navigator layers. +ATT&CK Navigator Layers are a set of annotations overlaid on top of the ATT&CK Matrix. +For more about ATT&CK Navigator layers, visit the ATT&CK Navigator repository. +The core module allows users to load, validate, manipulate, and save ATT&CK layers. +A brief overview of the components can be found below. +All scripts adhere to the MITRE ATT&CK Navigator Layer file format, +[version 4.3](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv4_3.md), +but will accept legacy [version 3.0](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv3.md) +and version 4.X layers, upgrading them to version 4.3. + +## Core Modules -#### Core Modules | script | description | |:-------|:------------| | [filter](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/navlayers/core/filter.py) | Implements a basic [filter object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv4_1.md#filter-object-properties). | @@ -13,50 +22,64 @@ This folder contains modules and scripts for working with ATT&CK Navigator layer | [metadata](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/navlayers/core/metadata.py) | Implements a basic [metadata object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv4_1.md#metadata-object-properties). | | [technique](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/navlayers/core/technique.py) | Implements a basic [technique object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv4_1.md#technique-object-properties). | | [versions](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/navlayers/core/versions.py) | Implements a basic [versions object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv4_1.md#versions-object-properties).| -#### Manipulator Scripts + +### Manipulator Scripts + | script | description | |:-------|:------------| | [layerops](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/navlayers/manipulators/layerops.py) | Provides a means by which to combine multiple ATT&CK layer objects in customized ways. A further breakdown can be found in the corresponding [section](#layerops.py) below. | -#### Exporter Scripts + +### Exporter Scripts + | script | description | |:-------|:------------| | [to_excel](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/navlayers/exporters/to_excel.py) | Provides a means by which to export an ATT&CK Layer to an excel file. A further breakdown can be found in the corresponding [section](#to_excel.py) below. | | [to_svg](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/navlayers/exporters/to_svg.py) | Provides a means by which to export an ATT&CK layer to an svg image file. A further breakdown can be found in the corresponding [section](#to_svg.py) below. This file also contains the `SVGConfig` object that can be used to configure the SVG export.| -##### Generator Scripts + +### Generator Scripts + | script | description | |:-------|:------------| | [overview_generator](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/navlayers/generators/overview_generator.py)| Provides a means by which to generate an ATT&CK Layer that summarizes, on a per technique basis, all instances of a given ATT&CK object type that reference/utilize each technique. A further explanation can be found in the corresponding [section](#overview_generator.py) below. | | [usage_generator](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/navlayers/generators/usage_generator.py)| Provides a means by which to generate an ATT&CK Layer that summarizes the techniques associated with a given ATT&CK object. A further explanation can be found in the corresponding [section](#usage_generator.py) below. | | [sum_generator](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/navlayers/generators/sum_generator.py)| Provides a means by which to generate a collection of ATT&CK Layers, one for each object in a given ATT&CK object class, that summarizes the coverage of that object. A further explanation can be found in the corresponding [section](#sum_generator.py) below. | -##### Utility Modules + +### Utility Modules + | script | description | |:-------|:------------| | [excel_templates](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/navlayers/exporters/excel_templates.py) | Provides a means by which to convert a matrix into a clean excel matrix template. | | [matrix_gen](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/navlayers/exporters/matrix_gen.py) | Provides a means by which to generate a matrix from raw data, either from the ATT&CK TAXII server, from a local STIX Bundle, or from an ATT&CK Workbench instance (via url). | | [svg_templates](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/navlayers/exporters/svg_templates.py) | Provides a means by which to convert a layer file into a marked up svg file. | | [svg_objects](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/navlayers/exporters/svg_objects.py) | Provides raw templates and supporting functionality for generating svg objects. | -##### Command Line Tools + +### Command Line Tools + | script | description | |:-------|:------------| | [layerExporter_cli.py](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/navlayers/layerExporter_cli.py) | A commandline utility to export Layer files to excel or svg formats using the exporter tools. Run with `-h` for usage. | | [layerGenerator_cli.py](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/navlayers/layerGenerator_cli.py) | A commandline utility to generate Layer files that correspond to various and collections of various stix objects. Run with `-h` for usage. | ## Layer -The Layer class provides format validation and read/write capabilities to aid in working with ATT&CK Navigator Layers in python. It is the primary interface through which other Layer-related classes defined in the core module should be used. The Layer class API and a usage example are below. The class currently supports version 3 and 4 of the ATT&CK Layer spec, and will upgrade version 3 layers into compatible version 4 ones whenever possible. + +The `Layer` class provides format validation and read/write capabilities to aid in working with ATT&CK Navigator Layers in python. +It is the primary interface through which other Layer-related classes defined in the core module should be used. +The Layer class API and a usage example are below. +The class currently supports version 3 and 4 of the ATT&CK Layer spec, and will upgrade version 3 layers into compatible version 4 ones whenever possible. | method [x = Layer()]| description | |:-------|:------------| -| x.from_str(_input_) | Loads an ATT&CK layer from a string representation of a json layer. | -| x.from_dict(_input_) | Loads an ATT&CK layer from a dictionary. | -| x.from_file(_filepath_) | Loads an ATT&CK layer from a file location specified by the _filepath_. | -| x.to_file(_filepath_) | Saves the current state of the loaded ATT&CK layer to a json file denoted by the _filepath_. | -| x.to_dict() | Returns a representation of the current ATT&CK layer object as a dictionary. | -| x.to_str() | Returns a representation of the current ATT&CK layer object as a string representation of a dictionary. | - -Examples on how to create a layer programmatically, as opposed to loading it from an existing medium, can be found +| `x.from_str(_input_)` | Loads an ATT&CK layer from a string representation of a json layer. | +| `x.from_dict(_input_)` | Loads an ATT&CK layer from a dictionary. | +| `x.from_file(_filepath_)` | Loads an ATT&CK layer from a file location specified by the _filepath_. | +| `x.to_file(_filepath_)` | Saves the current state of the loaded ATT&CK layer to a json file denoted by the _filepath_. | +| `x.to_dict()` | Returns a representation of the current ATT&CK layer object as a dictionary. | +| `x.to_str()` | Returns a representation of the current ATT&CK layer object as a string representation of a dictionary. | + +Examples on how to create a layer programmatically, as opposed to loading it from an existing medium, can be found [here](https://github.com/mitre-attack/mitreattack-python/blob/master/mitreattack/navlayers/core/README.md). -#### Example Usage +### Example Usage ```python example_layer3_dict = { @@ -91,24 +114,35 @@ layer3 = Layer() # Create a new layer object layer3.from_file(example_layer_location) # Load layer data from a file into existing layer object ``` -## layerops.py -Layerops.py provides the LayerOps class, which is a way to combine layer files in an automated way, using user defined lambda functions. Each LayerOps instance, when created, ingests the provided lambda functions, and stores them for use. An existing LayerOps class can be used to combine layer files according to the initialized lambda using the process method. The breakdown of this two step process is documented in the table below, while examples of both the list and dictionary modes of operation can be found below. +### layerops.py + +`Layerops.py` provides the `LayerOps` class, which is a way to combine layer files in an automated way, using user defined lambda functions. +Each LayerOps instance, when created, ingests the provided lambda functions, and stores them for use. +An existing `LayerOps` class can be used to combine layer files according to the initialized lambda using the process method. +The breakdown of this two step process is documented in the table below, while examples of both the list and dictionary modes of operation can be found below. + +#### LayerOps() -##### LayerOps() ```python - x = LayerOps(score=score, comment=comment, enabled=enabled, colors=colors, metadata=metadata, name=name, desc=desc, default_values=default_values) +x = LayerOps(score=score, comment=comment, enabled=enabled, colors=colors, metadata=metadata, name=name, desc=desc, default_values=default_values) ``` - Each of the _inputs_ takes a lambda function that will be used to combine technique object fields matching the parameter. The one exception to this is _default_values_, which is an optional dictionary argument containing default values to provide the lambda functions if techniques of the combined layers are missing them. +Each of the _inputs_ takes a lambda function that will be used to combine technique object fields matching the parameter. +The one exception to this is _default_values_, which is an optional dictionary argument containing default values +to provide the lambda functions if techniques of the combined layers are missing them. ##### .process() Method + ```python x.process(data, default_values=default_values) ``` -The process method applies the lambda functions stored during initialization to the layer objects in _data_. _data_ must be either a list or a dictionary of Layer objects, and is expected to match the format of the lambda equations provided during initialization. default_values is an optional dictionary argument that overrides the currently stored default - values with new ones for this specific processing operation. + +The process method applies the lambda functions stored during initialization to the layer objects in _data_. +_data_ must be either a list or a dictionary of Layer objects, and is expected to match the format of the lambda equations provided during initialization. +`default_values` is an optional dictionary argument that overrides the currently stored default values with new ones for this specific processing operation. #### Example Usage + ```python from mitreattack.navlayers.manipulators.layerops import LayerOps from mitreattack.navlayers.core.layer import Layer @@ -157,25 +191,36 @@ out_layer6.to_file("C:\demo_layer6.json") # Save combined co ``` ## to_excel.py -to_excel.py provides the ToExcel class, which is a way to export an existing layer file as an Excel -spreadsheet. The ToExcel class has an optional parameter for the initialization function, that -tells the exporter what data source to use when building the output matrix. Valid options include using live data from cti-taxii.mitre.org, using a local STIX bundle, or retrieving data from an ATT&CK Workbench instance. -##### ToExcel() +`to_excel.py` provides the `ToExcel` class, which is a way to export an existing layer file as an Excel spreadsheet. +The `ToExcel` class has an optional parameter for the initialization function, that tells the exporter what data source to use when building the output matrix. +Valid options include using live data from cti-taxii.mitre.org, using a local STIX bundle, or retrieving data from an ATT&CK Workbench instance. + +### ToExcel() + ```python x = ToExcel(domain='enterprise', source='taxii', resource=None) ``` -The ToExcel constructor takes domain, server, and resource arguments during instantiation. The domain can -be either `enterprise` or `mobile`, and can be pulled directly from a layer file as `layer.domain`. The source argument tells the matrix generation tool which data source to use when building the matrix. `taxii` indicates that the tool should utilize the official ATT&CK Taxii Server (`cti-taxii`) when building the matrix, while the `local` option indicates that it should use a local bundle, and the `remote` option indicates that it should utilize a remote ATT&CK Workbench instance. The `resource` argument is only required if the source is set to `local`, in which case it should be a path to a local stix bundle, or if the source is set to `remote`, in which case it should be the url of a ATT&CK workbench instance. -##### .to_xlsx() Method +The `ToExcel` constructor takes domain, server, and resource arguments during instantiation. +The domain can be either `enterprise` or `mobile`, and can be pulled directly from a layer file as `layer.domain`. +The source argument tells the matrix generation tool which data source to use when building the matrix. +`taxii` indicates that the tool should utilize the official ATT&CK Taxii Server (`cti-taxii`) when building the matrix, +while the `local` option indicates that it should use a local bundle, and the `remote` option indicates that +it should utilize a remote ATT&CK Workbench instance. +The `resource` argument is only required if the source is set to `local`, in which case it should be a path +to a local stix bundle, or if the source is set to `remote`, in which case it should be the url of a ATT&CK workbench instance. + +### .to_xlsx() Method + ```python x.to_xlsx(layerInit=layer, filepath="layer.xlsx") ``` -The to_xlsx method exports the layer file referenced as `layer`, as an excel file to the -`filepath` specified. + +The `to_xlsx` method exports the layer file referenced as `layer`, as an excel file to the `filepath` specified. #### Example Usage + ```python from mitreattack.navlayers import Layer from mitreattack.navlayers import ToExcel @@ -194,23 +239,43 @@ t3 = ToExcel(domain='ics', source='remote', resource=workbench_url) ``` ## to_svg.py -to_svg.py provides the ToSvg class, which is a way to export an existing layer file as an SVG image file. The ToSvg class, like the ToExcel class, has an optional parameter for the initialization function, that -tells the exporter what data source to use when building the output matrix. Valid options include using live data from cti-taxii.mitre.org, using a local STIX bundle, or utilizing a remote ATT&CK Workbench instance. -##### ToSvg() +`to_svg.py` provides the `ToSvg` class, which is a way to export an existing layer file as an SVG image file. +The `ToSvg` class, like the `ToExcel` class, has an optional parameter for the initialization function, +that tells the exporter what data source to use when building the output matrix. +Valid options include using live data from cti-taxii.mitre.org, using a local STIX bundle, or utilizing a remote ATT&CK Workbench instance. + +### ToSvg() + ```python x = ToSvg(domain='enterprise', source='taxii', resource=None, config=None) ``` -The ToSvg constructor, just like the ToExcel constructor, takes domain, server, and resource arguments during instantiation. The domain can be either `enterprise` or `mobile`, and can be pulled directly from a layer file as `layer.domain`. The source argument tells the matrix generation tool which data source to use when building the matrix. `taxii` indicates that the tool should utilize the `cti-taxii` server when building the matrix, while the `local` option indicates that it should use a local bundle, and the `remote` option indicates that it should utilize a remote ATT&CK Workbench instance. The `resource` argument is only required if the source is set to `local`, in which case it should be a path to a local stix bundle, or if the source is set to `remote`, in which case it should be the url of an ATT&CK Workbench instance. The `config` parameter is an optional SVGConfig object that can be used to configure the export as desired. If not provided, the configuration for the export will be set to default values. -##### SVGConfig() +The `ToSvg` constructor, just like the `ToExcel` constructor, takes domain, server, and resource arguments during instantiation. +The domain can be either `enterprise` or `mobile`, and can be pulled directly from a layer file as `layer.domain`. +The source argument tells the matrix generation tool which data source to use when building the matrix. +`taxii` indicates that the tool should utilize the `cti-taxii` server when building the matrix, +while the `local` option indicates that it should use a local bundle, and the `remote` option indicates that it should utilize a remote ATT&CK Workbench instance. +The `resource` argument is only required if the source is set to `local`, in which case it should be a path to a local stix bundle, +or if the source is set to `remote`, in which case it should be the url of an ATT&CK Workbench instance. +The `config` parameter is an optional `SVGConfig` object that can be used to configure the export as desired. +If not provided, the configuration for the export will be set to default values. + +### SVGConfig() + ```python y = SVGConfig(width=8.5, height=11, headerHeight=1, unit="in", showSubtechniques="expanded", font="sans-serif", tableBorderColor="#6B7279", showHeader=True, legendDocked=True, legendX=0, legendY=0, legendWidth=2, legendHeight=1, showLegend=True, showFilters=True, showAbout=True, showDomain=True, border=0.104) ``` -The SVGConfig object is used to configure how an SVG export behaves. The defaults for each of the available values can be found in the declaration above, and a brief explanation for each field is included in the table below. The config object should be provided to the ToSvg object during instantiation, but if values need to be updated on the fly, the currently loaded configuration can be interacted with at `ToSvg().config`. The configuration can also be populated from a json file using the `.load_from_file(filename="path/to/file.json")` method, or stored to one using the `.save_to_file(filename="path/to/file.json)` method. + +The `SVGConfig` object is used to configure how an SVG export behaves. +The defaults for each of the available values can be found in the declaration above, and a brief explanation for each field is included in the table below. +The config object should be provided to the `ToSvg` object during instantiation, but if values need to be updated on the fly, +the currently loaded configuration can be interacted with at `ToSvg().config`. +The configuration can also be populated from a json file using the `.load_from_file(filename="path/to/file.json")` method, +or stored to one using the `.save_to_file(filename="path/to/file.json)` method. | attribute| description | type | default value | |:-------|:------------|:------------|:------------| @@ -233,14 +298,16 @@ The SVGConfig object is used to configure how an SVG export behaves. The default | showAbout | Whether or not to show the About Header Block | bool | True | | border | What default border width to use | number | 0.104 | -##### .to_svg() Method +### .to_svg() Method + ```python x.to_svg(layerInit=layer, filepath="layer.svg") ``` -The to_svg method exports the layer file referenced as `layer`, as an excel file to the -`filepath` specified. + +The `to_svg` method exports the layer file referenced as `layer`, as an excel file to the `filepath` specified. #### Example Usage + ```python from mitreattack.navlayers import Layer from mitreattack.navlayers import ToSvg, SVGConfig @@ -262,43 +329,72 @@ workbench_url = "localhost:3000" t3 = ToSvg(domain='enterprise', source='remote', resource=workbench_url, config=conf) t3.to_svg(layerInit=lay, filepath="demo3.svg") ``` + ## overview_generator.py -overview_generator.py provides the OverviewLayerGenerator class, which is designed to allow users to -generate an ATT&CK layer that, on a per technique basis, has a score that corresponds to all instances -of the specified ATT&CK object type (group, mitigation, etc.), and a comment that lists all matching instance. -#### OverviewLayerGenerator() +`overview_generator.py` provides the `OverviewLayerGenerator` class, which is designed to allow users to +generate an ATT&CK layer that, on a per technique basis, has a score that corresponds to all instances +of the specified ATT&CK object type (group, mitigation, etc.), and a comment that lists all matching instance. + +### OverviewLayerGenerator() + ```python x = OverviewLayerGenerator(source='taxii', domain='enterprise', resource=None) ``` -The initialization function for OverviewLayerGenerator, like `ToSVG` and `ToExcel`, requires the specification of where -to retrieve data from (taxii server etc.). The domain can be either `enterprise`, `mobile`, or `ics`, and can be pulled directly from a layer file as `layer.domain`. The source argument tells the matrix generation tool which data source to use when building the matrix. `taxii` indicates that the tool should utilize the `cti-taxii` server when building the matrix, while the `local` option indicates that it should use a local bundle, and the `remote` option indicates that it should utilize a remote ATT&CK Workbench instance. The `resource` argument is only required if the source is set to `local`, in which case it should be a path to a local stix bundle, or if the source is set to `remote`, in which case it should be the url of an ATT&CK Workbench instance. If not provided, the configuration for the generator will be set to default values. -#### .generate_layer() +The initialization function for `OverviewLayerGenerator`, like `ToSVG` and `ToExcel`, requires the specification of where +to retrieve data from (taxii server etc.). +The domain can be either `enterprise`, `mobile`, or `ics`, and can be pulled directly from a layer file as `layer.domain`. +The source argument tells the matrix generation tool which data source to use when building the matrix. +`taxii` indicates that the tool should utilize the `cti-taxii` server when building the matrix, +while the `local` option indicates that it should use a local bundle, and the `remote` option indicates that it should utilize a remote ATT&CK Workbench instance. +The `resource` argument is only required if the source is set to `local`, in which case it should be a path to a local stix bundle, +or if the source is set to `remote`, in which case it should be the url of an ATT&CK Workbench instance. +If not provided, the configuration for the generator will be set to default values. + +### .generate_layer() + ```python x.generate_layer(obj_type=object_type_name) ``` -The `generate_layer` function generates a layer, customized to the input `object_type_name`. Valid values include `group`, `mitigation`, `software`, and `datasource`. + +The `generate_layer` function generates a layer, customized to the input `object_type_name`. +Valid values include `group`, `mitigation`, `software`, and `datasource`. ## usage_generator.py -usage_ generator.py provides the UsageLayerGenerator class, which is designed to allow users to -generate an ATT&CK layer that scores any relevant techniques that a given input ATT&CK object has. These objects can -be any `group`, `software`, `mitigation`, or `data component`, and can be referenced by ID or by any alias when -provided to the generator. -#### UsageLayerGenerator() +`usage_ generator.py` provides the `UsageLayerGenerator` class, which is designed to allow users to +generate an ATT&CK layer that scores any relevant techniques that a given input ATT&CK object has. +These objects can be any `group`, `software`, `mitigation`, or `data component`, +and can be referenced by ID or by any alias when provided to the generator. + +### UsageLayerGenerator() + ```python x = UsageLayerGenerator(source='taxii', domain='enterprise', resource=None) ``` -The initialization function for UsageLayerGenerator, like `ToSVG` and `ToExcel`, requires the specification of where -to retrieve data from (taxii server etc.). The domain can be either `enterprise`, `mobile`, or `ics`, and can be pulled directly from a layer file as `layer.domain`. The source argument tells the matrix generation tool which data source to use when building the matrix. `taxii` indicates that the tool should utilize the `cti-taxii` server when building the matrix, while the `local` option indicates that it should use a local bundle, and the `remote` option indicates that it should utilize a remote ATT&CK Workbench instance. The `resource` argument is only required if the source is set to `local`, in which case it should be a path to a local stix bundle, or if the source is set to `remote`, in which case it should be the url of an ATT&CK Workbench instance. If not provided, the configuration for the generator will be set to default values. -#### .generate_layer() +The initialization function for `UsageLayerGenerator`, like `ToSVG` and `ToExcel`, requires the specification of where +to retrieve data from (taxii server etc.). +The domain can be either `enterprise`, `mobile`, or `ics`, and can be pulled directly from a layer file as `layer.domain`. +The source argument tells the matrix generation tool which data source to use when building the matrix. +`taxii` indicates that the tool should utilize the `cti-taxii` server when building the matrix, +while the `local` option indicates that it should use a local bundle, and the `remote` option indicates that it should utilize a remote ATT&CK Workbench instance. +The `resource` argument is only required if the source is set to `local`, in which case it should be a path to a local stix bundle, +or if the source is set to `remote`, in which case it should be the url of an ATT&CK Workbench instance. +If not provided, the configuration for the generator will be set to default values. + +### .generate_layer() + ```python x.generate_layer(match=object_identifier) ``` -The `generate_layer` function generates a layer, customized to the input `object_identifier`. Valid values include `ATT&CK ID`, `name`, or any known `alias` for `group`, `mitigation`, `software`, and `data component` objects within the selected ATT&CK data. + +The `generate_layer` function generates a layer, customized to the input `object_identifier`. +Valid values include `ATT&CK ID`, `name`, or any known `alias` for `group`, `mitigation`, `software`, and `data component` objects within the selected ATT&CK data. + #### Example Usage + ```python from mitreattack.navlayers import UsageLayerGenerator @@ -309,20 +405,33 @@ layer2 = handle.generate_layer(match='Adups') ``` ## sum_generator.py -sum_generator.py provides the SumLayerGenerator class, which is designed to allow users to -generate a collection of ATT&CK layers that, on a per technique basis, have a score that corresponds to all instances -of the specified ATT&CK object type (group, mitigation, etc.), and a comment that lists all matching instance. Each one -of the generated layers will correspond to a single instance of the specified ATT&CK object type. -#### SumLayerGenerator() +`sum_generator.py` provides the `SumLayerGenerator` class, which is designed to allow users to +generate a collection of ATT&CK layers that, on a per technique basis, have a score that corresponds to all instances +of the specified ATT&CK object type (group, mitigation, etc.), and a comment that lists all matching instance. +Each one of the generated layers will correspond to a single instance of the specified ATT&CK object type. + +### SumLayerGenerator() + ```python x = SumLayerGenerator(source='taxii', domain='enterprise', resource=None) ``` -The initialization function for SumGeneratorLayer, like `ToSVG` and `ToExcel`, requires the specification of where -to retrieve data from (taxii server etc.). The domain can be either `enterprise`, `mobile`, or `ics`, and can be pulled directly from a layer file as `layer.domain`. The source argument tells the matrix generation tool which data source to use when building the matrix. `taxii` indicates that the tool should utilize the `cti-taxii` server when building the matrix, while the `local` option indicates that it should use a local bundle, and the `remote` option indicates that it should utilize a remote ATT&CK Workbench instance. The `resource` argument is only required if the source is set to `local`, in which case it should be a path to a local stix bundle, or if the source is set to `remote`, in which case it should be the url of an ATT&CK Workbench instance. If not provided, the configuration for the generator will be set to default values. -#### .generate_layer() +The initialization function for `SumGeneratorLayer`, like `ToSVG` and `ToExcel`, requires the specification of where +to retrieve data from (taxii server etc.). +The domain can be either `enterprise`, `mobile`, or `ics`, and can be pulled directly from a layer file as `layer.domain`. +The source argument tells the matrix generation tool which data source to use when building the matrix. +`taxii` indicates that the tool should utilize the `cti-taxii` server when building the matrix, +while the `local` option indicates that it should use a local bundle, and the `remote` option indicates that it should utilize a remote ATT&CK Workbench instance. +The `resource` argument is only required if the source is set to `local`, in which case it should be a path to a local stix bundle, +or if the source is set to `remote`, in which case it should be the url of an ATT&CK Workbench instance. +If not provided, the configuration for the generator will be set to default values. + +### .generate_layer() + ```python x.generate_layer(layers_type=object_type_name) ``` -The `generate_layer` function generates a collection of layers, each customized to one instance of the input `object_type_name`. Valid types include `group`, `mitigation`, `software`, and `datasource`. + +The `generate_layer` function generates a collection of layers, each customized to one instance of the input `object_type_name`. +Valid types include `group`, `mitigation`, `software`, and `datasource`. From ecea46f6ab5f98f30e2be8fb77a73680fc0756f4 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Tue, 12 Apr 2022 22:23:16 -0500 Subject: [PATCH 3/3] Bump version to 1.5.0 --- CHANGELOG.md | 59 +++++++++++++++++++++++++++++++++++++++++-------- docs/RELEASE.md | 1 + setup.py | 2 +- 3 files changed, 52 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b41dcd3..10221f95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,19 @@ +# v1.5.0 - 4/12/2022 + +## Improvements + +- Add diffStix module to be able to generate changelogs between different STIX bundles + # v1.4.6 - 3/25/2022 + ## Improvements -- Improved efficency of Excel generation capability +- Improved efficency of Excel generation capability # v1.4.5 3/9/2022 + ## Fixes + - Patched core layer code to properly store 8-hex colors - Patched core layer code to properly handle non-ascii characters when ingesting text - Patched core layer code to properly initialize layers during instantiation @@ -13,36 +22,50 @@ - Library now supports unicode characters in layers (UTF-16) # v1.4.4 - 2/22/2022 + ## Fixes + - Patched core layer code to support minor changes in the 4.3 layer format # v1.4.3 - 2/16/2022 + ## Improvements + - Added documentation regarding Release process - Added documentation regarding Contributing - Added standard test framework + ## Fixes + - Fixed tactic parsing in AttackToExcel so tactics are capitalized correctly in the output (Command and Control instead of Command And Control) - Corrected minor mistakes in the README documentation of some cli scripts # v1.4.2 - 1/11/2022 + ## Improvements + - Added support for multiple CAPEC IDs for a single technique in AttackToExcel - Tweaked AttackToExcel permissions sorting - Added parsing for all technique permissions in AttackToExcel - Added support for [ATT&CK Layer format 4.3](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv4_3.md) # v1.4.1 - 12/17/2021 + ## Improvements + - Added support for ATT&CK Workbench as a datasource - Added parsing for CAPEC IDs in AttackToExcel - Added support for data sources and data components when generating layers - Added parsing for relationship references/citations in AttackToExcel # v1.4.0 - 10/21/2021 + ## Fixes -- Updated stix2 and taxii2-client module version requirements to avoid potential bug + +- Updated stix2 and taxii2-client module version requirements to avoid potential bug + ## Improvements + - Created Collections module - Added method and cli to turn a collection index into a markdown file for human readability - Added method and cli to turn a collection into a collection index for summary purposes @@ -51,49 +74,67 @@ - Added Data Sources and Data Components support to attackToExcel # v1.3.1 - 9/22/2021 + Minor release that downgrades the required version of taxii2-client to 2.2.2 due to a bug in 2.3.0. # v1.3.0 - 8/20/2021 -This release introduces generator functionality to the library, as well as some improvements to excel matrix generation + +This release introduces generator functionality to the library, as well as some improvements to excel matrix generation through attackToExcel. + ## Fixes + - Addresses potential import issues for some operating systems + ## Improvements + - Updated attackToExcel to include platform information when generating excel matrices - Added layer generation capabilities to the library -- Added a cli integration for the layer generation capabilities +- Added a cli integration for the layer generation capabilities # v1.2.2 - 7/27/2021 + This bug fix patches a few outstanding issues with the library + ## Fixes + - Added missing fields to attackToExcel technique output: - - Enterprise: _Impact Type_, _System Requirements_, _Effective Permissions_ - - Mobile: _Tactic Type_ + - Enterprise: _Impact Type_, _System Requirements_, _Effective Permissions_ + - Mobile: _Tactic Type_ - Fixed typing mismatch in layerobj that caused issues with manipulator scripting - Fixed potential loading issue with enumeration that could cause issues with manipulator scripting - Improved error message handling during layer initialization # v1.2.1 - 16 June 2021 + This bug fix patches the ability to use the library with local data sources + ## Fixes + - Addressed issue with matrixGen initialization failing for local data sources # v1.2.0 - 2 June 2021 + This update adds some convenience features to make it easier to create layers programmatically, as well as documentation on how to do so. + ## Improvements -- Made it possible to directly initialize Layer objects in core + +- Made it possible to directly initialize Layer objects in core - Created README documenting how to create layers programmatically through various approaches # v1.1.0 - 29 April 2021 -With the release of the ATT&CK Navigator Layer version 4.2, this library now supports the new -aggregateScore functionality and associated format changes. + +With the release of the ATT&CK Navigator Layer version 4.2, this library now supports the new +aggregateScore functionality and associated format changes. ## Improvements + - Added Layer format v4.2 compatibility. - Added aggregateScore functionality to both the svg and excel exporting modules. - Updated exporting modules and their initialization arguments to utilize copies of provided input layers. - Added filtering functionality based on Platforms when generating a Matrix during export. ## Fixes + - Addressed issue with attackToExcel imports failing in some environments. diff --git a/docs/RELEASE.md b/docs/RELEASE.md index 6c74d031..a988af2e 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -1,4 +1,5 @@ # Release Process + In order to release a new version of mitreattack-python, follow the process outlined here: 1. Verify that all changes desired in the next release are present in the `develop` branch (unless this is an urgent bug fix, in which case more nuanced approaches may be necessary). diff --git a/setup.py b/setup.py index f3b4dd26..e556803c 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="mitreattack-python", - version="1.4.6", + version="1.5.0", author="MITRE ATT&CK, MITRE Corporation", author_email="attack@mitre.org", description="MITRE ATT&CK python library",