def generate(): """parse the STIX on MITRE/CTI and return a layer dict with techniques with randomized scores""" # 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"]) # get all techniques in STIX techniques = ms.query([Filter("type", "=", "attack-pattern")]) # parse techniques into layer format techniques_list = [] for technique in techniques: # skip deprecated and revoked 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"] # get the attackID techniques_list.append({ "techniqueID": techniqueID, "score": random.randint(1, 100) # random score }) # return the techniques in a layer dict return { "name": "heatmap example", "version": "3.0", "sorting": 3, # descending order of score "description": "An example layer where all techniques have a randomized score", "domain": "mitre-enterprise", "techniques": techniques_list, }
def convert(filename, output='output.json'): count = 0 with open(filename) as json_file: vList = [] data = json.load(json_file) print("Loaded the file") for cves in data['CVE_Items']: count += 1 # Getting the different fields name = cves['cve']['CVE_data_meta']['ID'] description = cves['cve']['description']['description_data'][0]["value"] cdate = cves['publishedDate'] mdate = cves['lastModifiedDate'] creator = cves['cve']['CVE_data_meta']['ASSIGNER'] # Creating the vulnerability with the extracted fields vuln = Vulnerability(name=name, created=cdate, modified=mdate, description=description) # Adding the vulnerability to the list of vulnerabilities vList.append(vuln) # Creating the bundle from the list of vulnerabilities bundle = Bundle(vList) # Creating a MemoryStore object from the bundle memorystore = MemoryStore(bundle) # Dumping this object to a file memorystore.save_to_file(output) print("Successfully converted " + str(count) + " vulnerabilities")
def get_stix_data(domain, version=None, remote=None): """ download the ATT&CK STIX data for the given domain and version from MITRE/CTI (or just domain if a remote workbench is specified). :param domain: the domain of ATT&CK to fetch data from, e.g "enterprise-attack" :param version: the version of attack to fetch data from, e.g "v8.1". If omitted, returns the latest version (not used for invocations that use remote) :param remote: optional url to a ATT&CK workbench instance. If specified, data will be retrieved from the target Workbench instead of MITRE/CTI :returns: a MemoryStore containing the domain data """ if remote: # Using Workbench Instance if ':' not in remote[6:]: remote += ":3000" if not remote.startswith('http'): remote = 'http://' + remote url = f"{remote}/api/stix-bundles?domain={domain}&includeRevoked=true&includeDeprecated=true" stix_json = requests.get(url).json() return MemoryStore(stix_json) else: # Using MITRE/CTI if version: url = f"https://raw.githubusercontent.com/mitre/cti/ATT%26CK-{version}/{domain}/{domain}.json" else: url = f"https://raw.githubusercontent.com/mitre/cti/master/{domain}/{domain}.json" stix_json = requests.get(url).json() return MemoryStore(stix_data=stix_json["objects"])
def getFrameworkOverviewLayers(controls, mappings, attackdata, domain, frameworkname, version): """ingest mappings and controls and attackdata, and return an array of layer jsons for layers according to control family""" # build list of control families familyIDToControls, familyIDToName, idToFamily = parseFamilyData(controls) outlayers = [{ "outfile": f"{frameworkname}-overview.json", "layer": layer( f"{frameworkname} overview", f"{frameworkname} heatmap overview of control mappings, where scores are the number of associated controls", domain, toTechniquelist(controls, mappings, attackdata, familyIDToControls, familyIDToName, idToFamily), version) }] for familyID in familyIDToControls: controlsInFamily = MemoryStore(stix_data=familyIDToControls[familyID]) techniquesInFamily = toTechniquelist(controlsInFamily, mappings, attackdata, familyIDToControls, familyIDToName, idToFamily) if len(techniquesInFamily ) > 0: # don't build heatmaps with no mappings # build family overview mapping outlayers.append({ "outfile": os.path.join("by_family", familyIDToName[familyID].replace(" ", "_"), f"{familyID}-overview.json"), "layer": layer( f"{familyIDToName[familyID]} overview", f"{frameworkname} heatmap for controls in the {familyIDToName[familyID]} family, where scores are the number of associated controls", domain, techniquesInFamily, version) }) # build layer for each control for control in familyIDToControls[familyID]: controlMs = MemoryStore(stix_data=control) control_id = control["external_references"][0]["external_id"] techniquesMappedToControl = toTechniquelist( controlMs, mappings, attackdata, familyIDToControls, familyIDToName, idToFamily) if len(techniquesMappedToControl ) > 0: # don't build heatmaps with no mappings outlayers.append({ "outfile": os.path.join( "by_family", familyIDToName[familyID].replace(" ", "_"), f"{'_'.join(control_id.split(' '))}.json"), "layer": layer(f"{control_id} mappings", f"{frameworkname} {control_id} mappings", domain, techniquesMappedToControl, version) }) return outlayers
def add_software(client, attack: MemoryStore, output_format: Text = "json") -> List[stix2.AttackPattern]: """ extract objects/facts related to ATT&CK Software Insert to ACT if client.baseurl is set, if not, print to stdout Args: attack (stix2): Stix attack instance """ notify = [] for software in attack.query([Filter("type", "in", ["tool", "malware"])]): tool_name = software.name.lower() # Tool category handle_fact(client.fact("category", software.type).source("tool", tool_name), output_format=output_format) if getattr(software, "revoked", None): # Object is revoked, add to notification list but do not add to facts that should be added to the platform notify.append(software) continue if getattr(software, "x_mitre_deprecated", None): # Object is revoked, add to notification list AND continue to add to facts that should be added to the platform notify.append(software) for alias in getattr(software, "x_mitre_aliases", []): if tool_name != alias.lower(): # Tool category (alias) handle_fact(client.fact("category", software.type).source( "tool", alias.lower()), output_format=output_format) handle_fact(client.fact("alias").bidirectional( "tool", tool_name, "tool", alias.lower()), output_format=output_format) # ATT&CK concept STIX Properties # ========================================================================== # Technqiues relationship where relationship_type == "uses", points to # a target object with type == "attack-pattern" for technique in attack.related_to(software, relationship_type="uses"): if technique.type != "attack-pattern": continue handle_fact(client.fact("implements").source( "tool", software.name.lower()).destination("technique", technique.name), output_format=output_format) return notify
def get_attack(url: str, proxy_string: str, timeout: int) -> MemoryStore: """Fetch Mitre ATT&CK JSON data in Stix2 format and return a Stix2 memory store""" attack = worker.fetch_json(url, proxy_string, timeout) # Create memory store mem = MemoryStore() # Add all objects to the memory store for obj in parse(attack, allow_custom=True).objects: mem.add(obj) return mem
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 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 __init__(self): """Download and store in memory the STIX data on instantiation.""" if self.kill_chain_name == "": raise ValueError( f"Kill chain name not specified in class {self.__class__.__name__}" ) if self.url == "": raise ValueError( f"URL not specified in class {self.__class__.__name__}") logging.info(f"Downloading STIX data at: {self.url}") stix_json = requests.get(self.url).json() self._memory_store = MemoryStore(stix_data=stix_json["objects"])
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 convert(filename, output='output.json'): # Create the default author author = Identity(name='The MITRE Corporation', identity_class='organization') count = 0 with open(filename) as json_file: vulnerabilities_bundle = [author] data = json.load(json_file) print("Loaded the file") for cves in data['CVE_Items']: count += 1 # Get the name name = cves['cve']['CVE_data_meta']['ID'] # Create external references external_reference = ExternalReference( source_name='NIST NVD', url='https://nvd.nist.gov/vuln/detail/' + name) external_references = [external_reference] for reference in cves['cve']['references']['reference_data']: external_reference = ExternalReference( source_name=reference['refsource'], url=reference['url']) external_references.append(external_reference) # Getting the different fields description = cves['cve']['description']['description_data'][0][ "value"] cdate = cves['publishedDate'] mdate = cves['lastModifiedDate'] # Creating the vulnerability with the extracted fields vuln = Vulnerability(name=name, created=cdate, modified=mdate, description=description, created_by_ref=author, external_references=external_references) # Adding the vulnerability to the list of vulnerabilities vulnerabilities_bundle.append(vuln) # Creating the bundle from the list of vulnerabilities bundle = Bundle(vulnerabilities_bundle) # Creating a MemoryStore object from the bundle memorystore = MemoryStore(bundle) # Dumping this object to a file memorystore.save_to_file(output) print("Successfully converted " + str(count) + " vulnerabilities")
def rel_mem_store(): cam = Campaign(id=CAMPAIGN_ID, **CAMPAIGN_KWARGS) idy = Identity(id=IDENTITY_ID, **IDENTITY_KWARGS) ind = Indicator(id=INDICATOR_ID, **INDICATOR_KWARGS) mal = Malware(id=MALWARE_ID, **MALWARE_KWARGS) rel1 = Relationship(ind, 'indicates', mal, id=RELATIONSHIP_IDS[0]) rel2 = Relationship(mal, 'targets', idy, id=RELATIONSHIP_IDS[1]) rel3 = Relationship(cam, 'uses', mal, id=RELATIONSHIP_IDS[2]) stix_objs = [cam, idy, ind, mal, rel1, rel2, rel3] yield MemoryStore(stix_objs)
def convert(parse_data, output='output.json'): # Create the default author author = Identity(name='The MS Bulletin Corporation', identity_class='organization') print(author) count = 0 vulnerabilities_bundle = [author] # Getting modified date mdate = parse_data["rss"]["channel"]["lastBuildDate"] for msb in parse_data["rss"]["channel"]["item"]: count += 1 # Get the name name = msb["title"] # Getting the create date cdate = msb["pubDate"] # Getting description description = msb["description"] # Create external references external_references = ExternalReference( source_name="Microsoft Security Bulletin", url=msb["link"] ) # Creating the vulnerability with the extracted fields vuln = Vulnerability( name=name, created=cdate, modified=mdate, description=description, created_by_ref=author, external_references=external_references ) # Adding the vulnerability to the list of vulnerabilities vulnerabilities_bundle.append(vuln) # Creating the bundle from the list of vulnerabilities bundle = Bundle(vulnerabilities_bundle) # Creating a MemoryStore object from the bundle memorystore = MemoryStore(bundle) # Dumping this object to a file memorystore.save_to_file(output) print("Successfully converted " + str(count) + " vulnerabilities")
def __init__(self, source='taxii', local=None): """ Initialization - Creates a matrix generator object :param server: Source to utilize (taxii or local) :param local: string path to local cache of stix data """ self.convert_data = {} if source.lower() not in ['taxii', 'local']: print( '[MatrixGen] - Unable to generate matrix, source {} is not one of "taxii" or "local"' .format(source)) raise ValueError if source.lower() == 'taxii': self.server = Server('https://cti-taxii.mitre.org/taxii') self.api_root = self.server.api_roots[0] self.collections = dict() for collection in self.api_root.collections: if collection.title != "PRE-ATT&CK": tc = Collection( 'https://cti-taxii.mitre.org/stix/collections/' + collection.id) self.collections[collection.title.split(' ') [0].lower()] = TAXIICollectionSource(tc) elif source.lower() == 'local': if local is not None: hd = MemoryStore() if 'mobile' in local.lower(): self.collections['mobile'] = hd.load_from_file(local) else: self.collections['enterprise'] = hd.load_from_file(local) else: print( '[MatrixGen] - "local" source specified, but path to local source not provided' ) raise ValueError self.matrix = {} self._build_matrix()
def get_stix_data(domain, version=None): """ download the ATT&CK STIX data for the given domain and version from MITRE/CTI. :param domain: the domain of ATT&CK to fetch data from, e.g "enterprise-attack" :param version: the version of attack to fetch data from, e.g "v8.1". If omitted, returns the latest version :returns: a MemoryStore containing the domain data """ if version: url = f"https://raw.githubusercontent.com/mitre/cti/ATT%26CK-{version}/{domain}/{domain}.json" else: url = f"https://raw.githubusercontent.com/mitre/cti/master/{domain}/{domain}.json" stix_json = requests.get(url).json() return MemoryStore(stix_data=stix_json["objects"])
def __init__(self, name=None, parentApp=None, framed=None, help=None, color='FORMDEFAULT', widget_list=None, cycle_widgets=False, *args, store: stix2.MemoryStore, **keywords): indicators = store.query([stix2.Filter('type', '=', 'indicator')]) self._provided_indicators = tuple(indicators) super().__init__(name, parentApp, framed, help, color, widget_list, cycle_widgets, *args, **keywords)
def getLayersByProperty(controls, mappings, attackdata, domain, frameworkname, x_mitre, version): """get layers grouping the mappings according to values of the given property""" propertyname = x_mitre.split("x_mitre_")[1] # remove prefix familyIDToControls, familyIDToName, idToFamily = parseFamilyData(controls) # group controls by the property propertyValueToControls = {} def addToDict(value, control): if value in propertyValueToControls: propertyValueToControls[value].append(control) else: propertyValueToControls[value] = [control] # iterate through controls, grouping by property isListType = False for control in controls.query([Filter("type", "=", "course-of-action")]): value = control.get(x_mitre) if not value: continue if isinstance(value, list): isListType = True for v in value: addToDict(v, control) else: addToDict(value, control) outlayers = [] for value in propertyValueToControls: # controls for the corresponding values controlsOfValue = MemoryStore(stix_data=propertyValueToControls[value]) techniques = toTechniquelist(controlsOfValue, mappings, attackdata, familyIDToControls, familyIDToName, idToFamily) if len(techniques) > 0: # build layer for this technique set outlayers.append({ "outfile": os.path.join(f"by_{propertyname}", f"{value}.json"), "layer": layer( f"{propertyname}={value} mappings", f"techniques where the {propertyname} of associated controls {'includes' if isListType else 'is'} {value}", domain, techniques, version) }) return outlayers
def add_techniques(client, attack: MemoryStore, output_format: Text = "json") -> List[stix2.AttackPattern]: """ extract objects/facts related to ATT&CK techniques Args: attack (stix2): Stix attack instance """ notify = [] # ATT&CK concept STIX Object type ACT object # ========================================================= # Technique attack-pattern technique # Filter out ATT&CK techniques (attack-pattern) from bundle for technique in attack.query([Filter("type", "=", "attack-pattern")]): if getattr(technique, "revoked", None): # Object is revoked, add to notification list but do not add to facts that should be added to the platform notify.append(technique) continue if getattr(technique, "x_mitre_deprecated", None): # Object is revoked, add to notification list AND continue to add to facts that should be added to the platform notify.append(technique) # Mitre ATT&CK Tactics are implemented in STIX as kill chain phases with kill_chain_name "mitre-attack" for tactic in technique.kill_chain_phases: if tactic.kill_chain_name != "mitre-attack": continue handle_fact(client.fact("accomplishes").source( "technique", technique.name).destination("tactic", tactic.phase_name), output_format=output_format) return notify
def load(url): """Load stix data from file""" src = MemoryStore() src.load_from_file(url) return src
from scripts.atcutils import ATCutils from stix2 import MemoryStore, CustomObject, properties ATCconfig = ATCutils.load_config("scripts/config.yml") stix_mem = MemoryStore() @CustomObject('x-react-stage', [ ( 'name', properties.StringProperty(required=True)), ( 'description', properties.StringProperty()), ( 'external_references', properties.ObjectReferenceProperty())] ) class ReactStage(object): def __init__(self, name=None, **kwargs): list_of_stages = ['Preparation','Identification','Containment','Eradication','Recovery','Lessons Learned'] if name and name not in list_of_stages: raise ValueError("'%s' is not a recognized stage of RE&CT." % name) @CustomObject( 'x-react-action', [ ( 'name', properties.StringProperty(required=True)), ( 'description', properties.StringProperty()), ( 'external_references', properties.ObjectReferenceProperty()), ( 'kill_chain_phases', properties.ListProperty(properties.DictionaryProperty)) ] ) class ReactAction(object): def __init__(self, name=None, **kwargs): pass @CustomObject('x-react-matrix', [ ( 'name', properties.StringProperty(required=True)), ( 'description', properties.StringProperty()), ( 'tactic_refs', properties.ListProperty(properties.StringProperty)) ] )
class MitreExtractor: """ This class extract Mitre techniques and sub techniques that are represented as "attack-pattern" in STIX format. The STIX data is collected in JSON format by requesting the specified URL. url: must point to json stix location kill_chain_name: mitre-attack, mitre-mbc... """ url = "" kill_chain_name = "" def __init__(self): """Download and store in memory the STIX data on instantiation.""" if self.kill_chain_name == "": raise ValueError( f"Kill chain name not specified in class {self.__class__.__name__}" ) if self.url == "": raise ValueError( f"URL not specified in class {self.__class__.__name__}") logging.info(f"Downloading STIX data at: {self.url}") stix_json = requests.get(self.url).json() self._memory_store = MemoryStore(stix_data=stix_json["objects"]) @staticmethod def _remove_deprecated_objetcs(stix_objects) -> List[AttackPattern]: """Remove any revoked or deprecated objects from queries made to the data source.""" return list( filter( lambda x: x.get("x_mitre_deprecated", False) is False and x. get("revoked", False) is False, stix_objects, )) def _get_tactics(self) -> List[Dict]: """Get tactics IDs from Mitre matrix.""" # Only one matrix for enterprise att&ck framework matrix = self._remove_deprecated_objetcs( self._memory_store.query([ Filter("type", "=", "x-mitre-matrix"), ]))[0] return list(map(self._memory_store.get, matrix["tactic_refs"])) def _get_techniques_from_tactic(self, tactic: str) -> List[AttackPattern]: """Get techniques and sub techniques from a Mitre tactic (kill_chain_phases->phase_name)""" techniques = self._remove_deprecated_objetcs( self._memory_store.query([ Filter("type", "=", "attack-pattern"), Filter("kill_chain_phases.phase_name", "=", tactic), Filter("kill_chain_phases.kill_chain_name", "=", self.kill_chain_name), ])) return techniques def _get_parent_technique_from_subtechnique( self, technique: AttackPattern) -> AttackPattern: """Get parent technique of a sub technique using the technique ID TXXXX.YYY""" sub_id = technique["external_references"][0]["external_id"].split( ".")[0] parent_technique = self._remove_deprecated_objetcs( self._memory_store.query([ Filter("type", "=", "attack-pattern"), Filter("external_references.external_id", "=", sub_id), ]))[0] return parent_technique def run(self) -> Dict[str, Dict[str, str]]: """Iterate over every technique over every tactic. If the technique is a sub technique, then we also search for the parent technique name. """ logging.info("Starting extraction...") data: Dict[str, Dict[str, str]] = {} for tactic in self._get_tactics(): data[tactic["name"]] = {} for technique in self._get_techniques_from_tactic( tactic["x_mitre_shortname"]): tid = technique["external_references"][0]["external_id"] technique_name = technique["name"].split("::")[0] if technique["x_mitre_is_subtechnique"]: parent_technique = self._get_parent_technique_from_subtechnique( technique) data[tactic["name"]][ tid] = f"{parent_technique['name']}::{technique_name}" else: data[tactic["name"]][tid] = technique_name return data
def __init__(self, source='taxii', resource=None): """ Initialization - Creates a matrix generator object :param source: Source to utilize (taxii, remote, or local) :param resource: string path to local cache of stix data (local) or url of an ATT&CK Workbench (remote) """ self.convert_data = {} self.collections = dict() if source.lower() not in ['taxii', 'local', 'remote']: print( '[MatrixGen] - Unable to generate matrix, source {} is not one of "taxii", "remote" or ' '"local"'.format(source)) raise ValueError if source.lower() == 'taxii': self.server = Server('https://cti-taxii.mitre.org/taxii') self.api_root = self.server.api_roots[0] for collection in self.api_root.collections: if collection.title != "PRE-ATT&CK": tc = Collection( 'https://cti-taxii.mitre.org/stix/collections/' + collection.id) self.collections[collection.title.split(' ') [0].lower()] = TAXIICollectionSource(tc) elif source.lower() == 'local': if resource is not None: hd = MemoryStore() hd.load_from_file(resource) if 'mobile' in resource.lower(): self.collections['mobile'] = hd else: self.collections['enterprise'] = hd else: print( '[MatrixGen] - "local" source specified, but path to local source not provided' ) raise ValueError elif source.lower() == 'remote': if resource is not None: if ':' not in resource[6:]: print( '[MatrixGen] - "remote" source missing port; assuming ":3000"' ) resource += ":3000" if not resource.startswith('http'): resource = 'http://' + resource for dataset in ['enterprise', 'mobile']: hd = MemoryStore() response = requests.get( f"{resource}/api/stix-bundles?domain={dataset}-" f"attack&includeRevoked=true&includeDeprecated=true") response.raise_for_status( ) # ensure we notice bad responses _add(hd, json.loads(response.text), True, None) self.collections[dataset] = hd else: print( f'[MatrixGen] - WARNING: "remote" selected without providing a "resource" url. The use of ' f'"remote" requires the inclusion of a "resource" url to an ATT&CK Workbench instance. No matrix ' f'will be generated...') self.matrix = {} self._build_matrix()
help= "if flag specified, will remove the contents the output folder before writing layers" ) parser.add_argument( "--build-directory", dest="buildDir", action="store_true", help= "if flag specified, will build a markdown file listing the output files for easy access in the Navigator" ) args = parser.parse_args() print("downloading ATT&CK data... ", end="", flush=True) attackdata = MemoryStore(stix_data=requests.get( f"https://raw.githubusercontent.com/mitre/cti/ATT%26CK-{args.version}/{args.domain}/{args.domain}.json" ).json()["objects"]) print("done") print("loading controls framework... ", end="", flush=True) with open(args.controls, "r") as f: controls = MemoryStore(stix_data=json.load(f)["objects"], allow_custom=True) print("done") print("loading mappings... ", end="", flush=True) with open(args.mappings, "r") as f: mappings = MemoryStore(stix_data=json.load(f)["objects"]) print("done") print("generating layers... ", end="", flush=True)
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 convert(filename, output="output.json"): # Create the default author author = Identity(name="The MITRE Corporation", identity_class="organization") count = 0 with open(filename) as json_file: vulnerabilities_bundle = [author] data = json.load(json_file) for cves in data["CVE_Items"]: count += 1 # Get the name name = cves["cve"]["CVE_data_meta"]["ID"] # Create external references external_reference = ExternalReference( source_name="NIST NVD", url="https://nvd.nist.gov/vuln/detail/" + name) external_references = [external_reference] for reference in cves["cve"]["references"]["reference_data"]: external_reference = ExternalReference( source_name=reference["refsource"], url=reference["url"]) external_references.append(external_reference) # Getting the different fields description = cves["cve"]["description"]["description_data"][0][ "value"] base_score = (cves["impact"]["baseMetricV3"]["cvssV3"]["baseScore"] if "baseMetricV3" in cves["impact"] else None) base_severity = ( cves["impact"]["baseMetricV3"]["cvssV3"]["baseSeverity"] if "baseMetricV3" in cves["impact"] else None) attack_vector = ( cves["impact"]["baseMetricV3"]["cvssV3"]["attackVector"] if "baseMetricV3" in cves["impact"] else None) integrity_impact = ( cves["impact"]["baseMetricV3"]["cvssV3"]["integrityImpact"] if "baseMetricV3" in cves["impact"] else None) availability_impact = ( cves["impact"]["baseMetricV3"]["cvssV3"]["availabilityImpact"] if "baseMetricV3" in cves["impact"] else None) cdate = cves["publishedDate"] mdate = cves["lastModifiedDate"] # Creating the vulnerability with the extracted fields vuln = Vulnerability( name=name, created=cdate, modified=mdate, description=description, created_by_ref=author, external_references=external_references, custom_properties={ "x_opencti_base_score": base_score, "x_opencti_base_severity": base_severity, "x_opencti_attack_vector": attack_vector, "x_opencti_integrity_impact": integrity_impact, "x_opencti_availability_impact": availability_impact, }, ) # Adding the vulnerability to the list of vulnerabilities vulnerabilities_bundle.append(vuln) # Creating the bundle from the list of vulnerabilities bundle = Bundle(vulnerabilities_bundle) # Creating a MemoryStore object from the bundle memorystore = MemoryStore(bundle) # Dumping this object to a file memorystore.save_to_file(output)
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 mem_store(): yield MemoryStore(STIX_OBJS1)
#!/usr/bin/env python3 import os import logging import sys import datetime import certstream import whois import requests import csv import urllib.request from tld import get_tld from bs4 import BeautifulSoup from stix2 import MemoryStore, Indicator # ne pas toucher, le fichier site-database sera ecrase sinon mem = MemoryStore() GREEN = "\033[38;5;2m" # Clean RED = "\033[38;5;1m" # Phishing LIGHT_RED = "\033[38;5;9m" # Grand danger GRAY = "\033[38;5;7m" # En calcul WHITE = "\033[0m" # Reset fname = open("list-fr.csv", 'r') file = csv.reader(fname) dico = { '0': ['o'], 'I': ['l', '1'], '8': ['b'], '1': ['l', 'i'], '5': ['s'], 'i': ['j'],
def add_groups(client, attack: MemoryStore, output_format: Text = "json") -> List[stix2.AttackPattern]: """ extract objects/facts related to ATT&CK Groups Args: attack (stix2): Stix attack instance """ notify = [] # ATT&CK concept STIX Object type ACT object # ========================================================= # Group intrusion-set threatActor # # Filter out ATT&CK groups (intrusion-set) from bundle for group in attack.query([Filter("type", "=", "intrusion-set")]): if getattr(group, "revoked", None): # Object is revoked, add to notification list but do not add to facts that should be added to the platform notify.append(group) continue if getattr(group, "x_mitre_deprecated", None): # Object is revoked, add to notification list AND continue to add to facts that should be added to the platform notify.append(group) for alias in getattr(group, "aliases", []): if group.name != alias: handle_fact(client.fact("alias").bidirectional( "threatActor", group.name, "threatActor", alias), output_format=output_format) # ATT&CK concept STIX Properties # ========================================================================== # Software relationship where relationship_type == "uses", # points to a target object with type== "malware" or "tool" for tool in attack.related_to(group, relationship_type="uses"): if tool.type not in ("malware", "tool"): continue chain = act.api.fact.fact_chain( client.fact("classifiedAs").source("content", "*").destination( "tool", tool.name.lower()), client.fact("observedIn").source("content", "*").destination( "event", "*"), client.fact("attributedTo").source("event", "*").destination( "incident", "*"), client.fact("attributedTo").source( "incident", "*").destination("threatActor", group.name)) for fact in chain: handle_fact(fact, output_format=output_format) # ATT&CK concept STIX Properties # ========================================================================== # Technqiues relationship where relationship_type == "uses", points to # a target object with type == "attack-pattern" for technique in attack.related_to(group, relationship_type="uses"): if technique.type != "attack-pattern": continue chain = act.api.fact.fact_chain( client.fact("classifiedAs").source("event", "*").destination( "technique", technique.name), client.fact("attributedTo").source("event", "*").destination( "incident", "*"), client.fact("attributedTo").source( "incident", "*").destination("threatActor", group.name)) for fact in chain: handle_fact(fact, output_format=output_format) return notify
filename = sys.argv[1] count = 0 with open(filename) as json_file: vList = [] data = json.load(json_file) print("Loaded the file") for cves in data['CVE_Items']: count += 1 # Getting the different fields name = cves['cve']['CVE_data_meta']['ID'] description = cves['cve']['description']['description_data'][0]["value"] cdate = cves['publishedDate'] mdate = cves['lastModifiedDate'] creator = cves['cve']['CVE_data_meta']['ASSIGNER'] # Creating the vulnerability with the extracted fields vuln = Vulnerability(name=name, created=cdate, modified=mdate, description=description) # Adding the vulnerability to the list of vulnerabilities vList.append(vuln) # Creating the bundle from the list of vulnerabilities bundle = Bundle(vList) # Creating a MemoryStore object from the bundle memorystore = MemoryStore(bundle) # Dumping this object to a file memorystore.save_to_file('output.json') print("Successfully converted " + str(count) + " vulnerabilities")