Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New HCL AppScan on Cloud SAST parser #11375

Merged
merged 19 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: "HCL AppScan on Cloud SAST"
toc_hide: true
---
HCL Appscan on Cloud can export the results in PDF, XML and CSV formats but this parser only supports the import of XML generated from HCL Appscan on Cloud for SAST scans.

### Sample Scan Data
Sample HCL AppScan on Cloud SAST scans can be found [here](https://github.com/DefectDojo/django-DefectDojo/tree/master/unittests/scans/hcl_asoc_sast).
2 changes: 1 addition & 1 deletion dojo/settings/.settings.dist.py.sha256sum
Original file line number Diff line number Diff line change
@@ -1 +1 @@
6e88f73d9310e9da23ff2b1c5078ed40a0b604d1cbda42d4f009bc1134330c38
6fd36fcdf01e6881e5d97fbf37fe8e10b2aad8ac7878691f9e362cecc4eb7cca
3 changes: 3 additions & 0 deletions dojo/settings/settings.dist.py
Original file line number Diff line number Diff line change
Expand Up @@ -1276,6 +1276,7 @@ def saml2_attrib_map_format(dict):
"Humble Json Importer": ["title"],
"MSDefender Parser": ["title", "description"],
"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 Scorecard Scan": ["title", "description", "severity"],
Expand Down Expand Up @@ -1363,6 +1364,7 @@ def saml2_attrib_map_format(dict):
"Wazuh": True,
"Nuclei Scan": True,
"Threagile risks report": True,
"HCL AppScan on Cloud SAST XML": True,
"AWS Inspector2 Scan": True,
}

Expand Down Expand Up @@ -1521,6 +1523,7 @@ def saml2_attrib_map_format(dict):
"Wazuh Scan": DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL,
"MSDefender Parser": DEDUPE_ALGO_HASH_CODE,
"HCLAppScan XML": DEDUPE_ALGO_HASH_CODE,
"HCL AppScan on Cloud SAST XML": DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL_OR_HASH_CODE,
"KICS Scan": DEDUPE_ALGO_HASH_CODE,
"MobSF Scan": DEDUPE_ALGO_HASH_CODE,
"MobSF Scorecard Scan": DEDUPE_ALGO_HASH_CODE,
Expand Down
1 change: 1 addition & 0 deletions dojo/tools/hcl_asoc_sast/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__author__ = "xpert98"
156 changes: 156 additions & 0 deletions dojo/tools/hcl_asoc_sast/parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
from xml.dom import NamespaceErr

from defusedxml import ElementTree as ET

from dojo.models import Finding


class HCLASoCSASTParser:
def get_scan_types(self):
return ["HCL AppScan on Cloud SAST XML"]

def get_label_for_scan_types(self, scan_type):
return scan_type

def get_description_for_scan_types(self, scan_type):
return "Import XML output of HCL AppScan on Cloud SAST"

def xmltreehelper(self, input):
if input.text is None:
output = None
elif "\n" in input.text:
output = ""
for i in input:
output = output + " " + i.text
else:
output = " " + input.text
return output

def get_findings(self, file, test):
findings = []
tree = ET.parse(file)
root = tree.getroot()
if "xml-report" not in root.tag:
msg = "This doesn't seem to be a valid HCL ASoC SAST xml file."
raise NamespaceErr(msg)
report = root.find("issue-group")
if report is not None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of putting the whole function after this point inside an if block when report is not None, just bail if report is None.

Suggested change
if report is not None:
if report is None:
return findings

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was keeping the overall style of the parser similar to the existing hcl_appscan (for DAST) parser for consistency. I can refactor if this is a dealbreaker.

for finding in report:
title = ""
description = ""
for item in finding:
match item.tag:
case "severity":
output = self.xmltreehelper(item)
if output is None:
severity = "Info"
else:
severity = output.strip(" ").capitalize()

Check failure on line 48 in dojo/tools/hcl_asoc_sast/parser.py

View workflow job for this annotation

GitHub Actions / ruff-linting

Ruff (SIM108)

