Skip to content

Commit 920503f

Browse files
author
Damien Carol
authored
Safety parser: Fix unit tests and add component (#3963)
* Safety parser: Fix unit tests and add component * Fix deduplication
1 parent 5047665 commit 920503f

File tree

5 files changed

+113
-59
lines changed

5 files changed

+113
-59
lines changed

dojo/settings/settings.dist.py

+1
Original file line numberDiff line numberDiff line change
@@ -863,6 +863,7 @@ def generate_url(scheme, double_slashes, user, password, host, port, path, param
863863
'Trivy Scan': DEDUPE_ALGO_HASH_CODE,
864864
'HackerOne Cases': DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL_OR_HASH_CODE,
865865
'Snyk Scan': DEDUPE_ALGO_HASH_CODE,
866+
'Safety Scan': DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL,
866867
}
867868

868869
DUPE_DELETE_MAX_PER_RUN = env('DD_DUPE_DELETE_MAX_PER_RUN')

dojo/tools/safety/parser.py

+48-58
Original file line numberDiff line numberDiff line change
@@ -18,73 +18,63 @@ def get_label_for_scan_types(self, scan_type):
1818
def get_description_for_scan_types(self, scan_type):
1919
return "Safety scan (--json) output file can be imported in JSON format."
2020

21-
def get_findings(self, json_output, test):
22-
23-
# Grab Safety DB for CVE lookup
21+
def get_safetydb(self):
22+
"""Grab Safety DB for CVE lookup"""
2423
url = "https://raw.githubusercontent.com/pyupio/safety-db/master/data/insecure_full.json"
2524
try:
2625
response = urllib.request.urlopen(url)
27-
safety_db = json.loads(response.read().decode('utf-8'))
26+
return json.load(response)
2827
except urllib.error.URLError as e:
2928
logger.warn("Error Message: %s", e)
3029
logger.warn("Could not resolve %s. Fallback is using the offline version from dojo/tools/safety/insecure_full.json.", url)
31-
with open("dojo/tools/safety/insecure_full.json", "r") as f:
32-
safety_db = json.load(f)
33-
f.close()
30+
with open("dojo/tools/safety/insecure_full.json", "r") as insecure_full:
31+
return json.load(insecure_full)
3432

35-
tree = self.parse_json(json_output)
36-
return self.get_items(tree, test, safety_db)
33+
def get_findings(self, json_output, test):
34+
safety_db = self.get_safetydb()
3735

38-
def parse_json(self, json_output):
39-
data = json_output.read() or '[]'
40-
try:
41-
json_obj = json.loads(str(data, 'utf-8'))
42-
except:
43-
json_obj = json.loads(data)
44-
tree = {l[4]: {'package': str(l[0]),
45-
'affected': str(l[1]),
46-
'installed': str(l[2]),
47-
'description': str(l[3]),
48-
'id': str(l[4])}
49-
for l in json_obj} # noqa: E741
50-
return tree
36+
tree = json.load(json_output)
5137

52-
def get_items(self, tree, test, safety_db):
5338
items = {}
54-
55-
for key, node in tree.items():
56-
item = get_item(node, test, safety_db)
57-
items[key] = item
39+
for node in tree:
40+
item_node = {
41+
'package': str(node[0]),
42+
'affected': str(node[1]),
43+
'installed': str(node[2]),
44+
'description': str(node[3]),
45+
'id': str(node[4])
46+
}
47+
severity = 'Medium' # Because Safety doesn't include severity rating
48+
cve = None
49+
for a in safety_db[item_node['package']]:
50+
if a['id'] == 'pyup.io-' + item_node['id']:
51+
if a['cve']:
52+
cve = a['cve']
53+
title = item_node['package'] + " (" + item_node['affected'] + ")"
54+
55+
finding = Finding(title=title + " | " + cve if cve else title,
56+
test=test,
57+
severity=severity,
58+
description="**Description:** " + item_node['description'] +
59+
"\n**Vulnerable Package:** " + item_node['package'] +
60+
"\n**Installed Version:** " + item_node['installed'] +
61+
"\n**Vulnerable Versions:** " + item_node['affected'] +
62+
"\n**CVE:** " + (cve or "N/A") +
63+
"\n**ID:** " + item_node['id'],
64+
cve=cve,
65+
cwe=1035, # Vulnerable Third Party Component
66+
mitigation="No mitigation provided",
67+
references="No reference provided",
68+
active=False,
69+
verified=False,
70+
false_p=False,
71+
duplicate=False,
72+
out_of_scope=False,
73+
mitigated=None,
74+
impact="No impact provided",
75+
component_name=item_node['package'],
76+
component_version=item_node['installed'],
77+
unique_id_from_tool=item_node['id'])
78+
items[finding.unique_id_from_tool] = finding
5879

5980
return list(items.values())
60-
61-
62-
def get_item(item_node, test, safety_db):
63-
severity = 'Info' # Because Safety doesn't include severity rating
64-
cve = ''.join(a['cve'] or ''
65-
for a in safety_db[item_node['package']]
66-
if a['id'] == 'pyup.io-' + item_node['id']) or None
67-
title = item_node['package'] + " (" + item_node['affected'] + ")"
68-
69-
finding = Finding(title=title + " | " + cve if cve else title,
70-
test=test,
71-
severity=severity,
72-
description=item_node['description'] +
73-
"\n Vulnerable Package: " + item_node['package'] +
74-
"\n Installed Version: " + item_node['installed'] +
75-
"\n Vulnerable Versions: " + item_node['affected'] +
76-
"\n CVE: " + (cve or "N/A") +
77-
"\n ID: " + item_node['id'],
78-
cve=cve,
79-
cwe=1035, # Vulnerable Third Party Component
80-
mitigation="No mitigation provided",
81-
references="No reference provided",
82-
active=False,
83-
verified=False,
84-
false_p=False,
85-
duplicate=False,
86-
out_of_scope=False,
87-
mitigated=None,
88-
impact="No impact provided")
89-
90-
return finding
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
[
2+
[
3+
"pyyaml",
4+
"<5.4",
5+
"5.3.1",
6+
"A vulnerability was discovered in the PyYAML library in versions before 5.4, where it is susceptible to arbitrary code execution when it processes untrusted YAML files through the full_load method or with the FullLoader loader. Applications that use the library to process untrusted input may be vulnerable to this flaw. This flaw allows an attacker to execute arbitrary code on the system by abusing the python/object/new constructor. This flaw is due to an incomplete fix for CVE-2020-1747. See CVE-2020-14343.",
7+
"39611",
8+
null,
9+
null
10+
],
11+
[
12+
"jinja2",
13+
">=0.0.0,<2.11.3",
14+
"2.11.1",
15+
"This affects the package jinja2 from 0.0.0 and before 2.11.3. The ReDOS vulnerability of the regex is mainly due to the sub-pattern [a-zA-Z0-9._-]+.[a-zA-Z0-9._-]+ This issue can be mitigated by Markdown to format user content instead of the urlize filter, or by implementing request timeouts and limiting process memory. See CVE-2020-28493.",
16+
"39525",
17+
null,
18+
null
19+
],
20+
[
21+
"httplib2",
22+
"<0.19.0",
23+
"0.18.1",
24+
"httplib2 is a comprehensive HTTP client library for Python. In httplib2 before version 0.19.0, a malicious server which responds with long series of \"\\xa0\" characters in the \"www-authenticate\" header may cause Denial of Service (CPU burn while parsing header) of the httplib2 client accessing said server. This is fixed in version 0.19.0 which contains a new implementation of auth headers parsing using the pyparsing library. See CVE-2021-21240.",
25+
"39608",
26+
null,
27+
null
28+
],
29+
[
30+
"django",
31+
"==2.2.17",
32+
"2.2.17",
33+
"Django 2.2.18 fixes a security issue with severity \"low\" in 2.2.17 (CVE-2021-3281).",
34+
"39523",
35+
null,
36+
null
37+
],
38+
[
39+
"django",
40+
">=2.2,<2.2.18",
41+
"2.2.17",
42+
"In Django 2.2 before 2.2.18, 3.0 before 3.0.12, and 3.1 before 3.1.6, the django.utils.archive.extract method (used by \"startapp --template\" and \"startproject --template\") allows directory traversal via an archive with absolute paths or relative paths with dot segments. See CVE-2021-3281.",
43+
"39526",
44+
null,
45+
null
46+
]
47+
]

dojo/unittests/tools/test_safety_parser.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,19 @@ def test_multiple_cves(self):
3131
parser = SafetyParser()
3232
findings = parser.get_findings(testfile, Test())
3333
self.assertEqual(1, len(findings))
34-
self.assertEqual("CVE-2019-12385", findings[0].cve)
34+
for finding in findings:
35+
if "37863" == finding.unique_id_from_tool:
36+
self.assertIsNone(finding.cve)
37+
38+
def test_multiple2(self):
39+
testfile = open("dojo/unittests/scans/safety/many_vulns.json")
40+
parser = SafetyParser()
41+
findings = parser.get_findings(testfile, Test())
42+
self.assertEqual(5, len(findings))
43+
for finding in findings:
44+
if "39608" == finding.unique_id_from_tool:
45+
self.assertEqual("httplib2", finding.component_name)
46+
self.assertEqual("0.18.1", finding.component_version)
47+
self.assertEqual("CVE-2021-21240", finding.cve)
48+
elif "39525" == finding.unique_id_from_tool:
49+
self.assertIsNone(finding.cve)

0 commit comments

Comments
 (0)