Skip to content

Commit 6343ef6

Browse files
authored
Merge pull request DanBeard#2 from mattdicarlo/master
Improvements to ignore_file CSV handling, report formatting.
2 parents 0a46910 + e45ce8a commit 6343ef6

2 files changed

Lines changed: 105 additions & 82 deletions

File tree

cli.py

Lines changed: 96 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,105 @@
11
#!/usr/bin/env python3
22

3-
43
from cve_lookup import parse_dbs, get_package_dict, get_vulns
5-
import jinja2
64
import argparse
5+
import csv
6+
import jinja2
7+
import sys
8+
import traceback
79

810

911
# NIST url to link to CVEs
1012
NIST_URL = 'https://web.nvd.nist.gov/view/vuln/detail?vulnId={}'
1113

12-
parser = argparse.ArgumentParser(description=('Lookup known vulnerabilities from yocto/RPM/SWID in the CVE. '
13-
'Output in JUnit style XML where a CVE = failure'))
14-
parser.add_argument('packages', help='The list of packages to run through the lookup')
15-
parser.add_argument('cve_loc', help='The folder that holds the CVE xml database files', type=str)
16-
parser.add_argument('output', help='The output html file to write.')
17-
parser.add_argument('-f', '--format', help='The format of the packages', choices=['swid', 'rpm', 'yocto', 'ls'], default=None)
18-
parser.add_argument('-t', '--severity_threshold', help='Value [0-10] over which CVEs will be considered severe.', type=float, default=3)
19-
parser.add_argument('-i', '--ignore_file', help=('CSV containing a list of specific CVEs to ignore. '
20-
'These CVEs will show up as skipped in the report'))
21-
22-
args = parser.parse_args()
23-
24-
root = parse_dbs(args.cve_loc)
25-
26-
with open(args.packages) as ff:
27-
errors, packages = get_package_dict(ff.read(), args.format)
28-
cves = get_vulns(packages, root)
29-
30-
ignore_source = {}
31-
if args.ignore_file is not None:
32-
with open(args.ignore_file) as ff:
33-
# First column is CVE ID, second column is human readable description of mitigation
34-
# eg: CVE-2015-7696, Device shall never allow decompression of arbitrary zip files
35-
for line in args.ignore_file:
36-
cols = line.split(',')
37-
if len(cols) > 0:
38-
cve_id = cols[0].strip()
39-
mitigation = cols[1] if len(cols) > 1 else 'N/A'
40-
ignore_source[cve_id] = mitigation
41-
42-
43-
high_vulns = []
44-
low_vulns = []
45-
ignored_vulns = []
46-
for package_name, info in cves.items():
47-
for ii in info:
48-
data = {
49-
'id': ii['@name'],
50-
'package': package_name,
51-
'severity': float(ii['@CVSS_score']),
52-
'published': ii['@published'],
53-
'link': NIST_URL.format(ii['@name']),
54-
}
55-
try:
56-
data['description'] = ii['desc']['descript']['#text']
57-
except TypeError:
58-
# Sometimes there are multiple descriptions, just try to use the first one.
59-
data['description'] = ii['desc']['descript'][0]['#text']
60-
except Exception:
61-
data['description'] = ''
62-
63-
if data['id'] in ignore_source:
64-
data['ignored'] = True
65-
data['mitigation'] = ignore_source[data['id']]
66-
ignored_vulns.append(data)
67-
else:
68-
if data['severity'] > args.severity_threshold:
69-
high_vulns.append(data)
70-
else:
71-
low_vulns.append(data)
72-
73-
for vuln_list in [high_vulns, low_vulns, ignored_vulns]:
74-
vuln_list.sort(key=lambda ii: ii['severity'], reverse=True)
75-
76-
jinja_env = jinja2.Environment(loader=jinja2.FileSystemLoader('templates'),
77-
autoescape=jinja2.select_autoescape(['html', 'xml']))
78-
79-
template = jinja_env.get_template('output.html')
80-
html = template.render(severity_threshold=args.severity_threshold,
81-
high_vulns=high_vulns,
82-
low_vulns=low_vulns,
83-
ignored_vulns=ignored_vulns)
84-
85-
with open(args.output, 'w') as ff:
86-
ff.write(html)
14+
15+
def _main():
16+
""" Main entry point.
17+
"""
18+
19+
try:
20+
parser = argparse.ArgumentParser(description='Compare a list of packages to the NVD and generate a vulnerability report.')
21+
22+
parser.add_argument('packages', help='The list of packages to scan for vulnerabilities.')
23+
parser.add_argument('cve_loc', help='The folder that holds the CVE XLM database files.', type=str)
24+
parser.add_argument('output', help='The output html file to write.')
25+
parser.add_argument('-f', '--format', help='The format of the package list.', choices=['swid', 'rpm', 'yocto', 'ls'])
26+
parser.add_argument('-t', '--severity_threshold', help='Value [0-10] over which CVEs will be considered important.', type=float, default=3)
27+
parser.add_argument('-i', '--ignore_file', help=('CSV containing a list of specific CVEs to ignore. '
28+
'These CVEs will show up as ignored in the report.'))
29+
30+
args = parser.parse_args()
31+
32+
root = parse_dbs(args.cve_loc)
33+
34+
with open(args.packages) as ff:
35+
errors, packages = get_package_dict(ff.read(), args.format)
36+
cves = get_vulns(packages, root)
37+
38+
ignore_source = {}
39+
if args.ignore_file is not None:
40+
with open(args.ignore_file) as ff:
41+
# First column is CVE ID, second column is human readable description of mitigation
42+
# eg: CVE-2015-7696, Device shall never allow decompression of arbitrary zip files
43+
reader = csv.reader(ff)
44+
ignore_source = {rr[0]: rr[1] for rr in reader}
45+
46+
high_vulns = []
47+
low_vulns = []
48+
ignored_vulns = []
49+
for package_name, info in cves.items():
50+
for ii in info:
51+
data = {
52+
'id': ii['@name'],
53+
'package': package_name,
54+
'severity': float(ii['@CVSS_score']),
55+
'published': ii['@published'],
56+
'link': NIST_URL.format(ii['@name']),
57+
}
58+
try:
59+
data['description'] = ii['desc']['descript']['#text']
60+
except TypeError:
61+
# Sometimes there are multiple descriptions, just try to use the first one.
62+
data['description'] = ii['desc']['descript'][0]['#text']
63+
except Exception:
64+
data['description'] = ''
65+
66+
if data['id'] in ignore_source:
67+
data['ignored'] = True
68+
data['mitigation'] = ignore_source[data['id']]
69+
ignored_vulns.append(data)
70+
else:
71+
if data['severity'] > args.severity_threshold:
72+
high_vulns.append(data)
73+
else:
74+
low_vulns.append(data)
75+
76+
for vuln_list in [high_vulns, low_vulns, ignored_vulns]:
77+
vuln_list.sort(key=lambda ii: ii['severity'], reverse=True)
78+
79+
jinja_env = jinja2.Environment(loader=jinja2.FileSystemLoader('templates'),
80+
autoescape=jinja2.select_autoescape(['html', 'xml']))
81+
82+
template = jinja_env.get_template('output.html')
83+
html = template.render(severity_threshold=args.severity_threshold,
84+
high_vulns=high_vulns,
85+
low_vulns=low_vulns,
86+
ignored_vulns=ignored_vulns)
87+
88+
with open(args.output, 'w') as ff:
89+
ff.write(html)
90+
91+
except Exception:
92+
print('-' * 60)
93+
print('Runtime exception caught:')
94+
traceback.print_exc()
95+
return 1
96+
97+
else:
98+
return 0
99+
100+
101+
if __name__ == '__main__':
102+
""" Called if this script is invoked directly.
103+
"""
104+
105+
sys.exit(_main())

