diff --git a/.gitignore b/.gitignore index a57b1721080..59588f224dc 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ attack-theme/templates/contribute attack-theme/templates/datasources attack-theme/templates/groups attack-theme/templates/campaigns +attack-theme/templates/assets attack-theme/templates/matrices attack-theme/templates/mitigations attack-theme/templates/resources diff --git a/attack-search/src/settings.js b/attack-search/src/settings.js index db171b6711a..a01ad11ff3e 100644 --- a/attack-search/src/settings.js +++ b/attack-search/src/settings.js @@ -2,6 +2,7 @@ const baseURL = ''; // TODO migrate from base_url (generated via Pelican) const searchFilePaths = [ "campaigns.json", + "assets.json", "datasources.json", "groups.json", "matrices.json", diff --git a/modules/assets/__init__.py b/modules/assets/__init__.py new file mode 100644 index 00000000000..f39002b53e5 --- /dev/null +++ b/modules/assets/__init__.py @@ -0,0 +1,9 @@ +from . import assets +from . import assets_config + + +def get_priority(): + return assets_config.priority + +def run_module(): + return (assets.generate_assets(), assets_config.module_name) diff --git a/modules/assets/assets.py b/modules/assets/assets.py new file mode 100644 index 00000000000..25c166e201e --- /dev/null +++ b/modules/assets/assets.py @@ -0,0 +1,248 @@ +import collections +import json +import os + +from loguru import logger + +from modules import util + +from . import assets_config +from .. import site_config + + +def generate_assets(): + """Responsible for verifying asset directory and starting off asset markdown generation.""" + # Create content pages directory if does not already exist + util.buildhelpers.create_content_pages_dir() + + # Move templates to templates directory + util.buildhelpers.move_templates(assets_config.module_name, assets_config.assets_templates_path) + + # Verify if directory exists + if not os.path.isdir(assets_config.asset_markdown_path): + os.mkdir(assets_config.asset_markdown_path) + + # Generate redirections + util.buildhelpers.generate_redirections( + redirections_filename=assets_config.assets_redirection_location, redirect_md=site_config.redirect_md + ) + + # Generates the markdown files to be used for page generation + assets_generated = generate_markdown_files() + + if not assets_generated: + util.buildhelpers.remove_module_from_menu(assets_config.module_name) + + +def generate_markdown_files(): + """ + Responsible for generating asset index page and getting shared data for all assets. + """ + has_asset = False + + asset_list = util.relationshipgetters.get_asset_list() + + asset_list_no_deprecated_revoked = util.buildhelpers.filter_deprecated_revoked(asset_list) + + if asset_list_no_deprecated_revoked: + has_asset = True + + if has_asset: + data = {} + + notes = util.relationshipgetters.get_objects_using_notes() + side_menu_data = util.buildhelpers.get_side_menu_data( + "Assets", "/assets/", asset_list_no_deprecated_revoked + ) + data["side_menu_data"] = side_menu_data + data["assets_table"] = get_assets_table_data(asset_list_no_deprecated_revoked) + data["assets_list_len"] = str(len(asset_list_no_deprecated_revoked)) + + subs = assets_config.asset_index_md + json.dumps(data) + + with open( + os.path.join(assets_config.asset_markdown_path, "overview.md"), "w", encoding="utf8" + ) as md_file: + md_file.write(subs) + + # Create the markdown for assets + for asset in asset_list: + generate_asset_md(asset, side_menu_data, notes) + + return has_asset + + +def generate_asset_md(asset, side_menu_data, notes): + """Responsible for generating markdown of all assets.""" + + attack_id = util.buildhelpers.get_attack_id(asset) + + if not attack_id: return + + data = {} + data["attack_id"] = attack_id + data["side_menu_data"] = side_menu_data + data["notes"] = notes.get(asset["id"]) + + dates = util.buildhelpers.get_created_and_modified_dates(asset) + if dates.get("created"): + data["created"] = dates["created"] + if dates.get("modified"): + data["modified"] = dates["modified"] + if asset.get("name"): + data["name"] = asset["name"] + if asset.get("x_mitre_version"): + data["version"] = asset["x_mitre_version"] + + if isinstance(asset.get("x_mitre_contributors"), collections.abc.Iterable): + data["contributors_list"] = asset["x_mitre_contributors"] + + if asset.get("x_mitre_platforms"): + asset["x_mitre_platforms"].sort() + data["platforms"] = ", ".join(asset["x_mitre_platforms"]) + + if asset.get("x_mitre_sectors"): + asset["x_mitre_sectors"].sort() + data["sectors"] = ", ".join(asset["x_mitre_sectors"]) + + # Get initial reference list + reference_list = {"current_number": 0} + + # Get initial reference list from asset object + reference_list = util.buildhelpers.update_reference_list(reference_list, asset) + + if asset.get("description"): + data["descr"] = asset["description"] + if asset.get("x_mitre_deprecated"): + data["deprecated"] = True + + # Get technique data for technique table + data["technique_table_data"] = get_techniques_targeting_asset_data(asset, reference_list) + + # Get navigator layers for this asset + layers = util.buildhelpers.get_navigator_layers( + data["name"], + data["attack_id"], + "asset", + "targeted by", + data["version"] if "version" in data else None, + data["technique_table_data"], + ) + + data["layers"] = [] + for layer in layers: + with open( + os.path.join( + assets_config.asset_markdown_path, + "-".join([data["attack_id"], "techniques", layer["domain"]]) + ".md", + ), + "w", + encoding="utf8", + ) as layer_json: + subs = site_config.layer_md.substitute( + { + "attack_id": data["attack_id"], + "path": "assets/" + data["attack_id"], + "domain": layer["domain"], + } + ) + subs = subs + layer["layer"] + layer_json.write(subs) + data["layers"].append( + { + "domain": layer["domain"], + "name": layer["name"], + "filename": layer["filename"], + "navigator_link": site_config.navigator_link, + } + ) + + if asset.get("x_mitre_related_assets"): + data["related_assets_table"] = get_related_asset_data(asset["x_mitre_related_assets"]) + + data["citations"] = reference_list + data["versioning_feature"] = site_config.check_versions_module() + + subs = assets_config.asset_md.substitute(data) + subs = subs + json.dumps(data) + + # Write out the markdown file + with open( + os.path.join(assets_config.asset_markdown_path, data["attack_id"] + ".md"), "w", encoding="utf8" + ) as md_file: + md_file.write(subs) + + +def get_assets_table_data(asset_list): + """Responsible for generating asset table data for the asset index page""" + assets_table_data = [] + for asset in asset_list: + attack_id = util.buildhelpers.get_attack_id(asset) + if not attack_id: continue + + domain_list = util.buildhelpers.get_domain_name(asset) + row = { + "id": attack_id, + "name": asset["name"] if asset.get("name") else attack_id, + } + + for domain_idx in range(len(domain_list)): + domain_list[domain_idx] = domain_list[domain_idx].replace('-attack','') + if domain_list[domain_idx] == "ics": + domain_list[domain_idx] = domain_list[domain_idx].upper() + else: + domain_list[domain_idx] = domain_list[domain_idx].capitalize() + row["domains"] = domain_list + + if asset.get("description"): + row["descr"] = asset["description"] + + if asset.get("x_mitre_deprecated"): + row["deprecated"] = True + + assets_table_data.append(row) + + return assets_table_data + + +def get_related_asset_data(related_assets): + if not related_assets: return [] + + related_asset_data = [] + for related_asset in related_assets: + row = { + "name": related_asset["name"], # required + } + if related_asset.get("related_asset_sectors"): + related_asset["related_asset_sectors"].sort() + row["sectors"] = ", ".join(related_asset["related_asset_sectors"]) + if related_asset.get("description"): + row["descr"] = related_asset["description"] + related_asset_data.append(row) + return related_asset_data + + +def get_techniques_targeting_asset_data(asset, reference_list): + """Given an asset and its reference list, get the techniques targeting the asset. + Check the reference list for citations, if not found in list, add it. + """ + technique_list = {} + techniques_targeting_assets = util.relationshipgetters.get_techniques_targeting_assets() + + if techniques_targeting_assets.get(asset.get("id")): + for technique in techniques_targeting_assets[asset["id"]]: + # Do not add if technique is deprecated + if not technique["object"].get("x_mitre_deprecated"): + technique_list = util.buildhelpers.technique_used_helper(technique_list, technique, reference_list) + + technique_data = [] + for item in technique_list: + technique_data.append(technique_list[item]) + # Sort by technique name + technique_data = sorted(technique_data, key=lambda k: k["name"].lower()) + + # Sort by domain name + technique_data = sorted( + technique_data, key=lambda k: [site_config.custom_alphabet.index(c) for c in k["domain"].lower()] + ) + return technique_data diff --git a/modules/assets/assets_config.py b/modules/assets/assets_config.py new file mode 100644 index 00000000000..deafdb527d0 --- /dev/null +++ b/modules/assets/assets_config.py @@ -0,0 +1,22 @@ +from string import Template + +module_name = "Assets" +priority = 7.2 + +# Markdown path for assets +asset_markdown_path = "content/pages/assets/" + +# String template for asset index page +asset_index_md = ( + "Title: Asset overview\nTemplate: assets/assets-index\nsave_as: assets/index.html\ndata: " +) + +# String template for asset page +asset_md = Template( + "Title: ${name}\nTemplate: assets/asset\nsave_as: assets/${attack_id}/index.html\ndata: " +) + +# Path for templates +assets_templates_path = "modules/assets/templates/" + +assets_redirection_location = "modules/assets/assets_redirections.json" diff --git a/modules/assets/assets_redirections.json b/modules/assets/assets_redirections.json new file mode 100644 index 00000000000..16b713b22f4 --- /dev/null +++ b/modules/assets/assets_redirections.json @@ -0,0 +1,7 @@ +[ + { + "title": "assets-redirect", + "from": "assets.html", + "to": "/assets/" + } +] diff --git a/modules/assets/templates/asset.html b/modules/assets/templates/asset.html new file mode 100644 index 00000000000..0dfb2452ac9 --- /dev/null +++ b/modules/assets/templates/asset.html @@ -0,0 +1,172 @@ +{% extends "general/two-column.html" %} +{% set parsed = page.data | from_json %} +{% set title = parsed.name + ", Asset " + parsed.attack_id + " | MITRE ATT&CK®" -%} +{% set active_page = "assets" -%} + +{% import 'macros/deprecated.html' as deprecated %} +{% import 'macros/navigator.html' as navigator %} +{% import 'macros/references.html' as references %} +{% import 'macros/navigation.html' as navigation %} +{% import 'macros/techniques_used.html' as techniques_used %} +{% import 'macros/versioning.html' as versioning %} +{% import 'macros/clean_output.html' as clean_output %} +{% import 'macros/citations.html' as citations %} +{% import 'macros/notes.html' as notes %} + +{% block head %} + {{ super () }} + {% if parsed.deprecated %} + + {% endif %} +{% endblock %} + +{% block innerleft %} + +
+ {{ navigation.sidenav(parsed.side_menu_data, output_file) }} +
+ +{% endblock %} + +{% block innerright %} +{{ super () }} + + +
+
+
+
+
+

