Skip to content

Commit 97e9f77

Browse files
authored
tools: NeuVector: introducing NeuVector (REST) scan type (DefectDojo#6809)
This commit adds the support of NeuVector (https://github.com/neuvector/neuvector) tool for importing scan results. Scan results can be exported via REST API in JSON format (that is why the tool is named 'NeuVector (REST)'). There is no GUI for that at the moment. Scan results are just a list of issues found in packages installed in a container or an image. Very similar to Twistlock. NeuVector also provides compliance scan results. This is not supported by the introduced tool.
1 parent 199578c commit 97e9f77

File tree

7 files changed

+424
-0
lines changed

7 files changed

+424
-0
lines changed

dojo/settings/settings.dist.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1202,6 +1202,7 @@ def saml2_attrib_map_format(dict):
12021202
'docker-bench-security Scan': ['unique_id_from_tool'],
12031203
'Veracode SourceClear Scan': ['title', 'vulnerability_ids', 'component_name', 'component_version'],
12041204
'Twistlock Image Scan': ['title', 'severity', 'component_name', 'component_version'],
1205+
'NeuVector (REST)': ['title', 'severity', 'component_name', 'component_version'],
12051206
}
12061207

12071208
# 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)
@@ -1364,6 +1365,7 @@ def saml2_attrib_map_format(dict):
13641365
'BlackDuck API': DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL,
13651366
'docker-bench-security Scan': DEDUPE_ALGO_HASH_CODE,
13661367
'Twistlock Image Scan': DEDUPE_ALGO_HASH_CODE,
1368+
'NeuVector (REST)': DEDUPE_ALGO_HASH_CODE,
13671369
}
13681370

13691371
DUPE_DELETE_MAX_PER_RUN = env('DD_DUPE_DELETE_MAX_PER_RUN')

dojo/tools/neuvector/__init__.py

Whitespace-only changes.

