def test_memory_store_save_load_file(mem_store): filename = 'memory_test/mem_store.json' mem_store.save_to_file(filename) contents = open(os.path.abspath(filename)).read() assert '"id": "indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f",' in contents assert '"id": "indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f",' in contents mem_store2 = MemoryStore() mem_store2.load_from_file(filename) assert mem_store2.get("indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f") assert mem_store2.get("indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f") shutil.rmtree(os.path.dirname(filename))
def test_memory_store_save_load_file_no_name_provided(fs_mem_store_no_name): filename = fs_mem_store_no_name # the fixture fs_mem_store yields filename where the memory store was written to # STIX2 contents of mem_store have already been written to file # (this is done in fixture 'fs_mem_store'), so can already read-in here contents = open(os.path.abspath(filename)).read() assert '"id": "indicator--00000000-0000-4000-8000-000000000001",' in contents assert '"id": "indicator--00000000-0000-4000-8000-000000000001",' in contents mem_store2 = MemoryStore() mem_store2.load_from_file(filename) assert mem_store2.get("indicator--00000000-0000-4000-8000-000000000001") assert mem_store2.get("indicator--00000000-0000-4000-8000-000000000001")
def test_memory_store_save_load_file(mem_store, fs_mem_store): filename = fs_mem_store # the fixture fs_mem_store yields filename where the memory store was written to # STIX2 contents of mem_store have already been written to file # (this is done in fixture 'fs_mem_store'), so can already read-in here contents = open(os.path.abspath(filename)).read() assert '"id": "indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f",' in contents assert '"id": "indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f",' in contents mem_store2 = MemoryStore() mem_store2.load_from_file(filename) assert mem_store2.get("indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f") assert mem_store2.get("indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f")
def generate(): """parse the STIX on MITRE/CTI and return a layer dict showing all techniques used by an APT group with phrase 'bear' in the group aliases.""" # import the STIX data from MITRE/CTI stix = requests.get( "https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json" ).json() ms = MemoryStore(stix_data=stix["objects"]) groups = ms.query([Filter("type", "=", "intrusion-set")]) # find bear groups bear_groups = [] #list of groups with bear in name for group in groups: # filter out deprecated and revoked groups if ("x_mitre_deprecated" in group and group["x_mitre_deprecated"]) or ("revoked" in group and group["revoked"]): continue # check all aliases for bear for alias in group["aliases"]: if re.match(".*bear.*", alias, re.IGNORECASE) is not None: bear_groups.append(group) break # don't match the same group multiple times # find techniques used by bear groups techniques_used = {} #attackID => using bear groups for bear in bear_groups: # construct the "bear" name for the comment # if bear occurs in multiple aliases, list them all bearnames = [] for alias in bear["aliases"]: if re.match(".*bear.*", alias, re.IGNORECASE) is not None: bearnames.append(alias) bearname = bearnames[0] if len(bearnames) > 1: bearname += " (AKA " + ",".join(bearnames[1:]) + ")" # get techniques used by this group relationships = ms.relationships(bear["id"]) for relationship in relationships: # skip all non-technique relationships if "attack-pattern" not in relationship["target_ref"]: continue technique = ms.get(relationship["target_ref"]) # filter out deprecated and revoked techniques if ("x_mitre_deprecated" in technique and technique["x_mitre_deprecated"]) or ( "revoked" in technique and technique["revoked"]): continue techniqueID = technique["external_references"][0]["external_id"] # store usage in techniques_used struct if techniqueID in techniques_used: techniques_used[techniqueID].append(bearname) else: techniques_used[techniqueID] = [bearname] # format the techniques for the output layer techniques_list = [] for techniqueID in techniques_used: techniques_list.append({ "techniqueID": techniqueID, "comment": "used by " + ", ".join(techniques_used[techniqueID]), "color": "#ff6666" }) # construct and return the layer as a dict return { "name": "*Bear APTs", "versions": { "layer": "4.1", "navigator": "4.1" }, "description": "All techniques used by an APT group with phrase 'bear' in the group aliases", "domain": "enterprise-attack", "techniques": techniques_list, "legendItems": [{ "label": "Used by a group the phrase 'bear' in the group aliases", "color": "#ff6666" }] }
def generate(softwaretype="software"): """ generate and return a layer dict showing techniques used by software If softwaretype is specified as "malware" or "tool", only shows software of that type. If softwaretype is specified as "software" output layer shows both malware and tools """ # import the STIX data from MITRE/CTI stix = requests.get( "https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json" ).json() ms = MemoryStore(stix_data=stix["objects"]) # software includes malware and tool types so perform two queries and merge the results software_filters = [] if softwaretype == "malware" or softwaretype == "software": software_filters.append([Filter('type', '=', 'malware')]) if softwaretype == "tool" or softwaretype == "software": software_filters.append([Filter('type', '=', 'tool')]) software = list(chain.from_iterable(ms.query(f) for f in software_filters)) # build a list of techniques used by software techniques_used = {} #attackID => using software names for thesoftware in software: # filter out revoked and deprecated software if ("x_mitre_deprecated" in thesoftware and thesoftware["x_mitre_deprecated"]) or ( "revoked" in thesoftware and thesoftware["revoked"]): continue for relationship in ms.relationships(thesoftware["id"]): # skip all non-technique relationships if "attack-pattern" not in relationship["target_ref"]: continue technique = ms.get(relationship["target_ref"]) # filter out deprecated and revoked techniques if ("x_mitre_deprecated" in technique and technique["x_mitre_deprecated"]) or ( "revoked" in technique and technique["revoked"]): continue techniqueID = technique["external_references"][0]["external_id"] # store usage in techniques_used struct if techniqueID in techniques_used: techniques_used[techniqueID].append(thesoftware["name"]) else: techniques_used[techniqueID] = [thesoftware["name"]] # format the techniques for the output layer techniques_list = [] highest_usage = 0 lowest_usage = 1 for techniqueID in techniques_used: # determine the number of used techniques for the score count = len(techniques_used[techniqueID]) highest_usage = max(highest_usage, count) lowest_usage = min(lowest_usage, count) # append technique struct to list of layer-formatted techniques techniques_list.append({ "techniqueID": techniqueID, "comment": "executed by " + ", ".join(techniques_used[techniqueID]), "score": count, }) # set up layer name and desc according to softwaretype if softwaretype != "software": plural = "tools" if softwaretype == "tool" else "malware" layername = f"Software ({softwaretype}) Execution" layerdescription = f"All techniques that can be executed by software of subtype {softwaretype}, where the score is the count of {plural} using the technique" else: layername = "Software Execution" layerdescription = f"All techniques that can be executed by software, where the score is the count of software using the technique" # construct and return the layer as a dict return { "name": layername, "description": layerdescription, "version": "3.0", "domain": "mitre-enterprise", "techniques": techniques_list, "sorting": 3, # order in descending order of score (count) "gradient": { "colors": [ "#fff7b3", # low counts are yellow "#ff6666", # high counts are red ], "minValue": lowest_usage, "maxValue": highest_usage }, }
def generate(show_nodetect=False): """ generate and return a layer dict showing techniques used by APT3 and APT29 as well as software used by those groups param show_nodetect, if true, causes techniques that have no data-sources to be highlighted as well """ stix = requests.get( "https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json" ).json() ms = MemoryStore(stix_data=stix["objects"]) apt3 = ms.get("intrusion-set--0bbdf25b-30ff-4894-a1cd-49260d0dd2d9") apt29 = ms.get("intrusion-set--899ce53f-13a0-479b-a0e4-67d46e241542") techniques_used = { } # attackID => {apt3: boolean, apt29: boolean, software: Set, detection: boolean} for apt in [apt3, apt29]: def use_technique(technique, software=None): """helper function to record a technique as used""" techniqueID = technique["external_references"][0]["external_id"] # init struct if the technique has not been seen before if not techniqueID in techniques_used: techniques_used[techniqueID] = { "APT3": False, "APT29": False, "software": set(), "datasources": [] } # record new data techniques_used[techniqueID][apt["name"]] = True if "x_mitre_data_sources" in technique and len( technique["x_mitre_data_sources"]) > 0: techniques_used[techniqueID]["datasources"] = technique[ "x_mitre_data_sources"] if software: techniques_used[techniqueID]["software"].add(software["name"]) # traverse relationships for relationship in ms.relationships(apt["id"]): target_obj = ms.get(relationship["target_ref"]) # skip relationships with deprecated objects if ("x_mitre_deprecated" in target_obj and target_obj["x_mitre_deprecated"]) or ( "revoked" in target_obj and target_obj["revoked"]): continue # technique type relationship if target_obj["type"] == "attack-pattern": # record technique usage use_technique(target_obj) # software type relationship, traverse to find software-used techniques if target_obj["type"] == "malware" or target_obj["type"] == "tool": software = target_obj for software_relationship in ms.relationships(software["id"]): software_target_obj = ms.get( software_relationship["target_ref"]) # skip relationships with deprecated objects if ("x_mitre_deprecated" in software_target_obj and software_target_obj["x_mitre_deprecated"]) or ( "revoked" in software_target_obj and software_target_obj["revoked"]): continue if software_target_obj["type"] == "attack-pattern": # record technique usage use_technique(software_target_obj, software) # format the techniques for the output layer techniques_list = [] def color_lookup(usage): if show_nodetect and not len(usage["datasources"]) > 0: return "#fc3b3b" if usage["APT3"] and usage["APT29"]: return "#74c476" if usage["APT3"]: return "#6baed6" if usage["APT29"]: return "#fce93b" for techniqueID in techniques_used: # determine the number of used techniques for the score comment = "" if show_nodetect: if len(techniques_used[techniqueID]["datasources"]) > 0: comment = f"considered detectable by a notional organization because it has data-sources {', '.join(techniques_used[techniqueID]['datasources'])}" else: comment = "considered undetectable by a notional organization because it has no data-sources" else: used = [] if techniques_used[techniqueID]["APT3"]: used.append("APT3") if techniques_used[techniqueID]["APT29"]: used.append("APT29") used += list(techniques_used[techniqueID]["software"]) comment = f"used by {', '.join(used)}" # append technique struct to list of layer-formatted techniques techniques_list.append({ "techniqueID": techniqueID, "color": color_lookup(techniques_used[techniqueID]), "comment": comment, }) # construct and return the layer as a dict # set up layer information according to show_nodetect name = "APT3 + APT29 with software" description = "This layer shows techniques (including techniques from software used by the groups) used by APT3 only in blue, APT29 only in yellow, and both APT3 and APT29 in green." legend = [{ "label": "Used by APT3 or a software APT3 uses", "color": color_lookup({ "APT3": True, "APT29": False, "datasources": ["placeholder"] }) }, { "label": "Used by APT29 or a software APT29 uses", "color": color_lookup({ "APT3": False, "APT29": True, "datasources": ["placeholder"] }) }, { "label": "Used by both APT3 or a softare APT3 uses and APT29 or a software APT29 uses", "color": color_lookup({ "APT3": True, "APT29": True, "datasources": ["placeholder"] }) }] # additional formatting when displaying notional detectability if show_nodetect: name += " and notional no detection" description += " The techniques in red denote techniques considered undetectable by a notional organization because they have no data-sources. Disclaimer: Data-sources in ATT&CK are sources of information that COULD be used to identify adversary actions, however the exactness of that evidence varies greatly. Therefore the presence of a data source for technique should only be considered a potential metric for detectability." legend.append({ "label": "Used by either APT3 or APT29 but considered undetectable by a notional organization because it has no data-sources", "color": color_lookup({ "APT3": True, "APT29": True, "datasources": [] }) }) # layer struct return { "name": name, "version": "3.0", "description": description, "domain": "mitre-enterprise", "techniques": techniques_list, "legendItems": legend }