{{ parsed.name }}

+ + {% if parsed.deprecated %} + {% if parsed.descr %} + {{ deprecated.deprecated(clean_output.stixToHTML(parsed.descr, firstParagraphOnly=True)) }} + {% else %} + {{ deprecated.deprecated("This asset has been deprecated.") }} + {% endif %} + {% else %} +
+
+ {% if parsed.descr %} +
+ {{ clean_output.stixToHTML(parsed.descr, parsed.citations) }} +
+ {% endif %} + {% if parsed.notes %} + {{ notes.notes_section(parsed.notes) }} + {% endif %} +
+
+
+
+ {% if parsed.attack_id %} +
+
+ ID: {{ parsed.attack_id }} +
+
+ {% endif %} + {% if parsed.platforms %} +
+
+ +
+
+ {{ "Platform" if parsed.platforms | length < 2 else "Platforms" }}: {{parsed.platforms}} +
+
+ {% endif %} + {% if parsed.sectors %} +
+
+ {{ "Sector" if parsed.sectors | length < 2 else "Sectors" }}: {{parsed.sectors}} +
+
+ {% endif %} + {% if parsed.contributors_list %} +
+
+ Contributors: {{ parsed.contributors_list|join("; ") }} +
+
+ {% endif %} + {% if parsed.version %} +
+
+ Version: {{ parsed.version }} +
+
+ {% endif %} + {% if parsed.created %} +
+
+ Created: {{ parsed.created }} +
+
+ {% endif %} + {% if parsed.modified %} +
+
+ Last Modified: {{ parsed.modified }} +
+
+ {% endif %} +
+
+ {% if parsed.versioning_feature %} + {{ versioning.permalink_button(output_file, parsed.attack_id)}} + {% endif %} +
+
+ + {% if parsed.related_assets_table %} +

