Skip to content

Commit

Permalink
feat: add CPE summary
Browse files Browse the repository at this point in the history
CPE summary table is used to display the number of CVEs by product as
well as the latest upstream stable version (retrieved thanks to
release-monitoring project).

This first iteration doesn't use a local cache and so only works in
online mode.

Signed-off-by: Fabrice Fontaine <fabrice.fontaine@orange.com>
  • Loading branch information
ffontaine committed Aug 18, 2023
1 parent e0fe54a commit f9505f9
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 1 deletion.
1 change: 1 addition & 0 deletions cve_bin_tool/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -991,6 +991,7 @@ def main(argv=None):
sbom_type=args["sbom_type"],
sbom_format=args["sbom_format"],
sbom_root=sbom_root,
offline=args["offline"],
)

if not args["quiet"]:
Expand Down
3 changes: 3 additions & 0 deletions cve_bin_tool/output_engine/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,7 @@ def __init__(
sbom_type: str = "spdx",
sbom_format: str = "tag",
sbom_root: str = "CVE_SBOM",
offline: bool = False,
):
self.logger = logger or LOGGER.getChild(self.__class__.__name__)
self.all_cve_version_info = all_cve_version_info
Expand All @@ -577,6 +578,7 @@ def __init__(
self.sbom_type = sbom_type
self.sbom_format = sbom_format
self.sbom_root = sbom_root
self.offline = offline

def output_cves(self, outfile, output_type="console"):
"""Output a list of CVEs
Expand Down Expand Up @@ -633,6 +635,7 @@ def output_cves(self, outfile, output_type="console"):
self.affected_versions,
self.exploits,
self.all_product_data,
self.offline,
outfile,
)

Expand Down
62 changes: 61 additions & 1 deletion cve_bin_tool/output_engine/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@
from ..theme import cve_theme
from ..util import ProductInfo, VersionInfo
from ..version import VERSION
from .util import format_path, format_version_range, get_cve_summary
from .util import (
format_path,
format_version_range,
get_cve_summary,
get_latest_upstream_stable_version,
)


def output_console(*args: Any):
Expand All @@ -46,6 +51,7 @@ def _output_console_nowrap(
affected_versions: int,
exploits: bool = False,
all_product_data=None,
offline: bool = False,
console: Console = Console(theme=cve_theme),
):
"""Output list of CVEs in a tabular format with color support"""
Expand Down Expand Up @@ -99,6 +105,60 @@ def _output_console_nowrap(
console.print(Panel("CVE SUMMARY", expand=False))
console.print(table)

# Create table instance for CPE Summary
table = Table()
# Add Head Columns to the Table
table.add_column("Vendor")
table.add_column("Product")
table.add_column("Version")
table.add_column("Latest Upstream Stable Version")
table.add_column("CRITICAL CVEs Count")
table.add_column("HIGH CVEs Count")
table.add_column("MEDIUM CVEs Count")
table.add_column("LOW CVEs Count")
table.add_column("UNKNWON CVEs Count")
table.add_column("TOTAL CVEs Count")
if all_product_data is not None:
for product_data in all_product_data:
color = None
summary = get_cve_summary(
{product_data: all_cve_data[product_data]}, exploits
)

# Display package with the color of the highest CVE
for severity, count in summary.items():
if color is None and count > 0:
color = summary_color[severity.split("-")[0]]

if all_product_data[product_data] != 0:
if offline:
latest_stable_version = "UNKNOWN (offline mode)"
else:
latest_stable_version = get_latest_upstream_stable_version(
product_data
)
cells = [
Text.styled(product_data.vendor, color),
Text.styled(product_data.product, color),
Text.styled(product_data.version, color),
Text.styled(latest_stable_version, color),
]
for severity, count in summary.items():
if count > 0:
color = summary_color[severity.split("-")[0]]
else:
color = "white"
cells += [
Text.styled(str(count), color),
]
cells += [
Text.styled(str(all_product_data[product_data]), color),
]
table.add_row(*cells)
# Print the table to the console
console.print(Panel("CPE SUMMARY", expand=False))
console.print(table)

cve_by_remarks: defaultdict[Remarks, list[dict[str, str]]] = defaultdict(list)
cve_by_paths: defaultdict[Remarks, list[dict[str, str]]] = defaultdict(list)
# group cve_data by its remarks and separately by paths
Expand Down
40 changes: 40 additions & 0 deletions cve_bin_tool/output_engine/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from collections import defaultdict
from datetime import datetime

import requests

from ..util import CVE, CVEData, ProductInfo, Remarks, VersionInfo


Expand Down Expand Up @@ -48,6 +50,44 @@ def get_cve_summary(
return summary


def get_latest_upstream_stable_version(product_info: ProductInfo) -> str:
"""
summary: Retrieve latest upstream stable version from release-monitoring.org
Args:
ProductInfo
Returns:
Latest upstream stable version
"""
latest_stable_version = "UNKNOWN"

# Special case to handle linux kernel prefix
if product_info.product == "linux_kernel":
cpe_id_prefix = "cpe:2.3:o:"
else:
cpe_id_prefix = "cpe:2.3:a:"

try:
response = requests.get(
"https://release-monitoring.org/api/v2/packages/?distribution=CPE NVD NIST&name="
+ cpe_id_prefix
+ product_info.vendor
+ ":"
+ product_info.product,
timeout=300,
)
except requests.exceptions.SSLError:
return latest_stable_version

response.raise_for_status()
jsonResponse = response.json()
if jsonResponse["total_items"] != 0:
latest_stable_version = jsonResponse["items"][0]["stable_version"]

return latest_stable_version


def generate_filename(extension: str, prefix: str = "output") -> str:
"""
summary: Generate a unique filename with extension provided.
Expand Down
3 changes: 3 additions & 0 deletions test/test_output_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,7 @@ def test_output_console(self):
affected_versions,
exploits,
all_product_data,
True,
console,
outfile,
)
Expand Down Expand Up @@ -924,6 +925,7 @@ def test_output_console_affected_versions(self):
affected_versions,
exploits,
all_product_data,
True,
console,
outfile,
)
Expand Down Expand Up @@ -970,6 +972,7 @@ def test_output_console_outfile(self):
affected_versions,
exploits,
all_product_data,
True,
outfile,
)

Expand Down

0 comments on commit f9505f9

Please sign in to comment.