Skip to content

Commit

Permalink
vulnxscan: Add cve-bin-tool scanner
Browse files Browse the repository at this point in the history
Adds cve-bin-tool scanner to vulnxscan

Signed-off-by: Henri Rosten <[email protected]>
  • Loading branch information
henrirosten committed Aug 9, 2023
1 parent 41c5d3a commit bc4ffb5
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 5 deletions.
67 changes: 67 additions & 0 deletions scripts/vulnxscan/cve-bin-tool.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# SPDX-FileCopyrightText: 2023 Technology Innovation Institute (TII)
#
# SPDX-License-Identifier: Apache-2.0

{ nixpkgs ? <nixpkgs>
, pkgs ? import nixpkgs {}
, pythonPackages ? pkgs.python3Packages
, lib ? pkgs.lib
}:

let
lib4sbom = pythonPackages.buildPythonPackage rec {
version = "0.3.1";
pname="lib4sbom";
format = "setuptools";
src = pkgs.fetchFromGitHub {
owner = "anthonyharrison";
repo = "lib4sbom";
rev = "v${version}";
hash = "sha256-RfJ7V4VRYlceGl4xlTMmm2kHNtxNryb+JHvZQakkM7w=";
};
propagatedBuildInputs = with pythonPackages; [
pyyaml
semantic-version
];
doCheck = false;
};
in
pythonPackages.buildPythonPackage rec {
version = "3.2.1";
pname = "cve-bin-tool";

src = pkgs.fetchFromGitHub {
owner = "henrirosten";
repo = pname;
rev = "7b5d0160032e067fcea69194c5c2e29ba4c6ae4d";
hash = "sha256-y7cQwV8xblcZ13QeRe0IaeXX7dJk5EmvAxjq2RzyEXw=";
};
propagatedBuildInputs = with pythonPackages; [
pkgs.google-cloud-sdk
lib4sbom
python-gnupg
jsonschema
plotly
beautifulsoup4
pyyaml
isort
py
jinja2
rpmfile
reportlab
zstandard
rich
aiohttp
toml
distro
aiodns
brotlipy
faust-cchardet
pillow
setuptools
xmlschema
cvss
packaging
];
doCheck = false;
}
3 changes: 2 additions & 1 deletion scripts/vulnxscan/vulnxscan.nix
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ pythonPackages.buildPythonPackage rec {

src = ../../.;
sbomnix = import ../../default.nix { pkgs=pkgs; };
cve-bin-tool = import ./cve-bin-tool.nix { pkgs=pkgs; };
makeWrapperArgs = [
"--prefix PATH : ${pkgs.lib.makeBinPath [ sbomnix pkgs.grype pkgs.nix vulnix ]}"
"--prefix PATH : ${pkgs.lib.makeBinPath [ sbomnix pkgs.grype pkgs.nix vulnix cve-bin-tool ]}"
];

propagatedBuildInputs = [
Expand Down
74 changes: 70 additions & 4 deletions scripts/vulnxscan/vulnxscan.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import json
import re
import subprocess
import datetime
from tempfile import NamedTemporaryFile
from shutil import which
import pandas as pd
Expand All @@ -30,6 +31,7 @@
LOG_SPAM,
df_to_csv_file,
df_from_csv_file,
df_log,
)

###############################################################################
Expand Down Expand Up @@ -85,6 +87,7 @@ def __init__(self):
self.df_vulnix = None
self.df_grype = None
self.df_osv = None
self.df_cvebin = None
self.df_report = None

def _parse_vulnix(self, json_str):
Expand Down Expand Up @@ -155,8 +158,8 @@ def scan_grype(self, sbom_path):
self._parse_grype(ret)

def _parse_osv(self, df_osv):
self.df_osv = df_osv
if not self.df_osv.empty:
if not df_osv.empty:
self.df_osv = df_osv
self.df_osv["scanner"] = "osv"
self.df_osv.replace(np.nan, "", regex=True, inplace=True)
self.df_osv.drop_duplicates(keep="first", inplace=True)
Expand All @@ -174,9 +177,61 @@ def scan_osv(self, sbom_path):
df_osv = osv.to_dataframe()
self._parse_osv(df_osv)

def _parse_cvebin(self, df_cvebin):
if not df_cvebin.empty:
df_log(df_cvebin, LOG_SPAM)
df_cvebin["scanner"] = "cvebin"
select_cols = {
"product": "package",
"version": "version",
"cve_number": "vuln_id",
"scanner": "scanner",
}
df_cvebin = df_cvebin.rename(columns=select_cols)[select_cols.values()]
df_cvebin["year_maybe"] = df_cvebin.apply(_guess_vuln_year, axis=1)
df_log(df_cvebin, LOG_SPAM)
# Drop old vulnerabilities. Below, we drop vulnerabilities that have not
# been fixed during the past ~2 years assuming they are false positives.
df_cvebin = df_cvebin[
df_cvebin["year_maybe"] > (datetime.date.today().year) - 2
]
df_cvebin.replace(np.nan, "", regex=True, inplace=True)
df_cvebin.drop_duplicates(keep="first", inplace=True)
self.df_cvebin = df_cvebin
if _LOG.level <= logging.DEBUG:
df_to_csv_file(self.df_cvebin, "df_cvebin.csv")

def scan_cvebin(self, sbom_path):
"""Run cve-bin-tool scan using the SBOM at sbom_path as input"""
_LOG.info("Running cve-bin-tool scan")
prefix = "cve_bin_tool_"
csv_suffix = ".csv"
with NamedTemporaryFile(delete=False, prefix=prefix, suffix=csv_suffix) as fcsv:
cmd = [
"cve-bin-tool",
"--update=daily",
# cve-bin-tool reports many false positive vulnerabilities.
# We disable OSV datasource for two reasons: (1) vulnxscan
# includes OSV vulnerabilities via the osv.py and (2) many
# (all?) OSV vulnerabilities reported by cve-bin-tool are
# not valid.
"--disable-data-source=OSV",
f"--sbom-file={sbom_path}",
"--sbom=cyclonedx",
f"--output-file={fcsv.name}",
"--format=csv",
]
exec_cmd(cmd, raise_on_error=False)
if pathlib.Path(fcsv.name).stat().st_size > 0:
df_cvebin = df_from_csv_file(fcsv.name)
self._parse_cvebin(df_cvebin)

def _generate_report(self):
# Concatenate vulnerability data from different scanners
df = pd.concat([self.df_vulnix, self.df_grype, self.df_osv], ignore_index=True)
df = pd.concat(
[self.df_vulnix, self.df_grype, self.df_osv, self.df_cvebin],
ignore_index=True,
)
if df.empty:
_LOG.debug("No scanners reported any findings")
return
Expand All @@ -194,7 +249,7 @@ def _generate_report(self):
df = df.pivot_table(index=group_cols, columns="scanner", values="count")
# Pivot creates a multilevel index, we'll get rid of it:
df.reset_index(drop=False, inplace=True)
scanners = ["grype", "osv"]
scanners = ["grype", "osv", "cvebin"]
if self.df_vulnix is not None:
scanners.append("vulnix")
df.reindex(group_cols + scanners, axis=1)
Expand All @@ -206,6 +261,7 @@ def _generate_report(self):
# Reformat values in 'scanner' columns
df["grype"] = df.apply(lambda row: _reformat_scanner(row.grype), axis=1)
df["osv"] = df.apply(lambda row: _reformat_scanner(row.osv), axis=1)
df["cvebin"] = df.apply(lambda row: _reformat_scanner(row.cvebin), axis=1)
if "vulnix" in scanners:
df["vulnix"] = df.apply(lambda row: _reformat_scanner(row.vulnix), axis=1)
# Add column 'url'
Expand Down Expand Up @@ -291,6 +347,14 @@ def _vuln_sortcol(row):
return str(row.vuln_id)


def _guess_vuln_year(row):
match = re.match(r".*[A-Za-z][-_]([1-2][0-9]{3})[-_][0-9]+.*", row.vuln_id)
if match:
year = match.group(1)
return int(year)
return int(datetime.date.today().year)


def _vuln_url(row):
osv_url = "https://osv.dev/"
nvd_url = "https://nvd.nist.gov/vuln/detail/"
Expand Down Expand Up @@ -369,6 +433,7 @@ def main():
# Fail early if following commands are not in path
exit_unless_command_exists("grype")
exit_unless_command_exists("vulnix")
exit_unless_command_exists("cve-bin-tool")

target_path = args.TARGET.as_posix()
target_path_abs = args.TARGET.resolve().as_posix()
Expand All @@ -387,6 +452,7 @@ def main():
_LOG.info("Using cdx SBOM '%s'", sbom_cdx_path)
_LOG.info("Using csv SBOM '%s'", sbom_csv_path)
scanner.scan_vulnix(target_path_abs, args.buildtime)
scanner.scan_cvebin(sbom_cdx_path)
scanner.scan_grype(sbom_cdx_path)
scanner.scan_osv(sbom_cdx_path)
scanner.report(args.out, target_path, sbom_csv_path, args.buildtime, args.sbom)
Expand Down
2 changes: 2 additions & 0 deletions shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
pkgs.mkShell rec {
name = "sbomnix-dev-shell";

cve-bin-tool = import ./scripts/vulnxscan/cve-bin-tool.nix { pkgs=pkgs; };
nixupdate = import ./scripts/nixupdate/nixupdate.nix { pkgs=pkgs; };
nix_visualize = import ./scripts/nixupdate/nix-visualize.nix { pkgs=pkgs; };
requests-ratelimiter = import ./scripts/repology/requests-ratelimiter.nix { pkgs=pkgs; };
Expand All @@ -17,6 +18,7 @@ pkgs.mkShell rec {
vulnxscan = import ./scripts/vulnxscan/vulnxscan.nix { pkgs=pkgs; };

buildInputs = [
cve-bin-tool
nixupdate
nix_visualize
requests-ratelimiter
Expand Down

0 comments on commit bc4ffb5

Please sign in to comment.