Related Assets

+ + + + + + + + + + {% for related_asset in parsed.related_assets_table %} + + + + + + {% endfor %} + +
NameSectorsDescription
{{ related_asset.name }}{{ related_asset.sectors }} + {{ clean_output.stixToHTML(related_asset.descr, parsed.citations) }} +
+ {% endif %} + + {% if parsed.technique_table_data %} + {{ navigator.layer_links(parsed.attack_id, "assets", parsed.layers) }} + {{ techniques_used.techniques_used(parsed.technique_table_data, "Techniques", true, "", parsed.citations) }} + {% endif %} + + {{ citations.reference_section(parsed.citations) }} + {% endif %} +
+
+
+
+ +{% endblock %} + +{% block scripts %} + {{ super() }} + + + + + +{% endblock %} diff --git a/modules/assets/templates/assets-index.html b/modules/assets/templates/assets-index.html new file mode 100644 index 00000000000..7c3d9f910d3 --- /dev/null +++ b/modules/assets/templates/assets-index.html @@ -0,0 +1,80 @@ +{% extends "general/two-column.html" %} +{% set title = "Assets | MITRE ATT&CK®" %} +{% set parsed = page.data | from_json %} +{% set active_page = "assets" -%} +{% import 'macros/navigation.html' as navigation %} +{% import 'macros/clean_output.html' as clean_output %} + +{% block innerleft %} + +
+ {{ navigation.sidenav(parsed.side_menu_data, output_file) }} +
+ +{% endblock %} + +{% block innerright %} + {{ super () }} + +
+
+
+
+
+
+

