def get_map(base=CTI_BASE): from stix2 import FileSystemSource, CompositeDataSource, Filter print("Loading CTI attack-pattern map...") enterprise_attack_fs = FileSystemSource( os.path.join(base, "enterprise-attack")) mobile_attack_fs = FileSystemSource(os.path.join(base, "mobile-attack")) composite_ds = CompositeDataSource() composite_ds.add_data_sources([enterprise_attack_fs, mobile_attack_fs]) filt = Filter('type', '=', 'attack-pattern') attack_map = {} for item in composite_ds.query(filt): name = item['name'] if item['revoked']: print( f"[WARN] Ignored {name.upper()}: This attack-pattern has been revoked." ) continue categories = [x['phase_name'] for x in item['kill_chain_phases']] desc = item['description'] platforms = item['x_mitre_platforms'] attack_id = None for er in item['external_references']: if er['source_name'] in [ "mitre-attack", "mobile-mitre-attack", "mitre-mobile-attack" ]: attack_id = er['external_id'] if attack_id: attack_map[attack_id] = { "name": name, "categories": categories, "description": desc, "platforms": platforms, "attack_id": attack_id } print(f"\tAdding {name.upper()} as ID: {attack_id}") else: print(f"[ERR] Ignored {name.upper()}: No attack ID found.") return attack_map
class MitreParser: """This class wraps all the function for interacting with the MitreAtt&ck Repository""" def __init__(self, cti_folder): self.cti_folder = cti_folder repo = Repo(cti_folder) origin = repo.remotes.origin try: origin.pull() except Exception as e: pass enterprise_attack_fs = FileSystemSource(cti_folder + "enterprise-attack") pre_attack_fs = FileSystemSource(cti_folder + "pre-attack") mobile_attack_fs = FileSystemSource(cti_folder + "mobile-attack") self.src = CompositeDataSource() self.src.add_data_sources( [enterprise_attack_fs, pre_attack_fs, mobile_attack_fs]) # self.columns_list = {"techniques":['mitre_id', 'name', 'description', 'permissions_required', 'platforms', 'adversary-opsec', 'build-capabilities', 'collection', 'command-and-control', 'compromise', 'credential-access', 'defense-evasion', 'discovery', 'effects', 'establish-&-maintain-infrastructure', 'execution', 'exfiltration', 'impact', 'initial-access', 'lateral-movement', 'launch', 'network-effects', 'organizational-information-gathering', 'organizational-weakness-identification', 'people-information-gathering', 'people-weakness-identification', 'persistence', 'persona-development', 'priority-definition-direction', 'priority-definition-planning', 'privilege-escalation', 'remote-service-effects', 'stage-capabilities', 'target-selection', 'technical-information-gathering', 'technical-weakness-identification', 'test-capabilities','kill_chain_phases']} self.columns_list = { "techniques": [ 'mitre_id', 'tactics', 'name', 'permissions_required', 'platforms' ] } def get_technique_by_group(self, stix_id): relations = self.src.relationships(stix_id, 'uses', source_only=True) return self.src.query([ Filter('type', '=', 'attack-pattern'), Filter('id', 'in', [r.target_ref for r in relations]) ]) def get_malware_by_group(self, stix_id): relations = self.src.relationships(stix_id, 'uses', source_only=True) query_malware = self.src.query([ Filter('type', '=', 'malware'), Filter('id', 'in', [r.target_ref for r in relations]) ]) dict_malware = [] for group in query_malware: string_report = group.serialize() dict_report = json.loads(string_report) dict_malware.append(dict_report) df_malware = pd.DataFrame(dict_malware) return df_malware def get_tool_by_group(self, stix_id): relations = self.src.relationships(stix_id, 'uses', source_only=True) query_tool = self.src.query([ Filter('type', '=', 'tool'), Filter('id', 'in', [r.target_ref for r in relations]) ]) dict_tool = [] for group in query_tool: string_report = group.serialize() dict_report = json.loads(string_report) dict_tool.append(dict_report) df_tool = pd.DataFrame(dict_tool) return df_tool def get_techniques_by_group(self, stix_id): group_uses = [ r for r in self.src.relationships(stix_id, 'uses', source_only=True) if get_type_from_id(r.target_ref) in ['malware', 'tool'] ] software_uses = self.src.query([ Filter('type', '=', 'relationship'), Filter('relationship_type', '=', 'uses'), Filter('source_ref', 'in', [r.source_ref for r in group_uses]) ]) techniques_query = self.src.query([ Filter('type', '=', 'attack-pattern'), Filter('id', 'in', [r.target_ref for r in software_uses]) ]) dict_techniques = [] for current_technique in techniques_query: string_report = current_technique.serialize() dict_report = json.loads(string_report) dict_techniques.append(dict_report) techniques_df = pd.DataFrame(dict_techniques) if techniques_df.empty: return techniques_df techniques_df = techniques_df.fillna("") techniques_df["kill_chain_phases"] = techniques_df[ "kill_chain_phases"].apply(lambda x: [ y["phase_name"] for y in x if 'mitre' in y['kill_chain_name'] ]) techniques_df = techniques_df.rename( index=str, columns={ "x_mitre_permissions_required": "permissions_required", "x_mitre_platforms": "platforms", "id": "mitre_id", "kill_chain_phases": "tactics" }) column_set = set(techniques_df.columns.values) for i in self.columns_list["techniques"]: column_set.add(i) techniques_df = techniques_df.reindex(columns=list(column_set), fill_value="") return techniques_df[self.columns_list["techniques"]] def get_group_by_alias(self, alias): return self.src.query([ Filter('type', '=', 'intrusion-set'), Filter('aliases', '=', alias) ]) def get_all_groups(self): filt = Filter('type', '=', 'intrusion-set') groups = self.src.query([filt]) dict_groups = [] for group in groups: string_report = group.serialize() dict_report = json.loads(string_report) hash_report = hashlib.sha1( string_report.encode('utf-8')).hexdigest() dict_report["hash"] = hash_report dict_groups.append(dict_report) group_df = pd.DataFrame(dict_groups) group_df = group_df.fillna("") return group_df def get_all_techniques(self): technique_query = self.src.query(Filter('type', '=', 'attack-pattern')) dict_techniques = [] for current_technique in technique_query: string_report = current_technique.serialize() dict_report = json.loads(string_report) dict_techniques.append(dict_report) techniques_df = pd.DataFrame(dict_techniques) techniques_df = techniques_df.fillna("") techniques_df["kill_chain_phases"] = techniques_df[ "kill_chain_phases"].apply(lambda x: [ y["phase_name"] for y in x if 'mitre' in y['kill_chain_name'] ]) techniques_df = techniques_df.rename( index=str, columns={ "x_mitre_permissions_required": "permissions_required", "x_mitre_platforms": "platforms", "id": "mitre_id" }) return techniques_df[self.columns_list["techniques"]] def get_all_tactics(self): tactic_query = self.src.query(Filter('type', '=', 'x-mitre-tactic')) tactics_df = pd.DataFrame(tactic_query) return tactics_df
class attack_client(object): TC_ENTERPRISE_SOURCE = None TC_PRE_SOURCE = None TC_MOBILE_SOURCE = None COMPOSITE_DS = None def __init__(self, local_path=None): if local_path is not None and os.path.isdir(os.path.join(local_path, ENTERPRISE_ATTCK_LOCAL_DIR)) \ and os.path.isdir(os.path.join(local_path, PRE_ATTCK_LOCAL_DIR)) \ and os.path.isdir(os.path.join(local_path, MOBILE_ATTCK_LOCAL_DIR)): self.TC_ENTERPRISE_SOURCE = FileSystemSource( os.path.join(local_path, ENTERPRISE_ATTCK_LOCAL_DIR)) self.TC_PRE_SOURCE = FileSystemSource( os.path.join(local_path, PRE_ATTCK_LOCAL_DIR)) self.TC_MOBILE_SOURCE = FileSystemSource( os.path.join(local_path, MOBILE_ATTCK_LOCAL_DIR)) else: ENTERPRISE_COLLECTION = Collection(ATTCK_STIX_COLLECTIONS + ENTERPRISE_ATTCK + "/") PRE_COLLECTION = Collection(ATTCK_STIX_COLLECTIONS + PRE_ATTCK + "/") MOBILE_COLLECTION = Collection(ATTCK_STIX_COLLECTIONS + MOBILE_ATTCK + "/") self.TC_ENTERPRISE_SOURCE = TAXIICollectionSource( ENTERPRISE_COLLECTION) self.TC_PRE_SOURCE = TAXIICollectionSource(PRE_COLLECTION) self.TC_MOBILE_SOURCE = TAXIICollectionSource(MOBILE_COLLECTION) self.COMPOSITE_DS = CompositeDataSource() self.COMPOSITE_DS.add_data_sources([ self.TC_ENTERPRISE_SOURCE, self.TC_PRE_SOURCE, self.TC_MOBILE_SOURCE ]) def translate_stix_objects(self, stix_objects): technique_stix_mapping = { "type": "type", "id": "id", "created_by_ref": "created_by_ref", "created": "created", "modified": "modified", "object_marking_refs": "object_marking_refs", "name": "technique", "description": "technique_description", "kill_chain_phases": "tactic", "x_mitre_detection": "technique_detection", "x_mitre_platforms": "platform", "x_mitre_data_sources": "data_sources", "x_mitre_defense_bypassed": "defense_bypassed", "x_mitre_permissions_required": "permissions_required", "x_mitre_effective_permissions": "effective_permissions", "x_mitre_system_requirements": "system_requirements", "x_mitre_network_requirements": "network_requirements", "x_mitre_remote_support": "remote_support", "x_mitre_contributors": "contributors", "x_mitre_detectable_by_common_defenses": "detectable_by_common_defenses", "x_mitre_detectable_by_common_defenses_explanation": "detectable_explanation", "x_mitre_difficulty_for_adversary": "difficulty_for_adversary", "x_mitre_difficulty_for_adversary_explanation": "difficulty_explanation", "x_mitre_tactic_type": "tactic_type", "x_mitre_impact_type": "impact_type", "external_references": "external_references" } mitigation_stix_mapping = { "type": "type", "id": "id", "created_by_ref": "created_by_ref", "created": "created", "modified": "modified", "name": "mitigation", "description": "mitigation_description", "external_references": "external_references", "x_mitre_old_attack_id": "old_mitigation_id" } group_stix_mapping = { "type": "type", "id": "id", "created_by_ref": "created_by_ref", "created": "created", "modified": "modified", "name": "group", "description": "group_description", "aliases": "group_aliases", "external_references": "external_references", "x_mitre_contributors": "contributors" } software_stix_mapping = { "type": "type", "id": "id", "created_by_ref": "created_by_ref", "created": "created", "modified": "modified", "name": "software", "description": "software_description", "labels": "software_labels", "x_mitre_aliases": "software_aliases", "x_mitre_platforms": "software_platform", "external_references": "external_references", "x_mitre_contributors": "contributors", "x_mitre_old_attack_id": "old_software_id" } relationship_stix_mapping = { "type": "type", "id": "id", "created_by_ref": "created_by_ref", "created": "created", "modified": "modified", "relationship_type": "relationship", "description": "relationship_description", "source_ref": "source_object", "target_ref": "target_object" } tactic_stix_mapping = { "type": "type", "id": "id", "created_by_ref": "created_by_ref", "created": "created", "modified": "modified", "object_marking_refs": "object_marking_refs", "name": "tactic", "description": "tactic_description", "x_mitre_shortname": "tactic_shortname", "external_references": "external_references" } matrix_stix_mapping = { "type": "type", "id": "id", "created_by_ref": "created_by_ref", "created": "created", "modified": "modified", "object_marking_refs": "object_marking_refs", "name": "matrix", "description": "matrix_description", "tactic_refs": "tactic_references", "external_references": "external_references" } identity_stix_mapping = { "type": "type", "id": "id", "created_by_ref": "created_by_ref", "created": "created", "definition_type": "marking_definition_type", "definition": "marking_definition" } marking_stix_mapping = { "type": "type", "id": "id", "created": "created", "modified": "modified", "object_marking_refs": "object_marking_refs", "name": "identity", "identity_class": "identity_class" } # ******** Helper Functions ******** def handle_list(list_object, object_type): if object_type == "external_references": obj_dict['url'] = list_object[0]['url'] obj_dict['matrix'] = list_object[0]['source_name'] if obj_dict['type'] == 'attack-pattern': for ref in list_object: if ref['source_name'] == 'capec': obj_dict['capec_id'] = ref['external_id'] obj_dict['capec_url'] = ref['url'] obj_dict['technique_id'] = list_object[0]['external_id'] elif obj_dict['type'] == 'course-of-action': obj_dict['mitigation_id'] = list_object[0]['external_id'] elif obj_dict['type'] == 'group': obj_dict['group_id'] = list_object[0]['external_id'] elif obj_dict['type'] == 'software': obj_dict['software_id'] = list_object[0]['external_id'] elif obj_dict['type'] == 'tactic': obj_dict['tactic_id'] = list_object[0]['external_id'] elif obj_dict['type'] == 'matrix': obj_dict['matrix_id'] = list_object[0]['external_id'] elif object_type == "kill_chain_phases": tactic_list = list() for phase in list_object: tactic_list.append(phase['phase_name']) obj_dict['tactic'] = tactic_list stix_objects_list = list() for obj in stix_objects: if isinstance(obj, dict): obj_dict = obj else: obj_dict = json.loads( obj.serialize()) # From STIX to Python Dict dict_keys = list(obj_dict.keys()) for key in dict_keys: if obj['type'] == "attack-pattern": stix_mapping = technique_stix_mapping elif obj['type'] == "course-of-action": stix_mapping = mitigation_stix_mapping elif obj['type'] == "intrusion-set": stix_mapping = group_stix_mapping elif obj['type'] == "malware" or obj['type'] == "tool": stix_mapping = software_stix_mapping elif obj['type'] == "relationship": stix_mapping = relationship_stix_mapping elif obj['type'] == "x-mitre-tactic": stix_mapping = tactic_stix_mapping elif obj['type'] == "x-mitre-matrix": stix_mapping = matrix_stix_mapping elif obj['type'] == "identity": stix_mapping = identity_stix_mapping elif obj['type'] == "marking-definition": stix_mapping = marking_stix_mapping else: exit if key in stix_mapping.keys(): if key == "external_references" or key == "kill_chain_phases": handle_list(obj_dict[key], key) else: new_key = stix_mapping[key] obj_dict[new_key] = obj_dict.pop(key) stix_objects_list.append(obj_dict) return stix_objects_list def remove_revoked(self, stix_objects, extract=False): handle_revoked = list() for obj in stix_objects: if 'revoked' in obj.keys() and obj['revoked'] == True: if extract: handle_revoked.append(obj) else: continue handle_revoked.append(obj) return handle_revoked # ******** Enterprise ATT&CK Technology Domain ******* def get_enterprise(self, stix_format=True): enterprise_filter_objects = { "techniques": Filter("type", "=", "attack-pattern"), "mitigations": Filter("type", "=", "course-of-action"), "groups": Filter("type", "=", "intrusion-set"), "malware": Filter("type", "=", "malware"), "tools": Filter("type", "=", "tool"), "relationships": Filter("type", "=", "relationship"), "tactics": Filter("type", "=", "x-mitre-tactic"), "matrix": Filter("type", "=", "x-mitre-matrix"), "identity": Filter("type", "=", "identity"), "marking-definition": Filter("type", "=", "marking-definition") } enterprise_stix_objects = {} for key in enterprise_filter_objects: enterprise_stix_objects[key] = (self.TC_ENTERPRISE_SOURCE.query( enterprise_filter_objects[key])) if not stix_format: enterprise_stix_objects[key] = self.translate_stix_objects( enterprise_stix_objects[key]) return enterprise_stix_objects def get_enterprise_techniques(self, stix_format=True): enterprise_techniques = self.TC_ENTERPRISE_SOURCE.query( Filter("type", "=", "attack-pattern")) if not stix_format: enterprise_techniques = self.translate_stix_objects( enterprise_techniques) return enterprise_techniques def get_enterprise_mitigations(self, stix_format=True): enterprise_mitigations = self.TC_ENTERPRISE_SOURCE.query( Filter("type", "=", "course-of-action")) if not stix_format: enterprise_mitigations = self.translate_stix_objects( enterprise_mitigations) return enterprise_mitigations def get_enterprise_groups(self, stix_format=True): enterprise_groups = self.TC_ENTERPRISE_SOURCE.query( Filter("type", "=", "intrusion-set")) if not stix_format: enterprise_groups = self.translate_stix_objects(enterprise_groups) return enterprise_groups def get_enterprise_malware(self, stix_format=True): enterprise_malware = self.TC_ENTERPRISE_SOURCE.query( Filter("type", "=", "malware")) if not stix_format: enterprise_malware = self.translate_stix_objects( enterprise_malware) return enterprise_malware def get_enterprise_tools(self, stix_format=True): enterprise_tools = self.TC_ENTERPRISE_SOURCE.query( Filter("type", "=", "tool")) if not stix_format: enterprise_tools = self.translate_stix_objects(enterprise_tools) return enterprise_tools def get_enterprise_relationships(self, stix_format=True): enterprise_relationships = self.TC_ENTERPRISE_SOURCE.query( Filter("type", "=", "relationship")) if not stix_format: enterprise_relationships = self.translate_stix_objects( enterprise_relationships) return enterprise_relationships def get_enterprise_tactics(self, stix_format=True): enterprise_tactics = self.TC_ENTERPRISE_SOURCE.query( Filter("type", "=", "x-mitre-tactic")) if not stix_format: enterprise_tactics = self.translate_stix_objects( enterprise_tactics) return enterprise_tactics # ******** Pre ATT&CK Domain ******* def get_pre(self, stix_format=True): pre_filter_objects = { "techniques": Filter("type", "=", "attack-pattern"), "groups": Filter("type", "=", "intrusion-set"), "relationships": Filter("type", "=", "relationship"), "tactics": Filter("type", "=", "x-mitre-tactic"), "matrix": Filter("type", "=", "x-mitre-matrix"), "identity": Filter("type", "=", "identity"), "marking-definition": Filter("type", "=", "marking-definition") } pre_stix_objects = {} for key in pre_filter_objects: pre_stix_objects[key] = self.TC_PRE_SOURCE.query( pre_filter_objects[key]) if not stix_format: pre_stix_objects[key] = self.translate_stix_objects( pre_stix_objects[key]) return pre_stix_objects def get_pre_techniques(self, stix_format=True): pre_techniques = self.TC_PRE_SOURCE.query( Filter("type", "=", "attack-pattern")) if not stix_format: pre_techniques = self.translate_stix_objects(pre_techniques) return pre_techniques def get_pre_groups(self, stix_format=True): pre_groups = self.TC_PRE_SOURCE.query( Filter("type", "=", "intrusion-set")) if not stix_format: pre_groups = self.translate_stix_objects(pre_groups) return pre_groups def get_pre_relationships(self, stix_format=True): pre_relationships = self.TC_PRE_SOURCE.query( Filter("type", "=", "relationship")) if not stix_format: pre_relationships = self.translate_stix_objects(pre_relationships) return pre_relationships def get_pre_tactics(self, stix_format=True): pre_tactics = self.TC_PRE_SOURCE.query( Filter("type", "=", "x-mitre-tactic")) if not stix_format: pre_tactics = self.translate_stix_objects(pre_tactics) return pre_tactics # ******** Mobile ATT&CK Technology Domain ******* def get_mobile(self, stix_format=True): mobile_filter_objects = { "techniques": Filter("type", "=", "attack-pattern"), "mitigations": Filter("type", "=", "course-of-action"), "groups": Filter("type", "=", "intrusion-set"), "malware": Filter("type", "=", "malware"), "tools": Filter("type", "=", "tool"), "relationships": Filter("type", "=", "relationship"), "tactics": Filter("type", "=", "x-mitre-tactic"), "matrix": Filter("type", "=", "x-mitre-matrix"), "identity": Filter("type", "=", "identity"), "marking-definition": Filter("type", "=", "marking-definition") } mobile_stix_objects = {} for key in mobile_filter_objects: mobile_stix_objects[key] = self.TC_MOBILE_SOURCE.query( mobile_filter_objects[key]) if not stix_format: mobile_stix_objects[key] = self.translate_stix_objects( mobile_stix_objects[key]) return mobile_stix_objects def get_mobile_techniques(self, stix_format=True): mobile_techniques = self.TC_MOBILE_SOURCE.query( Filter("type", "=", "attack-pattern")) if not stix_format: mobile_techniques = self.translate_stix_objects(mobile_techniques) return mobile_techniques def get_mobile_mitigations(self, stix_format=True): mobile_mitigations = self.TC_MOBILE_SOURCE.query( Filter("type", "=", "course-of-action")) if not stix_format: mobile_mitigations = self.translate_stix_objects( mobile_mitigations) return mobile_mitigations def get_mobile_groups(self, stix_format=True): mobile_groups = self.TC_MOBILE_SOURCE.query( Filter("type", "=", "intrusion-set")) if not stix_format: mobile_groups = self.translate_stix_objects(mobile_groups) return mobile_groups def get_mobile_malware(self, stix_format=True): mobile_malware = self.TC_MOBILE_SOURCE.query( Filter("type", "=", "malware")) if not stix_format: mobile_malware = self.translate_stix_objects(mobile_malware) return mobile_malware def get_mobile_tools(self, stix_format=True): mobile_tools = self.TC_MOBILE_SOURCE.query(Filter("type", "=", "tool")) if not stix_format: mobile_tools = self.translate_stix_objects(mobile_tools) return mobile_tools def get_mobile_relationships(self, stix_format=True): mobile_relationships = self.TC_MOBILE_SOURCE.query( Filter("type", "=", "relationship")) if not stix_format: mobile_relationships = self.translate_stix_objects( mobile_relationships) return mobile_relationships def get_mobile_tactics(self, stix_format=True): mobile_tactics = self.TC_MOBILE_SOURCE.query( Filter("type", "=", "x-mitre-tactic")) if not stix_format: mobile_tactics = self.translate_stix_objects(mobile_tactics) return mobile_tactics # ******** Get All Functions ******** def get_stix_objects(self, stix_format=True): enterprise_objects = self.get_enterprise() pre_objects = self.get_pre() mobile_objects = self.get_mobile() for keypre in pre_objects.keys(): for preobj in pre_objects[keypre]: if keypre in enterprise_objects.keys(): if preobj not in enterprise_objects[keypre]: enterprise_objects[keypre].append(preobj) for keymob in mobile_objects.keys(): for mobobj in mobile_objects[keymob]: if keymob in enterprise_objects.keys(): if mobobj not in enterprise_objects[keymob]: enterprise_objects[keymob].append(mobobj) if not stix_format: for enterkey in enterprise_objects.keys(): enterprise_objects[enterkey] = self.translate_stix_objects( enterprise_objects[enterkey]) return enterprise_objects def get_techniques(self, stix_format=True): all_techniques = self.COMPOSITE_DS.query( Filter("type", "=", "attack-pattern")) if not stix_format: all_techniques = self.translate_stix_objects(all_techniques) return all_techniques def get_groups(self, stix_format=True): all_groups = self.COMPOSITE_DS.query( Filter("type", "=", "intrusion-set")) if not stix_format: all_groups = self.translate_stix_objects(all_groups) return all_groups def get_mitigations(self, stix_format=True): enterprise_mitigations = self.get_enterprise_mitigations() mobile_mitigations = self.get_mobile_mitigations() for mm in mobile_mitigations: if mm not in enterprise_mitigations: enterprise_mitigations.append(mm) if not stix_format: enterprise_mitigations = self.translate_stix_objects( enterprise_mitigations) return enterprise_mitigations def get_software(self, stix_format=True): enterprise_malware = self.get_enterprise_malware() enterprise_tools = self.get_enterprise_tools() mobile_malware = self.get_mobile_malware() mobile_tools = self.get_mobile_tools() for mt in mobile_tools: if mt not in enterprise_tools: enterprise_tools.append(mt) for mmal in mobile_malware: if mmal not in enterprise_malware: enterprise_malware.append(mmal) all_software = enterprise_tools + enterprise_malware if not stix_format: all_software = self.translate_stix_objects(all_software) return all_software def get_relationships(self, stix_format=True): all_relationships = self.COMPOSITE_DS.query( Filter("type", "=", "relationship")) if not stix_format: all_relationships = self.translate_stix_objects(all_relationships) return all_relationships def get_tactics(self, stix_format=True): all_tactics = self.COMPOSITE_DS.query( Filter("type", "=", "x-mitre-tactic")) if not stix_format: all_tactics = self.translate_stix_objects(all_tactics) return all_tactics # ******** Custom Functions ******** def get_technique_by_name(self, name, case=True, stix_format=True):
class AttckMapper: """Mapper for ATT&CK Matrix IDs via STIX. Only file system access supported Args: base_cti_path: Base directory of CTI data. """ def __init__( self, base_cti_path: Path = Path(__file__).parents[1] / "cti", log_path: Path = Path("~/riskmap.log").expanduser(), ) -> None: self.base_cti_path = base_cti_path self.log_path = log_path # Configure sources self.src = CompositeDataSource() self.src.add_data_source( FileSystemSource(base_cti_path / "enterprise-attack")) self.src.add_data_source( FileSystemSource(base_cti_path / "mobile-attack")) self.src.add_data_source(FileSystemSource(base_cti_path / "ics-attack")) self.src.add_data_source(FileSystemSource(base_cti_path / "capec")) logger.remove() logger.add(sink=self.log_path, format="{message}") def lookup_by_attack_id(self, attack_id: str): return self.src.query([ Filter("external_references.external_id", "=", attack_id), Filter("type", "in", ["attack-pattern", "course-of-action"]), ]) def mapping(self, *ignore, enterprise: List = [], mobile: List = [], ics: List = []): """Maps enterprise, mobile, and ics IDs to a function as a dictionary.""" if ignore: raise TypeError("Mapping arguments must be explicit") def decorator(func: Callable): @wraps(func) def add_attribute(*args, **kwargs): ids = { "enterprise": enterprise, "mobile": mobile, "ics": ics, } try: now = datetime.utcnow() details = func(*args, **kwargs) or {} logger.info( json.dumps( { "command": func.__name__, "startTime": now.strftime("%m/%d/%Y %H:%M:%S"), "endTime": datetime.utcnow().strftime( "%m/%d/%Y %H:%M:%S"), "args": args, "kwargs": kwargs, "details": details, "mapping": ids, }, cls=CustomEncoder, )) except Exception as e: print(e) return add_attribute return decorator def get_map_info(self, map_object_name: str, command: Callable) -> List: """Lazy method to retrieve mapping of command to get details""" module = ast.parse(inspect.getsource(command)) definition = [] for node in ast.walk(module): if isinstance(node, ast.FunctionDef): for d in node.decorator_list: if d.func.value.id == map_object_name: for kw in d.keywords: param, values = kw.arg, kw.value.elts for v in values: definition += self.lookup_by_attack_id( str(v.value)) break break return definition def describe(self, map_object_name: str, command: Callable) -> namedtuple: definition = self.get_map_info(map_object_name, command) references = [] detections = [] mitigations = [] for pattern in definition: if pattern.type == "attack-pattern": if hasattr(pattern, "x_mitre_detection"): detections.append( (pattern.name, pattern.x_mitre_detection)) elif pattern.type == "course-of-action": mitigations.append((pattern.name, pattern.description)) for ref in pattern.external_references: if hasattr(ref, "external_id"): if ref.external_id not in [r[0] for r in references]: references.append([ref.external_id, ref.url]) summarytable = PrettyTable(title="Summary", header=False) reftable = PrettyTable(title="References", header=False) detecttable = PrettyTable(title="Detections", header=False) mititable = PrettyTable(title="Mitigations", header=False) summarytable.add_row(["Name", command.__name__]) summarytable.add_row(["Description", command.__doc__]) reftable.add_rows(references) detecttable.add_rows([[d[0], "\n".join(wrap(d[1]))] for d in detections]) mititable.add_rows([[m[0], "\n".join(wrap(m[1]))] for m in mitigations]) summarytable.align = "l" reftable.align = "l" detecttable.align = "l" mititable.align = "l" return NamedTuple( "Description", [ ("summary_table", PrettyTable), ("references_table", PrettyTable), ("detections_table", PrettyTable), ("mitigations_table", PrettyTable), ], )(summarytable, reftable, detecttable, mititable)