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

Feature: Checkmarx Cxflow SAST parser #9719

Open
wants to merge 25 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
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: "Checkmarx CxFlow SAST"
toc_hide: true
---
- `Checkmarx CxFlow SAST`: JSON report from Checkmarx Cxflow.
biennd279 marked this conversation as resolved.
Show resolved Hide resolved

### Sample Scan Data
Sample Checkmarx CxFlow SAST scans can be found [here](https://github.com/DefectDojo/django-DefectDojo/tree/master/unittests/scans/checkmarx_cxflow_sast).
1 change: 1 addition & 0 deletions dojo/settings/settings.dist.py
Original file line number Diff line number Diff line change
Expand Up @@ -1463,6 +1463,7 @@ def saml2_attrib_map_format(dict):
'MobSF Scan': DEDUPE_ALGO_HASH_CODE,
'OSV Scan': DEDUPE_ALGO_HASH_CODE,
'Nosey Parker Scan': DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL_OR_HASH_CODE,
'Checkmarx CxFlow SAST': DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL,
}

# Override the hardcoded settings here via the env var
Expand Down
Empty file.
153 changes: 153 additions & 0 deletions dojo/tools/checkmarx_cxflow_sast/parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import json
import dateutil.parser
import logging

from dojo.models import Finding

logger = logging.getLogger(__name__)


class _PathNode(object):
def __init__(self, file: str, line: str, column: str, node_object: str, length: str, snippet: str):
self.file = file
self.line = line
self.column = int(column)
self.node_object = node_object
self.length = int(length)
self.snippet = snippet

@classmethod
def from_json_object(cls, data):
return _PathNode(
data.get("file"),
data.get("line"),
data.get("column"),
data.get("object"),
data.get("length"),
data.get("snippet")
)


class _Path(object):
def __init__(self, sink: _PathNode, source: _PathNode, state: str, paths: [_PathNode]):
self.sink = sink
self.source = source
self.state = state
self.paths = paths


class CheckmarxCXFlowSastParser(object):
def __init__(self):
pass

def get_scan_types(self):
return ["Checkmarx CxFlow SAST"]

def get_label_for_scan_types(self, scan_type):
return scan_type # no custom label for now

def get_description_for_scan_types(self, scan_type):
return "Detailed Report. Import all vulnerabilities from checkmarx without aggregation"

def get_findings(self, file, test):
if file.name.strip().lower().endswith(".json"):
return self._get_findings_json(file, test)
else:
logger.warning(f"Not supported file format ${file}")
return list()

def _get_findings_json(self, file, test):
data = json.load(file)
findings = []
# deepLink = data.get("deepLink")
biennd279 marked this conversation as resolved.
Show resolved Hide resolved
additional_details = data.get("additionalDetails")
scan_start_date = additional_details.get("scanStartDate")

issues = data.get("xissues", [])

for issue in issues:
vulnerability = issue.get("vulnerability")
status = issue.get("vulnerabilityStatus")
cwe = issue.get("cwe")
description = issue.get("description")
language = issue.get("language")
severity = issue.get("severity")
link = issue.get("link")
filename = issue.get("filename")
similarity_id = issue.get("similarityId")

issue_additional_details = issue.get("additionalDetails")
categories = issue_additional_details.get("categories")
results = issue_additional_details.get("results")

map_paths = {}

for result in results:
# all path nodes exclude sink, source, state
path_keys = sorted(filter(lambda k: isinstance(k, str) and k.isnumeric(), result.keys()))

path = _Path(
sink=_PathNode.from_json_object(result.get("sink")),
source=_PathNode.from_json_object(result.get("source")),
state=result.get("state"),
paths=list([result[k] for k in path_keys])
)

map_paths[str(path.source.line)] = path

for detail_key in issue.get("details").keys():
if detail_key not in map_paths:
logger.warning(f"{detail_key} not found in path, ignore")
else:
detail = map_paths[detail_key]

finding_detail = f"**Category:** {categories}\n"
finding_detail += f"**Language:** {language}\n"
finding_detail += f"**Status:** {status}\n"
finding_detail += f"**Finding link:** [{link}]({link})\n"
finding_detail += f"**Description:** {description}\n"
finding_detail += f"**Source snippet:** `{detail.source.snippet if detail.source is not None else ''}`\n"
finding_detail += f"**Sink snippet:** `{detail.sink.snippet if detail.sink is not None else ''}`\n"