dojo/tools/neuvector/parser.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import json
2+
import logging
3+
4+
from dojo.models import Finding
5+
6+
logger = logging.getLogger(__name__)
7+
8+
NEUVECTOR_SCAN_NAME = 'NeuVector (REST)'
9+
NEUVECTOR_IMAGE_SCAN_ENGAGEMENT_NAME = 'NV image scan'
10+
NEUVECTOR_CONTAINER_SCAN_ENGAGEMENT_NAME = 'NV container scan'
11+
12+
13+
class NeuVectorJsonParser(object):
14+
def parse(self, json_output, test):
15+
tree = self.parse_json(json_output)
16+
items = []
17+
if tree:
18+
items = [data for data in self.get_items(tree, test)]
19+
return items
20+
21+
def parse_json(self, json_output):
22+
try:
23+
data = json_output.read()
24+
try:
25+
tree = json.loads(str(data, 'utf-8'))
26+
except:
27+
tree = json.loads(data)
28+
except:
29+
raise Exception("Invalid format")
30+
31+
return tree
32+
33+
def get_items(self, tree, test):
34+
items = {}
35+
if 'report' in tree:
36+
vulnerabilityTree = tree.get('report').get('vulnerabilities', [])
37+
for node in vulnerabilityTree:
38+
item = get_item(node, test)
39+
package_name = node.get('package_name')
40+
if len(package_name) > 64:
41+
package_name = package_name[-64:]
42+
unique_key = node.get('name') + str(package_name + str(
43+
node.get('package_version')) + str(node.get('severity')))
44+
items[unique_key] = item
45+
return list(items.values())
46+
47+
48+
def get_item(vulnerability, test):
49+
severity = convert_severity(vulnerability.get('severity')) if 'severity' in vulnerability else "Info"
50+
vector = vulnerability.get('vectors_v3') if 'vectors_v3' in vulnerability else "CVSSv3 vector not provided. "
51+
fixed_version = vulnerability.get('fixed_version') if 'fixed_version' in vulnerability else "There seems to be no fix yet. Please check description field."
52+
score_v3 = vulnerability.get('score_v3') if 'score_v3' in vulnerability else "No CVSSv3 score yet."
53+
package_name = vulnerability.get('package_name')
54+
if len(package_name) > 64:
55+
package_name = package_name[-64:]
56+
description = vulnerability.get('description') if 'description' in vulnerability else ""
57+
link = vulnerability.get('link') if 'link' in vulnerability else ""
58+
59+
# create the finding object
60+
finding = Finding(
61+
title=vulnerability.get('name') + ": " + package_name + " - " + vulnerability.get('package_version'),
62+
test=test,
63+
severity=severity,
64+
description=description + "<p> Vulnerable Package: " +
65+
package_name + "</p><p> Current Version: " + str(
66+
vulnerability['package_version']) + "</p>",
67+
mitigation=fixed_version.title(),
68+
references=link,
69+
component_name=package_name,
70+
component_version=vulnerability.get('package_version'),
71+
false_p=False,
72+
duplicate=False,
73+
out_of_scope=False,
74+
mitigated=None,
75+
severity_justification="{} (CVSS v3 base score: {})\n".format(vector, score_v3),
76+
impact=severity)
77+
finding.unsaved_vulnerability_ids = [vulnerability.get('name')]
78+
finding.description = finding.description.strip()
79+
80+
return finding
81+
82+
83+
# see neuvector/share/types.go
84+
def convert_severity(severity):
85+
if severity.lower() == 'critical':
86+
return "Critical"
87+
elif severity.lower() == 'high':
88+
return "High"
89+
elif severity.lower() == 'medium':
90+
return "Medium"
91+
elif severity.lower() == 'low':
92+
return "Low"
93+
elif severity == '':
94+
return "Info"
95+
else:
96+
return severity.title()
97+
98+
99+
class NeuVectorParser(object):
100+
101+
def get_scan_types(self):
102+
return [NEUVECTOR_SCAN_NAME]
103+
104+
def get_label_for_scan_types(self, scan_type):
105+
return NEUVECTOR_SCAN_NAME
106+
107+
def get_description_for_scan_types(self, scan_type):
108+
return "JSON output of /v1/scan/{entity}/{id} endpoint."
109+
110+
def get_findings(self, filename, test):
111+
if filename is None:
112+
return list()
113+
114+
if filename.name.lower().endswith('.json'):
115+
return NeuVectorJsonParser().parse(filename, test)
116+
else:
117+
raise Exception('Unknown File Format')
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
{
2+
"report": {
3+
"vulnerabilities": [
4+
{
5+
"name": "CVE-2015-8356",
6+
"score": 7.2,
7+
"severity": "High",
8+
"vectors": "",
9+
"description": "The setup_env function in group.c in sshd in OpenSSH allows local users to gain privileges.",
10+
"package_name": "openssh",
11+
"package_version": "7.2_p2-r0",
12+
"fixed_version": "1:7.2p2-3",
13+
"link": "https://security-tracker.debian.org/tracker/CVE-2015-8356",
14+
"score_v3": 7.3,
15+
"vectors_v3": "",
16+
"published_timestamp": 1516561260,
17+
"last_modified_timestamp": 1516561253,
18+
"cpes": [
19+
""
20+
],
21+
"cves": [
22+
""
23+
],
24+
"feed_rating": "",
25+
"in_base_image": true,
26+
"tags": [
27+
""
28+
]
29+
},{
30+
"name": "CVE-2017-18342",
31+
"score": 7.5,
32+
"severity": "High",
33+
"vectors": "AV:N/AC:L/Au:N/C:P/I:P/A:P",
34+
"description": "STATEMENT: PyYAML in channels for Red Hat MRG Messaging 2 should no longer be used, as a newer version is now available in Red Hat Enterprise Linux. Newer packages should be consumed from Red Hat Enterprise Linux channels. This issue affects the versions of the PyYAML package as shipped with Red Hat Satellite 5. However, this flaw is not known to be exploitable under any supported scenario in Satellite 5. A future update may address this issue. The PyYAML libary that is provided in the Red Hat OpenStack repositories is vulnerable. However, there are no instances where this library is used in a way which exposes the vulnerability. Any updates will be through the RHEL channels.",
35+
"package_name": "PyYAML",
36+
"package_version": "3.10-11.el7",
37+
"link": "https://access.redhat.com/security/cve/CVE-2017-18342",
38+
"score_v3": 9.8,
39+
"vectors_v3": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
40+
"published_timestamp": 1638748800,
41+
"last_modified_timestamp": 1638748800,
42+
"feed_rating": "Moderate"
43+
}
44+
],
45+
"modules": [
46+
{
47+
"name": "scanner",
48+
"version": "1.011",
49+
"source": "github",
50+
"cves": [
51+
{
52+
"name": "",
53+
"status": ""
54+
}
55+
],
56+
"cpes": [
57+
""
58+
]
59+
}
60+
],
61+
"checks": [
62+
{
63+
"catalog": "docker",
64+
"type": "",
65+
"level": "INFO",
66+
"test_number": "1",
67+
"profile": "Level 1",
68+
"scored": true,
69+
"automated": true,
70+
"description": "General Configuration",
71+
"message": [
72+
"Host Configuration"
73+
],
74+
"remediation": "",
75+
"group": "nv.calico"
76+
}
77+
],
78+
"secrets": [
79+
{
80+
"type": "",
81+
"evidence": "",
82+
"path": "",
83+
"suggestion": ""
84+
}
85+
],
86+
"setid_perms": [
87+
{
88+
"type": "",
89+
"evidence": "",
90+
"path": ""
91+
}
92+
],
93+
"envs": [
94+
[
95+
"PATH=/usr/local/sbin",
96+
"GOSU_VERSION=1.12",
97+
"REDIS_VERSION=6.0.2"
98+
]
99+
],
100+
"labels": {
101+
"key": [
102+
""
103+
],
104+
"value": ""
105+
},
106+
"cmds": [
107+
""
108+
]
109+
}
110+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
{
2+
"report": {
3+
"vulnerabilities": [
4+
],
5+
"modules": [
6+
{
7+
"name": "scanner",
8+
"version": "1.011",
9+
"source": "github",
10+
"cves": [
11+
{
12+
"name": "",
13+
"status": ""
14+
}
15+
],
16+
"cpes": [
17+
""
18+
]
19+
}
20+
],
21+
"checks": [
22+
{
23+
"catalog": "docker",
24+
"type": "",
25+
"level": "INFO",
26+
"test_number": "1",
27+
"profile": "Level 1",
28+
"scored": true,
29+
"automated": true,
30+
"description": "General Configuration",
31+
"message": [
32+
"Host Configuration"
33+
],
34+
"remediation": "",
35+
"group": "nv.calico"
36+
}
37+
],
38+
"secrets": [
39+
{
40+
"type": "",
41+
"evidence": "",
42+
"path": "",
43+
"suggestion": ""
44+
}
45+
],
46+
"setid_perms": [
47+
{
48+
"type": "",
49+
"evidence": "",
50+
"path": ""
51+
}
52+
],
53+
"envs": [
54+
[
55+
"PATH=/usr/local/sbin",
56+
"GOSU_VERSION=1.12",
57+
"REDIS_VERSION=6.0.2"
58+
]
59+
],
60+
"labels": {
61+
"key": [
62+
""
63+
],
64+
"value": ""
65+
},
66+
"cmds": [
67+
""
68+
]
69+
}
70+
}

0 commit comments

Comments
 (0)