templates/output.html

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
</head>
5959

6060
<body>
61-
<p class="header">Important Vulnerabilities</p>
61+
<p class="header">Important Vulnerabilities ({{ high_vulns|length }})</p>
6262
<p class="description">Vulnerabilities with a severity value over {{ severity_threshold }}.</p>
6363
<table>
6464
<tr>
@@ -78,7 +78,8 @@
7878
</table>
7979
<hr/>
8080

81-
<p class="header">Warnings</p>
81+
{% if low_vulns|length > 0 %}
82+
<p class="header">Warnings ({{ low_vulns|length }})</p>
8283
<p class="description">Vulnerabilities with a severity value under {{ severity_threshold }}.</p>
8384
<table>
8485
<tr>
@@ -97,27 +98,30 @@
9798
{% endfor %}
9899
</table>
99100
<hr/>
101+
{% endif %}
100102

101-
<p class="header">Ignored</p>
103+
{% if ignored_vulns|length > 0 %}
104+
<p class="header">Ignored ({{ ignored_vulns|length }})</p>
102105
<p class="description">Vulnerabilities that have been marked as ignored.</p>
103106
<table>
104107
<tr>
105108
<th class="col-id" scope="col">ID</th>
106109
<th class="col-package" scope="col">Package</th>
107110
<th scope="col">Description</th>
108-
<th scope="col">Severity</th>
109111
<th scope="col">Mitigation</th>
112+
<th scope="col">Severity</th>
110113
</tr>
111114
{% for vuln in ignored_vulns %}
112115
<tr>
113116
<th scope="row"><a href="{{ vuln.link }}">{{ vuln.id }}</a></th>
114117
<td>{{ vuln.package }}</td>
115118
<td>{{ vuln.description }}</td>
116-
<td>{{ vuln.severity }}</td>
117119
<td>{{ vuln.mitigation }}</td>
120+
<td>{{ vuln.severity }}</td>
118121
</tr>
119122
{% endfor %}
120123
</table>
121124
<hr/>
125+
{% endif %}
122126
</body>
123127
</html>

0 commit comments

Comments
 (0)