finding = Finding(
title=vulnerability.replace("_", " ") + " " + detail.sink.file.split("/")[
-1] if detail.sink is not None else "",
cwe=int(cwe),
date=dateutil.parser.parse(scan_start_date),
static_finding=True,
unique_id_from_tool=str(similarity_id) + str(detail_key),
biennd279 marked this conversation as resolved.
Show resolved Hide resolved
test=test,
sast_source_object=detail.source.node_object if detail.source is not None else None,
sast_sink_object=detail.sink.node_object if detail.sink is not None else None,
sast_source_file_path=detail.source.file if detail.source is not None else None,
sast_source_line=detail.source.line if detail.source is not None else None,
vuln_id_from_tool=similarity_id,
severity=severity,
file_path=filename,
line=detail.sink.line,
false_p=issue.get("details")[detail_key].get("falsePositive") or self.is_not_exploitable(detail.state),
description=finding_detail,
verified=self.is_verify(detail.state),
active=self.is_active(detail.state)
)

findings.append(finding)

return findings

def _get_findings_xml(self):
# TODO: move logic from checkmarx to here
biennd279 marked this conversation as resolved.
Show resolved Hide resolved
pass

def is_verify(self, state):
# Confirmed, urgent
verifiedStates = ["2", "3"]
return state in verifiedStates

def is_active(self, state):
# To verify, Confirmed, Urgent, Proposed not exploitable
activeStates = ["0", "2", "3", "4"]
return state in activeStates

