Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add latest upstream stable version in CPE summary #3267

Merged
merged 2 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cve_bin_tool/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,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 @@ -624,6 +624,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 @@ -649,6 +650,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 @@ -705,6 +707,7 @@ def output_cves(self, outfile, output_type="console"):
self.affected_versions,
self.exploits,
self.all_product_data,
self.offline,
outfile,
)

Expand Down
16 changes: 15 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 @@ -105,6 +111,7 @@ def _output_console_nowrap(
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")
Expand All @@ -124,10 +131,17 @@ def _output_console_nowrap(
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:
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 @@ -941,6 +941,7 @@ def test_output_console(self):
affected_versions,
exploits,
all_product_data,
True,
console,
outfile,
)
Expand Down Expand Up @@ -982,6 +983,7 @@ def test_output_console_affected_versions(self):
affected_versions,
exploits,
all_product_data,
True,
console,
outfile,
)
Expand Down Expand Up @@ -1028,6 +1030,7 @@ def test_output_console_outfile(self):
affected_versions,
exploits,
all_product_data,
True,
outfile,
)

Expand Down
Loading