Assets

+

+ <PLACEHOLDER DESCRIPTION> +

+ +
Assets: {{ parsed.assets_list_len }}
+ + + + + + + + + + + {% for row in parsed.assets_table %} + + + + + + + {% endfor %} + +
IDNameDomainDescription
+ {{ row.id | upper }} + + {{ row.name }} + + {% for dom in row.domains %} + {{dom}} +
+ {% endfor %} +
+ {% if row.deprecated %} + ****Deprecation Warning**** + {% endif %} + {{ clean_output.stixToHTML(row.descr) }} +
+
+
+
+
+
+ +{% endblock %} + +{% block scripts %} + {{ super() }} + + +{% endblock %} \ No newline at end of file diff --git a/modules/campaigns/campaigns.py b/modules/campaigns/campaigns.py index 0a4eba57575..4ebfa2aa80e 100644 --- a/modules/campaigns/campaigns.py +++ b/modules/campaigns/campaigns.py @@ -141,6 +141,7 @@ def generate_campaign_md(campaign, side_menu_data, notes): data["name"], data["attack_id"], "campaign", + "used by", data["version"] if "version" in data else None, data["technique_table_data"], ) diff --git a/modules/datasources/__init__.py b/modules/datasources/__init__.py index 5c33c5c4402..3a40ecd1342 100644 --- a/modules/datasources/__init__.py +++ b/modules/datasources/__init__.py @@ -24,6 +24,7 @@ def get_menu(): {"display_name": "Groups", "url": "/groups", "external_link": False, "children": []}, {"display_name": "Software", "url": "/software", "external_link": False, "children": []}, {"display_name": "Campaigns", "url": "/campaigns", "external_link": False, "children": []}, + {"display_name": "Assets", "url": "/assets", "external_link": False, "children": [] }, ], } diff --git a/modules/groups/groups.py b/modules/groups/groups.py index 5b2c888bd6c..28310a4e083 100644 --- a/modules/groups/groups.py +++ b/modules/groups/groups.py @@ -132,6 +132,7 @@ def generate_group_md(group, side_menu_data, notes): data["name"], data["attack_id"], "group", + "used by", data["version"] if "version" in data else None, data["technique_table_data"], inheritance, # extend legend to include color coding for inherited techniques, if applicable diff --git a/modules/mitigations/mitigations.py b/modules/mitigations/mitigations.py index e6e6eb64e3c..bb8ad266725 100644 --- a/modules/mitigations/mitigations.py +++ b/modules/mitigations/mitigations.py @@ -137,6 +137,7 @@ def generate_mitigation_md(mitigation, domain, side_menu_data, notes): data["name"], data["attack_id"], "mitigation", + "mitigated by", data["version"] if "version" in data else None, data["techniques_addressed_data"], ) diff --git a/modules/random_page/random_page.py b/modules/random_page/random_page.py index 11236283fc0..604e7e65e17 100644 --- a/modules/random_page/random_page.py +++ b/modules/random_page/random_page.py @@ -16,6 +16,7 @@ def generate_json(): "groups": "Group", "software": "Software", "campaigns": "Campaign", + "assets": "Asset" } routes = {} @@ -57,6 +58,8 @@ def generate_json(): add_to_json = True elif route == "campaigns" and re.search(r"C[0-9]{4}", thepath): add_to_json = True + elif route == "assets" and re.search(r"A[0-9]{4}", thepath): + add_to_json = True if add_to_json: json_data[value].append(thepath[6:]) diff --git a/modules/resources/resources.py b/modules/resources/resources.py index 4433bde8ddb..07f9940b32c 100644 --- a/modules/resources/resources.py +++ b/modules/resources/resources.py @@ -319,6 +319,7 @@ def generate_working_with_attack(): "techniques", "datasources", "campaigns", + "assets" ] # Verify if directories exists diff --git a/modules/software/software.py b/modules/software/software.py index b93a6faee0b..eb4d623b794 100644 --- a/modules/software/software.py +++ b/modules/software/software.py @@ -129,6 +129,7 @@ def generate_software_md(software, side_menu_data, notes): data["name"], data["attack_id"], "software", + "used by", data["version"] if "version" in data else None, data["technique_table_data"], ) diff --git a/modules/techniques/techniques.py b/modules/techniques/techniques.py index e5ab5e051c0..1c5980aaab8 100644 --- a/modules/techniques/techniques.py +++ b/modules/techniques/techniques.py @@ -55,9 +55,8 @@ def generate_techniques(): check_if_generated = generate_domain_markdown( domain["name"], techniques_no_sub, tactics, side_nav_data, notes, deprecated ) - if not technique_generated: - if check_if_generated: - technique_generated = True + if not technique_generated and check_if_generated: + technique_generated = True if not technique_generated: util.buildhelpers.remove_module_from_menu(techniques_config.module_name) @@ -220,6 +219,9 @@ def generate_data_for_md(technique_dict, technique, tactic_list, is_sub_techniqu # Get examples technique_dict["examples_table"] = get_examples_table_data(technique, reference_list) + # Get asset table + technique_dict["assets_table"] = get_assets_table_data(technique, reference_list) + # Get technique version if technique.get("x_mitre_version"): technique_dict["version"] = technique["x_mitre_version"] @@ -357,26 +359,60 @@ def get_mitigations_table_data(technique, reference_list): # Do not add deprecated mitigation to table if not mitigation["object"].get("x_mitre_deprecated"): attack_id = util.buildhelpers.get_attack_id(mitigation["object"]) - # Only add if mitigation attack id is found - if attack_id: - row = {} - row["mid"] = attack_id - row["name"] = mitigation["object"]["name"] - if mitigation["relationship"].get("description"): - # Get filtered description - reference_list = util.buildhelpers.update_reference_list( - reference_list, mitigation["relationship"] - ) - row["descr"] = mitigation["relationship"]["description"] + if not attack_id: continue + row = {} + row["mid"] = attack_id + row["name"] = mitigation["object"]["name"] + if mitigation["relationship"].get("description"): + # Get filtered description + reference_list = util.buildhelpers.update_reference_list( + reference_list, mitigation["relationship"] + ) + row["descr"] = mitigation["relationship"]["description"] - mitigation_data.append(row) + mitigation_data.append(row) if mitigation_data: mitigation_data = sorted(mitigation_data, key=lambda k: k["name"].lower()) return mitigation_data +def get_assets_table_data(technique, reference_list): + """Given a technique a reference list, find assets that are targeted by the + technique and return list with asset data. Also modifies the + reference list if it finds a reference that is not on the list + """ + asset_data = [] + + # Check if technique has assets + assets_targeted_by_techniques = util.relationshipgetters.get_assets_targeted_by_techniques().get(technique["id"]) + if assets_targeted_by_techniques: + # Iterate through technique assets + for asset in assets_targeted_by_techniques: + # Do not add deprecated assets to table + if not asset["object"].get("x_mitre_deprecated"): + attack_id = util.buildhelpers.get_attack_id(asset["object"]) + + # Only add if attack id is found + if not attack_id: continue + row = {} + row["id"] = attack_id + row["name"] = asset["object"]["name"] + if asset["relationship"].get("description"): + # Get filtered description + reference_list = util.buildhelpers.update_reference_list( + reference_list, asset["relationship"] + ) + row["descr"] = asset["relationship"]["description"] + + asset_data.append(row) + + if asset_data: + asset_data = sorted(asset_data, key=lambda k: k["name"].lower()) + return asset_data + + def get_examples_table_data(technique, reference_list): """Given a technique object, find examples in malware using technique, tools using technique and groups using technique. Return list with @@ -399,23 +435,23 @@ def get_examples_table_data(technique, reference_list): attack_id = util.buildhelpers.get_attack_id(example["object"]) # Only add example data if the attack id is found - if attack_id: - row = {} + if not attack_id: continue + row = {} - row["id"] = attack_id + row["id"] = attack_id - row["path"] = get_path_from_type(example["object"]) + row["path"] = get_path_from_type(example["object"]) - row["name"] = example["object"]["name"] + row["name"] = example["object"]["name"] - if example["relationship"].get("description"): - # Get filtered description - reference_list = util.buildhelpers.update_reference_list( - reference_list, example["relationship"] - ) - row["descr"] = example["relationship"]["description"] + if example["relationship"].get("description"): + # Get filtered description + reference_list = util.buildhelpers.update_reference_list( + reference_list, example["relationship"] + ) + row["descr"] = example["relationship"]["description"] - example_data.append(row) + example_data.append(row) if example_data: example_data = sorted(example_data, key=lambda k: k["name"].lower()) @@ -507,20 +543,21 @@ def get_techniques_list(techniques): if not technique.get("revoked") and not technique.get("x_mitre_deprecated"): attack_id = util.buildhelpers.get_attack_id(technique) - if attack_id: - technique_dict = {} - technique_dict["id"] = attack_id - technique_dict["stix_id"] = technique["id"] - technique_dict["name"] = technique["name"] - technique_dict["description"] = technique["description"] + if not attack_id: continue - if technique.get("kill_chain_phases"): - for elem in technique["kill_chain_phases"]: - # Fill dict - if elem["phase_name"] not in technique_list: - technique_list[elem["phase_name"]] = [] + technique_dict = {} + technique_dict["id"] = attack_id + technique_dict["stix_id"] = technique["id"] + technique_dict["name"] = technique["name"] + technique_dict["description"] = technique["description"] - technique_list[elem["phase_name"]].append(technique_dict) + if technique.get("kill_chain_phases"): + for elem in technique["kill_chain_phases"]: + # Fill dict + if elem["phase_name"] not in technique_list: + technique_list[elem["phase_name"]] = [] + + technique_list[elem["phase_name"]].append(technique_dict) for key, __ in technique_list.items(): technique_list[key] = sorted(technique_list[key], key=lambda k: k["name"].lower()) @@ -531,8 +568,6 @@ def get_techniques_list(techniques): def get_subtechniques(technique): """Given a technique, return the ID and name of the subtechnique.""" subtechs = [] - attack_id = util.buildhelpers.get_attack_id(technique) - subtechniques_of = util.relationshipgetters.get_subtechniques_of() if technique["id"] in subtechniques_of: diff --git a/modules/techniques/templates/technique.html b/modules/techniques/templates/technique.html index 12faafebbbf..79e8f44ace3 100644 --- a/modules/techniques/templates/technique.html +++ b/modules/techniques/templates/technique.html @@ -369,6 +369,29 @@