def is_not_exploitable(self, state):
return state == "1"
192 changes: 192 additions & 0 deletions unittests/scans/checkmarx_cxflow_sast/1-finding.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
{
"projectId": "6",
"team": "CxServer",
"project": "some-example",
"link": "http://CX-FLOW-CLEAN/CxWebClient/ViewerMain.aspx?scanid=1000026&projectid=6",
"files": "1",
"loc": "268",
"scanType": "Full",
"version":"8.9.0.210",
"additionalDetails": {
"flow-summary": {
"High": 1
},
"scanId": "1000026",
"scanStartDate": "Sunday, January 19, 2020 2:40:11 AM"
},
"xissues": [
{
"vulnerability": "Reflected_XSS_All_Clients",
"vulnerabilityStatus": "TO VERIFY",
"similarityId": "14660819",
"cwe": "79",
"description": "",
"language": "Java",
"severity": "High",
"link": "http://CX-FLOW-CLEAN/CxWebClient/ViewerMain.aspx?scanid=1000026&projectid=6&pathid=2",
"filename": "DOS_Login.java",
"falsePositiveCount": 0,
"details": {
"88": {
"falsePositive": false,
"codeSnippet": "username = s.getParser().getRawParameter(USERNAME);",
"comment": ""
}
},
"additionalDetails": {
"recommendedFix": "http://CX-FLOW-CLEAN/CxWebClient/ScanQueryDescription.aspx?queryID=591&queryVersionCode=56110529&queryTitle=Reflected_XSS_All_Clients",
"categories": "PCI DSS v3.2;PCI DSS (3.2) - 6.5.7 - Cross-site scripting (XSS),OWASP Top 10 2013;A3-Cross-Site Scripting (XSS),FISMA 2014;System And Information Integrity,NIST SP 800-53;SI-15 Information Output Filtering (P0),OWASP Top 10 2017;A7-Cross-Site Scripting (XSS)",
"results": [
{
"sink": {
"file": "AnotherFile.java",
"line": "107",
"column": "9",
"object": "username",
"length" : "8",
"snippet" : "+ username + \"' and password = '\" + password + \"'\";"
},
"state": "0",
"source": {
"file": "DOS_Login.java",
"line": "88",
"column": "46",
"object": "getRawParameter",
"length" : "1",
"snippet" : "username = s.getParser().getRawParameter(USERNAME);"
},
"1" : {
"snippet" : "username = s.getParser().getRawParameter(USERNAME);",
"file" : "DOS_Login.java",
"line" : "88",
"column" : "46",
"length" : "1",
"object" : "getRawParameter"
},
"2" : {
"snippet" : "username = s.getParser().getRawParameter(USERNAME);",
"file" : "DOS_Login.java",
"line" : "88",
"column" : "6",
"length" : "8",
"object" : "username"
},
"3" : {
"snippet" : "if (username.equals(\"jeff\") || username.equals(\"dave\"))",
"file" : "DOS_Login.java",
"line" : "92",
"column" : "37",
"length" : "8",
"object" : "username"
},
"4" : {
"snippet" : "if (username.equals(\"jeff\") || username.equals(\"dave\"))",
"file" : "DOS_Login.java",
"line" : "92",
"column" : "10",
"length" : "8",
"object" : "username"
},
"5" : {
"snippet" : "+ username + \"' and password = '\" + password + \"'\";",
"file" : "AnotherFile.java",
"line" : "107",
"column" : "9",
"length" : "8",
"object" : "username"
}
}
],
"CodeBashingLesson" : "https://cxa.codebashing.com/courses/"
},
"allFalsePositive": false
}
],
"unFilteredIssues": [ {
"vulnerability" : "Reflected_XSS_All_Clients",
"vulnerabilityStatus" : "TO VERIFY",
"similarityId" : "14660819",
"cwe" : "79",
"description" : "",
"language" : "Java",
"severity" : "High",
"link" : "http://CX-FLOW-CLEAN/CxWebClient/ViewerMain.aspx?scanid=1000026&projectid=6&pathid=2",
"filename" : "DOS_Login.java",
"gitUrl" : "",
"falsePositiveCount" : 0,
"details" : {
"88" : {
"falsePositive" : false,
"comment" : ""
}
},
"additionalDetails" : {
"recommendedFix" : "http://CX-FLOW-CLEAN/CxWebClient/ScanQueryDescription.aspx?queryID=591&queryVersionCode=56110529&queryTitle=Reflected_XSS_All_Clients",
"categories" : "PCI DSS v3.2;PCI DSS (3.2) - 6.5.7 - Cross-site scripting (XSS),OWASP Top 10 2013;A3-Cross-Site Scripting (XSS),FISMA 2014;System And Information Integrity,NIST SP 800-53;SI-15 Information Output Filtering (P0),OWASP Top 10 2017;A7-Cross-Site Scripting (XSS)",
"results" : [ {
"1" : {
"snippet" : "username = s.getParser().getRawParameter(USERNAME);",
"file" : "DOS_Login.java",
"line" : "88",
"column" : "46",
"length" : "1",
"object" : "getRawParameter"
},
"2" : {
"snippet" : "username = s.getParser().getRawParameter(USERNAME);",
"file" : "DOS_Login.java",
"line" : "88",
"column" : "6",
"length" : "8",
"object" : "username"
},
"3" : {
"snippet" : "if (username.equals(\"jeff\") || username.equals(\"dave\"))",
"file" : "DOS_Login.java",
"line" : "92",
"column" : "37",
"length" : "8",
"object" : "username"
},
"4" : {
"snippet" : "if (username.equals(\"jeff\") || username.equals(\"dave\"))",
"file" : "DOS_Login.java",
"line" : "92",
"column" : "10",
"length" : "8",
"object" : "username"
},
"5" : {
"snippet" : "+ username + \"' and password = '\" + password + \"'\";",
"file" : "AnotherFile.java",
"line" : "107",
"column" : "9",
"length" : "8",
"object" : "username"
},
"sink" : {
"snippet" : "+ username + \"' and password = '\" + password + \"'\";",
"file" : "AnotherFile.java",
"line" : "107",
"column" : "9",
"length" : "8",
"object" : "username"
},
"state" : "0",
"source" : {
"snippet" : "username = s.getParser().getRawParameter(USERNAME);",
"file" : "DOS_Login.java",
"line" : "88",
"column" : "46",
"length" : "1",
"object" : "getRawParameter"
}
} ]
},
"allFalsePositive" : false
} ],
"reportCreationTime":"Sunday, January 19, 2020 2:41:53 AM",
"deepLink":"http://CX-FLOW-CLEAN/CxWebClient/ViewerMain.aspx?scanid=1000026&projectid=6",
"scanTime":"00h:01m:30s",
"sastResults": false
}
Loading
Loading