Skip to content

Commit 992b92b

Browse files
committed
Adding parser for Veracode SCA (SourceClear) JSON/CSV files
1 parent d62a842 commit 992b92b

File tree

7 files changed

+294
-0
lines changed

7 files changed

+294
-0
lines changed

docs/content/en/integrations/parsers.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1183,6 +1183,10 @@ VCG output can be imported in CSV or Xml formats.
11831183

11841184
Detailed XML Report
11851185

1186+
### Veracode SourceClear
1187+
1188+
Import Project CSV or JSON report
1189+
11861190
### Wapiti Scan
11871191

11881192
Import XML report.

dojo/settings/settings.dist.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1143,6 +1143,7 @@ def saml2_attrib_map_format(dict):
11431143
'Whispers': ['vuln_id_from_tool', 'file_path', 'line'],
11441144
'Blackduck Hub Scan': ['title', 'vulnerability_ids', 'component_name', 'component_version'],
11451145
'docker-bench-security Scan': ['unique_id_from_tool'],
1146+
'Veracode SourceClear Scan': ['title', 'vulnerability_ids', 'component_name', 'component_version'],
11461147
}
11471148

11481149
# This tells if we should accept cwe=0 when computing hash_code with a configurable list of fields from HASHCODE_FIELDS_PER_SCANNER (this setting doesn't apply to legacy algorithm)
@@ -1177,6 +1178,7 @@ def saml2_attrib_map_format(dict):
11771178
'Semgrep JSON Report': True,
11781179
'Generic Findings Import': True,
11791180
'Edgescan Scan': True,
1181+
'Veracode SourceClear Scan': True,
11801182
}
11811183

11821184
# List of fields that are known to be usable in hash_code computation)
@@ -1249,6 +1251,7 @@ def saml2_attrib_map_format(dict):
12491251
'Clair Klar Scan': DEDUPE_ALGO_HASH_CODE,
12501252
# 'Qualys Webapp Scan': DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL, # Must also uncomment qualys webapp line in hashcode fields per scanner
12511253
'Veracode Scan': DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL_OR_HASH_CODE,
1254+
'Veracode SourceClear Scan': DEDUPE_ALGO_HASH_CODE,
12521255
# for backwards compatibility because someone decided to rename this scanner:
12531256
'Symfony Security Check': DEDUPE_ALGO_HASH_CODE,
12541257
'DSOP Scan': DEDUPE_ALGO_HASH_CODE,

dojo/tools/veracode_sca/__init__.py

Whitespace-only changes.