dojo/tools/hcl_asoc_sast/parser.py:45:29: SIM108 Use ternary operator `severity = "Info" if output is None else output.strip(" ").capitalize()` instead of `if`-`else`-block
case "cwe":
cwe = int(self.xmltreehelper(item))
case "issue-type":
title = self.xmltreehelper(item).strip()
description = description + "***Issue-Type:" + title + "\n"
xpert98 marked this conversation as resolved.
Show resolved Hide resolved
case "issue-type-name":
title = self.xmltreehelper(item).strip()
description = description + "***Issue-Type-Name:" + title + "\n"
xpert98 marked this conversation as resolved.
Show resolved Hide resolved
case "source-file":
location = self.xmltreehelper(item).strip()
description = description + "***Location:" + location + "\n"
xpert98 marked this conversation as resolved.
Show resolved Hide resolved
case "line":
line = int(self.xmltreehelper(item).strip())
description = description + "***Line:" + str(line) + "\n"
cneill marked this conversation as resolved.
Show resolved Hide resolved
case "threat-class":
threatclass = self.xmltreehelper(item)
description = description + "***Threat-Class:" + threatclass + "\n"
cneill marked this conversation as resolved.
Show resolved Hide resolved
case "entity":
entity = self.xmltreehelper(item)
title += "_" + entity.strip()
description = description + "***Entity:" + entity + "\n"
cneill marked this conversation as resolved.
Show resolved Hide resolved
case "security-risks":
security_risks = self.xmltreehelper(item)
description = description + "***Security-Risks:" + security_risks + "\n"
cneill marked this conversation as resolved.
Show resolved Hide resolved
case "cause-id":
causeid = self.xmltreehelper(item)
title += "_" + causeid.strip()
description = description + "***Cause-Id:" + causeid + "\n"
cneill marked this conversation as resolved.
Show resolved Hide resolved
case "element":
element = self.xmltreehelper(item)
description = description + "***Element:" + element + "\n"
cneill marked this conversation as resolved.
Show resolved Hide resolved
case "element-type":
elementtype = self.xmltreehelper(item)
description = description + "***ElementType:" + elementtype + "\n"
cneill marked this conversation as resolved.
Show resolved Hide resolved
case "variant-group":
variantgroup = item.iter()
description = description + "***Call Trace:" + "\n"
cneill marked this conversation as resolved.
Show resolved Hide resolved
for vitem in variantgroup:
if vitem.tag == "issue-information":
issueinformation = vitem.iter()
for iitem in issueinformation:
if iitem.tag == "context":
description = description + self.xmltreehelper(iitem) + "\n"

case "fix":
recommendations = ""
externalreferences = ""
issuetypename = ""
remediation = ""
fix = item.iter()
for fitem in fix:
if fitem.tag == "types":
type = fitem.iter()
for titem in type:
if titem.tag == "name":
issuetypename = self.xmltreehelper(titem)
if fitem.tag == "remediation":
remediation = self.xmltreehelper(fitem)

articlegroup = root.find("article-group")
if articlegroup is not None:
for articles in articlegroup:
if articles.attrib["id"] == issuetypename.strip() and articles.attrib["api"] == remediation.strip():
articledetails = articles.iter()
for aitem in articledetails:
if aitem.tag == "cause":
description = description + "***Cause:" + "\n"
cneill marked this conversation as resolved.
Show resolved Hide resolved
for causeitem in aitem:
if causeitem.attrib["type"] == "string":
if causeitem.text is not None:
xpert98 marked this conversation as resolved.
Show resolved Hide resolved
description = description + causeitem.text + "\n"
if aitem.tag == "recommendations":
for recitem in aitem:
if recitem.attrib["type"] == "string":
if recitem.text is not None:
xpert98 marked this conversation as resolved.
Show resolved Hide resolved
recommendations = recommendations + recitem.text + "\n"
elif recitem.attrib["type"] == "object":
codeblock = recitem.iter()
for codeitem in codeblock:
if codeitem.tag == "item" and codeitem.attrib["type"] == "string":
if codeitem.text is None:
recommendations = recommendations + "\n"
else:
recommendations = recommendations + self.xmltreehelper(codeitem) + "\n"

if aitem.tag == "externalReferences":
ref = aitem.iter()
for ritem in ref:
if ritem.tag == "title":
externalreferences = externalreferences + self.xmltreehelper(ritem).strip() + "\n"
if ritem.tag == "url":
externalreferences = externalreferences + self.xmltreehelper(ritem).strip() + "\n"

prepared_finding = Finding(
title=title,
description=description,
file_path=location,
line=line,
severity=severity,
cwe=cwe,
mitigation=recommendations,
references=externalreferences,
dynamic_finding=False,
static_finding=True,
)
findings.append(prepared_finding)
return findings
return findings
Loading
Loading