Skip to content

Commit

Permalink
Add new Mend Platform API 3.0 file types to existing Mend parser (#11259
Browse files Browse the repository at this point in the history
)

* Add new Mend Platform API 3.0 parser

Existing Mend parser is for 1.0 or 2.0 and Unified Agent based 'legacy' vulnerabilities schema, whereas a new Mend Platform and API 3.0 has changed this structure, requiring a need for a new parser.  This is for SCA only at the moment.

* Update test_mend_platform_api3_parser.py

* Update test_mend_platform_api3_parser.py

* Update test_mend_platform_api3_parser.py

* Update test_mend_platform_api3_parser.py

* Update parser.py

* Update parser.py

* add component path as file_path

This is mainly for SCA anyways - SAST contains a different set of vulns and I can write that parser to differentiate SAST vs SCA.

* updated parser

* Update parser.py

* Update parser.py

* refactor

* Update parser.py

* Update parser.py

* Update test_mend_platform_api3_parser.py

* Update test_mend_platform_api3_parser.py

* Update test_mend_platform_api3_parser.py

* Update test_mend_platform_api3_parser.py

* Update test_mend_platform_api3_parser.py

* Update parser.py

* change single quotes to double quotes - reformat broken json

* Try to refactor to harden Mend parser instead of creating additional parser

* Update parser.py

* adding author update

* Update parser.py

* Add elif for "component" in content for list of Findings

"vulnerabilities" is replaced with "response" since it is an API 3.0 Platform call to retrieve a project SCA dependency vulns.  Furthermore, "libraries" is replaced with "component" in the updated 3.0 Platform output

* Update parser.py

* Update parser.py

* Update parser.py

* Update parser.py

* Update parser.py

* Update parser.py

* preserve the original else statement for grabbing severity

* update parser to capture component_node array

* Update parser.py

* change how vulnerability description is retrieved

* Update parser.py

* Update parser.py

* updating vuln count

* Update parser.py

* Update parser.py

* Update parser.py

* Update parser.py

* Update parser.py

* Update parser.py

* Update parser.py

* Update parser.py

* Update parser.py

* Update parser.py

* Update parser.py

* Fix spacing and formatting for easier readability

* Rework Mitigation field - slight adjustment in formatting and edited for readability

* Fix comma and indents

* Fix typo

* fix commas

* fix spacing

* fix spacing for readability
  • Loading branch information
testaccount90009 authored Nov 22, 2024
1 parent 4c250e1 commit 47c7213
Show file tree
Hide file tree
Showing 5 changed files with 975 additions and 15 deletions.
112 changes: 97 additions & 15 deletions dojo/tools/mend/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from dojo.models import Finding

__author__ = "dr3dd589"
__author__ = "dr3dd589 + testaccount90009 aka SH"

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -35,7 +35,55 @@ def _build_common_output(node, lib_name=None):
cve = None
component_name = None
component_version = None
if "library" in node:
impact = None
description = "No Description Available"
cvss3_score = None
mitigation = "N/A"
if "component" in node:
description = (
"**Vulnerability Description**: "
+ node["vulnerability"].get("description", "No Description Available")
+ "\n\n"
+ "**Component Name**: "
+ node["component"].get("name", "")
+ "\n"
+ "**Component Type**: "
+ node["component"].get("componentType", "")
+ "\n"
+ "**Root Library**: "
+ str(node["component"].get("rootLibrary", ""))
+ "\n"
+ "**Library Type**: "
+ node["component"].get("libraryType", "")
+ "\n"
+ "**Location Found**: "
+ node["component"].get("path", "")
+ "\n"
+ "**Direct or Transitive Dependency**: "
+ node["component"].get("dependencyType", "")
+ "\n"
)
lib_name = node["component"].get("name")
component_name = node["component"].get("artifactId")
component_version = node["component"].get("version")
impact = node["component"].get("dependencyType")
cvss3_score = node["vulnerability"].get("score", None)
if "topFix" in node:
try:
topfix_node = node.get("topFix")
mitigation = (
"**Resolution**: "
+ topfix_node.get("date", "")
+ "\n"
+ topfix_node.get("message", "")
+ "\n"
+ topfix_node.get("fixResolution", "")
+ "\n"
)
except Exception:
logger.exception("Error handling topFix node.")

