Skip to content

Commit bc4ffb5

Browse files
committed
vulnxscan: Add cve-bin-tool scanner
Adds cve-bin-tool scanner to vulnxscan Signed-off-by: Henri Rosten <henri.rosten@unikie.com>
1 parent 41c5d3a commit bc4ffb5

4 files changed

Lines changed: 141 additions & 5 deletions

File tree

scripts/vulnxscan/cve-bin-tool.nix

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# SPDX-FileCopyrightText: 2023 Technology Innovation Institute (TII)
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
{ nixpkgs ? <nixpkgs>
6+
, pkgs ? import nixpkgs {}
7+
, pythonPackages ? pkgs.python3Packages
8+
, lib ? pkgs.lib
9+
}:
10+
11+
let
12+
lib4sbom = pythonPackages.buildPythonPackage rec {
13+
version = "0.3.1";
14+
pname="lib4sbom";
15+
format = "setuptools";
16+
src = pkgs.fetchFromGitHub {
17+
owner = "anthonyharrison";
18+
repo = "lib4sbom";
19+
rev = "v${version}";
20+
hash = "sha256-RfJ7V4VRYlceGl4xlTMmm2kHNtxNryb+JHvZQakkM7w=";
21+
};
22+
propagatedBuildInputs = with pythonPackages; [
23+
pyyaml
24+
semantic-version
25+
];
26+
doCheck = false;
27+
};
28+
in
29+
pythonPackages.buildPythonPackage rec {
30+
version = "3.2.1";
31+
pname = "cve-bin-tool";
32+
33+
src = pkgs.fetchFromGitHub {
34+
owner = "henrirosten";
35+
repo = pname;
36+
rev = "7b5d0160032e067fcea69194c5c2e29ba4c6ae4d";
37+
hash = "sha256-y7cQwV8xblcZ13QeRe0IaeXX7dJk5EmvAxjq2RzyEXw=";
38+
};
39+
propagatedBuildInputs = with pythonPackages; [
40+
pkgs.google-cloud-sdk
41+
lib4sbom
42+
python-gnupg
43+
jsonschema
44+
plotly
45+
beautifulsoup4
46+
pyyaml
47+
isort
48+
py
49+
jinja2
50+
rpmfile
51+
reportlab
52+
zstandard
53+
rich
54+
aiohttp
55+
toml
56+
distro
57+
aiodns
58+
brotlipy
59+
faust-cchardet
60+
pillow
61+
setuptools
62+
xmlschema
63+
cvss
64+
packaging
65+
];
66+
doCheck = false;
67+
}