dojo/tools/veracode_sca/parser.py

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
import csv
2+
import json
3+
import io
4+
5+
from cvss import parser as cvss_parser
6+
from dateutil import parser
7+
from datetime import datetime
8+
from dojo.models import Finding
9+
10+
11+
class VeracodeScaParser(object):
12+
13+
vc_severity_mapping = {
14+
1: 'Info',
15+
2: 'Low',
16+
3: 'Medium',
17+
4: 'High',
18+
5: 'Critical'
19+
}
20+
21+
def get_scan_types(self):
22+
return ["Veracode SourceClear Scan"]
23+
24+
def get_label_for_scan_types(self, scan_type):
25+
return "Veracode SourceClear Scan"
26+
27+
def get_description_for_scan_types(self, scan_type):
28+
return "Veracode SourceClear CSV or JSON report format"
29+
30+
def get_findings(self, file, test):
31+
if file is None:
32+
return ()
33+
34+
if file.name.strip().lower().endswith(".json"):
35+
return self._get_findings_json(file, test)
36+
37+
return self.get_findings_csv(file, test)
38+
39+
def _get_findings_json(self, file, test):
40+
"""Load a Veracode SCA file in JSON format"""
41+
data = json.load(file)
42+
43+
embedded = data.get("_embedded")
44+
findings = []
45+
if not embedded:
46+
return findings
47+
48+
for issue in embedded.get("issues", []):
49+
if issue.get('issue_type') != 'vulnerability':
50+
continue
51+
52+
date = parser.parse(issue.get("created_date"))
53+
library = issue.get("library")
54+
component_name = library.get("name")
55+
if library.get("id") and library.get("id").startswith("maven:"):
56+
component_name = library.get("id").split(":")[2]
57+
component_version = library.get("version")
58+
59+
vulnerability = issue.get("vulnerability")
60+
vuln_id = vulnerability.get("cve")
61+
if vuln_id and not (vuln_id.startswith("cve") or vuln_id.startswith("CVE")):
62+
vuln_id = "CVE-" + vuln_id
63+
cvss_score = issue.get("severity")
64+
if vulnerability.get("cvss3_score"):
65+
cvss_score = vulnerability.get("cvss3_score")
66+
severity = self.__cvss_to_severity(cvss_score)
67+
68+
description = 'This library has known vulnerabilities.\n'
69+
description += \
70+
"**CVE:** {0} ({1})\n" \
71+
"CVS Score: {2} ({3})\n" \
72+
"Project name: {4}\n" \
73+
"Title: \n>{5}" \
74+
"\n\n-----\n\n".format(
75+
vuln_id,
76+
date,
77+
cvss_score,
78+
severity,
79+
issue.get("project_name"),
80+
vulnerability.get('title'))
81+
82+
finding = Finding(test=test,
83+
title=f"{component_name}:{component_version} | {vuln_id}",
84+
description=description,
85+
severity=severity,
86+
component_name=component_name,
87+
component_version=component_version,
88+
static_finding=True,
89+
dynamic_finding=False,
90+
unique_id_from_tool=issue.get("id"),
91+
date=date,
92+
nb_occurences=1)
93+
94+
if vuln_id:
95+
finding.unsaved_vulnerability_ids = [vuln_id]
96+
97+
if vulnerability.get("cvss3_vector"):
98+
cvssv3_vector = vulnerability.get("cvss3_vector")
99+
if not cvssv3_vector.startswith("CVSS:3.1/"):
100+
cvssv3_vector = "CVSS:3.1/" + cvssv3_vector
101+
vectors = cvss_parser.parse_cvss_from_text(cvssv3_vector)
102+
if len(vectors) > 0:
103+
finding.cvssv3 = vectors[0].clean_vector()
104+
105+
if vulnerability.get("cwe_id"):
106+
cwe = vulnerability.get("cwe_id")
107+
if cwe:
108+
if cwe.startswith("CWE-") or cwe.startswith("cwe-"):
109+
cwe = cwe[4:]
110+
if cwe.isdigit():
111+
finding.cwe = int(cwe)
112+
113+
finding.references = "\n\n" + issue.get("_links").get("html").get("href")
114+
if (issue.get('Ignored') and issue.get('Ignored').capitalize() == 'True' or
115+
issue.get('issue_status') and issue.get('issue_status').capitalize() == 'Resolved'):
116+
finding.is_mitigated = True
117+
finding.active = False
118+
119+
findings.append(finding)
120+
121+
return findings
122+
123+
def get_findings_csv(self, file, test):
124+
content = file.read()
125+
if type(content) is bytes:
126+
content = content.decode('utf-8')
127+
reader = csv.DictReader(io.StringIO(content), delimiter=',', quotechar='"')
128+
csvarray = []
129+
130+
for row in reader:
131+
csvarray.append(row)
132+
133+
findings = []
134+
for row in csvarray:
135+
if row.get('Issue type') != 'Vulnerability':
136+
continue
137+
138+
issueId = row.get('Issue ID', None)
139+
if not issueId:
140+
# Workaround for possible encoding issue
141+
issueId = list(row.values())[0]
142+
library = row.get('Library', None)
143+
if row.get('Package manager') == 'MAVEN' and row.get('Coordinate 2'):
144+
library = row.get('Coordinate 2')
145+
version = row.get('Version in use', None)
146+
vuln_id = row.get('CVE', None)
147+
if vuln_id and not (vuln_id.startswith("cve") or vuln_id.startswith("CVE")):
148+
vuln_id = "CVE-" + vuln_id
149+
150+
severity = self.fix_severity(row.get('Severity', None))
151+
cvss_score = float(row.get('CVSS score', 0))
152+
date = datetime.strptime(row.get('Issue opened: Scan date'), '%d %b %Y %H:%M%p %Z')
153+
description = 'This library has known vulnerabilities.\n'
154+
description += \
155+
"**CVE:** {0} ({1})\n" \
156+
"CVS Score: {2} ({3})\n" \
157+
"Project name: {4}\n" \
158+
"Title: \n>{5}" \
159+
"\n\n-----\n\n".format(
160+
vuln_id,
161+
date,
162+
cvss_score,
163+
severity,
164+
row.get('Project'),
165+
row.get('Title'))
166+
167+
finding = Finding(test=test,
168+
title=f"{library}:{version} | {vuln_id}",
169+
description=description,
170+
severity=severity,
171+
component_name=library,
172+
component_version=version,
173+
static_finding=True,
174+
dynamic_finding=False,
175+
unique_id_from_tool=issueId,
176+
date=date,
177+
nb_occurences=1)
178+
179+
finding.unsaved_vulnerability_ids = [vuln_id]
180+
if cvss_score:
181+
finding.cvssv3_score = cvss_score
182+
183+
if (row.get('Ignored') and row.get('Ignored').capitalize() == 'True' or
184+
row.get('Status') and row.get('Status').capitalize() == 'Resolved'):
185+
finding.is_mitigated = True
186+
finding.active = False
187+
188+
findings.append(finding)
189+
190+
return findings
191+
192+
def fix_severity(self, severity):
193+
severity = severity.capitalize()
194+
if severity is None:
195+
severity = "Medium"
196+
elif "Unknown" == severity or "None" == severity:
197+
severity = "Info"
198+
return severity
199+
200+
@classmethod
201+
def __cvss_to_severity(cls, cvss):
202+
if cvss >= 9:
203+
return cls.vc_severity_mapping.get(5)
204+
elif cvss >= 7:
205+
return cls.vc_severity_mapping.get(4)
206+
elif cvss >= 4:
207+
return cls.vc_severity_mapping.get(3)
208+
elif cvss > 0:
209+
return cls.vc_severity_mapping.get(2)
210+
else:
211+
return cls.vc_severity_mapping.get(1)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
"Issue ID","Issue type","Ignored","Status","Project ID","Library","Version in use","Library release date","Package manager","Coordinate 1","Coordinate 2","Latest version","Latest release date","Project","Branch","Tag","Issue opened: Scan ID","Issue opened: Scan date","Issue fixed: Scan ID","Issue fixed: Scan date","Dependency (Transitive or Direct)","Scan","Scan date","Vulnerability ID","Title","CVSS score","Severity","CVE","Public or Veracode Customer Access","Disclosure date","Has vulnerable methods","Number of vulnerable methods","Updated release date","Release date","Updated Version","License","License Risk"
2+
127637430,"Vulnerability",false,"Open",369423,"AWS Java SDK for Amazon S3","1.11.951","8 Feb 2021 00:00AM GMT","MAVEN","com.amazonaws","aws-java-sdk-s3","1.12.277","8 Aug 2022 01:00AM GMT","some-project","master",,38648137,"7 Jul 2022 09:15AM GMT","","","Transitive",39777838,"8 Aug 2022 10:01AM GMT",36376,"Path Traversal",6.4,"Medium","2022-31159","Public Disclosure","15 Jul 2022 00:00AM GMT",false,0,"","",,,""
3+
122648496,"Vulnerability",false,"Open",369423,"spring-cloud-function-context","3.2.5","26 May 2022 01:00AM GMT","MAVEN","org.springframework.cloud","spring-cloud-function-context","4.0.0-M4","29 Jul 2022 01:00AM GMT","some-project","master",,37831009,"14 Jun 2022 11:34AM GMT","","","Transitive",39777838,"8 Aug 2022 10:01AM GMT",36006,"Denial Of Service (DoS)",5,"Medium","2022-22979","Public Disclosure","15 Jun 2022 00:00AM GMT",false,0,"","",,,""
4+
126041205,"Vulnerability",false,"Resolved",203830,"Apache Commons Configuration","2.1.1","5 Feb 2017 00:00AM GMT","MAVEN","org.apache.commons","commons-configuration2","2.8.0","30 Jun 2022 01:00AM GMT","some-project","master",,38492656,"2 Jul 2022 23:19PM GMT",39357916,"2022-07-27T08:27:30.217+00:00","Direct",38980296,"16 Jul 2022 23:05PM GMT",36282,"Arbitrary Code Execution",7.5,"High","2022-33980","Public Disclosure","14 Jun 2022 00:00AM GMT",false,0,"","",,,""
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"_embedded":{"issues":[{"id":"ddcc6e1b-3ed9-45c8-b77a-ead759fb5e2c","site_id":129556889,"created_date":"2022-07-29T05:13:00.924+0000","issue_status":"open","issue_type":"vulnerability","ignored":false,"severity":8.8,"workspace_id":"12345","project_id":"12345","project_name":"some-project","project_branch":"master","library":{"id":"maven:org.apache.calcite.avatica:avatica-core:1.11.0:","name":"Apache Calcite Avatica","version":"1.11.0","release_date":"2018-03-06","latest_version":"1.22.0","latest_version_release_date":"2022-07-26","direct":true,"transitive":false,"_links":{"self":{"href":"https://api.veracode.com/srcclr/v3/libraries/maven:org.apache.calcite.avatica:avatica-core:1.11.0:"}}},"vulnerability":{"id":"36527","title":"Arbitrary Code Execution","cve":"2022-36364","cvss2_vector":"(AV:L/AC:L/Au:S/C:P/I:P/A:P)","cvss3_vector":"AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H","cvss2_score":4.3,"cvss3_score":8.8,"cwe_id":"CWE-665","_links":{"self":{"href":"https://api.veracode.com/srcclr/v3/vulnerabilities/36527"}}},"vulnerable_method":false,"_links":{"vulnerability":{"href":"https://api.veracode.com/srcclr/v3/vulnerabilities/36527"},"workspace":{"href":"https://api.veracode.com/srcclr/v3/workspaces/123456"},"html":{"href":"https://sca.analysiscenter.veracode.com/teams/X33hjMQ/issues/vulnerabilities/12345"},"self":{"href":"https://api.veracode.com/srcclr/v3/issues/12355"}}}]},"_links":{"self":{"href":"https://api.veracode.com/srcclr/v3/workspaces/12345/issues?type=vulnerability&project_id=1234&page=0&size=200&sort=id,desc"}},"page":{"size":200,"total_elements":1,"total_pages":1,"number":0}}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import datetime
2+
3+
from ..dojo_test_case import DojoTestCase
4+
from dojo.tools.veracode_sca.parser import VeracodeScaParser
5+
from dojo.models import Test
6+
7+
from dateutil.tz import UTC
8+
9+
10+
class TestVeracodeScaScannerParser(DojoTestCase):
11+
12+
def test_parse_csv(self):
13+
testfile = open("unittests/scans/veracode_sca/veracode_sca.csv")
14+
parser = VeracodeScaParser()
15+
findings = parser.get_findings(testfile, Test())
16+
self.assertEqual(3, len(findings))
17+
18+
finding = findings[0]
19+
self.assertEqual("Medium", finding.severity)
20+
self.assertTrue(finding.active)
21+
self.assertFalse(finding.is_mitigated)
22+
self.assertEqual("aws-java-sdk-s3", finding.component_name)
23+
self.assertEqual("1.11.951", finding.component_version)
24+
self.assertEqual(1, len(finding.unsaved_vulnerability_ids))
25+
self.assertEqual("CVE-2022-31159", finding.unsaved_vulnerability_ids[0])
26+
self.assertEqual(6.4, finding.cvssv3_score)
27+
self.assertEqual("127637430", finding.unique_id_from_tool)
28+
self.assertEqual(datetime.datetime(2022, 7, 7, 9, 15, 0), finding.date)
29+
30+
finding = findings[1]
31+
self.assertEqual("Medium", finding.severity)
32+
self.assertTrue(finding.active)
33+
self.assertFalse(finding.is_mitigated)
34+
self.assertEqual("spring-cloud-function-context", finding.component_name)
35+
self.assertEqual("3.2.5", finding.component_version)
36+
self.assertEqual(1, len(finding.unsaved_vulnerability_ids))
37+
self.assertEqual("CVE-2022-22979", finding.unsaved_vulnerability_ids[0])
38+
self.assertEqual(5, finding.cvssv3_score)
39+
self.assertEqual("122648496", finding.unique_id_from_tool)
40+
self.assertEqual(datetime.datetime(2022, 6, 14, 11, 34, 0), finding.date)
41+
42+
finding = findings[2]
43+
self.assertEqual("High", finding.severity)
44+
self.assertFalse(finding.active)
45+
self.assertTrue(finding.is_mitigated)
46+
self.assertEqual("commons-configuration2", finding.component_name)
47+
self.assertEqual("2.1.1", finding.component_version)
48+
self.assertEqual(1, len(finding.unsaved_vulnerability_ids))
49+
self.assertEqual("CVE-2022-33980", finding.unsaved_vulnerability_ids[0])
50+
self.assertEqual(7.5, finding.cvssv3_score)
51+
self.assertEqual("126041205", finding.unique_id_from_tool)
52+
self.assertEqual(datetime.datetime(2022, 7, 2, 23, 19, 0), finding.date)
53+
54+
def test_parse_json(self):
55+
testfile = open("unittests/scans/veracode_sca/veracode_sca.json")
56+
parser = VeracodeScaParser()
57+
findings = parser.get_findings(testfile, Test())
58+
self.assertEqual(1, len(findings))
59+
60+
finding = findings[0]
61+
self.assertEqual("High", finding.severity)
62+
self.assertTrue(finding.active)
63+
self.assertFalse(finding.is_mitigated)
64+
self.assertEqual("avatica-core", finding.component_name)
65+
self.assertEqual("1.11.0", finding.component_version)
66+
self.assertEqual(1, len(finding.unsaved_vulnerability_ids))
67+
self.assertEqual("CVE-2022-36364", finding.unsaved_vulnerability_ids[0])
68+
self.assertEqual("CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", finding.cvssv3)
69+
self.assertEqual(665, finding.cwe)
70+
self.assertEqual("ddcc6e1b-3ed9-45c8-b77a-ead759fb5e2c", finding.unique_id_from_tool)
71+
self.assertEqual(datetime.datetime(2022, 7, 29, 5, 13, 0, 924000).astimezone(UTC), finding.date)

0 commit comments

Comments
 (0)