Skip to content

Commit a60667d

Browse files
authored
Implement support for SCA and KICS data in Checkmarx parser (DefectDojo#7552)
* First version * Add doc * Add new tests * Fix date * Add management for empty Id * Add more data and tags * Fix more things * Add more tests
1 parent 2e45501 commit a60667d

File tree

4 files changed

+45051
-21
lines changed

4 files changed

+45051
-21
lines changed

docs/content/en/integrations/parsers/file/checkmarx.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ To generate the OSA report using Checkmarx CLI:
1010

1111
That will generate three files, two of which are needed for defectdojo. Build the file for defectdojo with the jq utility:
1212
`jq -s . CxOSAVulnerabilities.json CxOSALibraries.json`
13+
14+
Data for SAST, SCA and KICS are supported.

dojo/tools/checkmarx/parser.py

Lines changed: 86 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11

2+
import datetime
23
import json
34
import logging
45

56
from dateutil import parser
67
from defusedxml import ElementTree
8+
79
from dojo.models import Finding
810
from dojo.utils import add_language
911

@@ -267,8 +269,8 @@ def getQueryElements(self, query):
267269
categories = query.get('categories')
268270
return name, cwe, categories, queryId
269271

270-
# Map checkmarx report state to active/inactive status
271272
def isActive(self, state):
273+
"""Map checkmarx report state to active/inactive status"""
272274
# To verify, Confirmed, Urgent, Proposed not exploitable
273275
activeStates = ["0", "2", "3", "4"]
274276
return state in activeStates
@@ -285,37 +287,100 @@ def get_findings(self, file, test):
285287
else:
286288
return self._get_findings_xml(file, test)
287289

290+
def _parse_date(self, value):
291+
if isinstance(value, str):
292+
return parser.parse(value)
293+
elif isinstance(value, dict) and isinstance(value.get("seconds"), int):
294+
return datetime.datetime.utcfromtimestamp(value.get("seconds"))
295+
else:
296+
return None
297+
288298
def _get_findings_json(self, file, test):
289299
""""""
290300
data = json.load(file)
291301
findings = []
292302
results = data.get("scanResults", [])
293303
for result_type in results:
294-
for language in results[result_type].get("languages"):
295-
for query in language.get("queries", []):
296-
descriptiondetails = query.get("description", "")
297-
group = ""
298-
title = query.get("queryName").replace("_", " ")
299-
if query.get('groupName'):
300-
group = query.get('groupName').replace('_', ' ')
301-
for vulnerability in query.get("vulnerabilities", []):
304+
# manage sca part
305+
if result_type == "sast":
306+
for language in results[result_type].get("languages", []):
307+
for query in language.get("queries", []):
308+
descriptiondetails = query.get("description", "")
309+
group = ""
310+
title = query.get("queryName").replace("_", " ")
311+
if query.get('groupName'):
312+
group = query.get('groupName').replace('_', ' ')
313+
for vulnerability in query.get("vulnerabilities", []):
314+
finding = Finding(
315+
description=descriptiondetails,
316+
title=title,
317+
date=self._parse_date(vulnerability.get("firstFoundDate")),
318+
severity=vulnerability.get("severity").title(),
319+
active=(vulnerability.get("status") != "Not exploitable"),
320+
verified=(vulnerability.get("status") != "To verify"),
321+
test=test,
322+
cwe=vulnerability.get("cweId"),
323+
static_finding=True,
324+
)
325+
if vulnerability.get("id"):
326+
finding.unique_id_from_tool = vulnerability.get("id")
327+
else:
328+
finding.unique_id_from_tool = str(vulnerability.get("similarityId"))
329+
# get the last node and set some values
330+
if vulnerability.get('nodes'):
331+
last_node = vulnerability['nodes'][-1]
332+
finding.file_path = last_node.get("fileName")
333+
finding.line = last_node.get("line")
334+
finding.unsaved_tags = [result_type]
335+
findings.append(finding)
336+
if result_type == "sca":
337+
for package in results[result_type].get("packages", []):
338+
component_name = package.get("name").split("-")[-2]
339+
component_version = package.get("name").split("-")[-1]
340+
for vulnerability in package.get('vulnerabilities', []):
341+
cve = vulnerability.get('cveId')
302342
finding = Finding(
303-
description=descriptiondetails,
304-
title=title,
305-
date=parser.parse(vulnerability.get("firstFoundDate")),
343+
title=f'{component_name}:{component_version} | {cve}',
344+
description=vulnerability.get("description"),
345+
date=self._parse_date(vulnerability.get("firstFoundDate")),
306346
severity=vulnerability.get("severity").title(),
307347
active=(vulnerability.get("status") != "Not exploitable"),
308-
verified=(vulnerability.get("status") != "To verify"),
348+
verified=(vulnerability.get("state") != "To verify"),
349+
component_name=component_name,
350+
component_version=component_version,
309351
test=test,
310-
cwe=vulnerability.get("cweId"),
311-
static_finding=(result_type == "sast"),
312-
unique_id_from_tool=vulnerability.get("id"),
352+
cwe=int(vulnerability.get("cwe", 0)),
353+
static_finding=True,
313354
)
314-
# get the last node and set some values
315-
if vulnerability.get('nodes'):
316-
last_node = vulnerability['nodes'][-1]
317-
finding.file_path = last_node.get("fileName")
318-
finding.line = last_node.get("line")
355+
if vulnerability.get("cveId"):
356+
finding.unsaved_vulnerability_ids = [vulnerability.get("cveId")]
357+
if vulnerability.get("id"):
358+
finding.unique_id_from_tool = vulnerability.get("id")
359+
else:
360+
finding.unique_id_from_tool = str(vulnerability.get("similarityId"))
319361
finding.unsaved_tags = [result_type]
320362
findings.append(finding)
363+
if result_type == "kics":
364+
for kics_type in results[result_type].get("results", []):
365+
name = kics_type.get('name')
366+
for vulnerability in kics_type.get('vulnerabilities', []):
367+
finding = Finding(
368+
title=f'{name} | {vulnerability.get("issueType")}',
369+
description=vulnerability.get("description"),
370+
date=self._parse_date(vulnerability.get("firstFoundDate")),
371+
severity=vulnerability.get("severity").title(),
372+
active=(vulnerability.get("status") != "Not exploitable"),
373+
verified=(vulnerability.get("state") != "To verify"),
374+
file_path=vulnerability.get("fileName"),
375+
line=vulnerability.get("line", 0),
376+
severity_justification=vulnerability.get("actualValue"),
377+
test=test,
378+
static_finding=True,
379+
)
380+
if vulnerability.get("id"):
381+
finding.unique_id_from_tool = vulnerability.get("id")
382+
else:
383+
finding.unique_id_from_tool = str(vulnerability.get("similarityId"))
384+
finding.unsaved_tags = [result_type, name]
385+
findings.append(finding)
321386
return findings

0 commit comments

Comments
 (0)