From e09e63f6bdd5eb3f2b0436b47b23a4118493d696 Mon Sep 17 00:00:00 2001 From: Isabel Tuson Date: Fri, 9 Oct 2020 10:33:49 -0400 Subject: [PATCH 01/12] removed pre-attack from diff_stix --- CHANGELOG.md | 4 ++++ scripts/README.md | 2 +- scripts/diff_stix.py | 18 +++++------------- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45da8b06..5a5ce823 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# Changes staged on develop +## Improvements +- Removed pre-ATT&CK domain from scripts to support migration of that content to enterprise tactics. See issue [#36](https://github.com/mitre-attack/attack-scripts/issues/36). + # v1.6 - 5 October 2020 ## Improvements - Added [layer to SVG](https://github.com/mitre-attack/attack-scripts/tree/master/layers#to_svgpy) converter. See issue [#1](https://github.com/mitre-attack/attack-scripts/issues/1). diff --git a/scripts/README.md b/scripts/README.md index 49f5e2ac..d3f0dfc2 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -6,5 +6,5 @@ This folder contains one-off scripts for working with ATT&CK content. These scri |:-------|:------------| | [techniques_from_data_source.py](techniques_from_data_source.py) | Fetches the current ATT&CK STIX 2.0 objects from the ATT&CK TAXII server, prints all of the data sources listed in Enterprise ATT&CK, and then lists all the Enterprise techniques containing a given data source. Run `python3 techniques_from_data_source.py -h` for usage instructions. | | [techniques_data_sources_vis.py](techniques_data_sources_vis.py) | Generate the csv data used to create the "Techniques Mapped to Data Sources" visualization in the ATT&CK roadmap. Run `python3 techniques_data_sources_vis.py -h` for usage instructions. | -| [diff_stix.py](diff_stix.py) | Create markdown and/or ATT&CK Navigator layers reporting on the changes between two versions of the STIX2 bundles representing the ATT&CK content. For default operation, put [enterprise-attack.json](https://github.com/mitre/cti/blob/master/enterprise-attack/enterprise-attack.json), [mobile-attack.json](https://github.com/mitre/cti/blob/master/mobile-attack/mobile-attack.json), and [pre-attack.json](https://github.com/mitre/cti/blob/master/pre-attack/pre-attack.json) bundles in 'old' and 'new' folders for the script to compare. Run `python3 diff_stix.py -h` for full usage instructions. | +| [diff_stix.py](diff_stix.py) | Create markdown and/or ATT&CK Navigator layers reporting on the changes between two versions of the STIX2 bundles representing the ATT&CK content. For default operation, put [enterprise-attack.json](https://github.com/mitre/cti/blob/master/enterprise-attack/enterprise-attack.json) and [mobile-attack.json](https://github.com/mitre/cti/blob/master/mobile-attack/mobile-attack.json) bundles in 'old' and 'new' folders for the script to compare. Run `python3 diff_stix.py -h` for full usage instructions. | | [technique_mappings_to_csv.py](technique_mappings_to_csv.py) | Fetches the current ATT&CK content expressed as STIX2 and creates spreadsheet mapping Techniques with Mitigations, Groups or Software. Run `python3 technique_mappings_to_csv.py -h` for usage instructions. | diff --git a/scripts/diff_stix.py b/scripts/diff_stix.py index d1930267..28821e3e 100644 --- a/scripts/diff_stix.py +++ b/scripts/diff_stix.py @@ -12,18 +12,15 @@ # helper maps domainToDomainLabel = { 'enterprise-attack': 'Enterprise', - 'pre-attack': 'PRE-ATT&CK', 'mobile-attack': 'Mobile' } domainToLayerFileDomain = { 'enterprise-attack': 'mitre-enterprise', - 'mobile-attack': 'mitre-mobile', - 'pre-attack': 'pre-attack' + 'mobile-attack': 'mitre-mobile' } domainToTaxiiCollectionId = { "enterprise-attack": "95ecc380-afe9-11e4-9b6c-751b66dd541e", "mobile-attack": "2f669986-b40b-4423-b720-4396ca6a462b", - "pre-attack": "062767bd-02d2-4b72-84ba-56caef0f8658" } attackTypeToStixFilter = { # stix filters for querying for each type of data 'technique': [Filter('type', '=', 'attack-pattern')], @@ -69,7 +66,7 @@ class DiffStix(object): """ def __init__( self, - domains=['enterprise-attack', 'pre-attack', 'mobile-attack'], + domains=['enterprise-attack', 'mobile-attack'], layers=None, markdown=None, minor_changes=False, @@ -502,10 +499,6 @@ def get_layers_dict(self): "tacticRowBackground": "#205b8f", "selectTechniquesAcrossTactics": True } - # default to show pre-attack on pre layer - if domain == "pre-attack": layer_json["filters"] = { - "stages": ["prepare"] - } layers[domain] = layer_json @@ -538,7 +531,6 @@ def layers_dict_to_files(outfiles, layers): # write each layer to separate files json.dump(layers['enterprise-attack'], open(outfiles[0], "w"), indent=4) json.dump(layers['mobile-attack'], open(outfiles[1], "w"), indent=4) - json.dump(layers['pre-attack'], open(outfiles[2], "w"), indent=4) verboseprint("done") @@ -554,7 +546,7 @@ def layers_dict_to_files(outfiles, layers): ] 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, mobile-attack.json, and pre-attack.json bundles in 'old' and 'new' folders for the script to compare." + 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", @@ -588,10 +580,10 @@ def layers_dict_to_files(outfiles, layers): nargs="+", metavar="DOMAIN", choices=[ - "enterprise-attack", "pre-attack", "mobile-attack" + "enterprise-attack", "mobile-attack" ], default=[ - "enterprise-attack", "pre-attack", "mobile-attack" + "enterprise-attack", "mobile-attack" ], help="which domains to report on. Choices (and defaults) are %(choices)s" ) From 6d9671277e45325fc5e282d7b0b4d86e1a3e74fd Mon Sep 17 00:00:00 2001 From: Isabel Tuson Date: Fri, 9 Oct 2020 10:38:09 -0400 Subject: [PATCH 02/12] update diff_stix for python 3.8 support --- scripts/diff_stix.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/diff_stix.py b/scripts/diff_stix.py index 28821e3e..8036f1e9 100644 --- a/scripts/diff_stix.py +++ b/scripts/diff_stix.py @@ -671,11 +671,11 @@ def verboseprint(*args, **kwargs): markdown_string_to_file(args.markdown, md_string) if args.layers is not None: - if len(args.layers) is 0: + if len(args.layers) == 0: # no files specified, e.g. '-layers', use defaults diffStix.layers = layer_defaults args.layers = layer_defaults - elif len(args.layers) is 3: + elif len(args.layers) == 3: # files specified, e.g. '-layers file.json file2.json file3.json', use specified diffStix.layers = args.layers # assumes order of files is enterprise, mobile, pre attack (same order as defaults) else: From 8b2e01771ce0c6f71193b9a5162edf6a8c813951 Mon Sep 17 00:00:00 2001 From: clittle Date: Fri, 9 Oct 2020 10:44:40 -0400 Subject: [PATCH 03/12] Updated filters to support version 4 of the layer format. Documentation has not been updated yet. --- layers/core/filter.py | 32 ++++++++++++++++++-------------- layers/core/layerobj.py | 12 ++++++++---- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/layers/core/filter.py b/layers/core/filter.py index 5b36a8aa..fc53674b 100644 --- a/layers/core/filter.py +++ b/layers/core/filter.py @@ -6,7 +6,7 @@ UNSETVALUE -class Filter: +class Filterv4: def __init__(self, domain="mitre-enterprise"): """ Initialization - Creates a filter object, with an optional @@ -15,22 +15,9 @@ def __init__(self, domain="mitre-enterprise"): :param domain: The domain used for this layer (mitre-enterprise or mitre-mobile) """ - self.__stages = UNSETVALUE self.domain = domain self.__platforms = UNSETVALUE - @property - def stages(self): - if self.__stages != UNSETVALUE: - return self.__stages - - @stages.setter - def stages(self, stage): - typeCheckerArray(type(self).__name__, stage, str, "stage") - categoryChecker(type(self).__name__, stage[0], ["act", "prepare"], - "stages") - self.__stages = stage - @property def platforms(self): if self.__platforms != UNSETVALUE: @@ -63,3 +50,20 @@ def get_dict(self): = listing[entry] if len(temp) > 0: return temp + +class Filter(Filterv4): + def __init__(self, domain="mitre-enterprise"): + self.__stages = UNSETVALUE + super().__init__(domain) + + @property + def stages(self): + if self.__stages != UNSETVALUE: + return self.__stages + + @stages.setter + def stages(self, stage): + typeCheckerArray(type(self).__name__, stage, str, "stage") + categoryChecker(type(self).__name__, stage[0], ["act", "prepare"], + "stages") + self.__stages = stage \ No newline at end of file diff --git a/layers/core/layerobj.py b/layers/core/layerobj.py index dda4a6a5..374aae37 100644 --- a/layers/core/layerobj.py +++ b/layers/core/layerobj.py @@ -1,5 +1,5 @@ try: - from ..core.filter import Filter + from ..core.filter import Filter, Filterv4 from ..core.layout import Layout from ..core.technique import Technique from ..core.gradient import Gradient @@ -51,7 +51,7 @@ def version(self): @version.setter def version(self, version): typeChecker(type(self).__name__, version, str, "version") - categoryChecker(type(self).__name__, version, ["3.0"], "version") + categoryChecker(type(self).__name__, version, ["3.0", "4.0"], "version") self.__version = version @property @@ -92,9 +92,13 @@ def filters(self): @filters.setter def filters(self, filters): - try: + if self.version == "4.0": + temp = Filterv4(self.domain) + else: temp = Filter(self.domain) - temp.stages = filters['stages'] + try: + if self.version != "4.0": + temp.stages = filters['stages'] temp.platforms = filters['platforms'] self.__filters = temp except KeyError as e: From 49dbd32939a51dbdbb3a8f97f68143a214e04ff1 Mon Sep 17 00:00:00 2001 From: Isabel Tuson Date: Wed, 14 Oct 2020 10:51:27 -0400 Subject: [PATCH 04/12] update scripts to use layer format v4.0 --- CHANGELOG.md | 1 + scripts/diff_stix.py | 5 ++++- scripts/layers/samples/apt3_apt29_software.py | 5 ++++- scripts/layers/samples/bear_APT.py | 5 ++++- scripts/layers/samples/heatmap.py | 5 ++++- scripts/layers/samples/software_execution.py | 5 ++++- 6 files changed, 21 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a5ce823..9d9e93e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changes staged on develop ## Improvements - Removed pre-ATT&CK domain from scripts to support migration of that content to enterprise tactics. See issue [#36](https://github.com/mitre-attack/attack-scripts/issues/36). +- Updated scripts which produce layers to use v4.0 of the Navigator Layer Format. See issue [#47](https://github.com/mitre-attack/attack-scripts/issues/47). # v1.6 - 5 October 2020 ## Improvements diff --git a/scripts/diff_stix.py b/scripts/diff_stix.py index 8036f1e9..64ded2a9 100644 --- a/scripts/diff_stix.py +++ b/scripts/diff_stix.py @@ -487,7 +487,10 @@ def get_layers_dict(self): # build layer structure layer_json = { - "version": "3.0", + "versions": { + "layer": "4.0", + "navigator": "4.0" + }, "name": f"{thedate} {domainToDomainLabel[domain]} Updates", "description": f"{domainToDomainLabel[domain]} updates for the {thedate} release of ATT&CK", "domain": domainToLayerFileDomain[domain], diff --git a/scripts/layers/samples/apt3_apt29_software.py b/scripts/layers/samples/apt3_apt29_software.py index 3901b35e..75db45fb 100644 --- a/scripts/layers/samples/apt3_apt29_software.py +++ b/scripts/layers/samples/apt3_apt29_software.py @@ -117,7 +117,10 @@ def color_lookup(usage): # layer struct return { "name": name, - "version": "3.0", + "versions": { + "layer": "4.0", + "navigator": "4.0" + }, "description": description, "domain": "mitre-enterprise", "techniques": techniques_list, diff --git a/scripts/layers/samples/bear_APT.py b/scripts/layers/samples/bear_APT.py index e62d3bbb..342c5548 100644 --- a/scripts/layers/samples/bear_APT.py +++ b/scripts/layers/samples/bear_APT.py @@ -62,7 +62,10 @@ def generate(): # construct and return the layer as a dict return { "name": "*Bear APTs", - "version": "3.0", + "versions": { + "layer": "4.0", + "navigator": "4.0" + }, "description": "All techniques used by an APT group with phrase 'bear' in the group aliases", "domain": "mitre-enterprise", "techniques": techniques_list, diff --git a/scripts/layers/samples/heatmap.py b/scripts/layers/samples/heatmap.py index 75bd64d2..048e583e 100644 --- a/scripts/layers/samples/heatmap.py +++ b/scripts/layers/samples/heatmap.py @@ -26,7 +26,10 @@ def generate(): # return the techniques in a layer dict return { "name": "heatmap example", - "version": "3.0", + "versions": { + "layer": "4.0", + "navigator": "4.0" + }, "sorting": 3, # descending order of score "description": "An example layer where all techniques have a randomized score", "domain": "mitre-enterprise", diff --git a/scripts/layers/samples/software_execution.py b/scripts/layers/samples/software_execution.py index df49839f..e3c970d9 100644 --- a/scripts/layers/samples/software_execution.py +++ b/scripts/layers/samples/software_execution.py @@ -68,7 +68,10 @@ def generate(softwaretype="software"): return { "name": layername, "description": layerdescription, - "version": "3.0", + "versions": { + "layer": "4.0", + "navigator": "4.0" + }, "domain": "mitre-enterprise", "techniques": techniques_list, "sorting": 3, # order in descending order of score (count) From c18ab47b750d4c53024ec9883a49bff634f48440 Mon Sep 17 00:00:00 2001 From: clittle Date: Wed, 14 Oct 2020 11:58:21 -0400 Subject: [PATCH 05/12] Added support for versions and domain changes in v4 --- layers/README.md | 14 +++++++--- layers/core/filter.py | 5 ++-- layers/core/layer.py | 14 ++++++++-- layers/core/layerobj.py | 34 +++++++++++++++++++----- layers/core/versions.py | 59 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 112 insertions(+), 14 deletions(-) create mode 100644 layers/core/versions.py diff --git a/layers/README.md b/layers/README.md index ef5e92a5..8341682a 100644 --- a/layers/README.md +++ b/layers/README.md @@ -12,7 +12,7 @@ This folder contains modules and scripts for working with ATT&CK Navigator layer | [legenditem](core/legenditem.py) | Implements a basic [legenditem object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv3.md#legenditem-object-properties). | | [metadata](core/metadata.py) | Implements a basic [metadata object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv3.md#metadata-object-properties). | | [technique](core/technique.py) | Implements a basic [technique object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv3.md#technique-object-properties). | - +| [version](core/versions.py) | Impelments a basic [version object]().| #### Manipulator Scripts | script | description | |:-------|:------------| @@ -36,7 +36,7 @@ This folder contains modules and scripts for working with ATT&CK Navigator layer | [layerExporter_cli.py](layerExporter_cli.py) | A commandline utility to export Layer files to excel or svg formats using the exporter tools. 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 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 | |:-------|:------------| @@ -56,6 +56,14 @@ example_layer_dict = { "domain": "mitre-enterprise" } +example_layer4_dict = { + "name": "layer v4 example", + "versions" : { + "layer" : "4.0" + }, + "domain": "enterprise-attack" +} + example_layer_location = "/path/to/layer/file.json" example_layer_out_location = "/path/to/new/layer/file.json" @@ -65,7 +73,7 @@ layer1 = Layer(example_layer_dict) # Create a new layer and load ex layer1.to_file(example_layer_out_location) # Write out the loaded layer to the specified file layer2 = Layer() # Create a new layer object -layer2.from_dict(example_layer_dict) # Load layer data into existing layer object +layer2.from_dict(example_layer4_dict) # Load layer data into existing layer object print(layer2.to_dict()) # Retrieve the loaded layer's data as a dictionary, and print it layer3 = Layer() # Create a new layer object diff --git a/layers/core/filter.py b/layers/core/filter.py index fc53674b..d99973e7 100644 --- a/layers/core/filter.py +++ b/layers/core/filter.py @@ -46,8 +46,9 @@ def get_dict(self): if entry == 'domain': continue if listing[entry] != UNSETVALUE: - temp[entry.split(type(self).__name__ + '__')[-1]] \ - = listing[entry] + subname = entry.split('__')[-1] + if subname != 'stages': + temp[subname] = listing[entry] if len(temp) > 0: return temp diff --git a/layers/core/layer.py b/layers/core/layer.py index cb17a099..e7c226a9 100644 --- a/layers/core/layer.py +++ b/layers/core/layer.py @@ -75,7 +75,17 @@ def _build(self): Loads the data stored in self.data into a LayerObj (self.layer) """ try: - self.__layer = _LayerObj(self._data['version'], self._data['name'], + if 'version' in self._data: + ver_obj = dict(layer=self._data['version']) + else: + ver_obj = self._data['versions'] + except: + handler(type(self).__name__, 'Layer version is malformed. ' + 'Unable to load') + self.__layer = None + return + try: + self.__layer = _LayerObj(ver_obj, self._data['name'], self._data['domain']) except BadType or BadInput as e: handler(type(self).__name__, 'Layer is malformed: {}. ' @@ -89,7 +99,7 @@ def _build(self): return for key in self._data: - if key not in ['version', 'name', 'domain']: + if key not in ['version', 'versions', 'name', 'domain']: try: self.__layer._linker(key, self._data[key]) except Exception as e: diff --git a/layers/core/layerobj.py b/layers/core/layerobj.py index 374aae37..ae2f8824 100644 --- a/layers/core/layerobj.py +++ b/layers/core/layerobj.py @@ -5,6 +5,7 @@ from ..core.gradient import Gradient from ..core.legenditem import LegendItem from ..core.metadata import Metadata + from ..core.versions import Versions from ..core.exceptions import UNSETVALUE, typeChecker, BadInput, handler, \ categoryChecker, UnknownLayerProperty except ValueError: @@ -14,11 +15,12 @@ from core.gradient import Gradient from core.legenditem import LegendItem from core.metadata import Metadata + from core.versions import Versions from core.exceptions import UNSETVALUE, typeChecker, BadInput, handler, \ categoryChecker, UnknownLayerProperty class _LayerObj: - def __init__(self, version, name, domain): + def __init__(self, versions, name, domain): """ Initialization - Creates a layer object @@ -27,7 +29,7 @@ def __init__(self, version, name, domain): :param domain: The domain for this layer (mitre-enterprise or mitre-mobile) """ - self.version = version + self.versions = versions self.name = name self.__description = UNSETVALUE self.domain = domain @@ -46,13 +48,28 @@ def __init__(self, version, name, domain): @property def version(self): - return self.__version + return self.__versions.layer @version.setter def version(self, version): typeChecker(type(self).__name__, version, str, "version") categoryChecker(type(self).__name__, version, ["3.0", "4.0"], "version") - self.__version = version + self.__versions.layer = version + + @property + def versions(self): + return self.__versions + + @versions.setter + def versions(self, versions): + typeChecker(type(self).__name__, versions, dict, "version") + attack = None + nav = None + if 'attack' in versions: + attack = versions['attack'] + if 'navigator' in versions: + nav = versions['navigator'] + self.__versions = Versions(versions['layer'], attack, nav) @property def name(self): @@ -70,8 +87,11 @@ def domain(self): @domain.setter def domain(self, domain): typeChecker(type(self).__name__, domain, str, "domain") - categoryChecker(type(self).__name__, domain, ["mitre-enterprise", - "mitre-mobile"], + dom = domain + if dom.startswith('mitre'): + dom = dom.split('-')[-1] + '-attack' + categoryChecker(type(self).__name__, dom, ["enterprise-attack", + "mobile-attack"], "domain") self.__domain = domain @@ -301,7 +321,7 @@ def get_dict(self): Converts the currently loaded layer into a dict :returns: A dict representation of the current layer object """ - temp = dict(name=self.name, version=self.version, domain=self.domain) + temp = dict(name=self.name, versions=self.versions.get_dict(), domain=self.domain) if self.description: temp['description'] = self.description diff --git a/layers/core/versions.py b/layers/core/versions.py new file mode 100644 index 00000000..a9f32670 --- /dev/null +++ b/layers/core/versions.py @@ -0,0 +1,59 @@ +try: + from ..core.exceptions import typeChecker, categoryChecker, UNSETVALUE +except ValueError: + from core.exceptions import typeChecker, categoryChecker, UNSETVALUE + + +class Versions: + def __init__(self, layer="4.0", attack=UNSETVALUE, navigator=UNSETVALUE): + """ + Initialization - Creates a v4 Versions object + + :param layer: The layer version + :param attack: The attack version + :param navigator: The navigator version + """ + self.layer = layer + self.attack = attack + self.navigator = navigator + + @property + def attack(self): + return self.__attack + + @attack.setter + def attack(self, attack): + typeChecker(type(self).__name__, attack, str, "attack") + self.__attack = attack + + @property + def navigator(self): + return self.__navigator + + @navigator.setter + def navigator(self, navigator): + typeChecker(type(self).__name__, navigator, str, "navigator") + self.__navigator = navigator + + @property + def layer(self): + return self.__layer + + @layer.setter + def layer(self, layer): + typeChecker(type(self).__name__, layer, str, "layer") + categoryChecker(type(self).__name__, layer, ["3.0", "4.0"], "version") + self.__layer = layer + + def get_dict(self): + """ + Converts the currently loaded data into a dict + :returns: A dict representation of the local Versions object + """ + temp = dict() + listing = vars(self) + for entry in listing: + if listing[entry] != UNSETVALUE: + subname = entry.split('__')[-1] + temp[subname] = listing[entry] + return temp From cf8292cbecc0c6c793591c060537baac3f414313 Mon Sep 17 00:00:00 2001 From: clittle Date: Fri, 16 Oct 2020 11:18:55 -0400 Subject: [PATCH 06/12] Updated to full v4 spec, improved handling for load errors --- layers/core/exceptions.py | 20 +++++ layers/core/layer.py | 15 +--- layers/core/layerobj.py | 117 ++++++++++++++++-------------- layers/core/versions.py | 10 ++- layers/exporters/svg_templates.py | 9 +-- 5 files changed, 97 insertions(+), 74 deletions(-) diff --git a/layers/core/exceptions.py b/layers/core/exceptions.py index b3dfcc37..e68537d5 100644 --- a/layers/core/exceptions.py +++ b/layers/core/exceptions.py @@ -21,6 +21,9 @@ class UnknownTechniqueProperty(Exception): pass +class MissingParameters(Exception): + pass + def handler(caller, msg): """ Prints a debug/warning/error message @@ -85,3 +88,20 @@ def categoryChecker(caller, testee, valid, field): if testee not in valid: handler(caller, '{} not a valid value for {}'.format(testee, field)) raise BadInput + +def loadChecker(caller, testee, required, field): + """ + Verifies that the tested object contains all required fields + :param caller: the entity that called this function (used for error + messages) + :param testee: the element to test + :param requireds: a list of required values for the testee + :param field: what the element is to be used as (used for error + messages) + :raises BadInput: error denoting the testee element is not one of + the valid options + """ + for entry in required: + if entry not in testee: + handler(caller, '{} is not present in {} [{}]'.format(entry, field, testee)) + raise MissingParameters \ No newline at end of file diff --git a/layers/core/layer.py b/layers/core/layer.py index e7c226a9..e204e816 100644 --- a/layers/core/layer.py +++ b/layers/core/layer.py @@ -75,18 +75,7 @@ def _build(self): Loads the data stored in self.data into a LayerObj (self.layer) """ try: - if 'version' in self._data: - ver_obj = dict(layer=self._data['version']) - else: - ver_obj = self._data['versions'] - except: - handler(type(self).__name__, 'Layer version is malformed. ' - 'Unable to load') - self.__layer = None - return - try: - self.__layer = _LayerObj(ver_obj, self._data['name'], - self._data['domain']) + self.__layer = _LayerObj(self._data['name'], self._data['domain']) except BadType or BadInput as e: handler(type(self).__name__, 'Layer is malformed: {}. ' 'Unable to load.'.format(e)) @@ -99,7 +88,7 @@ def _build(self): return for key in self._data: - if key not in ['version', 'versions', 'name', 'domain']: + if key not in ['name', 'domain']: try: self.__layer._linker(key, self._data[key]) except Exception as e: diff --git a/layers/core/layerobj.py b/layers/core/layerobj.py index ae2f8824..9102cf24 100644 --- a/layers/core/layerobj.py +++ b/layers/core/layerobj.py @@ -7,7 +7,7 @@ from ..core.metadata import Metadata from ..core.versions import Versions from ..core.exceptions import UNSETVALUE, typeChecker, BadInput, handler, \ - categoryChecker, UnknownLayerProperty + categoryChecker, UnknownLayerProperty, loadChecker, MissingParameters except ValueError: from core.filter import Filter from core.layout import Layout @@ -17,19 +17,18 @@ from core.metadata import Metadata from core.versions import Versions from core.exceptions import UNSETVALUE, typeChecker, BadInput, handler, \ - categoryChecker, UnknownLayerProperty + categoryChecker, UnknownLayerProperty, loadChecker, MissingParameters class _LayerObj: - def __init__(self, versions, name, domain): + def __init__(self, name, domain): """ Initialization - Creates a layer object - :param version: The corresponding att&ck layer version :param name: The name for this layer - :param domain: The domain for this layer (mitre-enterprise - or mitre-mobile) + :param domain: The domain for this layer (enterprise-attack + or mobile-attack) """ - self.versions = versions + self.__versions = UNSETVALUE self.name = name self.__description = UNSETVALUE self.domain = domain @@ -48,28 +47,35 @@ def __init__(self, versions, name, domain): @property def version(self): - return self.__versions.layer + if self.__versions != UNSETVALUE: + return self.__versions.layer @version.setter def version(self, version): typeChecker(type(self).__name__, version, str, "version") categoryChecker(type(self).__name__, version, ["3.0", "4.0"], "version") + if self.__versions is UNSETVALUE: + self.__versions = Versions() self.__versions.layer = version @property def versions(self): - return self.__versions + if self.__versions != UNSETVALUE: + return self.__versions @versions.setter def versions(self, versions): typeChecker(type(self).__name__, versions, dict, "version") - attack = None - nav = None + attack = UNSETVALUE if 'attack' in versions: attack = versions['attack'] - if 'navigator' in versions: - nav = versions['navigator'] - self.__versions = Versions(versions['layer'], attack, nav) + try: + loadChecker(type(self).__name__, versions, ['layer', 'navigator'], "versions") + self.__versions = Versions(versions['layer'], attack, versions['navigator']) + except MissingParameters as e: + handler(type(self).__name__, 'versions {} is missing parameters: ' + '{}. Skipping.' + .format(versions, e)) @property def name(self): @@ -112,20 +118,16 @@ def filters(self): @filters.setter def filters(self, filters): - if self.version == "4.0": - temp = Filterv4(self.domain) - else: - temp = Filter(self.domain) + temp = Filterv4(self.domain) try: - if self.version != "4.0": - temp.stages = filters['stages'] + loadChecker(type(self).__name__, filters, ['platforms'], "filters") + # force upgrade to v4 temp.platforms = filters['platforms'] self.__filters = temp - except KeyError as e: - handler(type(self).__name__, "Unable to properly extract " - "information from filter: {}." - .format(e)) - raise BadInput + except MissingParameters as e: + handler(type(self).__name__, 'Filters {} is missing parameters: ' + '{}. Skipping.' + .format(filters, e)) @property def sorting(self): @@ -173,17 +175,17 @@ def techniques(self): def techniques(self, techniques): typeChecker(type(self).__name__, techniques, list, "techniques") self.__techniques = [] - entry = "" - try: - for entry in techniques: + + for entry in techniques: + try: + loadChecker(type(self).__name__, entry, ['techniqueID'], "technique") temp = Technique(entry['techniqueID']) temp._loader(entry) self.__techniques.append(temp) - except KeyError as e: - handler(type(self).__name__, "Unable to properly extract " - "information from technique {}: {}." - .format(entry, e)) - raise BadInput + except MissingParameters as e: + handler(type(self).__name__, 'Technique {} is missing parameters: ' + '{}. Skipping.' + .format(entry, e)) @property def gradient(self): @@ -193,12 +195,12 @@ def gradient(self): @gradient.setter def gradient(self, gradient): try: - self.__gradient = Gradient(gradient['colors'], - gradient['minValue'], - gradient['maxValue']) - except KeyError as e: - handler(type(self).__name__, 'Gradient is missing parameters: {}. ' - 'Unable to load.'.format(e)) + loadChecker(type(self).__name__, gradient, ['colors', 'minValue', 'maxValue'], "gradient") + self.__gradient = Gradient(gradient['colors'], gradient['minValue'], gradient['maxValue']) + except MissingParameters as e: + handler(type(self).__name__, 'Gradient {} is missing parameters: ' + '{}. Skipping.' + .format(gradient, e)) @property def legendItems(self): @@ -209,15 +211,15 @@ def legendItems(self): def legendItems(self, legendItems): typeChecker(type(self).__name__, legendItems, list, "legendItems") self.__legendItems = [] - entry = "" - try: - for entry in legendItems: + for entry in legendItems: + try: + loadChecker(type(self).__name__, entry, ['label', 'color'], "legendItem") temp = LegendItem(entry['label'], entry['color']) self.__legendItems.append(temp) - except KeyError as e: - handler(type(self).__name__, 'LegendItem {} is missing parameters:' - ' {}. Unable to load.' - .format(entry, e)) + except MissingParameters as e: + handler(type(self).__name__, 'Legend Item {} is missing parameters: ' + '{}. Skipping.' + .format(entry, e)) @property def showTacticRowBackground(self): @@ -272,13 +274,13 @@ def metadata(self): def metadata(self, metadata): typeChecker(type(self).__name__, metadata, list, "metadata") self.__metadata = [] - entry = "" - try: - for entry in metadata: + for entry in metadata: + try: + loadChecker(type(self).__name__, entry, ['name', 'value'], "metadata") self.__metadata.append(Metadata(entry['name'], entry['value'])) - except KeyError as e: - handler(type(self).__name__, 'Metadata {} is missing parameters: ' - '{}. Unable to load.' + except MissingParameters as e: + handler(type(self).__name__, 'Metadata {} is missing parameters: ' + '{}. Skipping.' .format(entry, e)) def _enumerate(self): @@ -321,10 +323,12 @@ def get_dict(self): Converts the currently loaded layer into a dict :returns: A dict representation of the current layer object """ - temp = dict(name=self.name, versions=self.versions.get_dict(), domain=self.domain) + temp = dict(name=self.name, domain=self.domain) if self.description: temp['description'] = self.description + if self.versions: + temp['versions'] = self.versions.get_dict() if self.filters: temp['filters'] = self.filters.get_dict() if self.sorting: @@ -364,6 +368,13 @@ def _linker(self, field, data): """ if field == 'description': self.description = data + elif field.startswith('version'): + if not field.endswith('s'): + # force upgrade + ver_obj = dict(layer="4.0", navigator="4.0") + self.versions = ver_obj + else: + self.versions = data elif field == 'filters': self.filters = data elif field == 'sorting': diff --git a/layers/core/versions.py b/layers/core/versions.py index a9f32670..75343b16 100644 --- a/layers/core/versions.py +++ b/layers/core/versions.py @@ -5,7 +5,7 @@ class Versions: - def __init__(self, layer="4.0", attack=UNSETVALUE, navigator=UNSETVALUE): + def __init__(self, layer="4.0", attack=UNSETVALUE, navigator="4.0"): """ Initialization - Creates a v4 Versions object @@ -14,7 +14,7 @@ def __init__(self, layer="4.0", attack=UNSETVALUE, navigator=UNSETVALUE): :param navigator: The navigator version """ self.layer = layer - self.attack = attack + self.__attack = attack self.navigator = navigator @property @@ -33,6 +33,7 @@ def navigator(self): @navigator.setter def navigator(self, navigator): typeChecker(type(self).__name__, navigator, str, "navigator") + categoryChecker(type(self).__name__, navigator, ["4.0"], "navigator version") self.__navigator = navigator @property @@ -42,7 +43,10 @@ def layer(self): @layer.setter def layer(self, layer): typeChecker(type(self).__name__, layer, str, "layer") - categoryChecker(type(self).__name__, layer, ["3.0", "4.0"], "version") + categoryChecker(type(self).__name__, layer, ["3.0", "4.0"], "layer version") + if layer == '3.0': + print('[NOTICE] - Forcibly upgrading version from {} to 4.0.'.format(layer)) + layer = "4.0" self.__layer = layer def get_dict(self): diff --git a/layers/exporters/svg_templates.py b/layers/exporters/svg_templates.py index 2ef69199..ee720ff7 100644 --- a/layers/exporters/svg_templates.py +++ b/layers/exporters/svg_templates.py @@ -8,13 +8,13 @@ from exporters.svg_objects import G, SVG_HeaderBlock, SVG_Technique, Text, convertToPx,_optimalFontSize, \ _getstringwidth from core.gradient import Gradient - from core.filter import Filter + from core.filter import Filterv4 except ModuleNotFoundError: from ..exporters.matrix_gen import MatrixGen from ..exporters.svg_objects import G, SVG_HeaderBlock, SVG_Technique, Text, convertToPx, _optimalFontSize, \ _getstringwidth from ..core.gradient import Gradient - from ..core.filter import Filter + from ..core.filter import Filterv4 class BadTemplateException(Exception): @@ -91,11 +91,10 @@ def _build_headers(self, name, config, desc=None, filters=None, gradient=None): if config.showFilters: fi = filters if fi is None: - fi = Filter() + fi = Filterv4() fi.platforms = ["Windows", "Linux", "macOS"] - fi.stages = ["act"] g2 = SVG_HeaderBlock().build(height=header_height, width=header_width, label='filters', - t1text=', '.join(fi.platforms), t2text=fi.stages[0], config=config) + t1text=', '.join(fi.platforms), config=config) b2 = G(tx=operation_x / header_count * psych + 1.5 * border * psych) header.append(b2) b2.append(g2) From 73eb254a7c1214a95547f37dc66732fa6dccc64c Mon Sep 17 00:00:00 2001 From: clittle Date: Fri, 16 Oct 2020 11:20:59 -0400 Subject: [PATCH 07/12] Updated Readme --- layers/README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/layers/README.md b/layers/README.md index 8341682a..f8cffa43 100644 --- a/layers/README.md +++ b/layers/README.md @@ -5,14 +5,14 @@ This folder contains modules and scripts for working with ATT&CK Navigator layer #### Core Modules | script | description | |:-------|:------------| -| [filter](core/filter.py) | Implements a basic [filter object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv3.md#filter-object-properties). | -| [gradient](core/gradient.py) | Implements a basic [gradient object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv3.md#gradient-object-properties). | +| [filter](core/filter.py) | Implements a basic [filter object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv4.md#filter-object-properties). | +| [gradient](core/gradient.py) | Implements a basic [gradient object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv4.md#gradient-object-properties). | | [layer](core/layer.py) | Provides an interface for interacting with core module's layer representation. A further breakdown can be found in the corresponding [section](#Layer) below. | -| [layout](core/layout.py) | Implements a basic [layout object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv3.md#layout-object-properties). | -| [legenditem](core/legenditem.py) | Implements a basic [legenditem object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv3.md#legenditem-object-properties). | -| [metadata](core/metadata.py) | Implements a basic [metadata object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv3.md#metadata-object-properties). | -| [technique](core/technique.py) | Implements a basic [technique object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv3.md#technique-object-properties). | -| [version](core/versions.py) | Impelments a basic [version object]().| +| [layout](core/layout.py) | Implements a basic [layout object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv4.md#layout-object-properties). | +| [legenditem](core/legenditem.py) | Implements a basic [legenditem object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv4.md#legenditem-object-properties). | +| [metadata](core/metadata.py) | Implements a basic [metadata object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv4.md#metadata-object-properties). | +| [technique](core/technique.py) | Implements a basic [technique object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv4.md#technique-object-properties). | +| [versions](core/versions.py) | Impelments a basic [versions object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv4.md#versions-object-properties).| #### Manipulator Scripts | script | description | |:-------|:------------| @@ -59,7 +59,8 @@ example_layer_dict = { example_layer4_dict = { "name": "layer v4 example", "versions" : { - "layer" : "4.0" + "layer" : "4.0", + "navigator": "4.0" }, "domain": "enterprise-attack" } From 088d955a12bccde61f7241edb69e8bcb47cc8ff4 Mon Sep 17 00:00:00 2001 From: Isabel Tuson Date: Mon, 19 Oct 2020 09:01:11 -0400 Subject: [PATCH 08/12] update domain format in layer scripts --- scripts/diff_stix.py | 6 +----- scripts/layers/samples/apt3_apt29_software.py | 2 +- scripts/layers/samples/bear_APT.py | 2 +- scripts/layers/samples/heatmap.py | 2 +- scripts/layers/samples/software_execution.py | 2 +- 5 files changed, 5 insertions(+), 9 deletions(-) diff --git a/scripts/diff_stix.py b/scripts/diff_stix.py index 64ded2a9..feb3e816 100644 --- a/scripts/diff_stix.py +++ b/scripts/diff_stix.py @@ -14,10 +14,6 @@ 'enterprise-attack': 'Enterprise', 'mobile-attack': 'Mobile' } -domainToLayerFileDomain = { - 'enterprise-attack': 'mitre-enterprise', - 'mobile-attack': 'mitre-mobile' -} domainToTaxiiCollectionId = { "enterprise-attack": "95ecc380-afe9-11e4-9b6c-751b66dd541e", "mobile-attack": "2f669986-b40b-4423-b720-4396ca6a462b", @@ -493,7 +489,7 @@ def get_layers_dict(self): }, "name": f"{thedate} {domainToDomainLabel[domain]} Updates", "description": f"{domainToDomainLabel[domain]} updates for the {thedate} release of ATT&CK", - "domain": domainToLayerFileDomain[domain], + "domain": domain, "techniques": techniques, "sorting": 0, "hideDisabled": False, diff --git a/scripts/layers/samples/apt3_apt29_software.py b/scripts/layers/samples/apt3_apt29_software.py index 75db45fb..9db90300 100644 --- a/scripts/layers/samples/apt3_apt29_software.py +++ b/scripts/layers/samples/apt3_apt29_software.py @@ -122,7 +122,7 @@ def color_lookup(usage): "navigator": "4.0" }, "description": description, - "domain": "mitre-enterprise", + "domain": "enterprise-attack", "techniques": techniques_list, "legendItems": legend } diff --git a/scripts/layers/samples/bear_APT.py b/scripts/layers/samples/bear_APT.py index 342c5548..91420845 100644 --- a/scripts/layers/samples/bear_APT.py +++ b/scripts/layers/samples/bear_APT.py @@ -67,7 +67,7 @@ def generate(): "navigator": "4.0" }, "description": "All techniques used by an APT group with phrase 'bear' in the group aliases", - "domain": "mitre-enterprise", + "domain": "enterprise-attack", "techniques": techniques_list, "legendItems": [{ "label": "Used by a group the phrase 'bear' in the group aliases", diff --git a/scripts/layers/samples/heatmap.py b/scripts/layers/samples/heatmap.py index 048e583e..73d4f40a 100644 --- a/scripts/layers/samples/heatmap.py +++ b/scripts/layers/samples/heatmap.py @@ -32,7 +32,7 @@ def generate(): }, "sorting": 3, # descending order of score "description": "An example layer where all techniques have a randomized score", - "domain": "mitre-enterprise", + "domain": "enterprise-attack", "techniques": techniques_list, } diff --git a/scripts/layers/samples/software_execution.py b/scripts/layers/samples/software_execution.py index e3c970d9..d01412eb 100644 --- a/scripts/layers/samples/software_execution.py +++ b/scripts/layers/samples/software_execution.py @@ -72,7 +72,7 @@ def generate(softwaretype="software"): "layer": "4.0", "navigator": "4.0" }, - "domain": "mitre-enterprise", + "domain": "enterprise-attack", "techniques": techniques_list, "sorting": 3, # order in descending order of score (count) "gradient": { From dfaa2d343f112bbc0570285272764c451aacaad0 Mon Sep 17 00:00:00 2001 From: clittle Date: Tue, 20 Oct 2020 11:47:41 -0400 Subject: [PATCH 09/12] Updated readme to explain v4 targeting --- layers/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/layers/README.md b/layers/README.md index f8cffa43..e9f74a93 100644 --- a/layers/README.md +++ b/layers/README.md @@ -1,6 +1,6 @@ # layers -This folder contains modules and scripts for working with ATT&CK Navigator layers. ATT&CK Navigator Layers are a set of annotations overlayed 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 3.0](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv3.md). +This folder contains modules and scripts for working with ATT&CK Navigator layers. ATT&CK Navigator Layers are a set of annotations overlayed 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.0](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv4.md), but will accept legacy [version 3.0](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv3.md) layers, upgrading them to version 4. #### Core Modules | script | description | @@ -50,7 +50,7 @@ The Layer class provides format validation and read/write capabilities to aid in #### Example Usage ```python -example_layer_dict = { +example_layer3_dict = { "name": "example layer", "version": "3.0", "domain": "mitre-enterprise" @@ -70,7 +70,7 @@ example_layer_out_location = "/path/to/new/layer/file.json" from layers.core import Layer -layer1 = Layer(example_layer_dict) # Create a new layer and load existing data +layer1 = Layer(example_layer3_dict) # Create a new layer and load existing data layer1.to_file(example_layer_out_location) # Write out the loaded layer to the specified file layer2 = Layer() # Create a new layer object From f8bbbfc964dfff04124b26a8ee67407630b9b13f Mon Sep 17 00:00:00 2001 From: clittle Date: Tue, 20 Oct 2020 11:57:08 -0400 Subject: [PATCH 10/12] Added warning flags for field upgrades --- layers/core/exceptions.py | 2 +- layers/core/layerobj.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/layers/core/exceptions.py b/layers/core/exceptions.py index e68537d5..bcfd2c34 100644 --- a/layers/core/exceptions.py +++ b/layers/core/exceptions.py @@ -104,4 +104,4 @@ def loadChecker(caller, testee, required, field): for entry in required: if entry not in testee: handler(caller, '{} is not present in {} [{}]'.format(entry, field, testee)) - raise MissingParameters \ No newline at end of file + raise MissingParameters diff --git a/layers/core/layerobj.py b/layers/core/layerobj.py index 9102cf24..f0a1b460 100644 --- a/layers/core/layerobj.py +++ b/layers/core/layerobj.py @@ -122,6 +122,8 @@ def filters(self, filters): try: loadChecker(type(self).__name__, filters, ['platforms'], "filters") # force upgrade to v4 + if 'stages' in filters: + print('[Filters] - V3 Field "stages" detected. Upgrading Filters object to V4.') temp.platforms = filters['platforms'] self.__filters = temp except MissingParameters as e: @@ -371,6 +373,7 @@ def _linker(self, field, data): elif field.startswith('version'): if not field.endswith('s'): # force upgrade + print('[Version] - V3 version field detected. Upgrading to V4 Versions object.') ver_obj = dict(layer="4.0", navigator="4.0") self.versions = ver_obj else: From b1834b4c0b405a9c63af1bc103a12887d483c767 Mon Sep 17 00:00:00 2001 From: clittle Date: Wed, 21 Oct 2020 09:30:13 -0400 Subject: [PATCH 11/12] Address minor style issues, old default value --- layers/core/filter.py | 6 +++--- layers/core/layerobj.py | 4 ++-- layers/exporters/svg_templates.py | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/layers/core/filter.py b/layers/core/filter.py index d99973e7..ad59e0a8 100644 --- a/layers/core/filter.py +++ b/layers/core/filter.py @@ -6,8 +6,8 @@ UNSETVALUE -class Filterv4: - def __init__(self, domain="mitre-enterprise"): +class Filter: + def __init__(self, domain="enterprise-attack"): """ Initialization - Creates a filter object, with an optional domain input @@ -52,7 +52,7 @@ def get_dict(self): if len(temp) > 0: return temp -class Filter(Filterv4): +class Filterv3(Filter): def __init__(self, domain="mitre-enterprise"): self.__stages = UNSETVALUE super().__init__(domain) diff --git a/layers/core/layerobj.py b/layers/core/layerobj.py index f0a1b460..d92b80f1 100644 --- a/layers/core/layerobj.py +++ b/layers/core/layerobj.py @@ -1,5 +1,5 @@ try: - from ..core.filter import Filter, Filterv4 + from ..core.filter import Filter from ..core.layout import Layout from ..core.technique import Technique from ..core.gradient import Gradient @@ -118,7 +118,7 @@ def filters(self): @filters.setter def filters(self, filters): - temp = Filterv4(self.domain) + temp = Filter(self.domain) try: loadChecker(type(self).__name__, filters, ['platforms'], "filters") # force upgrade to v4 diff --git a/layers/exporters/svg_templates.py b/layers/exporters/svg_templates.py index ee720ff7..43a657c8 100644 --- a/layers/exporters/svg_templates.py +++ b/layers/exporters/svg_templates.py @@ -8,13 +8,13 @@ from exporters.svg_objects import G, SVG_HeaderBlock, SVG_Technique, Text, convertToPx,_optimalFontSize, \ _getstringwidth from core.gradient import Gradient - from core.filter import Filterv4 + from core.filter import Filter except ModuleNotFoundError: from ..exporters.matrix_gen import MatrixGen from ..exporters.svg_objects import G, SVG_HeaderBlock, SVG_Technique, Text, convertToPx, _optimalFontSize, \ _getstringwidth from ..core.gradient import Gradient - from ..core.filter import Filterv4 + from ..core.filter import Filter class BadTemplateException(Exception): @@ -91,7 +91,7 @@ def _build_headers(self, name, config, desc=None, filters=None, gradient=None): if config.showFilters: fi = filters if fi is None: - fi = Filterv4() + fi = Filter() fi.platforms = ["Windows", "Linux", "macOS"] g2 = SVG_HeaderBlock().build(height=header_height, width=header_width, label='filters', t1text=', '.join(fi.platforms), config=config) From 80e82db50606182f9c81f36e86abee1558cf01b1 Mon Sep 17 00:00:00 2001 From: Isabel Tuson Date: Tue, 27 Oct 2020 11:09:33 -0400 Subject: [PATCH 12/12] add release date to changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d9e93e5..83bf1969 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# Changes staged on develop +# v1.7 - 27 October 2020 ## Improvements - Removed pre-ATT&CK domain from scripts to support migration of that content to enterprise tactics. See issue [#36](https://github.com/mitre-attack/attack-scripts/issues/36). - Updated scripts which produce layers to use v4.0 of the Navigator Layer Format. See issue [#47](https://github.com/mitre-attack/attack-scripts/issues/47).