Procedure Examples

{% endif %} + {% if parsed.assets_table %} +

Targeted Assets

+ + + + + + + + + {% for asset in parsed.assets_table %} + + + + + {% endfor %} + +
IDAsset
+ {{asset.id}} + + {{asset.name}} +
+ {% endif %} {% if parsed.domain != 'pre' %}

Mitigations

{% if parsed.mitigation_table %} diff --git a/modules/util/buildhelpers.py b/modules/util/buildhelpers.py index ea0c6eb92ab..20b82e56d26 100644 --- a/modules/util/buildhelpers.py +++ b/modules/util/buildhelpers.py @@ -507,17 +507,17 @@ def replace_html_chars(to_be_replaced): } -def get_navigator_layers(name, attack_id, obj_type, version, techniques_used, inheritance=False): +def get_navigator_layers(name, attack_id, obj_type, rel_type, version, techniques_used, inheritance=False): """Generate the Enterprise, Mobile, and ICS Navigator JSON layers for the given object.""" # Generate Enterprise base layer - enterprise_layer = build_base_layer("enterprise-attack", name, obj_type, attack_id, version, inheritance) + enterprise_layer = build_base_layer("enterprise-attack", name, obj_type, rel_type, attack_id, version, inheritance) # Generate Mobile base layer - mobile_layer = build_base_layer("mobile-attack", name, obj_type, attack_id, version, inheritance) + mobile_layer = build_base_layer("mobile-attack", name, obj_type, rel_type, attack_id, version, inheritance) # Generate ICS base layer - ics_layer = build_base_layer("ics-attack", name, obj_type, attack_id, version, inheritance) + ics_layer = build_base_layer("ics-attack", name, obj_type, rel_type, attack_id, version, inheritance) # Add technique data to layer for technique in techniques_used: @@ -584,12 +584,12 @@ def get_navigator_layers(name, attack_id, obj_type, version, techniques_used, in return layers -def build_base_layer(domain, object_name, object_type, attack_id, version, inheritance=False): +def build_base_layer(domain, object_name, object_type, rel_type, attack_id, version, inheritance=False): """Build the base Navigator layer for the given object.""" layer = {} # Layer description - layer["description"] = f"{domain_name_map[domain]} techniques used by {object_name}, ATT&CK {object_type} {attack_id}" + layer["description"] = f"{domain_name_map[domain]} techniques {rel_type} {object_name}, ATT&CK {object_type} {attack_id}" if version: # Add object version number if it exists layer["description"] += f" (v{version})" @@ -616,14 +616,14 @@ def build_base_layer(domain, object_name, object_type, attack_id, version, inher # Layer legend layer["legendItems"] = [ - {"label": f"used by {object_name}", "color": colorMap[1]} + {"label": f"{rel_type} {object_name}", "color": colorMap[1]} ] # Add campaign inheritance to legend, if applicable if inheritance: layer["legendItems"].extend([ - {"label": f"used by a campaign attributed to {object_name}", "color": colorMap[2]}, - {"label": f"used by {object_name} and used by a campaign attributed to {object_name}", "color": colorMap[3]} + {"label": f"{rel_type} a campaign attributed to {object_name}", "color": colorMap[2]}, + {"label": f"{rel_type} {object_name} and {rel_type} a campaign attributed to {object_name}", "color": colorMap[3]} ]) return layer diff --git a/modules/util/relationshipgetters.py b/modules/util/relationshipgetters.py index 8cd981f9a56..ff92451a1a0 100644 --- a/modules/util/relationshipgetters.py +++ b/modules/util/relationshipgetters.py @@ -11,6 +11,7 @@ techniques_used_by_tools = {} techniques_used_by_groups = {} techniques_used_by_campaigns = {} +techniques_targeting_assets = {} techniques_detected_by_datacomponent = {} groups_using_tool = {} groups_using_malware = {} @@ -20,6 +21,7 @@ tools_using_technique = {} malware_using_technique = {} groups_using_technique = {} +assets_targeted_by_techniques = {} campaigns_using_technique = {} campaigns_using_tool = {} campaigns_using_malware = {} @@ -44,6 +46,7 @@ datacomponent_list = [] mitigation_list = [] campaign_list = [] +asset_list = [] technique_to_domain = {} @@ -130,6 +133,25 @@ def get_techniques_used_by_campaigns(): return techniques_used_by_campaigns +def get_techniques_targeting_assets(): + """techniques targeting assets getter""" + global techniques_targeting_assets + + if not techniques_targeting_assets: + techniques_targeting_assets = rsh.techniques_targeting_assets(get_srcs()) + + return techniques_targeting_assets + + +def get_assets_targeted_by_techniques(): + """assets targeted by techniques getter""" + global assets_targeted_by_techniques + + if not assets_targeted_by_techniques: + assets_targeted_by_techniques = rsh.assets_targeted_by_techniques(get_srcs()) + + return assets_targeted_by_techniques + def get_techniques_detected_by_datacomponent(): global techniques_detected_by_datacomponent @@ -431,6 +453,15 @@ def get_campaign_list(): return campaign_list +def get_asset_list(): + """asset list getter""" + global asset_list + + if not asset_list: + asset_list = get_resources()["assets"] + + return asset_list + def get_technique_to_domain(): """technique to domain getter""" diff --git a/modules/util/relationshiphelpers.py b/modules/util/relationshiphelpers.py index 13f6ab734e1..3a8d42db620 100644 --- a/modules/util/relationshiphelpers.py +++ b/modules/util/relationshiphelpers.py @@ -247,6 +247,21 @@ def campaigns_attributed_to_group(srcs): return get_related(srcs, "campaign", "attributed-to", "intrusion-set", reverse=True) +# technique:asset +def techniques_targeting_assets(srcs): + """Return asset_id => {technique, relationship} for each technique targeting the asset. + + srcs should be an array of memorystores for enterprise, mobile, and pre + """ + return get_related(srcs, "attack-pattern", "targets", "x-mitre-asset", reverse=True) + +def assets_targeted_by_techniques(srcs): + """Return technique_id => {asset, relationship} for each asset targeted by the technique. + + srcs should be an array of memorystores for enterprise, mobile, and pre + """ + return get_related(srcs, "attack-pattern", "targets", "x-mitre-asset") + # technique:malware def techniques_used_by_malware(srcs): """Return malware => {technique, relationship} for each technique used by the malware. diff --git a/modules/util/stixhelpers.py b/modules/util/stixhelpers.py index 3925b04fdfa..165b4ac6388 100644 --- a/modules/util/stixhelpers.py +++ b/modules/util/stixhelpers.py @@ -359,6 +359,9 @@ def get_domain_resources(types): # Generates list of campaigns campaign_list = get_domain_resources(["campaign"]) + # Generates list of assets + asset_list = get_domain_resources(["x-mitre-asset"]) + # Generates list of relationships rel_list = [] for domain in site_config.domains: @@ -377,6 +380,7 @@ def get_domain_resources(types): "techniques": tech_list, "mitigations": coa_list, "campaigns": campaign_list, + "assets": asset_list } return resources @@ -430,6 +434,7 @@ def get_contributors(ms): "x-mitre-data-component", "x-mitre-data-source", "x-mitre-tactic", + "x-mitre-asset", ] src = ms[domain["name"]] obj_list = [] diff --git a/modules/website_build/website_build.py b/modules/website_build/website_build.py index 1270d672d3c..a4ef579bfe4 100644 --- a/modules/website_build/website_build.py +++ b/modules/website_build/website_build.py @@ -168,6 +168,7 @@ def generate_index_page(): "groups": "Group", "software": "Software", "campaigns": "Campaign", + "assets": "Asset", } routes = {} diff --git a/update-attack.py b/update-attack.py index 6bf29a89952..4b4fc048bdb 100644 --- a/update-attack.py +++ b/update-attack.py @@ -22,6 +22,7 @@ "tactics", "techniques", "campaigns", + "assets", "tour", "website_build", "random_page",