diff --git a/mappings/aws/attack-9.0/aws-09.21.2021/enterprise/aws-09.21.2021_attack-9.0-enterprise.json b/mappings/aws/attack-9.0/aws-09.21.2021/enterprise/aws-09.21.2021_attack-9.0-enterprise.json index a05ca383..954f8f2a 100644 --- a/mappings/aws/attack-9.0/aws-09.21.2021/enterprise/aws-09.21.2021_attack-9.0-enterprise.json +++ b/mappings/aws/attack-9.0/aws-09.21.2021/enterprise/aws-09.21.2021_attack-9.0-enterprise.json @@ -2403,7 +2403,7 @@ "status": "complete" }, { - "comments": null, + "comments": "This control is not mappable because it does not provide any security capabilities other than allowing for use of security features contained within AWS Organizations and AWS Identity and Access Management.", "attack_object_id": null, "attack_object_name": null, "references": null, @@ -4010,7 +4010,7 @@ "status": "complete" }, { - "comments": null, + "comments": "This control is not mappable because it does not provide any detection of malicious techniques. It primarily provides a way to log and record events within AWS which then can be piped to other security controls to determine if malicious activity has occurred.", "attack_object_id": null, "attack_object_name": null, "references": null, @@ -4024,7 +4024,7 @@ "status": "non_mappable" }, { - "comments": null, + "comments": "This control was not mapped because it is primarily acting as a connector for Microsoft Active Directory with AWS services and does not provide any security functions other than allowing use of other AWS security controls.", "attack_object_id": null, "attack_object_name": null, "references": null, @@ -4038,7 +4038,7 @@ "status": "non_mappable" }, { - "comments": null, + "comments": "This control was not mapped because AWS Artifact provides access to reports and information but does not protect against any ATT&CK techniques. All protections against ATT&CK techniques are provided by the lower-level services evaluated by and referenced in those reports.", "attack_object_id": null, "attack_object_name": null, "references": null, @@ -5214,7 +5214,7 @@ "status": "complete" }, { - "comments": null, + "comments": "Although this service can be scored as a Response control (Minimal/Data Enrichment/Forensics), due to the generic nature of its functionality, currently it does not look to be reasonably mappable to specific (sub-)techniques of MITRE ATT&CK.", "attack_object_id": null, "attack_object_name": null, "references": null, @@ -5914,7 +5914,7 @@ "status": "complete" }, { - "comments": null, + "comments": "This control was not mapped because AWS Firewall Manager is simply a management service for other AWS security services. It does not inherently protect against any ATT&CK (sub-)techniques. All protections against ATT&CK (sub-)techniques are provided by the lower-level services that it manages (e.g., AWS WAF, AWS Network Firewall, etc.). This is evident by the fact that to use firewall rules or security groups, they must first be configured in the respective lower-level services. ", "attack_object_id": null, "attack_object_name": null, "references": null, @@ -6110,7 +6110,7 @@ "status": "complete" }, { - "comments": null, + "comments": "This control was not mapped because AWS Certificate Manager simply issues certificates for use in other AWS services such as Elastic Load Balancing, Amazon CloudFront, AWS Elastic Beanstalk, Amazon API Gateway, AWS Nitro Enclaves, and AWS CloudFormation. It does not inherently protect against any ATT&CK techniques as it cannot be used to deploy certificates to other AWS services. That must be done either manually or with services integrated into AWS Certificate Manager.", "attack_object_id": null, "attack_object_name": null, "references": null, @@ -7082,7 +7082,7 @@ "status": "complete" }, { - "comments": null, + "comments": "This control was not mapped because AWS Audit Manager is used to aggregate evidence from other services in order to produce audit-ready reports, not provide protection against any ATT&CK techniques or adversary behaviors. All protections against ATT&CK techniques are provided by the lower-level services used for the evidence collection, which are assessed in different mappings.", "attack_object_id": null, "attack_object_name": null, "references": null, diff --git a/mappings/gcp/attack-10.0/gcp-06.28.2022/enterprise/gcp-06.28.2022_attack-10.0-enterprise.json b/mappings/gcp/attack-10.0/gcp-06.28.2022/enterprise/gcp-06.28.2022_attack-10.0-enterprise.json index 8a80b271..e5518912 100644 --- a/mappings/gcp/attack-10.0/gcp-06.28.2022/enterprise/gcp-06.28.2022_attack-10.0-enterprise.json +++ b/mappings/gcp/attack-10.0/gcp-06.28.2022/enterprise/gcp-06.28.2022_attack-10.0-enterprise.json @@ -67,7 +67,7 @@ }, "mapping_objects": [ { - "comments": null, + "comments": "Siemplify primarily acts as a layer for alerts generated by other controls to be collected and trigger mitigation and remediation actions to be taken by other controls provided by the Google Cloud Platform. On its own, Siemplify does not provide additional coverage of Attack techniques and is not mappable.", "attack_object_id": null, "attack_object_name": null, "references": null, @@ -171,7 +171,7 @@ "status": "complete" }, { - "comments": null, + "comments": "This is not a security control and the controls that fall under the Hybrid Connectivity umbrella have their own mapping files.", "attack_object_id": null, "attack_object_name": null, "references": null, @@ -185,7 +185,7 @@ "status": "non_mappable" }, { - "comments": null, + "comments": "This control was not mapped because Deployment Manager does not provide a security capability as a stand-alone tool and would require a 3rd party tool (e.g., Terraform) to mitigate denial of service type of cyber-attacks.", "attack_object_id": null, "attack_object_name": null, "references": null, @@ -215,7 +215,7 @@ "status": "complete" }, { - "comments": null, + "comments": "This control is not mappable because it does not provide significant detection of malicious techniques. Some of the other security controls that this control maps to are Azure DNS Analytics, AWS CloudTrail, AWS S3, and AWS Audit Manager. The S3 server access logging feature was not mapped because it was deemed to be a data source that can be used with other detective controls rather than a security control in of itself.", "attack_object_id": null, "attack_object_name": null, "references": null, @@ -769,7 +769,7 @@ "status": "complete" }, { - "comments": null, + "comments": "This control was not mapped because the Data Catalog service isn't considered a security control capable of defending against MITRE's ATT&CK techniques, and would require the use of a secondary product, such as DLP, for cyber defense.", "attack_object_id": null, "attack_object_name": null, "references": null, @@ -1106,7 +1106,7 @@ "status": "complete" }, { - "comments": null, + "comments": "This tool provides the functional ability to clone traffic, but is not considered a stand-alone security control as it requires a secondary security tool (e.g., IDS/IPS) to enable cyber defense and digital forensics.", "attack_object_id": null, "attack_object_name": null, "references": null, @@ -1120,7 +1120,7 @@ "status": "non_mappable" }, { - "comments": null, + "comments": "Assure workloads doesn't appear to provide any specific mitigation for TTPs. Rather, it focuses on enabling customers to apply other security controls in ways to support regulatory compliance. As a result, we have not mapped any TTPs to this control.\t", "attack_object_id": null, "attack_object_name": null, "references": null, @@ -1948,7 +1948,7 @@ "status": "complete" }, { - "comments": null, + "comments": "In its current state, this control was scored as not mappable as it does not look reasonable to correlate to specific (sub-) techniques of MITRE\u2019s ATT&CK.\n\nWhile Terraform provides some security capabilities specific to Terraform processes (encryption between Terraform Clients, encrypting workspace variables, \nIsolation between Terraform executions and Cloud tenants) the capabilities don't necessarily benefit the entire organization. Terraform's primary function is to support the provisioning of Google resources with configuration management. Therefore, this control has been identified as not-mappable.", "attack_object_id": null, "attack_object_name": null, "references": null, @@ -2172,7 +2172,7 @@ "status": "complete" }, { - "comments": null, + "comments": "This control doesn't appear to provide coverage for any ATT&CK Techniques.", "attack_object_id": null, "attack_object_name": null, "references": null, @@ -5806,7 +5806,7 @@ "status": "complete" }, { - "comments": null, + "comments": "This control was not mapped as it is not considered a security control but rather an alternative to deploying and managing Google Cloud.", "attack_object_id": null, "attack_object_name": null, "references": null, diff --git a/src/mapex_convert/parse_security_stack_mappings.py b/src/mapex_convert/parse_security_stack_mappings.py index 13474184..b8044238 100644 --- a/src/mapex_convert/parse_security_stack_mappings.py +++ b/src/mapex_convert/parse_security_stack_mappings.py @@ -60,7 +60,7 @@ def configure_security_stack_mappings(data, parsed_mappings): ] = data["name"] parsed_mappings["mapping_objects"].append( { - "comments": None, + "comments": data["comments"], "attack_object_id": None, "attack_object_name": None, "references": None, diff --git a/src/mappings_explorer/site_builder.py b/src/mappings_explorer/site_builder.py index c1dba3bf..76b7032c 100644 --- a/src/mappings_explorer/site_builder.py +++ b/src/mappings_explorer/site_builder.py @@ -22,6 +22,7 @@ class Capability: mappings = [] capability_group = "" num_mappings = 0 + non_mappable_comment = "" class Technique: @@ -70,6 +71,7 @@ class ExternalControl: capabilities = [] non_mappables = [] has_non_mappables = True + has_non_mappable_comments = False all_attack_versions = [ @@ -256,6 +258,7 @@ def load_projects(): aws.resources = [ {"link": "about/methodology/ssm-methodology/", "label": "Mapping Methodology"}, ] + aws.has_non_mappable_comments = True azure = ExternalControl() azure.id = "azure" @@ -276,6 +279,7 @@ def load_projects(): azure.resources = [ {"link": "about/methodology/ssm-methodology/", "label": "Mapping Methodology"}, ] + azure.has_non_mappable_comments = True gcp = ExternalControl() gcp.id = "gcp" @@ -298,6 +302,7 @@ def load_projects(): gcp.resources = [ {"link": "about/methodology/ssm-methodology/", "label": "Mapping Methodology"}, ] + gcp.has_non_mappable_comments = True projects = [ nist, @@ -382,7 +387,9 @@ def parse_capability_groups( g.capabilities = [] project.capability_groups.append(g) filtered_mappings = [ - m for m in mappings if (m.get("capability_group") == g.id) + m + for m in mappings + if (m.get("capability_group") == g.id and m["status"] != "non_mappable") ] g.num_mappings = len(filtered_mappings) g.mappings = filtered_mappings @@ -394,17 +401,24 @@ def parse_capability_groups( parse_capabilities( mappings, project, project_version, attack_version, attack_domain ) - mappings = [m for m in mappings if m["mapping_type"] != "non_mappable"] project.mappings.append( { "attack_version": attack_version, "project_version": project_version, "attack_domain": attack_domain, - "mappings": mappings, + "mappings": [m for m in mappings if m["status"] != "non_mappable"], } ) if project.id == "nist" or project.id == "cve": - for capability in project.capabilities: + # if the project has non mappable comments and we are therefore building the + # capability page even though it is non_mappable, get non_mappable capabilities' + # descriptions as well + capabilities_to_get_description = ( + project.capabilities + if not project.has_non_mappable_comments + else project.capabilities.extend(project.non_mappables) + ) + for capability in capabilities_to_get_description: get_description_for_capability(capability, project, project_version) if project.id == "aws" or project.id == "gcp" or project.id == "azure": get_security_stack_descriptions(project=project) @@ -422,12 +436,19 @@ def get_security_stack_descriptions(project: ExternalControl): if dir.lower() == project.id: rootdir = root / dir + capabilities = project.capabilities + # if the project has non mappable comments and we are therefore building the + # capability page even though it is non_mappable, get non_mappable capabilities' + # descriptions as well + if project.has_non_mappable_comments: + capabilities.extend(project.non_mappables) + # iterate through mappings files for file in os.listdir(rootdir): data = read_yaml_file(rootdir / file) name = data["name"] description = data["description"] - for c in project.capabilities: + for c in capabilities: if c.id.lower().replace(" ", "_") == name.lower().replace(" ", "_"): c.description = description c.label = data["name"] @@ -615,34 +636,59 @@ def parse_capabilities( for id in capabilityIds: c = Capability() c.id = id - c.mappings = [m for m in mappings if (m["capability_id"] == id)] - c.num_mappings = len(c.mappings) - c.label = c.mappings[0]["capability_description"] - for mapping in c.mappings: - mapping["project"] = project.id - mapping["project_version"] = project_version - mapping["attack_version"] = attack_version - mapping["attack_domain"] = attack_domain - if c.mappings[0].get("capability_group"): - capability_group = [ - g - for g in project.capability_groups - if (g.id == mapping["capability_group"]) - ] - capability_group[0].capabilities.append(c) - capability_group[0].num_capabilities += 1 - c.capability_group = capability_group[0] - else: - print(c.mappings[0]) - logger.trace( - "for capability {id} the number of mappings is {count}", - id=c.id, - count=str(len(c.mappings)), + + capability_mappable_mappings = [ + m + for m in mappings + if (m["capability_id"] == id) and m["status"] != "non_mappable" + ] + capability_non_mappables = [ + m + for m in mappings + if (m["capability_id"] == id) and m["status"] == "non_mappable" + ] + capability_not_mappable = ( + len(capability_mappable_mappings) == 0 and len(capability_non_mappables) > 0 ) - if project.has_non_mappables and c.mappings[0]["status"] == "non_mappable": - non_mappables.append(c) - else: + + c.num_mappings = len(capability_mappable_mappings) + c.mappings = capability_mappable_mappings + + if not capability_not_mappable: + c.label = capability_mappable_mappings[0]["capability_description"] + for mapping in capability_mappable_mappings: + mapping["project"] = project.id + mapping["project_version"] = project_version + mapping["attack_version"] = attack_version + mapping["attack_domain"] = attack_domain + if capability_mappable_mappings[0].get("capability_group"): + capability_group = [ + g + for g in project.capability_groups + if (g.id == mapping["capability_group"]) + ] + capability_group[0].capabilities.append(c) + capability_group[0].num_capabilities += 1 + c.capability_group = capability_group[0] + else: + print(capability_mappable_mappings[0]) + logger.trace( + "for capability {id} the number of mappings is {count}", + id=c.id, + count=str(len(c.mappings)), + ) capabilities.append(c) + else: + c.label = capability_non_mappables[0]["capability_description"] + if capability_non_mappables[0].get("capability_group"): + capability_group = [ + g + for g in project.capability_groups + if (g.id == capability_non_mappables[0]["capability_group"]) + ] + c.capability_group = capability_group[0] + c.non_mappable_comment = capability_non_mappables[0].get("comments", None) + non_mappables.append(c) project.non_mappables = non_mappables project.capabilities = capabilities @@ -722,6 +768,13 @@ def build_external_landing( ("id", "Capability ID"), ("label", "Capability Description"), ] + + if project.has_non_mappable_comments: + non_mappable_headers = [ + ("id", "Capability ID", "id", external_prefix), + ("label", "Capability Description", "id", external_prefix), + ] + project_id = project.id if project_id == "nist": project_id = "nist_800_53" @@ -740,7 +793,7 @@ def build_external_landing( mappings=mappings, headers=headers, group_headers=capability_group_headers, - capability_groups=project.capability_groups, + capability_groups=[g for g in project.capability_groups if g.num_mappings > 0], valid_versions=project.validVersions, breadcrumbs=breadcrumbs, non_mappable_headers=non_mappable_headers, @@ -765,32 +818,29 @@ def build_external_landing( capability_group_dir = domain_dir / "capability-groups" previous_link = external_prefix for capability_group in project.capability_groups: - nav = breadcrumbs + [ - ( - f"{external_prefix}capability-groups/{capability_group.id}/", - f"{capability_group.label} Capability Group", + if capability_group.num_mappings > 0: + nav = breadcrumbs + [ + ( + f"{external_prefix}capability-groups/{capability_group.id}/", + f"{capability_group.label} Capability Group", + ) + ] + build_capability_group( + project=project, + capability_group=capability_group, + url_prefix=url_prefix, + parent_dir=capability_group_dir, + project_version=project_version, + attack_version=attack_version, + headers=headers, + attack_domain=attack_domain, + breadcrumbs=nav, + capability_group_headers=capability_group_headers, + previous_link=previous_link, ) - ] - build_capability_group( - project=project, - capability_group=capability_group, - url_prefix=url_prefix, - parent_dir=capability_group_dir, - project_version=project_version, - attack_version=attack_version, - headers=headers, - attack_domain=attack_domain, - breadcrumbs=nav, - capability_group_headers=capability_group_headers, - previous_link=previous_link, - ) for capability in project.capabilities: if capability.capability_group: capability_nav = breadcrumbs + [ - ( - f"{external_prefix}capability-groups/{capability.capability_group.id}/", - f"{capability.capability_group.label} Capability Group", - ), ( f"{external_prefix}{capability.id}/", f"{capability.label if capability.label else capability.id}", @@ -808,6 +858,26 @@ def build_external_landing( breadcrumbs=capability_nav, previous_link=previous_link, ) + if project.has_non_mappable_comments: + for non_mappable in project.non_mappables: + capability_nav = breadcrumbs + [ + ( + f"{external_prefix}{non_mappable.id}/", + f"{non_mappable.label if non_mappable.label else non_mappable.id}", + ), + ] + build_external_capability( + project=project, + url_prefix=url_prefix, + parent_dir=domain_dir, + project_version=project_version, + attack_version=attack_version, + headers=headers, + capability=non_mappable, + attack_domain=attack_domain, + breadcrumbs=capability_nav, + previous_link=previous_link, + ) def build_external_pages(projects: list, url_prefix: str, breadcrumbs: list): @@ -847,6 +917,7 @@ def build_external_pages(projects: list, url_prefix: str, breadcrumbs: list): for m in project.mappings if m["attack_version"] == attack_version and m["project_version"] == project_version + and m["attack_domain"] == attack_domain ][0] mappings = m["mappings"] logger.trace("project parsed successfully") @@ -1000,6 +1071,7 @@ def parse_techniques( m for m in project.mappings if float(m["attack_version"]) == float(attack_version) + and m["attack_domain"] == attack_domain ] if len(m) > 0: m = m[len(m) - 1] diff --git a/src/mappings_explorer/templates/capability.html.j2 b/src/mappings_explorer/templates/capability.html.j2 index 6b038272..e36354bb 100644 --- a/src/mappings_explorer/templates/capability.html.j2 +++ b/src/mappings_explorer/templates/capability.html.j2 @@ -13,9 +13,11 @@

{{project.label}} {{ capability.id }} Mappings

{{description | safe}} +

+ {{capability.non_mappable_comment}} +

-