scripts/vulnxscan/vulnxscan.nix

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ pythonPackages.buildPythonPackage rec {
1414

1515
src = ../../.;
1616
sbomnix = import ../../default.nix { pkgs=pkgs; };
17+
cve-bin-tool = import ./cve-bin-tool.nix { pkgs=pkgs; };
1718
makeWrapperArgs = [
18-
"--prefix PATH : ${pkgs.lib.makeBinPath [ sbomnix pkgs.grype pkgs.nix vulnix ]}"
19+
"--prefix PATH : ${pkgs.lib.makeBinPath [ sbomnix pkgs.grype pkgs.nix vulnix cve-bin-tool ]}"
1920
];
2021

2122
propagatedBuildInputs = [

scripts/vulnxscan/vulnxscan.py

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import json
1717
import re
1818
import subprocess
19+
import datetime
1920
from tempfile import NamedTemporaryFile
2021
from shutil import which
2122
import pandas as pd
@@ -30,6 +31,7 @@
3031
LOG_SPAM,
3132
df_to_csv_file,
3233
df_from_csv_file,
34+
df_log,
3335
)
3436

3537
###############################################################################
@@ -85,6 +87,7 @@ def __init__(self):
8587
self.df_vulnix = None
8688
self.df_grype = None
8789
self.df_osv = None
90+
self.df_cvebin = None
8891
self.df_report = None
8992

9093
def _parse_vulnix(self, json_str):
@@ -155,8 +158,8 @@ def scan_grype(self, sbom_path):
155158
self._parse_grype(ret)
156159

157160
def _parse_osv(self, df_osv):
158-
self.df_osv = df_osv
159-
if not self.df_osv.empty:
161+
if not df_osv.empty:
162+
self.df_osv = df_osv
160163
self.df_osv["scanner"] = "osv"
161164
self.df_osv.replace(np.nan, "", regex=True, inplace=True)
162165
self.df_osv.drop_duplicates(keep="first", inplace=True)
@@ -174,9 +177,61 @@ def scan_osv(self, sbom_path):
174177
df_osv = osv.to_dataframe()
175178
self._parse_osv(df_osv)
176179

180+
def _parse_cvebin(self, df_cvebin):
181+
if not df_cvebin.empty:
182+
df_log(df_cvebin, LOG_SPAM)
183+
df_cvebin["scanner"] = "cvebin"
184+
select_cols = {
185+
"product": "package",
186+
"version": "version",
187+
"cve_number": "vuln_id",
188+
"scanner": "scanner",
189+
}
190+
df_cvebin = df_cvebin.rename(columns=select_cols)[select_cols.values()]
191+
df_cvebin["year_maybe"] = df_cvebin.apply(_guess_vuln_year, axis=1)
192+
df_log(df_cvebin, LOG_SPAM)
193+
# Drop old vulnerabilities. Below, we drop vulnerabilities that have not
194+
# been fixed during the past ~2 years assuming they are false positives.
195+
df_cvebin = df_cvebin[
196+
df_cvebin["year_maybe"] > (datetime.date.today().year) - 2
197+
]
198+
df_cvebin.replace(np.nan, "", regex=True, inplace=True)
199+
df_cvebin.drop_duplicates(keep="first", inplace=True)
200+
self.df_cvebin = df_cvebin
201+
if _LOG.level <= logging.DEBUG:
202+
df_to_csv_file(self.df_cvebin, "df_cvebin.csv")
203+
204+
def scan_cvebin(self, sbom_path):
205+
"""Run cve-bin-tool scan using the SBOM at sbom_path as input"""
206+
_LOG.info("Running cve-bin-tool scan")
207+
prefix = "cve_bin_tool_"
208+
csv_suffix = ".csv"
209+
with NamedTemporaryFile(delete=False, prefix=prefix, suffix=csv_suffix) as fcsv:
210+
cmd = [
211+
"cve-bin-tool",
212+
"--update=daily",
213+
# cve-bin-tool reports many false positive vulnerabilities.
214+
# We disable OSV datasource for two reasons: (1) vulnxscan
215+
# includes OSV vulnerabilities via the osv.py and (2) many
216+
# (all?) OSV vulnerabilities reported by cve-bin-tool are
217+
# not valid.
218+
"--disable-data-source=OSV",
219+
f"--sbom-file={sbom_path}",
220+
"--sbom=cyclonedx",
221+
f"--output-file={fcsv.name}",
222+
"--format=csv",
223+
]
224+
exec_cmd(cmd, raise_on_error=False)
225+
if pathlib.Path(fcsv.name).stat().st_size > 0:
226+
df_cvebin = df_from_csv_file(fcsv.name)
227+
self._parse_cvebin(df_cvebin)
228+
177229
def _generate_report(self):
178230
# Concatenate vulnerability data from different scanners
179-
df = pd.concat([self.df_vulnix, self.df_grype, self.df_osv], ignore_index=True)
231+
df = pd.concat(
232+
[self.df_vulnix, self.df_grype, self.df_osv, self.df_cvebin],
233+
ignore_index=True,
234+
)
180235
if df.empty:
181236
_LOG.debug("No scanners reported any findings")
182237
return
@@ -194,7 +249,7 @@ def _generate_report(self):
194249
df = df.pivot_table(index=group_cols, columns="scanner", values="count")
195250
# Pivot creates a multilevel index, we'll get rid of it:
196251
df.reset_index(drop=False, inplace=True)
197-
scanners = ["grype", "osv"]
252+
scanners = ["grype", "osv", "cvebin"]
198253
if self.df_vulnix is not None:
199254
scanners.append("vulnix")
200255
df.reindex(group_cols + scanners, axis=1)
@@ -206,6 +261,7 @@ def _generate_report(self):
206261
# Reformat values in 'scanner' columns
207262
df["grype"] = df.apply(lambda row: _reformat_scanner(row.grype), axis=1)
208263
df["osv"] = df.apply(lambda row: _reformat_scanner(row.osv), axis=1)
264+
df["cvebin"] = df.apply(lambda row: _reformat_scanner(row.cvebin), axis=1)
209265
if "vulnix" in scanners:
210266
df["vulnix"] = df.apply(lambda row: _reformat_scanner(row.vulnix), axis=1)
211267
# Add column 'url'
@@ -291,6 +347,14 @@ def _vuln_sortcol(row):
291347
return str(row.vuln_id)
292348

293349

350+
def _guess_vuln_year(row):
351+
match = re.match(r".*[A-Za-z][-_]([1-2][0-9]{3})[-_][0-9]+.*", row.vuln_id)
352+
if match:
353+
year = match.group(1)
354+
return int(year)
355+
return int(datetime.date.today().year)
356+
357+
294358
def _vuln_url(row):
295359
osv_url = "https://osv.dev/"
296360
nvd_url = "https://nvd.nist.gov/vuln/detail/"
@@ -369,6 +433,7 @@ def main():
369433
# Fail early if following commands are not in path
370434
exit_unless_command_exists("grype")
371435
exit_unless_command_exists("vulnix")
436+
exit_unless_command_exists("cve-bin-tool")
372437

373438
target_path = args.TARGET.as_posix()
374439
target_path_abs = args.TARGET.resolve().as_posix()
@@ -387,6 +452,7 @@ def main():
387452
_LOG.info("Using cdx SBOM '%s'", sbom_cdx_path)
388453
_LOG.info("Using csv SBOM '%s'", sbom_csv_path)
389454
scanner.scan_vulnix(target_path_abs, args.buildtime)
455+
scanner.scan_cvebin(sbom_cdx_path)
390456
scanner.scan_grype(sbom_cdx_path)
391457
scanner.scan_osv(sbom_cdx_path)
392458
scanner.report(args.out, target_path, sbom_csv_path, args.buildtime, args.sbom)

shell.nix

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
pkgs.mkShell rec {
1010
name = "sbomnix-dev-shell";
1111

12+
cve-bin-tool = import ./scripts/vulnxscan/cve-bin-tool.nix { pkgs=pkgs; };
1213
nixupdate = import ./scripts/nixupdate/nixupdate.nix { pkgs=pkgs; };
1314
nix_visualize = import ./scripts/nixupdate/nix-visualize.nix { pkgs=pkgs; };
1415
requests-ratelimiter = import ./scripts/repology/requests-ratelimiter.nix { pkgs=pkgs; };
@@ -17,6 +18,7 @@ pkgs.mkShell rec {
1718
vulnxscan = import ./scripts/vulnxscan/vulnxscan.nix { pkgs=pkgs; };
1819

1920
buildInputs = [
21+
cve-bin-tool
2022
nixupdate
2123
nix_visualize
2224
requests-ratelimiter

0 commit comments

Comments
 (0)