From 7fbd92dc4c8af00a98b6c75156cc625f69e08761 Mon Sep 17 00:00:00 2001 From: Harold Blankenship <36673698+hblankenship@users.noreply.github.com> Date: Thu, 21 Nov 2024 23:21:03 -0600 Subject: [PATCH 1/7] save reviewers (#11289) --- dojo/models.py | 3 ++- dojo/templatetags/display_tags.py | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/dojo/models.py b/dojo/models.py index 117b8d0ebe..9668d299aa 100644 --- a/dojo/models.py +++ b/dojo/models.py @@ -4613,7 +4613,7 @@ def __str__(self): auditlog.register(Dojo_User, exclude_fields=["password"]) auditlog.register(Endpoint) auditlog.register(Engagement) - auditlog.register(Finding) + auditlog.register(Finding, m2m_fields={"reviewers"}) auditlog.register(Finding_Group) auditlog.register(Product_Type) auditlog.register(Product) @@ -4623,6 +4623,7 @@ def __str__(self): auditlog.register(Cred_User, exclude_fields=["password"]) auditlog.register(Notification_Webhooks, exclude_fields=["header_name", "header_value"]) + from dojo.utils import calculate_grade, to_str_typed # noqa: E402 # there is issue due to a circular import tagulous.admin.register(Product.tags) diff --git a/dojo/templatetags/display_tags.py b/dojo/templatetags/display_tags.py index cd3627d41b..79050985d6 100644 --- a/dojo/templatetags/display_tags.py +++ b/dojo/templatetags/display_tags.py @@ -328,7 +328,10 @@ def action_log_entry(value, autoescape=None): history = json.loads(value) text = "" for k in history.keys(): - text += k.capitalize() + ' changed from "' + \ + if isinstance(history[k], dict): + text += k.capitalize() + " operation: " + history[k].get("operation", "unknown") + ": " + str(history[k].get("objects", "unknown")) + else: + text += k.capitalize() + ' changed from "' + \ history[k][0] + '" to "' + history[k][1] + '"\n' return text From 3f125925ad438e87125b2a1be5f86f66a5835ded Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Fri, 22 Nov 2024 06:21:41 +0100 Subject: [PATCH 2/7] :tada: Add Trivy Operator clustercompliance report (#11279) --- .../clustercompliance_handler.py | 45 ++++++++ dojo/tools/trivy_operator/parser.py | 17 ++- .../clustercompliancereport.json | 105 ++++++++++++++++++ unittests/tools/test_trivy_operator_parser.py | 6 + 4 files changed, 167 insertions(+), 6 deletions(-) create mode 100644 dojo/tools/trivy_operator/clustercompliance_handler.py create mode 100644 unittests/scans/trivy_operator/clustercompliancereport.json diff --git a/dojo/tools/trivy_operator/clustercompliance_handler.py b/dojo/tools/trivy_operator/clustercompliance_handler.py new file mode 100644 index 0000000000..5f56872549 --- /dev/null +++ b/dojo/tools/trivy_operator/clustercompliance_handler.py @@ -0,0 +1,45 @@ +from dojo.models import Finding + +TRIVY_SEVERITIES = { + "CRITICAL": "Critical", + "HIGH": "High", + "MEDIUM": "Medium", + "LOW": "Low", + "UNKNOWN": "Info", +} + + +class TrivyClusterComplianceHandler: + def handle_clustercompliance(self, controls, clustercompliance, test): + findings = [] + for result in clustercompliance.get("controlCheck"): + if int(result.get("totalFail", 0)) > 0: + description = "" + result_id = result.get("id", "") + vulnerabilityids = [] + for control in controls: + if control.get("id") == result_id: + vulnids = control.get("checks", []) + for vulnid in vulnids: + vulnerabilityids.append(vulnid.get("id")) + description += "**description:** " + control.get("description") + "\n" + result_name = result.get("name", "") + result_severity = result.get("severity", "") + result_totalfail = str(result.get("totalFail", "")) + severity = TRIVY_SEVERITIES[result_severity] + description += "**id:** " + result_id + "\n" + description += "**name:** " + result_name + "\n" + description += "**totalfail:** " + result_totalfail + "\n" + title = "TrivyClusterCompliance " + result_id + " totalFail: " + result_totalfail + finding = Finding( + test=test, + title=title, + description=description, + severity=severity, + static_finding=False, + dynamic_finding=True, + ) + if vulnerabilityids != []: + finding.unsaved_vulnerability_ids = vulnerabilityids + findings.append(finding) + return findings diff --git a/dojo/tools/trivy_operator/parser.py b/dojo/tools/trivy_operator/parser.py index ba7cc730a1..6ba1be3f15 100644 --- a/dojo/tools/trivy_operator/parser.py +++ b/dojo/tools/trivy_operator/parser.py @@ -3,6 +3,7 @@ import json from dojo.tools.trivy_operator.checks_handler import TrivyChecksHandler +from dojo.tools.trivy_operator.clustercompliance_handler import TrivyClusterComplianceHandler from dojo.tools.trivy_operator.compliance_handler import TrivyComplianceHandler from dojo.tools.trivy_operator.secrets_handler import TrivySecretsHandler from dojo.tools.trivy_operator.vulnerability_handler import TrivyVulnerabilityHandler @@ -38,6 +39,7 @@ def get_findings(self, scan_file, test): return findings def output_findings(self, data, test): + findings = [] if data is None: return [] metadata = data.get("metadata", None) @@ -47,10 +49,6 @@ def output_findings(self, data, test): if labels is None: return [] report = data.get("report", None) - benchmark = data.get("status", None) - if benchmark is not None: - benchmarkreport = benchmark.get("detailReport", None) - findings = [] if report is not None: vulnerabilities = report.get("vulnerabilities", None) if vulnerabilities is not None: @@ -61,6 +59,13 @@ def output_findings(self, data, test): secrets = report.get("secrets", None) if secrets is not None: findings += TrivySecretsHandler().handle_secrets(labels, secrets, test) - elif benchmarkreport is not None: - findings += TrivyComplianceHandler().handle_compliance(benchmarkreport, test) + status = data.get("status", None) + if status is not None: + benchmarkreport = status.get("detailReport", None) + if benchmarkreport is not None: + findings += TrivyComplianceHandler().handle_compliance(benchmarkreport, test) + clustercompliance = status.get("summaryReport", None) + if clustercompliance is not None: + if int(status.get("summary").get("failCount", 0)) > 0: + findings += TrivyClusterComplianceHandler().handle_clustercompliance(controls=data.get("spec").get("compliance").get("controls"), clustercompliance=clustercompliance, test=test) return findings diff --git a/unittests/scans/trivy_operator/clustercompliancereport.json b/unittests/scans/trivy_operator/clustercompliancereport.json new file mode 100644 index 0000000000..c3f5656b20 --- /dev/null +++ b/unittests/scans/trivy_operator/clustercompliancereport.json @@ -0,0 +1,105 @@ +[ + { + "apiVersion": "aquasecurity.github.io/v1alpha1", + "kind": "ClusterComplianceReport", + "metadata": { + "annotations": { + "meta.helm.sh/release-name": "trivy-operator-asdf", + "meta.helm.sh/release-namespace": "asdf" + }, + "creationTimestamp": "2021-1-1T10:10:10Z", + "generation": 1, + "labels": { + "app.kubernetes.io/instance": "trivy-operator", + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "trivy-operator", + "app.kubernetes.io/version": "0.17.1" + }, + "name": "cis", + "resourceVersion": "asdf", + "uid": "afsewfwefwfw" + }, + "spec": { + "compliance": { + "controls": [ + { + "checks": [ + { + "id": "AVD-KCV-0087" + } + ], + "description": "Security relevant information should be captured. The --event-qps flag on the Kubelet can be used to limit the rate at which events are gathered", + "id": "4.2.9", + "name": "Ensure that the --event-qps argument is set to 0 or a level which ensures appropriate event capture", + "severity": "HIGH" + }, + { + "checks": [ + { + "id": "AVD-KCV-0088" + }, + { + "id": "AVD-KCV-0089" + } + ], + "description": "Setup TLS connection on the Kubelets", + "id": "4.2.10", + "name": "Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate", + "severity": "CRITICAL" + }, + { + "checks": [ + { + "id": "AVD-KCV-0090" + } + ], + "description": "Enable kubelet client certificate rotation", + "id": "4.2.11", + "name": "Ensure that the --rotate-certificates argument is not set to false", + "severity": "CRITICAL" + } + ], + "description": "CIS Kubernetes Benchmarks", + "id": "cis", + "relatedResources": [ + "https://www.cisecurity.org/benchmark/kubernetes" + ], + "title": "CIS Kubernetes Benchmarks v1.23", + "version": "1.0" + }, + "cron": "0 */6 * * *", + "reportType": "summary" + }, + "status": { + "summary": { + "failCount": 2, + "passCount": 1 + }, + "summaryReport": { + "controlCheck": [ + { + "id": "4.2.9", + "name": "Ensure that the --event-qps argument is set to 0 or a level which ensures appropriate event capture", + "severity": "HIGH", + "totalFail": 0 + }, + { + "id": "4.2.10", + "name": "Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate", + "severity": "CRITICAL", + "totalFail": 10 + }, + { + "id": "4.2.11", + "name": "Ensure that the --rotate-certificates argument is not set to false", + "severity": "CRITICAL", + "totalFail": 5 + } + ], + "id": "cis", + "title": "CIS Kubernetes Benchmarks v1.23" + }, + "updateTimestamp": "2021-1-1T10:10:10Z" + } + } + ] \ No newline at end of file diff --git a/unittests/tools/test_trivy_operator_parser.py b/unittests/tools/test_trivy_operator_parser.py index 0acf11cb70..2c657d5bae 100644 --- a/unittests/tools/test_trivy_operator_parser.py +++ b/unittests/tools/test_trivy_operator_parser.py @@ -163,3 +163,9 @@ def test_findings_all_reports_in_dict(self): parser = TrivyOperatorParser() findings = parser.get_findings(test_file, Test()) self.assertEqual(len(findings), 43) + + def test_findings_clustercompliancereport(self): + with open(sample_path("clustercompliancereport.json"), encoding="utf-8") as test_file: + parser = TrivyOperatorParser() + findings = parser.get_findings(test_file, Test()) + self.assertEqual(len(findings), 2) From 1111ff68e249c437ba4394af8c7e3637a2dcdbd8 Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Fri, 22 Nov 2024 06:22:19 +0100 Subject: [PATCH 3/7] Ruff: Fix Ruff FURB189 on bugfix (#11290) * Ruff: Fix FURB189 on bugfix * Update dojo/api_v2/serializers.py Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> * fix * Update dojo/api_v2/serializers.py Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> * Update dojo/api_v2/serializers.py Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> * Update dojo/api_v2/serializers.py Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> * ruff --------- Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> --- dojo/api_v2/serializers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dojo/api_v2/serializers.py b/dojo/api_v2/serializers.py index 6e100f43b5..00861b1b55 100644 --- a/dojo/api_v2/serializers.py +++ b/dojo/api_v2/serializers.py @@ -1,3 +1,4 @@ +import collections import json import logging import re @@ -280,10 +281,10 @@ def _pop_tags(self, validated_data): return (to_be_tagged, validated_data) -class RequestResponseDict(list): +class RequestResponseDict(collections.UserList): def __init__(self, *args, **kwargs): pretty_print = kwargs.pop("pretty_print", True) - list.__init__(self, *args, **kwargs) + collections.UserList.__init__(self, *args, **kwargs) self.pretty_print = pretty_print def __add__(self, rhs): From 7dfc46cb5a5fe0f2502229fc5a8071822ef092ad Mon Sep 17 00:00:00 2001 From: Harold Blankenship <36673698+hblankenship@users.noreply.github.com> Date: Thu, 21 Nov 2024 23:25:48 -0600 Subject: [PATCH 4/7] AnchoreCTL Policies: Additional checks for severity in description (#11269) * change severity and active * Include UNKNOWN option * status, not gate * And add unittest * newline --- dojo/tools/anchorectl_policies/parser.py | 29 +++++++++++++++---- .../one_violation_description_severity.json | 23 +++++++++++++++ .../tools/test_anchorectl_policies_parser.py | 10 +++++++ 3 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 unittests/scans/anchorectl_policies/one_violation_description_severity.json diff --git a/dojo/tools/anchorectl_policies/parser.py b/dojo/tools/anchorectl_policies/parser.py index 818b21d1c2..381edecb75 100644 --- a/dojo/tools/anchorectl_policies/parser.py +++ b/dojo/tools/anchorectl_policies/parser.py @@ -40,7 +40,7 @@ def get_findings(self, filename, test): image_name = result["tag"] trigger_id = result["triggerId"] repo, tag = image_name.split(":", 2) - severity = map_gate_action_to_severity(status) + severity, active = get_severity(status, description) vulnerability_id = extract_vulnerability_id(trigger_id) title = ( policy_id @@ -54,6 +54,7 @@ def get_findings(self, filename, test): test=test, description=description, severity=severity, + active=active, references=f"Policy ID: {policy_id}\nTrigger ID: {trigger_id}", file_path=search_filepath(description), component_name=repo, @@ -77,14 +78,32 @@ def get_findings(self, filename, test): return items -def map_gate_action_to_severity(gate): +def map_gate_action_to_severity(status): gate_action_to_severity = { "stop": "Critical", "warn": "Medium", } - if gate in gate_action_to_severity: - return gate_action_to_severity[gate] - return "Low" + if status in gate_action_to_severity: + return gate_action_to_severity[status], True + + return "Low", True + + +def get_severity(status, description): + parsed_severity = description.split()[0] + valid_severities = ["LOW", "INFO", "UNKNOWN", "CRITICAL", "MEDIUM"] + if parsed_severity in valid_severities: + severity = "Info" + if parsed_severity == "UNKNOWN": + severity = "Info" + elif status != "go": + severity = parsed_severity.lower().capitalize() + + active = False if status == "go" else True + + return severity, active + + return map_gate_action_to_severity(status) def policy_name(policies, policy_id): diff --git a/unittests/scans/anchorectl_policies/one_violation_description_severity.json b/unittests/scans/anchorectl_policies/one_violation_description_severity.json new file mode 100644 index 0000000000..2ae889fdf5 --- /dev/null +++ b/unittests/scans/anchorectl_policies/one_violation_description_severity.json @@ -0,0 +1,23 @@ +[ + { + "detail": [ + { + "description": "CRITICAL User root found as effective user, which is not on the allowed list", + "gate": "dockerfile", + "imageId": "d26f0119b9634091a541b081dd8bdca435ab52e114e4b4328575c0bc2c69768b", + "policyId": "RootUser", + "status": "warn", + "tag": "test/testimage:testtag", + "triggerId": "b2605c2ddbdb02b8e2365c9248dada5a", + "triggerName": "effective_user" + } + ], + "digest": "sha256:8htz0bf942cfcd6hg8cf6435afd318b65d23e4c1a80044304c6e3ed20", + "finalAction": "stop", + "finalActionReason": "policy_evaluation", + "lastEvaluation": "2022-09-20T08:25:52Z", + "policyId": "9e104ade-7b57-4cdc-93fb-4949bf3b36b6", + "status": "fail", + "tag": "test/testimage:testtag" + } +] \ No newline at end of file diff --git a/unittests/tools/test_anchorectl_policies_parser.py b/unittests/tools/test_anchorectl_policies_parser.py index c8bdb4b4ef..1ad4eb91cc 100644 --- a/unittests/tools/test_anchorectl_policies_parser.py +++ b/unittests/tools/test_anchorectl_policies_parser.py @@ -25,3 +25,13 @@ def test_anchore_engine_parser_has_many_findings(self): parser = AnchoreCTLPoliciesParser() findings = parser.get_findings(testfile, Test()) self.assertEqual(3, len(findings)) + + def test_anchore_engine_parser_has_one_finding_and_description_has_severity(self): + with open("unittests/scans/anchorectl_policies/one_violation_description_severity.json", encoding="utf-8") as testfile: + parser = AnchoreCTLPoliciesParser() + findings = parser.get_findings(testfile, Test()) + self.assertEqual(1, len(findings)) + singleFinding = findings[0] + self.assertEqual(singleFinding.severity, "Critical") + self.assertEqual(singleFinding.title, "RootUser - gate|dockerfile - trigger|b2605c2ddbdb02b8e2365c9248dada5a") + self.assertEqual(singleFinding.description, "CRITICAL User root found as effective user, which is not on the allowed list") From 43833fa577e835008e85b10dfc246a3fe96c8c07 Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Fri, 22 Nov 2024 06:27:07 +0100 Subject: [PATCH 5/7] :bug: fix trivyoperator tags (#11276) * :bug: fix trivyoperator tags * ruff * fix unittest * review * ruff --- dojo/tools/trivy_operator/checks_handler.py | 3 ++- dojo/tools/trivy_operator/secrets_handler.py | 3 ++- dojo/tools/trivy_operator/vulnerability_handler.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/dojo/tools/trivy_operator/checks_handler.py b/dojo/tools/trivy_operator/checks_handler.py index 2a260ff568..66e4199cfb 100644 --- a/dojo/tools/trivy_operator/checks_handler.py +++ b/dojo/tools/trivy_operator/checks_handler.py @@ -45,8 +45,9 @@ def handle_checks(self, labels, checks, test): static_finding=True, dynamic_finding=False, service=service, - tags=[resource_namespace], ) + if resource_namespace != "": + finding.tags = resource_namespace if check_id: finding.unsaved_vulnerability_ids = [UniformTrivyVulnID().return_uniformed_vulnid(check_id)] findings.append(finding) diff --git a/dojo/tools/trivy_operator/secrets_handler.py b/dojo/tools/trivy_operator/secrets_handler.py index 6509835b4f..018c3650c3 100644 --- a/dojo/tools/trivy_operator/secrets_handler.py +++ b/dojo/tools/trivy_operator/secrets_handler.py @@ -53,7 +53,8 @@ def handle_secrets(self, labels, secrets, test): static_finding=True, dynamic_finding=False, service=service, - tags=[resource_namespace], ) + if resource_namespace != "": + finding.tags = resource_namespace findings.append(finding) return findings diff --git a/dojo/tools/trivy_operator/vulnerability_handler.py b/dojo/tools/trivy_operator/vulnerability_handler.py index 99faa009d1..54a951cdc3 100644 --- a/dojo/tools/trivy_operator/vulnerability_handler.py +++ b/dojo/tools/trivy_operator/vulnerability_handler.py @@ -83,7 +83,7 @@ def handle_vulns(self, labels, vulnerabilities, test): dynamic_finding=False, service=service, file_path=file_path, - tags=finding_tags, + tags=[tag for tag in finding_tags if tag != ""], ) if vuln_id: finding.unsaved_vulnerability_ids = [UniformTrivyVulnID().return_uniformed_vulnid(vuln_id)] From b26751e55f6bd952c41e266b477a3d8ab6caecba Mon Sep 17 00:00:00 2001 From: Harold Blankenship <36673698+hblankenship@users.noreply.github.com> Date: Thu, 21 Nov 2024 23:28:22 -0600 Subject: [PATCH 6/7] Update CheckMarx One parser for imports where description is None (#11308) * fix case where description is none * switch to using queryName instead of id * add unittest --- dojo/tools/checkmarx_one/parser.py | 9 + .../checkmarx_one_format_two.json | 190 ++++++++++++++++++ unittests/tools/test_checkmarx_one_parser.py | 12 ++ 3 files changed, 211 insertions(+) create mode 100644 unittests/scans/checkmarx_one/checkmarx_one_format_two.json diff --git a/dojo/tools/checkmarx_one/parser.py b/dojo/tools/checkmarx_one/parser.py index 7a85cd521d..f1a673c52d 100644 --- a/dojo/tools/checkmarx_one/parser.py +++ b/dojo/tools/checkmarx_one/parser.py @@ -262,6 +262,9 @@ def get_results_sast( description = vulnerability.get("description") file_path = vulnerability.get("data").get("nodes")[0].get("fileName") unique_id_from_tool = vulnerability.get("id", vulnerability.get("similarityId")) + if description is None: + description = vulnerability.get("severity").title() + " " + vulnerability.get("data").get("queryName").replace("_", " ") + return Finding( description=description, title=description, @@ -280,6 +283,9 @@ def get_results_kics( description = vulnerability.get("description") file_path = vulnerability.get("data").get("filename", vulnerability.get("data").get("fileName")) unique_id_from_tool = vulnerability.get("id", vulnerability.get("similarityId")) + if description is None: + description = vulnerability.get("severity").title() + " " + vulnerability.get("data").get("queryName").replace("_", " ") + return Finding( title=description, description=description, @@ -298,6 +304,9 @@ def get_results_sca( ) -> Finding: description = vulnerability.get("description") unique_id_from_tool = vulnerability.get("id", vulnerability.get("similarityId")) + if description is None: + description = vulnerability.get("severity").title() + " " + vulnerability.get("data").get("queryName").replace("_", " ") + finding = Finding( title=description, description=description, diff --git a/unittests/scans/checkmarx_one/checkmarx_one_format_two.json b/unittests/scans/checkmarx_one/checkmarx_one_format_two.json new file mode 100644 index 0000000000..246c45a339 --- /dev/null +++ b/unittests/scans/checkmarx_one/checkmarx_one_format_two.json @@ -0,0 +1,190 @@ +{ + "results": [ + { + "type": "sast", + "label": "sast", + "id": "1ZOFSPJzlZAqW4XH/43v0l2qI7w=", + "similarityId": "587440289", + "status": "RECURRENT", + "state": "TO_VERIFY", + "severity": "LOW", + "created": "2024-11-18T15:05:11Z", + "firstFoundAt": "2024-07-22T14:05:10Z", + "foundAt": "2024-11-18T15:05:11Z", + "firstScanId": "6f25a9f8-551f-4601-923f-d8582b3c57b9", + "data": { + "queryId": 9509477347196366877, + "queryName": "Insufficiently_Protected_Credentials", + "group": "Java_Low_Visibility", + "resultHash": "1ZOFSPJzlZAqW4XH/43v0l2qI7w=", + "languageName": "Java", + "nodes": [ + { + "id": "UxUup49ByptYWuChHWZoBchsZd8=", + "line": 24, + "name": "query", + "column": 30, + "length": 1, + "nodeID": 67173, + "fileName": "/src/main/java/org/owasp/webgoat/lessons/missingac/MissingAccessControlUserRepository.java", + "fullName": "org.owasp.webgoat.lessons.missingac.MissingAccessControlUserRepository.jdbcTemplate.query", + "methodLine": 23 + }, + { + "id": "eDR+tHqxvcYE2rgp7B3f983Dq04=", + "line": 57, + "name": "findAllUsers", + "column": 54, + "length": 1, + "nodeID": 67713, + "fileName": "/src/main/java/org/owasp/webgoat/lessons/missingac/MissingFunctionACUsers.java", + "fullName": "org.owasp.webgoat.lessons.missingac.MissingFunctionACUsers.userRepository.findAllUsers", + "methodLine": 53 + }, + { + "id": "/hXgmF9sr5y4seOsFuhCqb1lEtk=", + "line": 57, + "name": "allUsers", + "column": 16, + "length": 8, + "nodeID": 67709, + "fileName": "/src/main/java/org/owasp/webgoat/lessons/missingac/MissingFunctionACUsers.java", + "fullName": "org.owasp.webgoat.lessons.missingac.MissingFunctionACUsers.listUsers.allUsers", + "methodLine": 53 + }, + { + "id": "oMZ/Q99zBPxILDltpl6l3ddtR0A=", + "line": 58, + "name": "allUsers", + "column": 33, + "length": 8, + "nodeID": 67734, + "fileName": "/src/main/java/org/owasp/webgoat/lessons/missingac/MissingFunctionACUsers.java", + "fullName": "org.owasp.webgoat.lessons.missingac.MissingFunctionACUsers.listUsers.allUsers", + "methodLine": 53 + }, + { + "id": "VfswcWP4EzOl6XMvPn8SkMTrSvc=", + "line": 61, + "name": "allUsers", + "column": 22, + "length": 8, + "nodeID": 67759, + "fileName": "/src/main/java/org/owasp/webgoat/lessons/missingac/MissingFunctionACUsers.java", + "fullName": "org.owasp.webgoat.lessons.missingac.MissingFunctionACUsers.listUsers.allUsers", + "methodLine": 53 + }, + { + "id": "CnToQc0fEqfrjai8Mo8iUroxv68=", + "line": 61, + "name": "user", + "column": 15, + "length": 4, + "nodeID": 67785, + "fileName": "/src/main/java/org/owasp/webgoat/lessons/missingac/MissingFunctionACUsers.java", + "fullName": "org.owasp.webgoat.lessons.missingac.MissingFunctionACUsers.listUsers.user", + "methodLine": 53 + }, + { + "id": "/ui2MhZkLzZXPFwpCiDhUBLLaU8=", + "line": 62, + "name": "user", + "column": 40, + "length": 4, + "nodeID": 67776, + "fileName": "/src/main/java/org/owasp/webgoat/lessons/missingac/MissingFunctionACUsers.java", + "fullName": "org.owasp.webgoat.lessons.missingac.MissingFunctionACUsers.listUsers.user", + "methodLine": 53 + }, + { + "id": "3F7euZ73MP4t3ztmwv21yChSdtw=", + "line": 42, + "name": "user", + "column": 27, + "length": 4, + "nodeID": 66862, + "fileName": "/src/main/java/org/owasp/webgoat/lessons/missingac/DisplayUser.java", + "fullName": "org.owasp.webgoat.lessons.missingac.DisplayUser.DisplayUser.user", + "methodLine": 42 + }, + { + "id": "F8Q12rQW6CQtmEFwW3SWNhVYVMg=", + "line": 43, + "name": "user", + "column": 21, + "length": 4, + "nodeID": 66874, + "fileName": "/src/main/java/org/owasp/webgoat/lessons/missingac/DisplayUser.java", + "fullName": "org.owasp.webgoat.lessons.missingac.DisplayUser.DisplayUser.user", + "methodLine": 42 + }, + { + "id": "rAn4QPxFVCelr/RJLQqicCn60es=", + "line": 44, + "name": "user", + "column": 18, + "length": 4, + "nodeID": 66884, + "fileName": "/src/main/java/org/owasp/webgoat/lessons/missingac/DisplayUser.java", + "fullName": "org.owasp.webgoat.lessons.missingac.DisplayUser.DisplayUser.user", + "methodLine": 42 + }, + { + "id": "ZAMbHREBMi+/+KFaFf1dk1iD3pc=", + "line": 47, + "name": "user", + "column": 55, + "length": 4, + "nodeID": 66911, + "fileName": "/src/main/java/org/owasp/webgoat/lessons/missingac/DisplayUser.java", + "fullName": "org.owasp.webgoat.lessons.missingac.DisplayUser.DisplayUser.user", + "methodLine": 42 + }, + { + "id": "W1j8VcWjryKeRe0m9I7TQoTK56s=", + "line": 47, + "name": "getPassword", + "column": 71, + "length": 1, + "nodeID": 66914, + "fileName": "/src/main/java/org/owasp/webgoat/lessons/missingac/DisplayUser.java", + "fullName": "org.owasp.webgoat.lessons.missingac.DisplayUser.DisplayUser.user.getPassword", + "methodLine": 42 + }, + { + "id": "0KiaSGj0VNOrzTBqcbiZOFvIoYE=", + "line": 53, + "name": "password", + "column": 56, + "length": 8, + "nodeID": 67027, + "fileName": "/src/main/java/org/owasp/webgoat/lessons/missingac/DisplayUser.java", + "fullName": "org.owasp.webgoat.lessons.missingac.DisplayUser.genUserHash.password", + "methodLine": 53 + } + ] + }, + "comments": {}, + "vulnerabilityDetails": { + "cweId": 522, + "cvss": {}, + "compliances": [ + "OWASP Top 10 2021", + "FISMA 2014", + "MOIS(KISA) Secure Coding 2021", + "OWASP Top 10 2017", + "PCI DSS v3.2.1", + "ASD STIG 5.3", + "CWE top 25", + "NIST SP 800-53", + "OWASP ASVS", + "OWASP Top 10 2013", + "SANS top 25", + "PCI DSS v4.0" + ] + } + } + ], + "totalCount": 1, + "scanID": "7083ee4e-2eff-4e2f-9d98-1aae8023169f" +} \ No newline at end of file diff --git a/unittests/tools/test_checkmarx_one_parser.py b/unittests/tools/test_checkmarx_one_parser.py index f2cde3169c..2c1efcce11 100644 --- a/unittests/tools/test_checkmarx_one_parser.py +++ b/unittests/tools/test_checkmarx_one_parser.py @@ -68,6 +68,18 @@ def test_checkmarx_one_sca_10770(self): self.assertEqual("High", finding_test.severity) self.assertEqual(89, finding_test.cwe) + def test_checkmarx_one_no_description(self): + with open("unittests/scans/checkmarx_one/checkmarx_one_format_two.json", encoding="utf-8") as testfile: + parser = CheckmarxOneParser() + findings = parser.get_findings(testfile, Test()) + self.assertEqual(1, len(findings)) + with self.subTest(i=0): + for finding in findings: + self.assertIsNotNone(finding.title) + self.assertIsNotNone(finding.description) + finding_test = findings[0] + self.assertEqual("Low", finding_test.severity) + def test_checkmarx_vulnerabilities_from_scan_results(self): def test_iac_finding(finding): self.assertEqual("Dockerfile: Healthcheck Instruction Missing", finding.title) From bead48ce58256d01b43c20f48cdef992f4de0ba7 Mon Sep 17 00:00:00 2001 From: Dmitrii Mariushkin Date: Fri, 22 Nov 2024 08:29:31 +0300 Subject: [PATCH 7/7] Mobsfscan report files parsing fix (#11278) * Fix multi files parsing * Fix multi files parsing * Fix multi files parsing --------- Co-authored-by: Dmitry Maryushkin --- dojo/settings/.settings.dist.py.sha256sum | 2 +- dojo/settings/settings.dist.py | 2 +- dojo/tools/mobsfscan/parser.py | 52 +++++++++++++------- unittests/scans/mobsfscan/many_findings.json | 12 +++++ unittests/tools/test_mobsfscan_parser.py | 29 +++++++---- 5 files changed, 67 insertions(+), 30 deletions(-) diff --git a/dojo/settings/.settings.dist.py.sha256sum b/dojo/settings/.settings.dist.py.sha256sum index 6e1a934f57..3f596c0890 100644 --- a/dojo/settings/.settings.dist.py.sha256sum +++ b/dojo/settings/.settings.dist.py.sha256sum @@ -1 +1 @@ -f85484f23e59aabe591b30db10e0de05aaeeb9d8979d236d565dc8279e03e116 +ea62fff7e384d73568e1293c739ce1a116e090cd4550a403ac272821a7785e0b diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py index c5453c018d..57cfafee1e 100644 --- a/dojo/settings/settings.dist.py +++ b/dojo/settings/settings.dist.py @@ -1213,7 +1213,7 @@ def saml2_attrib_map_format(dict): "Dependency Check Scan": ["title", "cwe", "file_path"], "Dockle Scan": ["title", "description", "vuln_id_from_tool"], "Dependency Track Finding Packaging Format (FPF) Export": ["component_name", "component_version", "vulnerability_ids"], - "Mobsfscan Scan": ["title", "severity", "cwe"], + "Mobsfscan Scan": ["title", "severity", "cwe", "file_path", "description"], "Tenable Scan": ["title", "severity", "vulnerability_ids", "cwe", "description"], "Nexpose Scan": ["title", "severity", "vulnerability_ids", "cwe"], # possible improvement: in the scanner put the library name into file_path, then dedup on cwe + file_path + severity diff --git a/dojo/tools/mobsfscan/parser.py b/dojo/tools/mobsfscan/parser.py index 83c69a5894..0473b7c649 100644 --- a/dojo/tools/mobsfscan/parser.py +++ b/dojo/tools/mobsfscan/parser.py @@ -51,29 +51,43 @@ def get_findings(self, filename, test): else: severity = "Info" - finding = Finding( - title=f"{key}", - test=test, - severity=severity, - nb_occurences=1, - cwe=cwe, - description=description, - references=references, - ) + files = [] + if item.get("files"): for file in item.get("files"): - file_path = file.get("file_path") - line = file.get("match_lines")[0] + file_path = file.get("file_path", "") + line = file.get("match_lines", [0])[0] + snippet = file.get("match_string", "") + + files.append((file_path, line, snippet)) + else: + files.append(("", 0, "")) + + for file_path, line, snippet in files: + + finding = Finding( + title=f"{key}", + test=test, + severity=severity, + nb_occurences=1, + cwe=cwe, + description=description, + references=references, + ) + + if file_path: finding.file_path = file_path finding.line = line + finding.description = f"{description}\n**Snippet:** `{snippet}`" - dupe_key = hashlib.sha256( - (key + str(cwe) + masvs + owasp_mobile).encode("utf-8"), - ).hexdigest() + dupe_key = hashlib.sha256( + (key + str(cwe) + masvs + owasp_mobile + file_path).encode("utf-8"), + ).hexdigest() + + if dupe_key in dupes: + finding = dupes[dupe_key] + finding.nb_occurences += 1 + else: + dupes[dupe_key] = finding - if dupe_key in dupes: - finding = dupes[dupe_key] - finding.nb_occurences += 1 - else: - dupes[dupe_key] = finding return list(dupes.values()) diff --git a/unittests/scans/mobsfscan/many_findings.json b/unittests/scans/mobsfscan/many_findings.json index 5e38baf88d..30701b8850 100644 --- a/unittests/scans/mobsfscan/many_findings.json +++ b/unittests/scans/mobsfscan/many_findings.json @@ -25,6 +25,18 @@ 271 ], "match_string": "key = \"hmi_busroutes_health\"" + }, + { + "file_path": "app/src/main/java/com/routes/domain/analytics/event/Signatures2.kt", + "match_lines": [ + 20, + 20 + ], + "match_position": [ + 243, + 271 + ], + "match_string": "key2 = \"hmi_busroutes_health2\"" } ], "metadata": { diff --git a/unittests/tools/test_mobsfscan_parser.py b/unittests/tools/test_mobsfscan_parser.py index 9a41cb0203..a4051bd3a5 100644 --- a/unittests/tools/test_mobsfscan_parser.py +++ b/unittests/tools/test_mobsfscan_parser.py @@ -15,7 +15,7 @@ def test_parse_many_findings(self): with open("unittests/scans/mobsfscan/many_findings.json", encoding="utf-8") as testfile: parser = MobsfscanParser() findings = parser.get_findings(testfile, Test()) - self.assertEqual(7, len(findings)) + self.assertEqual(8, len(findings)) with self.subTest(i=0): finding = findings[0] @@ -39,6 +39,17 @@ def test_parse_many_findings(self): with self.subTest(i=2): finding = findings[2] + self.assertEqual("android_kotlin_hardcoded", finding.title) + self.assertEqual("Medium", finding.severity) + self.assertEqual(1, finding.nb_occurences) + self.assertIsNotNone(finding.description) + self.assertEqual(798, finding.cwe) + self.assertIsNotNone(finding.references) + self.assertEqual("app/src/main/java/com/routes/domain/analytics/event/Signatures2.kt", finding.file_path) + self.assertEqual(20, finding.line) + + with self.subTest(i=3): + finding = findings[3] self.assertEqual("android_prevent_screenshot", finding.title) self.assertEqual("Low", finding.severity) self.assertEqual(1, finding.nb_occurences) @@ -46,8 +57,8 @@ def test_parse_many_findings(self): self.assertEqual(200, finding.cwe) self.assertIsNotNone(finding.references) - with self.subTest(i=3): - finding = findings[3] + with self.subTest(i=4): + finding = findings[4] self.assertEqual("android_root_detection", finding.title) self.assertEqual("Low", finding.severity) self.assertEqual(1, finding.nb_occurences) @@ -55,8 +66,8 @@ def test_parse_many_findings(self): self.assertEqual(919, finding.cwe) self.assertIsNotNone(finding.references) - with self.subTest(i=4): - finding = findings[4] + with self.subTest(i=5): + finding = findings[5] self.assertEqual("android_safetynet", finding.title) self.assertEqual("Low", finding.severity) self.assertEqual(1, finding.nb_occurences) @@ -64,8 +75,8 @@ def test_parse_many_findings(self): self.assertEqual(353, finding.cwe) self.assertIsNotNone(finding.references) - with self.subTest(i=5): - finding = findings[5] + with self.subTest(i=6): + finding = findings[6] self.assertEqual("android_ssl_pinning", finding.title) self.assertEqual("Low", finding.severity) self.assertEqual(1, finding.nb_occurences) @@ -73,8 +84,8 @@ def test_parse_many_findings(self): self.assertEqual(295, finding.cwe) self.assertIsNotNone(finding.references) - with self.subTest(i=6): - finding = findings[6] + with self.subTest(i=7): + finding = findings[7] self.assertEqual("android_tapjacking", finding.title) self.assertEqual("Low", finding.severity) self.assertEqual(1, finding.nb_occurences)