From c4f0f57c4051f7464d00130775e0693dd5514e9c Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Fri, 23 May 2025 19:04:33 +0200 Subject: [PATCH 01/12] :hammer: Merge the MobSF scanner --- .../parsers/file/mobsf.md | 2 +- .../parsers/file/mobsfscan.md | 8 - dojo/tools/mobsf/api_report_json.py | 388 +++++++++++++++++ dojo/tools/mobsf/parser.py | 391 +----------------- .../{mobsfscan/parser.py => mobsf/report.py} | 17 +- dojo/tools/mobsfscan/__init__.py | 0 .../{mobsfscan => mobsf}/many_findings.json | 0 .../many_findings_cwe_lower.json | 0 .../{mobsfscan => mobsf}/no_findings.json | 0 unittests/tools/test_mobsf_parser.py | 159 +++++++ unittests/tools/test_mobsfscan_parser.py | 165 -------- 11 files changed, 558 insertions(+), 572 deletions(-) delete mode 100644 docs/content/en/connecting_your_tools/parsers/file/mobsfscan.md create mode 100644 dojo/tools/mobsf/api_report_json.py rename dojo/tools/{mobsfscan/parser.py => mobsf/report.py} (84%) delete mode 100644 dojo/tools/mobsfscan/__init__.py rename unittests/scans/{mobsfscan => mobsf}/many_findings.json (100%) rename unittests/scans/{mobsfscan => mobsf}/many_findings_cwe_lower.json (100%) rename unittests/scans/{mobsfscan => mobsf}/no_findings.json (100%) delete mode 100644 unittests/tools/test_mobsfscan_parser.py diff --git a/docs/content/en/connecting_your_tools/parsers/file/mobsf.md b/docs/content/en/connecting_your_tools/parsers/file/mobsf.md index 44985929fdb..d82ef9eb0e1 100644 --- a/docs/content/en/connecting_your_tools/parsers/file/mobsf.md +++ b/docs/content/en/connecting_your_tools/parsers/file/mobsf.md @@ -2,7 +2,7 @@ title: "MobSF Scanner" toc_hide: true --- -Export a JSON file using the API, api/v1/report\_json. +Export a JSON file using the API, api/v1/report\_json and import it to Defectdojo or import a JSON report from ### Sample Scan Data Sample MobSF Scanner scans can be found [here](https://github.com/DefectDojo/django-DefectDojo/tree/master/unittests/scans/mobsf). \ No newline at end of file diff --git a/docs/content/en/connecting_your_tools/parsers/file/mobsfscan.md b/docs/content/en/connecting_your_tools/parsers/file/mobsfscan.md deleted file mode 100644 index 7209f80b403..00000000000 --- a/docs/content/en/connecting_your_tools/parsers/file/mobsfscan.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: "Mobsfscan" -toc_hide: true ---- -Import JSON report from - -### Sample Scan Data -Sample Mobsfscan scans can be found [here](https://github.com/DefectDojo/django-DefectDojo/tree/master/unittests/scans/mobsfscan). \ No newline at end of file diff --git a/dojo/tools/mobsf/api_report_json.py b/dojo/tools/mobsf/api_report_json.py new file mode 100644 index 00000000000..6f5bd1c6c75 --- /dev/null +++ b/dojo/tools/mobsf/api_report_json.py @@ -0,0 +1,388 @@ +from datetime import datetime + +from html2text import html2text + +from dojo.models import Finding + + +class MobSFapireport: + def get_findings(self, data, test): + dupes = {} + find_date = datetime.now() + + test_description = "" + if "name" in data: + test_description = "**Info:**\n" + if "packagename" in data: + test_description = "{} **Package Name:** {}\n".format(test_description, data["packagename"]) + + if "mainactivity" in data: + test_description = "{} **Main Activity:** {}\n".format(test_description, data["mainactivity"]) + + if "pltfm" in data: + test_description = "{} **Platform:** {}\n".format(test_description, data["pltfm"]) + + if "sdk" in data: + test_description = "{} **SDK:** {}\n".format(test_description, data["sdk"]) + + if "min" in data: + test_description = "{} **Min SDK:** {}\n".format(test_description, data["min"]) + + if "targetsdk" in data: + test_description = "{} **Target SDK:** {}\n".format(test_description, data["targetsdk"]) + + if "minsdk" in data: + test_description = "{} **Min SDK:** {}\n".format(test_description, data["minsdk"]) + + if "maxsdk" in data: + test_description = "{} **Max SDK:** {}\n".format(test_description, data["maxsdk"]) + + test_description = f"{test_description}\n**File Information:**\n" + + if "name" in data: + test_description = "{} **Name:** {}\n".format(test_description, data["name"]) + + if "md5" in data: + test_description = "{} **MD5:** {}\n".format(test_description, data["md5"]) + + if "sha1" in data: + test_description = "{} **SHA-1:** {}\n".format(test_description, data["sha1"]) + + if "sha256" in data: + test_description = "{} **SHA-256:** {}\n".format(test_description, data["sha256"]) + + if "size" in data: + test_description = "{} **Size:** {}\n".format(test_description, data["size"]) + + if "urls" in data: + curl = "" + for url in data["urls"]: + for durl in url["urls"]: + curl = f"{durl}\n" + + if curl: + test_description = f"{test_description}\n**URL's:**\n {curl}\n" + + if "bin_anal" in data: + test_description = "{} \n**Binary Analysis:** {}\n".format(test_description, data["bin_anal"]) + + test.description = html2text(test_description) + + mobsf_findings = [] + # Mobile Permissions + if "permissions" in data: + # for permission, details in data["permissions"].items(): + if isinstance(data["permissions"], list): + for details in data["permissions"]: + mobsf_item = { + "category": "Mobile Permissions", + "title": details.get("name", ""), + "severity": self.getSeverityForPermission(details.get("status")), + "description": "**Permission Type:** " + details.get("name", "") + " (" + details.get("status", "") + ")\n\n**Description:** " + details.get("description", "") + "\n\n**Reason:** " + details.get("reason", ""), + "file_path": None, + } + mobsf_findings.append(mobsf_item) + else: + for permission, details in list(data["permissions"].items()): + mobsf_item = { + "category": "Mobile Permissions", + "title": permission, + "severity": self.getSeverityForPermission(details.get("status", "")), + "description": "**Permission Type:** " + permission + "\n\n**Description:** " + details.get("description", ""), + "file_path": None, + } + mobsf_findings.append(mobsf_item) + + # Insecure Connections + if "insecure_connections" in data: + for details in data["insecure_connections"]: + insecure_urls = "" + for url in details.split(","): + insecure_urls = insecure_urls + url + "\n" + + mobsf_item = { + "category": None, + "title": "Insecure Connections", + "severity": "Low", + "description": insecure_urls, + "file_path": None, + } + mobsf_findings.append(mobsf_item) + + # Certificate Analysis + if "certificate_analysis" in data: + if data["certificate_analysis"] != {}: + certificate_info = data["certificate_analysis"]["certificate_info"] + for details in data["certificate_analysis"]["certificate_findings"]: + if len(details) == 3: + mobsf_item = { + "category": "Certificate Analysis", + "title": details[2], + "severity": details[0].title(), + "description": details[1] + "\n\n**Certificate Info:** " + certificate_info, + "file_path": None, + } + mobsf_findings.append(mobsf_item) + elif len(details) == 2: + mobsf_item = { + "category": "Certificate Analysis", + "title": details[1], + "severity": details[0].title(), + "description": details[1] + "\n\n**Certificate Info:** " + certificate_info, + "file_path": None, + } + mobsf_findings.append(mobsf_item) + + # Manifest Analysis + if "manifest_analysis" in data: + if data["manifest_analysis"] != {} and isinstance(data["manifest_analysis"], dict): + if data["manifest_analysis"]["manifest_findings"]: + for details in data["manifest_analysis"]["manifest_findings"]: + mobsf_item = { + "category": "Manifest Analysis", + "title": details["title"], + "severity": details["severity"].title(), + "description": details["description"] + "\n\n " + details["name"], + "file_path": None, + } + mobsf_findings.append(mobsf_item) + else: + for details in data["manifest_analysis"]: + mobsf_item = { + "category": "Manifest Analysis", + "title": details["title"], + "severity": details["stat"].title(), + "description": details["desc"] + "\n\n " + details["name"], + "file_path": None, + } + mobsf_findings.append(mobsf_item) + + # Code Analysis + if "code_analysis" in data: + if data["code_analysis"] != {}: + if data["code_analysis"].get("findings"): + for details in data["code_analysis"]["findings"]: + metadata = data["code_analysis"]["findings"][details] + mobsf_item = { + "category": "Code Analysis", + "title": details, + "severity": metadata["metadata"]["severity"].title(), + "description": metadata["metadata"]["description"], + "file_path": None, + } + mobsf_findings.append(mobsf_item) + else: + for details in data["code_analysis"]: + metadata = data["code_analysis"][details] + if metadata.get("metadata"): + mobsf_item = { + "category": "Code Analysis", + "title": details, + "severity": metadata["metadata"]["severity"].title(), + "description": metadata["metadata"]["description"], + "file_path": None, + } + mobsf_findings.append(mobsf_item) + + # Binary Analysis + if "binary_analysis" in data: + if isinstance(data["binary_analysis"], list): + for details in data["binary_analysis"]: + for binary_analysis_type in details: + if binary_analysis_type != "name": + mobsf_item = { + "category": "Binary Analysis", + "title": details[binary_analysis_type]["description"].split(".")[0], + "severity": details[binary_analysis_type]["severity"].title(), + "description": details[binary_analysis_type]["description"], + "file_path": details["name"], + } + mobsf_findings.append(mobsf_item) + elif data["binary_analysis"].get("findings"): + for details in data["binary_analysis"]["findings"].values(): + # "findings":{ + # "Binary makes use of insecure API(s)":{ + # "detailed_desc":"The binary may contain the following insecure API(s) _memcpy\n, _strlen\n", + # "severity":"high", + # "cvss":6, + # "cwe":"CWE-676: Use of Potentially Dangerous Function", + # "owasp-mobile":"M7: Client Code Quality", + # "masvs":"MSTG-CODE-8" + # }, + mobsf_item = { + "category": "Binary Analysis", + "title": details["detailed_desc"], + "severity": details["severity"].title(), + "description": details["detailed_desc"], + "file_path": None, + } + mobsf_findings.append(mobsf_item) + else: + for details in data["binary_analysis"].values(): + # "Binary makes use of insecure API(s)":{ + # "detailed_desc":"The binary may contain the following insecure API(s) _vsprintf.", + # "severity":"high", + # "cvss":6, + # "cwe":"CWE-676 - Use of Potentially Dangerous Function", + # "owasp-mobile":"M7: Client Code Quality", + # "masvs":"MSTG-CODE-8" + # } + mobsf_item = { + "category": "Binary Analysis", + "title": details["detailed_desc"], + "severity": details["severity"].title(), + "description": details["detailed_desc"], + "file_path": None, + } + mobsf_findings.append(mobsf_item) + + # specific node for Android reports + if "android_api" in data: + # "android_insecure_random": { + # "files": { + # "u/c/a/b/a/c.java": "9", + # "kotlinx/coroutines/repackaged/net/bytebuddy/utility/RandomString.java": "3", + # ... + # "hu/mycompany/vbnmqweq/gateway/msg/Response.java": "13" + # }, + # "metadata": { + # "id": "android_insecure_random", + # "description": "The App uses an insecure Random Number Generator.", + # "type": "Regex", + # "pattern": "java\\.util\\.Random;", + # "severity": "high", + # "input_case": "exact", + # "cvss": 7.5, + # "cwe": "CWE-330 Use of Insufficiently Random Values", + # "owasp-mobile": "M5: Insufficient Cryptography", + # "masvs": "MSTG-CRYPTO-6" + # } + # }, + for api, details in list(data["android_api"].items()): + mobsf_item = { + "category": "Android API", + "title": details["metadata"]["description"], + "severity": details["metadata"]["severity"].title(), + "description": "**API:** " + api + "\n\n**Description:** " + details["metadata"]["description"], + "file_path": None, + } + mobsf_findings.append(mobsf_item) + + # Manifest + if "manifest" in data: + for details in data["manifest"]: + mobsf_item = { + "category": "Manifest", + "title": details["title"], + "severity": details["stat"], + "description": details["desc"], + "file_path": None, + } + mobsf_findings.append(mobsf_item) + + # MobSF Findings + if "findings" in data: + for title, finding in list(data["findings"].items()): + description = title + file_path = None + + if "path" in finding: + description += "\n\n**Files:**\n" + for path in finding["path"]: + if file_path is None: + file_path = path + description = description + " * " + path + "\n" + + mobsf_item = { + "category": "Findings", + "title": title, + "severity": finding["level"], + "description": description, + "file_path": file_path, + } + + mobsf_findings.append(mobsf_item) + if isinstance(data, list): + for finding in data: + mobsf_item = { + "category": finding["category"], + "title": finding["name"], + "severity": finding["severity"], + "description": finding["description"] + "\n" + "**apk_exploit_dict:** " + str(finding["apk_exploit_dict"]) + "\n" + "**line_number:** " + str(finding["line_number"]), + "file_path": finding["file_object"], + } + mobsf_findings.append(mobsf_item) + for mobsf_finding in mobsf_findings: + title = mobsf_finding["title"] + sev = self.getCriticalityRating(mobsf_finding["severity"]) + description = "" + file_path = None + if mobsf_finding["category"]: + description += "**Category:** " + mobsf_finding["category"] + "\n\n" + description += html2text(mobsf_finding["description"]) + finding = Finding( + title=title, + cwe=919, # Weaknesses in Mobile Applications + test=test, + description=description, + severity=sev, + references=None, + date=find_date, + static_finding=True, + dynamic_finding=False, + nb_occurences=1, + ) + if mobsf_finding["file_path"]: + finding.file_path = mobsf_finding["file_path"] + dupe_key = sev + title + description + mobsf_finding["file_path"] + else: + dupe_key = sev + title + description + if mobsf_finding["category"]: + dupe_key += mobsf_finding["category"] + if dupe_key in dupes: + find = dupes[dupe_key] + if description is not None: + find.description += description + find.nb_occurences += 1 + else: + dupes[dupe_key] = finding + return list(dupes.values()) + + def getSeverityForPermission(self, status): + """ + Convert status for permission detection to severity + + In MobSF there is only 4 know values for permission, + we map them as this: + dangerous => High (Critical?) + normal => Info + signature => Info (it's positive so... Info) + signatureOrSystem => Info (it's positive so... Info) + """ + if status == "dangerous": + return "High" + return "Info" + + # Criticality rating + def getCriticalityRating(self, rating): + criticality = "Info" + if rating.lower() == "good": + criticality = "Info" + elif rating.lower() == "warning": + criticality = "Low" + elif rating.lower() == "vulnerability": + criticality = "Medium" + else: + criticality = rating.lower().capitalize() + return criticality + + def suite_data(self, suites): + suite_info = "" + suite_info += suites["name"] + "\n" + suite_info += "Cipher Strength: " + str(suites["cipherStrength"]) + "\n" + if "ecdhBits" in suites: + suite_info += "ecdhBits: " + str(suites["ecdhBits"]) + "\n" + if "ecdhStrength" in suites: + suite_info += "ecdhStrength: " + str(suites["ecdhStrength"]) + suite_info += "\n\n" + return suite_info diff --git a/dojo/tools/mobsf/parser.py b/dojo/tools/mobsf/parser.py index c61065ea892..d9e8b419aa0 100644 --- a/dojo/tools/mobsf/parser.py +++ b/dojo/tools/mobsf/parser.py @@ -1,10 +1,8 @@ import json -from datetime import datetime -from html2text import html2text - -from dojo.models import Finding +from dojo.tools.mobsf.api_report_json import MobSFapireport +from dojo.tools.mobsf.report import MobSFjsonreport class MobSFParser: @@ -16,7 +14,7 @@ def get_label_for_scan_types(self, scan_type): return "MobSF Scan" def get_description_for_scan_types(self, scan_type): - return "Export a JSON file using the API, api/v1/report_json." + return "Import JSON report from mobsfscan report file or from api/v1/report_json" def get_findings(self, filename, test): tree = filename.read() @@ -24,381 +22,8 @@ def get_findings(self, filename, test): data = json.loads(str(tree, "utf-8")) except: data = json.loads(tree) - find_date = datetime.now() - dupes = {} - test_description = "" - if "name" in data: - test_description = "**Info:**\n" - if "packagename" in data: - test_description = "{} **Package Name:** {}\n".format(test_description, data["packagename"]) - - if "mainactivity" in data: - test_description = "{} **Main Activity:** {}\n".format(test_description, data["mainactivity"]) - - if "pltfm" in data: - test_description = "{} **Platform:** {}\n".format(test_description, data["pltfm"]) - - if "sdk" in data: - test_description = "{} **SDK:** {}\n".format(test_description, data["sdk"]) - - if "min" in data: - test_description = "{} **Min SDK:** {}\n".format(test_description, data["min"]) - - if "targetsdk" in data: - test_description = "{} **Target SDK:** {}\n".format(test_description, data["targetsdk"]) - - if "minsdk" in data: - test_description = "{} **Min SDK:** {}\n".format(test_description, data["minsdk"]) - - if "maxsdk" in data: - test_description = "{} **Max SDK:** {}\n".format(test_description, data["maxsdk"]) - - test_description = f"{test_description}\n**File Information:**\n" - - if "name" in data: - test_description = "{} **Name:** {}\n".format(test_description, data["name"]) - - if "md5" in data: - test_description = "{} **MD5:** {}\n".format(test_description, data["md5"]) - - if "sha1" in data: - test_description = "{} **SHA-1:** {}\n".format(test_description, data["sha1"]) - - if "sha256" in data: - test_description = "{} **SHA-256:** {}\n".format(test_description, data["sha256"]) - - if "size" in data: - test_description = "{} **Size:** {}\n".format(test_description, data["size"]) - - if "urls" in data: - curl = "" - for url in data["urls"]: - for durl in url["urls"]: - curl = f"{durl}\n" - - if curl: - test_description = f"{test_description}\n**URL's:**\n {curl}\n" - - if "bin_anal" in data: - test_description = "{} \n**Binary Analysis:** {}\n".format(test_description, data["bin_anal"]) - - test.description = html2text(test_description) - - mobsf_findings = [] - # Mobile Permissions - if "permissions" in data: - # for permission, details in data["permissions"].items(): - if isinstance(data["permissions"], list): - for details in data["permissions"]: - mobsf_item = { - "category": "Mobile Permissions", - "title": details.get("name", ""), - "severity": self.getSeverityForPermission(details.get("status")), - "description": "**Permission Type:** " + details.get("name", "") + " (" + details.get("status", "") + ")\n\n**Description:** " + details.get("description", "") + "\n\n**Reason:** " + details.get("reason", ""), - "file_path": None, - } - mobsf_findings.append(mobsf_item) - else: - for permission, details in list(data["permissions"].items()): - mobsf_item = { - "category": "Mobile Permissions", - "title": permission, - "severity": self.getSeverityForPermission(details.get("status", "")), - "description": "**Permission Type:** " + permission + "\n\n**Description:** " + details.get("description", ""), - "file_path": None, - } - mobsf_findings.append(mobsf_item) - - # Insecure Connections - if "insecure_connections" in data: - for details in data["insecure_connections"]: - insecure_urls = "" - for url in details.split(","): - insecure_urls = insecure_urls + url + "\n" - - mobsf_item = { - "category": None, - "title": "Insecure Connections", - "severity": "Low", - "description": insecure_urls, - "file_path": None, - } - mobsf_findings.append(mobsf_item) - - # Certificate Analysis - if "certificate_analysis" in data: - if data["certificate_analysis"] != {}: - certificate_info = data["certificate_analysis"]["certificate_info"] - for details in data["certificate_analysis"]["certificate_findings"]: - if len(details) == 3: - mobsf_item = { - "category": "Certificate Analysis", - "title": details[2], - "severity": details[0].title(), - "description": details[1] + "\n\n**Certificate Info:** " + certificate_info, - "file_path": None, - } - mobsf_findings.append(mobsf_item) - elif len(details) == 2: - mobsf_item = { - "category": "Certificate Analysis", - "title": details[1], - "severity": details[0].title(), - "description": details[1] + "\n\n**Certificate Info:** " + certificate_info, - "file_path": None, - } - mobsf_findings.append(mobsf_item) - - # Manifest Analysis - if "manifest_analysis" in data: - if data["manifest_analysis"] != {} and isinstance(data["manifest_analysis"], dict): - if data["manifest_analysis"]["manifest_findings"]: - for details in data["manifest_analysis"]["manifest_findings"]: - mobsf_item = { - "category": "Manifest Analysis", - "title": details["title"], - "severity": details["severity"].title(), - "description": details["description"] + "\n\n " + details["name"], - "file_path": None, - } - mobsf_findings.append(mobsf_item) - else: - for details in data["manifest_analysis"]: - mobsf_item = { - "category": "Manifest Analysis", - "title": details["title"], - "severity": details["stat"].title(), - "description": details["desc"] + "\n\n " + details["name"], - "file_path": None, - } - mobsf_findings.append(mobsf_item) - - # Code Analysis - if "code_analysis" in data: - if data["code_analysis"] != {}: - if data["code_analysis"].get("findings"): - for details in data["code_analysis"]["findings"]: - metadata = data["code_analysis"]["findings"][details] - mobsf_item = { - "category": "Code Analysis", - "title": details, - "severity": metadata["metadata"]["severity"].title(), - "description": metadata["metadata"]["description"], - "file_path": None, - } - mobsf_findings.append(mobsf_item) - else: - for details in data["code_analysis"]: - metadata = data["code_analysis"][details] - if metadata.get("metadata"): - mobsf_item = { - "category": "Code Analysis", - "title": details, - "severity": metadata["metadata"]["severity"].title(), - "description": metadata["metadata"]["description"], - "file_path": None, - } - mobsf_findings.append(mobsf_item) - - # Binary Analysis - if "binary_analysis" in data: - if isinstance(data["binary_analysis"], list): - for details in data["binary_analysis"]: - for binary_analysis_type in details: - if binary_analysis_type != "name": - mobsf_item = { - "category": "Binary Analysis", - "title": details[binary_analysis_type]["description"].split(".")[0], - "severity": details[binary_analysis_type]["severity"].title(), - "description": details[binary_analysis_type]["description"], - "file_path": details["name"], - } - mobsf_findings.append(mobsf_item) - elif data["binary_analysis"].get("findings"): - for details in data["binary_analysis"]["findings"].values(): - # "findings":{ - # "Binary makes use of insecure API(s)":{ - # "detailed_desc":"The binary may contain the following insecure API(s) _memcpy\n, _strlen\n", - # "severity":"high", - # "cvss":6, - # "cwe":"CWE-676: Use of Potentially Dangerous Function", - # "owasp-mobile":"M7: Client Code Quality", - # "masvs":"MSTG-CODE-8" - # }, - mobsf_item = { - "category": "Binary Analysis", - "title": details["detailed_desc"], - "severity": details["severity"].title(), - "description": details["detailed_desc"], - "file_path": None, - } - mobsf_findings.append(mobsf_item) - else: - for details in data["binary_analysis"].values(): - # "Binary makes use of insecure API(s)":{ - # "detailed_desc":"The binary may contain the following insecure API(s) _vsprintf.", - # "severity":"high", - # "cvss":6, - # "cwe":"CWE-676 - Use of Potentially Dangerous Function", - # "owasp-mobile":"M7: Client Code Quality", - # "masvs":"MSTG-CODE-8" - # } - mobsf_item = { - "category": "Binary Analysis", - "title": details["detailed_desc"], - "severity": details["severity"].title(), - "description": details["detailed_desc"], - "file_path": None, - } - mobsf_findings.append(mobsf_item) - - # specific node for Android reports - if "android_api" in data: - # "android_insecure_random": { - # "files": { - # "u/c/a/b/a/c.java": "9", - # "kotlinx/coroutines/repackaged/net/bytebuddy/utility/RandomString.java": "3", - # ... - # "hu/mycompany/vbnmqweq/gateway/msg/Response.java": "13" - # }, - # "metadata": { - # "id": "android_insecure_random", - # "description": "The App uses an insecure Random Number Generator.", - # "type": "Regex", - # "pattern": "java\\.util\\.Random;", - # "severity": "high", - # "input_case": "exact", - # "cvss": 7.5, - # "cwe": "CWE-330 Use of Insufficiently Random Values", - # "owasp-mobile": "M5: Insufficient Cryptography", - # "masvs": "MSTG-CRYPTO-6" - # } - # }, - for api, details in list(data["android_api"].items()): - mobsf_item = { - "category": "Android API", - "title": details["metadata"]["description"], - "severity": details["metadata"]["severity"].title(), - "description": "**API:** " + api + "\n\n**Description:** " + details["metadata"]["description"], - "file_path": None, - } - mobsf_findings.append(mobsf_item) - - # Manifest - if "manifest" in data: - for details in data["manifest"]: - mobsf_item = { - "category": "Manifest", - "title": details["title"], - "severity": details["stat"], - "description": details["desc"], - "file_path": None, - } - mobsf_findings.append(mobsf_item) - - # MobSF Findings - if "findings" in data: - for title, finding in list(data["findings"].items()): - description = title - file_path = None - - if "path" in finding: - description += "\n\n**Files:**\n" - for path in finding["path"]: - if file_path is None: - file_path = path - description = description + " * " + path + "\n" - - mobsf_item = { - "category": "Findings", - "title": title, - "severity": finding["level"], - "description": description, - "file_path": file_path, - } - - mobsf_findings.append(mobsf_item) - if isinstance(data, list): - for finding in data: - mobsf_item = { - "category": finding["category"], - "title": finding["name"], - "severity": finding["severity"], - "description": finding["description"] + "\n" + "**apk_exploit_dict:** " + str(finding["apk_exploit_dict"]) + "\n" + "**line_number:** " + str(finding["line_number"]), - "file_path": finding["file_object"], - } - mobsf_findings.append(mobsf_item) - for mobsf_finding in mobsf_findings: - title = mobsf_finding["title"] - sev = self.getCriticalityRating(mobsf_finding["severity"]) - description = "" - file_path = None - if mobsf_finding["category"]: - description += "**Category:** " + mobsf_finding["category"] + "\n\n" - description += html2text(mobsf_finding["description"]) - finding = Finding( - title=title, - cwe=919, # Weaknesses in Mobile Applications - test=test, - description=description, - severity=sev, - references=None, - date=find_date, - static_finding=True, - dynamic_finding=False, - nb_occurences=1, - ) - if mobsf_finding["file_path"]: - finding.file_path = mobsf_finding["file_path"] - dupe_key = sev + title + description + mobsf_finding["file_path"] - else: - dupe_key = sev + title + description - if mobsf_finding["category"]: - dupe_key += mobsf_finding["category"] - if dupe_key in dupes: - find = dupes[dupe_key] - if description is not None: - find.description += description - find.nb_occurences += 1 - else: - dupes[dupe_key] = finding - return list(dupes.values()) - - def getSeverityForPermission(self, status): - """ - Convert status for permission detection to severity - - In MobSF there is only 4 know values for permission, - we map them as this: - dangerous => High (Critical?) - normal => Info - signature => Info (it's positive so... Info) - signatureOrSystem => Info (it's positive so... Info) - """ - if status == "dangerous": - return "High" - return "Info" - - # Criticality rating - def getCriticalityRating(self, rating): - criticality = "Info" - if rating.lower() == "good": - criticality = "Info" - elif rating.lower() == "warning": - criticality = "Low" - elif rating.lower() == "vulnerability": - criticality = "Medium" - else: - criticality = rating.lower().capitalize() - return criticality - - def suite_data(self, suites): - suite_info = "" - suite_info += suites["name"] + "\n" - suite_info += "Cipher Strength: " + str(suites["cipherStrength"]) + "\n" - if "ecdhBits" in suites: - suite_info += "ecdhBits: " + str(suites["ecdhBits"]) + "\n" - if "ecdhStrength" in suites: - suite_info += "ecdhStrength: " + str(suites["ecdhStrength"]) - suite_info += "\n\n" - return suite_info + if isinstance(data, list) or data.get("results") is None: + return MobSFapireport().get_findings(data, test) + if len(data.get("results")) == 0: + return [] + return MobSFjsonreport().get_findings(data, test) diff --git a/dojo/tools/mobsfscan/parser.py b/dojo/tools/mobsf/report.py similarity index 84% rename from dojo/tools/mobsfscan/parser.py rename to dojo/tools/mobsf/report.py index 49995720acb..3f076e2f8a5 100644 --- a/dojo/tools/mobsfscan/parser.py +++ b/dojo/tools/mobsf/report.py @@ -1,11 +1,10 @@ import hashlib -import json import re from dojo.models import Finding -class MobsfscanParser: +class MobSFjsonreport: """A class that can be used to parse the mobsfscan (https://github.com/MobSF/mobsfscan) JSON report file.""" @@ -15,19 +14,7 @@ class MobsfscanParser: "INFO": "Low", } - def get_scan_types(self): - return ["Mobsfscan Scan"] - - def get_label_for_scan_types(self, scan_type): - return "Mobsfscan Scan" - - def get_description_for_scan_types(self, scan_type): - return "Import JSON report for mobsfscan report file." - - def get_findings(self, filename, test): - data = json.load(filename) - if len(data.get("results")) == 0: - return [] + def get_findings(self, data, test): dupes = {} for key, item in data.get("results").items(): metadata = item.get("metadata") diff --git a/dojo/tools/mobsfscan/__init__.py b/dojo/tools/mobsfscan/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/unittests/scans/mobsfscan/many_findings.json b/unittests/scans/mobsf/many_findings.json similarity index 100% rename from unittests/scans/mobsfscan/many_findings.json rename to unittests/scans/mobsf/many_findings.json diff --git a/unittests/scans/mobsfscan/many_findings_cwe_lower.json b/unittests/scans/mobsf/many_findings_cwe_lower.json similarity index 100% rename from unittests/scans/mobsfscan/many_findings_cwe_lower.json rename to unittests/scans/mobsf/many_findings_cwe_lower.json diff --git a/unittests/scans/mobsfscan/no_findings.json b/unittests/scans/mobsf/no_findings.json similarity index 100% rename from unittests/scans/mobsfscan/no_findings.json rename to unittests/scans/mobsf/no_findings.json diff --git a/unittests/tools/test_mobsf_parser.py b/unittests/tools/test_mobsf_parser.py index 53ddbb8a3e6..88719e57d88 100644 --- a/unittests/tools/test_mobsf_parser.py +++ b/unittests/tools/test_mobsf_parser.py @@ -136,3 +136,162 @@ def test_parse_damnvulnrablebank(self): findings = parser.get_findings(testfile, test) testfile.close() self.assertEqual(80, len(findings)) + + def test_parse_no_findings(self): + with (get_unit_tests_scans_path("mobsf") / "no_findings.json").open(encoding="utf-8") as testfile: + parser = MobSFParser() + findings = parser.get_findings(testfile, Test()) + self.assertEqual(0, len(findings)) + + def test_parse_many_findings(self): + with (get_unit_tests_scans_path("mobsf") / "many_findings.json").open(encoding="utf-8") as testfile: + parser = MobSFParser() + findings = parser.get_findings(testfile, Test()) + self.assertEqual(8, len(findings)) + + with self.subTest(i=0): + finding = findings[0] + self.assertEqual("android_certificate_transparency", finding.title) + self.assertEqual("Low", finding.severity) + self.assertEqual(1, finding.nb_occurences) + self.assertIsNotNone(finding.description) + self.assertEqual(295, finding.cwe) + self.assertIsNotNone(finding.references) + + with self.subTest(i=1): + finding = findings[1] + 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/Signatures.kt", finding.file_path) + self.assertEqual(10, finding.line) + + 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) + self.assertIsNotNone(finding.description) + self.assertEqual(200, finding.cwe) + self.assertIsNotNone(finding.references) + + 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) + self.assertIsNotNone(finding.description) + self.assertEqual(919, finding.cwe) + self.assertIsNotNone(finding.references) + + 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) + self.assertIsNotNone(finding.description) + self.assertEqual(353, finding.cwe) + self.assertIsNotNone(finding.references) + + 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) + self.assertIsNotNone(finding.description) + self.assertEqual(295, finding.cwe) + self.assertIsNotNone(finding.references) + + 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) + self.assertIsNotNone(finding.description) + self.assertEqual(200, finding.cwe) + self.assertIsNotNone(finding.references) + + def test_parse_many_findings_cwe_lower(self): + with (get_unit_tests_scans_path("mobsf") / "many_findings_cwe_lower.json").open(encoding="utf-8") as testfile: + parser = MobSFParser() + findings = parser.get_findings(testfile, Test()) + self.assertEqual(7, len(findings)) + + with self.subTest(i=0): + finding = findings[0] + self.assertEqual("android_certificate_transparency", finding.title) + self.assertEqual("Low", finding.severity) + self.assertEqual(1, finding.nb_occurences) + self.assertIsNotNone(finding.description) + self.assertEqual(295, finding.cwe) + self.assertIsNotNone(finding.references) + + with self.subTest(i=1): + finding = findings[1] + 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/Signatures.kt", finding.file_path) + self.assertEqual(10, finding.line) + + with self.subTest(i=2): + finding = findings[2] + self.assertEqual("android_prevent_screenshot", finding.title) + self.assertEqual("Low", finding.severity) + self.assertEqual(1, finding.nb_occurences) + self.assertIsNotNone(finding.description) + self.assertEqual(200, finding.cwe) + self.assertIsNotNone(finding.references) + + with self.subTest(i=3): + finding = findings[3] + self.assertEqual("android_root_detection", finding.title) + self.assertEqual("Low", finding.severity) + self.assertEqual(1, finding.nb_occurences) + self.assertIsNotNone(finding.description) + self.assertEqual(919, finding.cwe) + self.assertIsNotNone(finding.references) + + with self.subTest(i=4): + finding = findings[4] + self.assertEqual("android_safetynet", finding.title) + self.assertEqual("Low", finding.severity) + self.assertEqual(1, finding.nb_occurences) + self.assertIsNotNone(finding.description) + self.assertEqual(353, finding.cwe) + self.assertIsNotNone(finding.references) + + with self.subTest(i=5): + finding = findings[5] + self.assertEqual("android_ssl_pinning", finding.title) + self.assertEqual("Low", finding.severity) + self.assertEqual(1, finding.nb_occurences) + self.assertIsNotNone(finding.description) + self.assertEqual(295, finding.cwe) + self.assertIsNotNone(finding.references) + + with self.subTest(i=6): + finding = findings[6] + self.assertEqual("android_tapjacking", finding.title) + self.assertEqual("Low", finding.severity) + self.assertEqual(1, finding.nb_occurences) + self.assertIsNotNone(finding.description) + self.assertEqual(200, finding.cwe) + self.assertIsNotNone(finding.references) diff --git a/unittests/tools/test_mobsfscan_parser.py b/unittests/tools/test_mobsfscan_parser.py deleted file mode 100644 index cbb6245c227..00000000000 --- a/unittests/tools/test_mobsfscan_parser.py +++ /dev/null @@ -1,165 +0,0 @@ -from dojo.models import Test -from dojo.tools.mobsfscan.parser import MobsfscanParser -from unittests.dojo_test_case import DojoTestCase, get_unit_tests_scans_path - - -class TestMobsfscanParser(DojoTestCase): - - def test_parse_no_findings(self): - with (get_unit_tests_scans_path("mobsfscan") / "no_findings.json").open(encoding="utf-8") as testfile: - parser = MobsfscanParser() - findings = parser.get_findings(testfile, Test()) - self.assertEqual(0, len(findings)) - - def test_parse_many_findings(self): - with (get_unit_tests_scans_path("mobsfscan") / "many_findings.json").open(encoding="utf-8") as testfile: - parser = MobsfscanParser() - findings = parser.get_findings(testfile, Test()) - self.assertEqual(8, len(findings)) - - with self.subTest(i=0): - finding = findings[0] - self.assertEqual("android_certificate_transparency", finding.title) - self.assertEqual("Low", finding.severity) - self.assertEqual(1, finding.nb_occurences) - self.assertIsNotNone(finding.description) - self.assertEqual(295, finding.cwe) - self.assertIsNotNone(finding.references) - - with self.subTest(i=1): - finding = findings[1] - 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/Signatures.kt", finding.file_path) - self.assertEqual(10, finding.line) - - 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) - self.assertIsNotNone(finding.description) - self.assertEqual(200, finding.cwe) - self.assertIsNotNone(finding.references) - - 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) - self.assertIsNotNone(finding.description) - self.assertEqual(919, finding.cwe) - self.assertIsNotNone(finding.references) - - 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) - self.assertIsNotNone(finding.description) - self.assertEqual(353, finding.cwe) - self.assertIsNotNone(finding.references) - - 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) - self.assertIsNotNone(finding.description) - self.assertEqual(295, finding.cwe) - self.assertIsNotNone(finding.references) - - 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) - self.assertIsNotNone(finding.description) - self.assertEqual(200, finding.cwe) - self.assertIsNotNone(finding.references) - - def test_parse_many_findings_cwe_lower(self): - with (get_unit_tests_scans_path("mobsfscan") / "many_findings_cwe_lower.json").open(encoding="utf-8") as testfile: - parser = MobsfscanParser() - findings = parser.get_findings(testfile, Test()) - self.assertEqual(7, len(findings)) - - with self.subTest(i=0): - finding = findings[0] - self.assertEqual("android_certificate_transparency", finding.title) - self.assertEqual("Low", finding.severity) - self.assertEqual(1, finding.nb_occurences) - self.assertIsNotNone(finding.description) - self.assertEqual(295, finding.cwe) - self.assertIsNotNone(finding.references) - - with self.subTest(i=1): - finding = findings[1] - 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/Signatures.kt", finding.file_path) - self.assertEqual(10, finding.line) - - with self.subTest(i=2): - finding = findings[2] - self.assertEqual("android_prevent_screenshot", finding.title) - self.assertEqual("Low", finding.severity) - self.assertEqual(1, finding.nb_occurences) - self.assertIsNotNone(finding.description) - self.assertEqual(200, finding.cwe) - self.assertIsNotNone(finding.references) - - with self.subTest(i=3): - finding = findings[3] - self.assertEqual("android_root_detection", finding.title) - self.assertEqual("Low", finding.severity) - self.assertEqual(1, finding.nb_occurences) - self.assertIsNotNone(finding.description) - self.assertEqual(919, finding.cwe) - self.assertIsNotNone(finding.references) - - with self.subTest(i=4): - finding = findings[4] - self.assertEqual("android_safetynet", finding.title) - self.assertEqual("Low", finding.severity) - self.assertEqual(1, finding.nb_occurences) - self.assertIsNotNone(finding.description) - self.assertEqual(353, finding.cwe) - self.assertIsNotNone(finding.references) - - with self.subTest(i=5): - finding = findings[5] - self.assertEqual("android_ssl_pinning", finding.title) - self.assertEqual("Low", finding.severity) - self.assertEqual(1, finding.nb_occurences) - self.assertIsNotNone(finding.description) - self.assertEqual(295, finding.cwe) - self.assertIsNotNone(finding.references) - - with self.subTest(i=6): - finding = findings[6] - self.assertEqual("android_tapjacking", finding.title) - self.assertEqual("Low", finding.severity) - self.assertEqual(1, finding.nb_occurences) - self.assertIsNotNone(finding.description) - self.assertEqual(200, finding.cwe) - self.assertIsNotNone(finding.references) From 9b5fbf012ad5bfd368ca01b5bd84f6736291fbf8 Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Mon, 26 May 2025 09:48:09 +0200 Subject: [PATCH 02/12] add migration --- dojo/db_migrations/0229_merge_mobsf.py | 55 ++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 dojo/db_migrations/0229_merge_mobsf.py diff --git a/dojo/db_migrations/0229_merge_mobsf.py b/dojo/db_migrations/0229_merge_mobsf.py new file mode 100644 index 00000000000..6d2bb9ca33a --- /dev/null +++ b/dojo/db_migrations/0229_merge_mobsf.py @@ -0,0 +1,55 @@ +from django.db import migrations +import logging + + +logger = logging.getLogger(__name__) + + +PARSER_REFERENCES = ['Mobsfscan Scan'] + + +def update_parser_test(test, parser_test_type) -> None: + if test.test_type.name in PARSER_REFERENCES or test.scan_type in PARSER_REFERENCES: + test.test_type = parser_test_type + test.scan_type = parser_test_type.name + test.save() + + +# Update the found_by field to remove Mobsfscan Scan and add MobSF Scan +def update_parser_finding(finding, newparser_test_type, parser_test_type) -> None: + # Check if nessus is in found by list and remove + if parser_test_type in finding.found_by.all(): + finding.found_by.remove(parser_test_type.id) + # Check if tenable is already in list somehow before adding it + if newparser_test_type not in finding.found_by.all(): + finding.found_by.add(newparser_test_type.id) + finding.save() + + +# Update all finding objects that came from Mobsfscan Scan reports +def forward_merge_parser(apps, schema_editor): + finding_model = apps.get_model('dojo', 'Finding') + test_type_model = apps.get_model('dojo', 'Test_Type') + # Get or create MobSF Scan Test Type and fetch the Mobsfscan Scan test types + newparser_test_type, _ = test_type_model.objects.get_or_create(name="MobSF Scan", defaults={"active": True}) + parser_test_type = test_type_model.objects.filter(name="Mobsfscan Scan").first() + # Get all the findings found by Mobsfscan Scan + findings = finding_model.objects.filter(test__scan_type__in=PARSER_REFERENCES) + logger.warning(f'We identified {findings.count()} Mobsfscan Scan findings to migrate to MobSF Scan findings') + # Iterate over all findings and change + for finding in findings: + # Update the found by field + update_parser_finding(finding, newparser_test_type, parser_test_type) + # Update the test object + update_parser_test(finding.test, newparser_test_type) + + +class Migration(migrations.Migration): + + dependencies = [ + ('dojo', '0228_alter_jira_username_password'), + ] + + operations = [ + migrations.RunPython(forward_merge_parser), + ] From 92843a695e923f6727f8a92205be0d310333e1d2 Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Mon, 26 May 2025 13:55:03 +0200 Subject: [PATCH 03/12] udpate --- dojo/settings/settings.dist.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py index 159d696b779..0fcbea46163 100644 --- a/dojo/settings/settings.dist.py +++ b/dojo/settings/settings.dist.py @@ -1267,7 +1267,6 @@ def saml2_attrib_map_format(din): "Dockle Scan": ["title", "description", "vuln_id_from_tool"], "Dependency Track Finding Packaging Format (FPF) Export": ["component_name", "component_version", "vulnerability_ids"], "Horusec Scan": ["title", "description", "file_path", "line"], - "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 @@ -1328,7 +1327,7 @@ def saml2_attrib_map_format(din): "HCLAppScan XML": ["title", "description"], "HCL AppScan on Cloud SAST XML": ["title", "file_path", "line", "severity"], "KICS Scan": ["file_path", "line", "severity", "description", "title"], - "MobSF Scan": ["title", "description", "severity"], + "MobSF Scan": ["title", "severity", "cwe", "file_path", "description"], "MobSF Scorecard Scan": ["title", "description", "severity"], "OSV Scan": ["title", "description", "severity"], "Snyk Code Scan": ["vuln_id_from_tool", "file_path"], @@ -1386,7 +1385,6 @@ def saml2_attrib_map_format(din): "Cloudsploit Scan": True, "SonarQube Scan": False, "Dependency Check Scan": True, - "Mobsfscan Scan": False, "Tenable Scan": True, "Nexpose Scan": True, "NPM Audit Scan": True, @@ -1495,7 +1493,6 @@ def saml2_attrib_map_format(din): "Crunch42 Scan": DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL, "Dependency Track Finding Packaging Format (FPF) Export": DEDUPE_ALGO_HASH_CODE, "Horusec Scan": DEDUPE_ALGO_HASH_CODE, - "Mobsfscan Scan": DEDUPE_ALGO_HASH_CODE, "SonarQube Scan detailed": DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL, "SonarQube Scan": DEDUPE_ALGO_HASH_CODE, "SonarQube API Import": DEDUPE_ALGO_HASH_CODE, From a1b9717e50d8c4c969337450e22d7c8a90e00588 Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Mon, 26 May 2025 23:08:02 +0200 Subject: [PATCH 04/12] Update 0229_merge_mobsf.py --- dojo/db_migrations/0229_merge_mobsf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dojo/db_migrations/0229_merge_mobsf.py b/dojo/db_migrations/0229_merge_mobsf.py index 6d2bb9ca33a..3ae29748c66 100644 --- a/dojo/db_migrations/0229_merge_mobsf.py +++ b/dojo/db_migrations/0229_merge_mobsf.py @@ -17,10 +17,10 @@ def update_parser_test(test, parser_test_type) -> None: # Update the found_by field to remove Mobsfscan Scan and add MobSF Scan def update_parser_finding(finding, newparser_test_type, parser_test_type) -> None: - # Check if nessus is in found by list and remove + # Check if old parser is in found by list and remove if parser_test_type in finding.found_by.all(): finding.found_by.remove(parser_test_type.id) - # Check if tenable is already in list somehow before adding it + # Check if new parser is already in list somehow before adding it if newparser_test_type not in finding.found_by.all(): finding.found_by.add(newparser_test_type.id) finding.save() From 5f2e335614e3eec53b774aa4d715cdb71174524a Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Tue, 27 May 2025 10:20:37 +0200 Subject: [PATCH 05/12] udpate --- docs/content/en/open_source/upgrading/2.48.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 docs/content/en/open_source/upgrading/2.48.md diff --git a/docs/content/en/open_source/upgrading/2.48.md b/docs/content/en/open_source/upgrading/2.48.md new file mode 100644 index 00000000000..b0ea8bda09e --- /dev/null +++ b/docs/content/en/open_source/upgrading/2.48.md @@ -0,0 +1,9 @@ +--- +title: 'Upgrading to DefectDojo Version 2.48.x' +toc_hide: true +weight: -20250505 +description: Drop support for PostgreSQL-HA in HELM +--- +### Merging Mobsfscan Scan and MobSF Scan + +The two scan types Mobsfscan Scan and MobSF Scan were merged in this release. We recommend to recalculate the hashcodes if you use these parsers as the deduplication settings have been changed. \ No newline at end of file From f395ff3288f601edf827509df8312b26ca80e4bd Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Tue, 27 May 2025 10:21:27 +0200 Subject: [PATCH 06/12] Update 2.48.md --- docs/content/en/open_source/upgrading/2.48.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/en/open_source/upgrading/2.48.md b/docs/content/en/open_source/upgrading/2.48.md index b0ea8bda09e..bece60c0691 100644 --- a/docs/content/en/open_source/upgrading/2.48.md +++ b/docs/content/en/open_source/upgrading/2.48.md @@ -2,8 +2,8 @@ title: 'Upgrading to DefectDojo Version 2.48.x' toc_hide: true weight: -20250505 -description: Drop support for PostgreSQL-HA in HELM +description: Recalculate hashes for MobSF parser --- ### Merging Mobsfscan Scan and MobSF Scan -The two scan types Mobsfscan Scan and MobSF Scan were merged in this release. We recommend to recalculate the hashcodes if you use these parsers as the deduplication settings have been changed. \ No newline at end of file +The two scan types Mobsfscan Scan and MobSF Scan were merged in this release. We recommend to recalculate the hashcodes if you use these parsers as the deduplication settings have been changed. From b5666150d4e5adbf6e1ad3bfd167bf21b2fc71bc Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Fri, 30 May 2025 08:52:41 +0200 Subject: [PATCH 07/12] Update settings.dist.py --- dojo/settings/settings.dist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py index 0fcbea46163..ce1bb06c70e 100644 --- a/dojo/settings/settings.dist.py +++ b/dojo/settings/settings.dist.py @@ -1327,7 +1327,7 @@ def saml2_attrib_map_format(din): "HCLAppScan XML": ["title", "description"], "HCL AppScan on Cloud SAST XML": ["title", "file_path", "line", "severity"], "KICS Scan": ["file_path", "line", "severity", "description", "title"], - "MobSF Scan": ["title", "severity", "cwe", "file_path", "description"], + "MobSF Scan": ["title", "description", "severity", "file_path"], "MobSF Scorecard Scan": ["title", "description", "severity"], "OSV Scan": ["title", "description", "severity"], "Snyk Code Scan": ["vuln_id_from_tool", "file_path"], From cf4591314eb43b03293e9871f9c05c5bcec27619 Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Fri, 30 May 2025 09:09:00 +0200 Subject: [PATCH 08/12] update --- dojo/db_migrations/{0229_merge_mobsf.py => 0230_merge_mobsf.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename dojo/db_migrations/{0229_merge_mobsf.py => 0230_merge_mobsf.py} (97%) diff --git a/dojo/db_migrations/0229_merge_mobsf.py b/dojo/db_migrations/0230_merge_mobsf.py similarity index 97% rename from dojo/db_migrations/0229_merge_mobsf.py rename to dojo/db_migrations/0230_merge_mobsf.py index 3ae29748c66..ed0135a3239 100644 --- a/dojo/db_migrations/0229_merge_mobsf.py +++ b/dojo/db_migrations/0230_merge_mobsf.py @@ -47,7 +47,7 @@ def forward_merge_parser(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('dojo', '0228_alter_jira_username_password'), + ('dojo', '0229_alter_finding_unique_id_from_tool'), ] operations = [ From 0cd937c0c3ce1185d3d422e87a74485add26a14c Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Tue, 10 Jun 2025 12:56:03 +0200 Subject: [PATCH 09/12] Update 2.48.md --- docs/content/en/open_source/upgrading/2.48.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/en/open_source/upgrading/2.48.md b/docs/content/en/open_source/upgrading/2.48.md index 39cb3d68033..14e7bbda5c9 100644 --- a/docs/content/en/open_source/upgrading/2.48.md +++ b/docs/content/en/open_source/upgrading/2.48.md @@ -6,4 +6,4 @@ description: Recalculate hashes for MobSF parser --- ### Merging Mobsfscan Scan and MobSF Scan -The two scan types Mobsfscan Scan and MobSF Scan were merged in this release. We recommend to recalculate the hashcodes if you use these parsers as the deduplication settings have been changed. \ No newline at end of file +The two scan types Mobsfscan Scan and MobSF Scan were merged in this release. Thus, please use MobSF Scan instead of Mobsfscan Scan in the future. We recommend to recalculate the hashcodes if you use these parsers as the deduplication settings have been changed. From 663f5b63d9ced51debf37b9a204b05fb5dcea02a Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Fri, 20 Jun 2025 13:57:48 +0200 Subject: [PATCH 10/12] update --- docs/content/en/open_source/upgrading/2.48.md | 6 +- dojo/db_migrations/0230_merge_mobsf.py | 55 ------------------- dojo/settings/settings.dist.py | 3 + dojo/tools/mobsf/parser.py | 2 +- 4 files changed, 6 insertions(+), 60 deletions(-) delete mode 100644 dojo/db_migrations/0230_merge_mobsf.py diff --git a/docs/content/en/open_source/upgrading/2.48.md b/docs/content/en/open_source/upgrading/2.48.md index 14e7bbda5c9..984fae67fdb 100644 --- a/docs/content/en/open_source/upgrading/2.48.md +++ b/docs/content/en/open_source/upgrading/2.48.md @@ -2,8 +2,6 @@ title: 'Upgrading to DefectDojo Version 2.48.x' toc_hide: true weight: -20250602 -description: Recalculate hashes for MobSF parser +description: No special instructions. --- -### Merging Mobsfscan Scan and MobSF Scan - -The two scan types Mobsfscan Scan and MobSF Scan were merged in this release. Thus, please use MobSF Scan instead of Mobsfscan Scan in the future. We recommend to recalculate the hashcodes if you use these parsers as the deduplication settings have been changed. +There are no special instructions for upgrading to 2.48.x. Check the [Release Notes](https://github.com/DefectDojo/django-DefectDojo/releases/tag/2.48.0) for the contents of the release. \ No newline at end of file diff --git a/dojo/db_migrations/0230_merge_mobsf.py b/dojo/db_migrations/0230_merge_mobsf.py deleted file mode 100644 index ed0135a3239..00000000000 --- a/dojo/db_migrations/0230_merge_mobsf.py +++ /dev/null @@ -1,55 +0,0 @@ -from django.db import migrations -import logging - - -logger = logging.getLogger(__name__) - - -PARSER_REFERENCES = ['Mobsfscan Scan'] - - -def update_parser_test(test, parser_test_type) -> None: - if test.test_type.name in PARSER_REFERENCES or test.scan_type in PARSER_REFERENCES: - test.test_type = parser_test_type - test.scan_type = parser_test_type.name - test.save() - - -# Update the found_by field to remove Mobsfscan Scan and add MobSF Scan -def update_parser_finding(finding, newparser_test_type, parser_test_type) -> None: - # Check if old parser is in found by list and remove - if parser_test_type in finding.found_by.all(): - finding.found_by.remove(parser_test_type.id) - # Check if new parser is already in list somehow before adding it - if newparser_test_type not in finding.found_by.all(): - finding.found_by.add(newparser_test_type.id) - finding.save() - - -# Update all finding objects that came from Mobsfscan Scan reports -def forward_merge_parser(apps, schema_editor): - finding_model = apps.get_model('dojo', 'Finding') - test_type_model = apps.get_model('dojo', 'Test_Type') - # Get or create MobSF Scan Test Type and fetch the Mobsfscan Scan test types - newparser_test_type, _ = test_type_model.objects.get_or_create(name="MobSF Scan", defaults={"active": True}) - parser_test_type = test_type_model.objects.filter(name="Mobsfscan Scan").first() - # Get all the findings found by Mobsfscan Scan - findings = finding_model.objects.filter(test__scan_type__in=PARSER_REFERENCES) - logger.warning(f'We identified {findings.count()} Mobsfscan Scan findings to migrate to MobSF Scan findings') - # Iterate over all findings and change - for finding in findings: - # Update the found by field - update_parser_finding(finding, newparser_test_type, parser_test_type) - # Update the test object - update_parser_test(finding.test, newparser_test_type) - - -class Migration(migrations.Migration): - - dependencies = [ - ('dojo', '0229_alter_finding_unique_id_from_tool'), - ] - - operations = [ - migrations.RunPython(forward_merge_parser), - ] diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py index 24bdb2b0acd..de2e1041412 100644 --- a/dojo/settings/settings.dist.py +++ b/dojo/settings/settings.dist.py @@ -1262,6 +1262,7 @@ def saml2_attrib_map_format(din): "Dockle Scan": ["title", "description", "vuln_id_from_tool"], "Dependency Track Finding Packaging Format (FPF) Export": ["component_name", "component_version", "vulnerability_ids"], "Horusec Scan": ["title", "description", "file_path", "line"], + "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 @@ -1380,6 +1381,7 @@ def saml2_attrib_map_format(din): "Cloudsploit Scan": True, "SonarQube Scan": False, "Dependency Check Scan": True, + "Mobsfscan Scan": False, "Tenable Scan": True, "Nexpose Scan": True, "NPM Audit Scan": True, @@ -1488,6 +1490,7 @@ def saml2_attrib_map_format(din): "Crunch42 Scan": DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL, "Dependency Track Finding Packaging Format (FPF) Export": DEDUPE_ALGO_HASH_CODE, "Horusec Scan": DEDUPE_ALGO_HASH_CODE, + "Mobsfscan Scan": DEDUPE_ALGO_HASH_CODE, "SonarQube Scan detailed": DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL, "SonarQube Scan": DEDUPE_ALGO_HASH_CODE, "SonarQube API Import": DEDUPE_ALGO_HASH_CODE, diff --git a/dojo/tools/mobsf/parser.py b/dojo/tools/mobsf/parser.py index d9e8b419aa0..ff5a7122655 100644 --- a/dojo/tools/mobsf/parser.py +++ b/dojo/tools/mobsf/parser.py @@ -8,7 +8,7 @@ class MobSFParser: def get_scan_types(self): - return ["MobSF Scan"] + return ["MobSF Scan", "Mobsfscan Scan"] def get_label_for_scan_types(self, scan_type): return "MobSF Scan" From b6c87b1dbad0131d73a97848082549a302c23a2a Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Thu, 3 Jul 2025 08:27:26 +0200 Subject: [PATCH 11/12] update docs --- docs/content/en/connecting_your_tools/parsers/file/mobsf.md | 2 ++ docs/content/en/open_source/upgrading/2.48.md | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/content/en/connecting_your_tools/parsers/file/mobsf.md b/docs/content/en/connecting_your_tools/parsers/file/mobsf.md index d82ef9eb0e1..8928f878b15 100644 --- a/docs/content/en/connecting_your_tools/parsers/file/mobsf.md +++ b/docs/content/en/connecting_your_tools/parsers/file/mobsf.md @@ -2,6 +2,8 @@ title: "MobSF Scanner" toc_hide: true --- +"Mobsfscan Scan" has been merged into the "MobSF Scan" parser. The "Mobsfscan Scan" scan_type has been retained to keep deduplication working for existing Tests, but users are encouraged to move to the "MobSF Scan" scan_type. + Export a JSON file using the API, api/v1/report\_json and import it to Defectdojo or import a JSON report from ### Sample Scan Data diff --git a/docs/content/en/open_source/upgrading/2.48.md b/docs/content/en/open_source/upgrading/2.48.md index 984fae67fdb..1027a52cb65 100644 --- a/docs/content/en/open_source/upgrading/2.48.md +++ b/docs/content/en/open_source/upgrading/2.48.md @@ -4,4 +4,7 @@ toc_hide: true weight: -20250602 description: No special instructions. --- -There are no special instructions for upgrading to 2.48.x. Check the [Release Notes](https://github.com/DefectDojo/django-DefectDojo/releases/tag/2.48.0) for the contents of the release. \ No newline at end of file +There are no special instructions for upgrading to 2.48.x. Check the [Release Notes](https://github.com/DefectDojo/django-DefectDojo/releases/tag/2.48.0) for the contents of the release. + +## Merge of MobSF parsers +"Mobsfscan Scan" has been merged into the "MobSF Scan" parser. The "Mobsfscan Scan" scan_type has been retained to keep deduplication working for existing Tests, but users are encouraged to move to the "MobSF Scan" scan_type. \ No newline at end of file From c1ddc7c556cdfbddb3e198e52ede0a476dc9e3d7 Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Thu, 3 Jul 2025 08:29:49 +0200 Subject: [PATCH 12/12] Update 2.48.md --- docs/content/en/open_source/upgrading/2.48.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/en/open_source/upgrading/2.48.md b/docs/content/en/open_source/upgrading/2.48.md index b7fb1c0e126..ffbc26e57e9 100644 --- a/docs/content/en/open_source/upgrading/2.48.md +++ b/docs/content/en/open_source/upgrading/2.48.md @@ -2,7 +2,7 @@ title: 'Upgrading to DefectDojo Version 2.48.x' toc_hide: true weight: -20250602 -description: Better pushing to JIRA for Finding Groups +description: MobSF parser merged, Better pushing to JIRA for Finding Groups --- ## Merge of MobSF parsers