elif "library" in node:
node.get("project")
description = (
"**Description** : "
Expand All @@ -57,8 +105,18 @@ def _build_common_output(node, lib_name=None):
lib_name = node["library"].get("filename")
component_name = node["library"].get("artifactId")
component_version = node["library"].get("version")
cvss3_score = node.get("cvss3_score", None)
if "topFix" in node:
try:
topfix_node = node.get("topFix")
mitigation = "**Resolution** ({}): {}\n".format(
topfix_node.get("date"),
topfix_node.get("fixResolution"),
)
except Exception:
logger.exception("Error handling topFix node.")
else:
description = node.get("description")
description = node.get("description", "Unknown")

cve = node.get("name")
if cve is None:
Expand All @@ -69,27 +127,29 @@ def _build_common_output(node, lib_name=None):
# homogeneous behavior.
if "cvss3_severity" in node:
cvss_sev = node.get("cvss3_severity")
elif "vulnerability" in node:
cvss_sev = node["vulnerability"].get("severity")
else:
cvss_sev = node.get("severity")
severity = cvss_sev.lower().capitalize()

cvss3_score = node.get("cvss3_score", None)
cvss3_vector = node.get("scoreMetadataVector", None)
severity_justification = "CVSS v3 score: {} ({})".format(
cvss3_score if cvss3_score is not None else "N/A", cvss3_vector if cvss3_vector is not None else "N/A",
)
cwe = 1035 # default OWASP a9 until the report actually has them

mitigation = "N/A"
if "topFix" in node:
try:
topfix_node = node.get("topFix")
mitigation = "**Resolution** ({}): {}\n".format(
topfix_node.get("date"),
topfix_node.get("fixResolution"),
)
except Exception:
logger.exception("Error handling topFix node.")
# comment out the below for now - working on adding this into the above conditional statements since format can be slightly different
# mitigation = "N/A"
# if "topFix" in node:
# try:
# topfix_node = node.get("topFix")
# mitigation = "**Resolution** ({}): {}\n".format(
# topfix_node.get("date"),
# topfix_node.get("fixResolution"),
# )
# except Exception:
# logger.exception("Error handling topFix node.")

filepaths = []
if "sourceFiles" in node:
Expand Down Expand Up @@ -134,6 +194,7 @@ def _build_common_output(node, lib_name=None):
dynamic_finding=True,
cvssv3=cvss3_vector,
cvssv3_score=float(cvss3_score) if cvss3_score is not None else None,
impact=impact,
)
if cve:
new_finding.unsaved_vulnerability_ids = [cve]
Expand Down Expand Up @@ -164,8 +225,29 @@ def _build_common_output(node, lib_name=None):
for node in tree_node:
findings.append(_build_common_output(node))

elif "components" in content:
# likely a Mend Platform or 3.0 API SCA output - "library" is replaced as "component"
tree_components = content.get("components")
for comp_node in tree_components:
# get component info here, before going into vulns
if (
"response" in comp_node
and len(comp_node.get("response")) > 0
):
for vuln in comp_node.get("response"):
findings.append(
_build_common_output(vuln, comp_node.get("name")),
)

elif "response" in content:
# New schema: handle response array
tree_node = content["response"]
if tree_node:
for node in tree_node:
findings.append(_build_common_output(node))

def create_finding_key(f: Finding) -> str:
"""Hashes the finding's description and title to retrieve a key for deduplication."""
# """Hashes the finding's description and title to retrieve a key for deduplication."""
return hashlib.md5(
f.description.encode("utf-8")
+ f.title.encode("utf-8"),
Expand Down
Loading

0 comments on commit 47c7213

Please sign in to comment.