From ed05458e2f403ad09983a5d1c79a997c6d234ba1 Mon Sep 17 00:00:00 2001 From: "sanskarsharma3110@gmail.com" Date: Mon, 22 Jul 2024 12:40:43 +0530 Subject: [PATCH 1/8] feat: changes from pr4160 --- cve_bin_tool/cve_scanner.py | 64 ++++++++++++++++++++++++++++++++----- cve_bin_tool/util.py | 21 +++++++++++- 2 files changed, 76 insertions(+), 9 deletions(-) diff --git a/cve_bin_tool/cve_scanner.py b/cve_bin_tool/cve_scanner.py index 578df60804..e731656fcc 100644 --- a/cve_bin_tool/cve_scanner.py +++ b/cve_bin_tool/cve_scanner.py @@ -16,7 +16,7 @@ from cve_bin_tool.input_engine import TriageData from cve_bin_tool.log import LOGGER from cve_bin_tool.theme import cve_theme -from cve_bin_tool.util import CVE, CVEData, ProductInfo, VersionInfo +from cve_bin_tool.util import CVE, CVEData, ProductInfo, Remarks, VersionInfo from cve_bin_tool.version_compare import Version @@ -180,8 +180,13 @@ def get_cves(self, product_info: ProductInfo, triage_data: TriageData): end_excluding=version_end_excluding, ) - # Go through and get all the severities + product_info_data: CVEData | None = self.all_cve_data.get(product_info) + prev_cves: List[CVE] = ( + product_info_data.get("cves", []) if product_info_data is not None else [] # type: ignore + ) cves: List[CVE] = [] + + # Go through and get all the severities if cve_list: finished = False max_cves = 500 @@ -223,15 +228,26 @@ def get_cves(self, product_info: ProductInfo, triage_data: TriageData): if duplicate_found: continue + # Check if we already found this CVE with a previous scan. + # In that case we need to check where to get our triage info + # from. + # TODO: turn the list of CVEs into a set to avoid needing + # the linear-time lookup. + prev_cve = next( + ( + cve + for cve in prev_cves + if cve.cve_number == row["cve_number"] + ), + None, + ) + triage = triage_data.get(row["cve_number"]) or triage_data.get( "default" ) - # Only scan cves if triage is not None. - # Triage will only be None if triage_data don't have default attribute. - # NOTE: Triage can be empty dictionary so checking `if triage:` won't suffice. - if triage is not None: + if prev_cve is None: row_dict = dict(row) - row_dict.update(triage) + # print(row_dict) row_dict["severity"] = row_dict["severity"] or row["severity"] # Checking for exploits @@ -274,7 +290,39 @@ def get_cves(self, product_info: ProductInfo, triage_data: TriageData): f'metrics found in CVE {row_dict["cve_number"]} is {row_dict["metric"]}' ) cve = CVE(**row_dict) - cves.append(cve) + else: + cve = prev_cve + + # We assume that only one source has the triage info. + # We try to figure out here which one. + # If we have useful info in the triage data we received, + # then we use it. + if triage is not None and ( + # Either the new cve does not have triage data, + # or it is trivial (newly found cve) + not cve.remarks + or cve.remarks == Remarks.NewFound + ): + for key in [ + "remarks", + "comments", + "response", + "justification", + "severity", + ]: + data = triage.get(key) + if data: + if ( + key == "severity" + and self.check_exploits + and row_dict["cve_number"] in self.exploits_list + ): + data += "-EXPLOIT" + + self.logger.debug(f"Setting field {key} to: {data}") + cve = cve._replace(**{key: data}) + + cves.append(cve) if cves: self.products_with_cve += 1 diff --git a/cve_bin_tool/util.py b/cve_bin_tool/util.py index 2564464cdf..8c8498e66d 100644 --- a/cve_bin_tool/util.py +++ b/cve_bin_tool/util.py @@ -1,7 +1,8 @@ # Copyright (C) 2021 Intel Corporation # SPDX-License-Identifier: GPL-3.0-or-later -""" Utility classes for the CVE Binary Tool """ +"""Utility classes for the CVE Binary Tool""" + from __future__ import annotations import fnmatch @@ -165,6 +166,24 @@ class ProductInfo(NamedTuple): location: str purl: str | None = None + def __identity_members(self): + """The members that will be used for eq and hash implementations. + We do not include location here since it can take on different values + depending on where the product info is coming from and we want to be + able to properly identify products that are actually the same. + """ + # TODO: what is the meaning of the location field exactly? + return (self.vendor, self.product, self.version) + + def __eq__(self, other): + if type(other) is type(self): + return self.__identity_members() == other.__identity_members() + else: + return False + + def __hash__(self): + return hash(self.__identity_members()) + class ScanInfo(NamedTuple): """ From bb8dfda6f054ef4caac8bd2fb3409d62ea01c48e Mon Sep 17 00:00:00 2001 From: "sanskarsharma3110@gmail.com" Date: Tue, 23 Jul 2024 00:12:49 +0530 Subject: [PATCH 2/8] feat: intial improved triage process --- cve_bin_tool/cli.py | 56 +++++++-- cve_bin_tool/cve_scanner.py | 36 ++++++ cve_bin_tool/input_engine.py | 162 +-------------------------- cve_bin_tool/vex_manager/generate.py | 15 +-- 4 files changed, 90 insertions(+), 179 deletions(-) diff --git a/cve_bin_tool/cli.py b/cve_bin_tool/cli.py index 24b8560190..6404a8d22a 100644 --- a/cve_bin_tool/cli.py +++ b/cve_bin_tool/cli.py @@ -75,6 +75,7 @@ from cve_bin_tool.util import ProductInfo from cve_bin_tool.version import VERSION from cve_bin_tool.version_scanner import VersionScanner +from cve_bin_tool.vex_manager.parse import VEXParse sys.excepthook = excepthook # Always install excepthook for entrypoint module. @@ -380,6 +381,12 @@ def main(argv=None): default="", help="Vendor/Supplier of Product", ) + output_group.add_argument( + "--filter-triage", + action="store", + default=True, + help="Filter cves based on triage data from Vex file", + ) parser.add_argument( "-e", "--exclude", @@ -1021,18 +1028,6 @@ def main(argv=None): LOGGER.debug(f"{product_info}, {triage_data}") cve_scanner.get_cves(product_info, triage_data) - if args["triage_input_file"]: - input_engine = InputEngine( - args["triage_input_file"], - logger=LOGGER, - error_mode=error_mode, - filetype="vex", - ) - parsed_data = input_engine.parse_input() - for product_info, triage_data in parsed_data.items(): - LOGGER.debug(f"{product_info}, {triage_data}") - cve_scanner.get_cves(product_info, triage_data) - if args["input_file"]: input_engine = InputEngine( args["input_file"], logger=LOGGER, error_mode=error_mode @@ -1092,6 +1087,40 @@ def main(argv=None): LOGGER.debug(f"{product_info}, {triage_data}") cve_scanner.get_cves(product_info, triage_data) + if args["vex_file"]: + # for now use cyclonedx as auto detection is not implemented in latest pypi package of lib4vex + vexdata = VEXParse( + filename=args["vex_file"], + vextype="cyclonedx", + logger=LOGGER, + ) + parsed_vex_data = vexdata.parse_vex() + if parsed_data.is_empty(): + # assume the vex file being scanned is a standalone file + args["filter_triage"] = False + parsed_data = parsed_vex_data + for product_info, triage_data in parsed_data.items(): + LOGGER.debug(f"{product_info}, {triage_data}") + cve_scanner.get_cves(product_info, triage_data) + else: + LOGGER.info( + f"VEX file {args['vex_file']} is not a standalone file and will be used as a triage file" + ) + # need to do validation on the sbom part + # need to implement is_linked() function which will check the linkage. + if args["sbom_file"]: + LOGGER.warning( + f"SBOM file: {args['sbom_file']} is not linked to VEX file: {args['vex_file']}." + ) + for product_info, triage_data in parsed_vex_data.items(): + LOGGER.debug(f"{product_info}, {triage_data}") + if product_info in parsed_data: + cve_scanner.get_cves(product_info, triage_data) + else: + LOGGER.info( + f"Product: {product_info.product} with Version: {product_info.version} not found in Parsed Data, is valid vex file being used?" + ) + LOGGER.info("Overall CVE summary: ") LOGGER.info( f"There are {cve_scanner.products_with_cve} products with known CVEs detected" @@ -1118,6 +1147,9 @@ def main(argv=None): "Please provide --product, --release and --vendor for VEX generation" ) return ERROR_CODES[InsufficientArgs] + + if args["vex_file"] and args["filter_triage"]: + cve_scanner.filter_triage_data() # Creates an Object for OutputEngine output = OutputEngine( all_cve_data=cve_scanner.all_cve_data, diff --git a/cve_bin_tool/cve_scanner.py b/cve_bin_tool/cve_scanner.py index e731656fcc..08aecbffb5 100644 --- a/cve_bin_tool/cve_scanner.py +++ b/cve_bin_tool/cve_scanner.py @@ -350,6 +350,42 @@ def get_cves(self, product_info: ProductInfo, triage_data: TriageData): if product_info not in self.all_product_data: self.all_product_data[product_info] = len(cves) + def filter_triage_data(self): + """ + Filter out triage data that is not relevant to the CVEs found, + specifically those marked as NotAffected or FalsePositives. + """ + to_delete: List[ProductInfo] = [] + + for product_info, cve_data in self.all_cve_data.items(): + original_cves = cve_data["cves"] + filtered_cves = [ + cve + for cve in original_cves + if cve.remarks not in {Remarks.NotAffected, Remarks.FalsePositive} + ] + + filtered_out_cves = set(original_cves) - set(filtered_cves) + for cve in filtered_out_cves: + self.logger.info( + f"Filtered CVE: {cve.cve_number} for Product: {product_info.product}" + ) + + if filtered_cves: + cve_data["cves"] = filtered_cves + else: + to_delete.append(product_info) + + self.logger.debug( + f"Filtered triage data for {product_info.product}: {[cve.cve_number for cve in filtered_cves]}" + ) + + for product_info in to_delete: + del self.all_cve_data[product_info] + self.logger.debug( + f"Removed product info for {product_info.product} due to no relevant CVEs" + ) + def affected(self): """Returns list of vendor.product and version tuples identified from scan""" diff --git a/cve_bin_tool/input_engine.py b/cve_bin_tool/input_engine.py index f6ab2fcac6..3e23f0fd87 100644 --- a/cve_bin_tool/input_engine.py +++ b/cve_bin_tool/input_engine.py @@ -10,7 +10,6 @@ import csv import json -import re from collections import defaultdict from logging import Logger from pathlib import Path @@ -35,7 +34,7 @@ class InputEngine: """ Class: InputEngine - This class is responsible for parsing various input file formats (CSV, VEX, JSON) in the CVE Bin Tool. + This class is responsible for parsing various input file formats (CSV, JSON) in the CVE Bin Tool. Attributes: - parsed_data (DefaultDict[ProductInfo, TriageData]): Dictionary containing parsed input data. @@ -45,7 +44,7 @@ class InputEngine: Initializes the InputEngine with the specified filename, logger, error mode, and filetype. - parse_input(self) -> DefaultDict[ProductInfo, TriageData]: - Parses the input file based on its type (CSV, VEX, JSON) and returns the parsed data. + Parses the input file based on its type (CSV, JSON) and returns the parsed data. - input_csv(self) -> None: Parses input data from a CSV file. @@ -53,12 +52,6 @@ class InputEngine: - input_json(self) -> None: Parses input data from a JSON file. - - input_vex(self) -> None: - Parses input data from a CycloneDX VEX file. - - - validate_product(self, product: str) -> bool: - Validates if a product name conforms to the CPE 2.3 standard. - - parse_data(self, fields: Set[str], data: Iterable) -> None: Parses common data structure for CSV and JSON input formats. @@ -106,8 +99,6 @@ def parse_input(self) -> DefaultDict[ProductInfo, TriageData]: raise FileNotFoundError(self.filename) if self.filename.endswith(".csv"): self.input_csv() - elif self.filename.endswith(".vex") or self.filetype == "vex": - self.input_vex() elif self.filename.endswith(".json"): self.input_json() return self.parsed_data @@ -144,155 +135,6 @@ def input_json(self) -> None: self.parse_data(set(json_data[0].keys()), json_data) - def validate_product(self, product: str) -> bool: - """ - Validates if a product name conforms to the CPE 2.3 standard. - - Args: - - product (str): Product name. - - Returns: - - bool: True if the product name is valid, False otherwise. - - """ - """ - Ensure product name conforms to CPE 2.3 standard. - See https://csrc.nist.gov/schema/cpe/2.3/cpe-naming_2.3.xsd for naming specification - """ - cpe_regex = r"\A([A-Za-z0-9\._\-~ %])+\Z" - return re.search(cpe_regex, product) is not None - - def input_vex(self) -> None: - """ - Parses input data from a VEX file. - """ - with open(self.filename) as json_file: - json_data = json.load(json_file) - - # Only handle CycloneDX VEX file format - if json_data["bomFormat"] == "CycloneDX": - self.input_vex_cyclone_dx(json_data) - - def input_vex_cyclone_dx(self, json_data): - """ - Parses input data from a CycloneDX VEX file. - """ - - def strip_remark(detail) -> str: - detail = re.sub("^" + Remarks.NewFound.name + "(: )?", "", detail) - detail = re.sub("^" + Remarks.Unexplored.name + "(: )?", "", detail) - detail = re.sub("^" + Remarks.Confirmed.name + "(: )?", "", detail) - detail = re.sub("^" + Remarks.Mitigated.name + "(: )?", "", detail) - detail = re.sub("^" + Remarks.FalsePositive.name + "(: )?", "", detail) - detail = re.sub("^" + Remarks.NotAffected.name + "(: )?", "", detail) - return detail - - # Map CycloneDX v1.4 anaylsis state to the Remarks enumeration. - remarks_lookup = { - "resolved": Remarks.Mitigated, - "resolved_with_pedigree": Remarks.Mitigated, - "exploitable": Remarks.Confirmed, - "in_triage": Remarks.Unexplored, - "false_positive": Remarks.FalsePositive, - "not_affected": Remarks.NotAffected, - } - - # Not all data from the BOM needs to be read because it will be updated from the - # CVE DB. The analysis fields may have been updated in the VEX and should be - # read. - for vulnerability in json_data["vulnerabilities"]: - id = vulnerability["id"] - analysis_state = vulnerability["analysis"]["state"].lower() - remarks = Remarks.Unexplored - if analysis_state in remarks_lookup: - remarks = remarks_lookup[analysis_state] - justification = vulnerability["analysis"].get("justification", None) - response = vulnerability["analysis"].get("response", None) - comments = strip_remark(vulnerability["analysis"]["detail"]) - severity = None - if "ratings" in vulnerability: - for rating in vulnerability["ratings"]: - severity = rating["severity"].upper() - for affect in vulnerability["affects"]: - product_info = self.decode_bom_ref(affect["ref"]) - - if product_info is not None: - self.parsed_data[product_info][id.strip() or "default"] = { - "remarks": remarks, - "comments": comments.strip(), - "response": response, - } - if justification: - self.parsed_data[product_info][id.strip() or "default"][ - "justification" - ] = justification.strip() - if severity: - self.parsed_data[product_info][id.strip() or "default"][ - "severity" - ] = severity.strip() - self.parsed_data[product_info]["paths"] = {} - - def decode_bom_ref(self, ref) -> ProductInfo: - """ - Decodes the BOM reference for each component. - - Args: - - ref (str): BOM reference string - - Returns: - - bool: ProductInfo object containing the vendor, product, and version. - - """ - # urn:cbt:{bom_version}/{vendor}#{product}-{version} - urn_cbt_ref = re.compile( - r"urn:cbt:(?P.*?)\/(?P.*?)#(?P.*?)-(?P.*)" - ) - - # This URN was added to support CPE's that have dashes in their version field. - # urn:cbt:{bom_version}/{vendor}#{product}:{version} - urn_cbt_ext_ref = re.compile( - r"urn:cbt:(?P.*?)\/(?P.*?)#(?P.*?):(?P.*)" - ) - - # urn:cdx:serialNumber/version#bom-ref (https://cyclonedx.org/capabilities/bomlink/) - urn_cdx = re.compile( - r"urn:cdx:(?P.*?)\/(?P.*?)#(?P.*)" - ) - location = "location/to/product" - if urn_cbt_ext_ref.match(ref): - urn_dict = urn_cbt_ext_ref.match(ref).groupdict() - vendor = urn_dict["vendor"] - product = urn_dict["product"] - version = urn_dict["version"] - elif urn_cbt_ref.match(ref): - urn_dict = urn_cbt_ref.match(ref).groupdict() - vendor = urn_dict["vendor"] - product = urn_dict["product"] - version = urn_dict["version"] - elif urn_cdx.match(ref): - urn_dict = urn_cdx.match(ref).groupdict() - cdx_bom_ref = urn_dict["bom_ref"] - # Try to decode the CDX BOM reference. This can be any unique identifier but may contain - # product:version - # or it could be a Package URL. - try: - product, version = cdx_bom_ref.rsplit("-", 1) - except ValueError: - product, version = None, None - vendor = "UNKNOWN" - else: - product = None - version = None - vendor = None - - product_info = None - if product is not None and self.validate_product(product): - product_info = ProductInfo( - vendor.strip(), product.strip(), version.strip(), location - ) - - return product_info - def parse_data(self, fields: Set[str], data: Iterable) -> None: """ Parses common data structure for CSV and JSON input formats. diff --git a/cve_bin_tool/vex_manager/generate.py b/cve_bin_tool/vex_manager/generate.py index 1b4cc819cc..6e066d5e0b 100644 --- a/cve_bin_tool/vex_manager/generate.py +++ b/cve_bin_tool/vex_manager/generate.py @@ -1,7 +1,6 @@ # Copyright (C) 2024 Intel Corporation # SPDX-License-Identifier: GPL-3.0-or-later import os -from datetime import datetime from logging import Logger from pathlib import Path from typing import Dict, List, Optional @@ -75,12 +74,14 @@ def generate_vex(self) -> None: if self.sbom: kwargs["sbom"] = self.sbom vexgen.set_product(**kwargs) - if Path(self.filename).is_file(): - self.logger.warning( - f"Failed to write '{self.filename}'. File already exists" + if not self.filename: + self.logger.info( + "No filename defined, Generating a new filename with Default Naming Convention" ) - self.logger.info("Generating a new filename with Default Naming Convention") self.filename = self.generate_vex_filename() + if Path(self.filename).is_file(): + self.logger.info(f"Updating the vex file: {self.filename}") + vexgen.generate( project_name=self.product, vex_data=self.get_vulnerabilities(), @@ -95,10 +96,10 @@ def generate_vex_filename(self) -> str: Returns: str: The generated VEX filename. """ - now = datetime.now().strftime("%Y-%m-%d.%H-%M-%S") filename = os.path.abspath( os.path.join( - os.getcwd(), f"{self.product}_{self.release}_{self.vextype}.{now}.json" + os.getcwd(), + f"{self.product}_{self.release}_{self.vendor}_{self.vextype}.json", ) ) return filename From 346f3f20087696a8ca8f471b57ce202da94c0422 Mon Sep 17 00:00:00 2001 From: "sanskarsharma3110@gmail.com" Date: Thu, 25 Jul 2024 23:09:27 +0530 Subject: [PATCH 3/8] fix: minor bugs and added revision reason --- cve_bin_tool/cli.py | 11 ++++++++++- cve_bin_tool/cve_scanner.py | 21 +++++++++++---------- cve_bin_tool/output_engine/__init__.py | 1 + cve_bin_tool/vex_manager/generate.py | 4 ++++ 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/cve_bin_tool/cli.py b/cve_bin_tool/cli.py index 6404a8d22a..6d69bb439b 100644 --- a/cve_bin_tool/cli.py +++ b/cve_bin_tool/cli.py @@ -381,6 +381,13 @@ def main(argv=None): default="", help="Vendor/Supplier of Product", ) + output_group.add_argument( + "-rr", + "--revision-reason", + action="store", + default="", + help="a reason for the update to the vex document should be specified in double quotes", + ) output_group.add_argument( "--filter-triage", action="store", @@ -924,6 +931,7 @@ def main(argv=None): and not args["package_list"] and not args["merge"] and not args["sbom_file"] + and not args["vex_file"] ): parser.print_usage() with ErrorHandler(logger=LOGGER, mode=ErrorMode.NoTrace): @@ -1095,7 +1103,7 @@ def main(argv=None): logger=LOGGER, ) parsed_vex_data = vexdata.parse_vex() - if parsed_data.is_empty(): + if not parsed_data: # assume the vex file being scanned is a standalone file args["filter_triage"] = False parsed_data = parsed_vex_data @@ -1141,6 +1149,7 @@ def main(argv=None): "product": args["product"], "release": args["release"], "vendor": args["vendor"], + "revision_reason": args["revision_reason"], } else: LOGGER.error( diff --git a/cve_bin_tool/cve_scanner.py b/cve_bin_tool/cve_scanner.py index 08aecbffb5..19ad7c14e5 100644 --- a/cve_bin_tool/cve_scanner.py +++ b/cve_bin_tool/cve_scanner.py @@ -359,13 +359,15 @@ def filter_triage_data(self): for product_info, cve_data in self.all_cve_data.items(): original_cves = cve_data["cves"] - filtered_cves = [ - cve - for cve in original_cves - if cve.remarks not in {Remarks.NotAffected, Remarks.FalsePositive} - ] + filtered_cves = [] + filtered_out_cves = [] + + for cve in original_cves: + if cve.remarks not in {Remarks.NotAffected, Remarks.FalsePositive}: + filtered_cves.append(cve) + else: + filtered_out_cves.append(cve) - filtered_out_cves = set(original_cves) - set(filtered_cves) for cve in filtered_out_cves: self.logger.info( f"Filtered CVE: {cve.cve_number} for Product: {product_info.product}" @@ -373,13 +375,12 @@ def filter_triage_data(self): if filtered_cves: cve_data["cves"] = filtered_cves + self.logger.debug( + f"Filtered triage data for {product_info.product}: {[cve.cve_number for cve in filtered_cves]}" + ) else: to_delete.append(product_info) - self.logger.debug( - f"Filtered triage data for {product_info.product}: {[cve.cve_number for cve in filtered_cves]}" - ) - for product_info in to_delete: del self.all_cve_data[product_info] self.logger.debug( diff --git a/cve_bin_tool/output_engine/__init__.py b/cve_bin_tool/output_engine/__init__.py index 404da41f0f..227eacc2f4 100644 --- a/cve_bin_tool/output_engine/__init__.py +++ b/cve_bin_tool/output_engine/__init__.py @@ -797,6 +797,7 @@ def output_cves(self, outfile, output_type="console"): self.vex_product_info["product"], self.vex_product_info["release"], self.vex_product_info["vendor"], + self.vex_product_info["revision_reason"], self.vex_filename, self.vex_type, self.all_cve_data, diff --git a/cve_bin_tool/vex_manager/generate.py b/cve_bin_tool/vex_manager/generate.py index 6e066d5e0b..682973cf9e 100644 --- a/cve_bin_tool/vex_manager/generate.py +++ b/cve_bin_tool/vex_manager/generate.py @@ -45,6 +45,7 @@ def __init__( product: str, release: str, vendor: str, + revision_reason: str, filename: str, vextype: str, all_cve_data: Dict[ProductInfo, CVEData], @@ -55,6 +56,7 @@ def __init__( self.product = product self.release = release self.vendor = vendor + self.revision_reason = revision_reason self.sbom = sbom self.filename = filename self.vextype = vextype @@ -109,6 +111,8 @@ def get_metadata(self) -> Dict: "id": f"{self.product.upper()}-{self.release}-VEX", "supplier": self.vendor, } + if self.revision_reason: + metadata["revision_reason"] = self.revision_reason # other metadata can be added here return metadata From 8977fda847ec7b5249ca3c478c8bb3fc71121015 Mon Sep 17 00:00:00 2001 From: "sanskarsharma3110@gmail.com" Date: Fri, 26 Jul 2024 12:24:57 +0530 Subject: [PATCH 4/8] fix: test and remove existing test --- cve_bin_tool/output_engine/__init__.py | 2 +- cve_bin_tool/vex_manager/generate.py | 2 +- test/test_input_engine.py | 205 ------------------------- 3 files changed, 2 insertions(+), 207 deletions(-) diff --git a/cve_bin_tool/output_engine/__init__.py b/cve_bin_tool/output_engine/__init__.py index 227eacc2f4..0bb6188b42 100644 --- a/cve_bin_tool/output_engine/__init__.py +++ b/cve_bin_tool/output_engine/__init__.py @@ -797,10 +797,10 @@ def output_cves(self, outfile, output_type="console"): self.vex_product_info["product"], self.vex_product_info["release"], self.vex_product_info["vendor"], - self.vex_product_info["revision_reason"], self.vex_filename, self.vex_type, self.all_cve_data, + self.vex_product_info["revision_reason"], logger=self.logger, ) vexgen.generate_vex() diff --git a/cve_bin_tool/vex_manager/generate.py b/cve_bin_tool/vex_manager/generate.py index 682973cf9e..0a4cddbd1e 100644 --- a/cve_bin_tool/vex_manager/generate.py +++ b/cve_bin_tool/vex_manager/generate.py @@ -45,10 +45,10 @@ def __init__( product: str, release: str, vendor: str, - revision_reason: str, filename: str, vextype: str, all_cve_data: Dict[ProductInfo, CVEData], + revision_reason: str = "", sbom: Optional[str] = None, logger: Optional[Logger] = None, validate: bool = True, diff --git a/test/test_input_engine.py b/test/test_input_engine.py index 87a4eda9eb..0d36911331 100644 --- a/test/test_input_engine.py +++ b/test/test_input_engine.py @@ -3,7 +3,6 @@ import re from ast import literal_eval -from collections import defaultdict from pathlib import Path import pytest @@ -59,148 +58,6 @@ class TestInputEngine: "paths": {""}, }, } - VEX_TRIAGE_DATA = { - ProductInfo("d.r.commander", "libjpeg-turbo", "2.0.1", "location/to/product"): { - "CVE-2018-19664": { - "comments": "High priority need to resolve fast", - "remarks": Remarks.Confirmed, - "justification": "protected_by_compiler", - "response": ["will_not_fix"], - "severity": "CRITICAL", - }, - "paths": {}, - }, - ProductInfo("gnu", "glibc", "2.33", "location/to/product"): { - "CVE-2021-1234": { - "comments": "", - "remarks": Remarks.Unexplored, - "response": ["workaround_available", "update"], - "severity": "HIGH", - }, - "paths": {}, - }, - } - # cyclonedx currently doesn't have vendors - VEX_TRIAGE_DATA_CYCLONEDX = { - ProductInfo("UNKNOWN", "libjpeg-turbo", "2.0.1", "location/to/product"): { - "CVE-2018-19664": { - "comments": "High priority need to resolve fast", - "remarks": Remarks.Confirmed, - "response": [], - "severity": "CRITICAL", - }, - "paths": {}, - }, - ProductInfo("UNKNOWN", "glibc", "2.33", "location/to/product"): { - "CVE-2021-1234": { - "comments": "", - "remarks": Remarks.Unexplored, - "response": [], - "severity": "HIGH", - }, - "paths": {}, - }, - } - VEX_TRIAGE_DATA_CYCLONEDX_CASE13 = { - ProductInfo( - vendor="UNKNOWN", - product="acme-product", - version="1", - location="location/to/product", - ): { - "CVE-2020-25649": { - "comments": "Automated " - "dataflow " - "analysis " - "and " - "manual " - "code " - "review " - "indicates " - "that " - "the " - "vulnerable " - "code " - "is " - "not " - "reachable, " - "either " - "directly " - "or " - "indirectly.", - "justification": "code_not_reachable", - "remarks": Remarks.NotAffected, - "response": ["will_not_fix", "update"], - "severity": "NONE", - }, - "paths": {}, - }, - ProductInfo( - vendor="UNKNOWN", - product="acme-product", - version="2", - location="location/to/product", - ): { - "CVE-2020-25649": { - "comments": "Automated " - "dataflow " - "analysis " - "and " - "manual " - "code " - "review " - "indicates " - "that " - "the " - "vulnerable " - "code " - "is " - "not " - "reachable, " - "either " - "directly " - "or " - "indirectly.", - "justification": "code_not_reachable", - "remarks": Remarks.NotAffected, - "response": ["will_not_fix", "update"], - "severity": "NONE", - }, - "paths": {}, - }, - ProductInfo( - vendor="UNKNOWN", - product="acme-product", - version="3", - location="location/to/product", - ): { - "CVE-2020-25649": { - "comments": "Automated " - "dataflow " - "analysis " - "and " - "manual " - "code " - "review " - "indicates " - "that " - "the " - "vulnerable " - "code " - "is " - "not " - "reachable, " - "either " - "directly " - "or " - "indirectly.", - "remarks": Remarks.Confirmed, - "response": None, - }, - "paths": {}, - }, - } - MISSING_FIELD_REGEX = re.compile( r"({[' ,](([a-z])+[' ,]{1,4})+}) are required fields" ) @@ -268,65 +125,3 @@ def test_valid_file(self, filepath, parsed_data): print("Parsed Data Actual:", parsed_data_actual) print("Expected Data:", parsed_data) assert parsed_data_actual[product_info] == expected_data - - @pytest.mark.parametrize( - "filepath, parsed_data", - ( - (str(VEX_PATH / "test_triage.vex"), VEX_TRIAGE_DATA), - ( - str(VEX_PATH / "test_triage_cyclonedx_case13.vex"), - VEX_TRIAGE_DATA_CYCLONEDX_CASE13, - ), - (str(VEX_PATH / "test_triage_cyclonedx.vex"), VEX_TRIAGE_DATA_CYCLONEDX), - (str(VEX_PATH / "bad.vex"), defaultdict(dict)), - ), - ) - def test_vex_file(self, filepath, parsed_data): - input_engine = InputEngine(filepath, error_mode=ErrorMode.FullTrace) - assert dict(input_engine.parse_input()) == parsed_data - - @pytest.mark.parametrize( - "product, product_result", - ( - ("gcc", True), - ("not_a_bad%product", True), - ("12!", False), - ("!Superproduct", False), - ), - ) - def test_valid_product_name(self, product, product_result): - input_engine = InputEngine("temp.txt", error_mode=ErrorMode.FullTrace) - assert input_engine.validate_product(product) == product_result - - @pytest.mark.parametrize( - "version", - ( - "sky%2fx6069_trx_l601_sky%2fx6069_trx_l601_sky%3a6.0%2fmra58k%2f1482897127%3auser%2frelease-keys", - "v4.02.15%282335dn_mfp%29_11-22-2010", - "_", - "-", - "y", - "2024-01-23", - ), - ) - def test_cpe_versions(self, version): - # Based on the National Vulnerability Database (NVD) - # official-cpe-dictionary_v2.3.xml (2024-02-28T04:51:31.141Z) the - # following are possible characters is a version string: [a-z0-9.%-_] - input_engine = InputEngine("temp.txt", error_mode=ErrorMode.FullTrace) - vex = { - "vulnerabilities": [ - { - "id": "CVE-2018-15007", - "analysis": { - "state": "not_affected", - "response": [], - "justification": "", - "detail": "1", - }, - "affects": [{"ref": f"urn:cbt:1/vendor#product:{version}"}], - } - ] - } - input_engine.input_vex_cyclone_dx(vex) - assert list(input_engine.parsed_data.keys())[0].version == version From 01d64dcbb458d8b55d0d4117b0e44165ddfc1b63 Mon Sep 17 00:00:00 2001 From: "sanskarsharma3110@gmail.com" Date: Mon, 29 Jul 2024 00:03:14 +0530 Subject: [PATCH 5/8] fix: fix filtering of the cves in triage process --- cve_bin_tool/cli.py | 10 ++-------- cve_bin_tool/cve_scanner.py | 38 +++++++++++++++++++++++++++++++++---- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/cve_bin_tool/cli.py b/cve_bin_tool/cli.py index 6d69bb439b..ecf13b2ff3 100644 --- a/cve_bin_tool/cli.py +++ b/cve_bin_tool/cli.py @@ -181,12 +181,6 @@ def main(argv=None): default="", help="provide input filename", ) - input_group.add_argument( - "--triage-input-file", - action="store", - default="", - help="provide input filename for triage data", - ) input_group.add_argument( "-C", "--config", action="store", default="", help="provide config file" ) @@ -390,8 +384,8 @@ def main(argv=None): ) output_group.add_argument( "--filter-triage", - action="store", - default=True, + action="store_true", + default=False, help="Filter cves based on triage data from Vex file", ) parser.add_argument( diff --git a/cve_bin_tool/cve_scanner.py b/cve_bin_tool/cve_scanner.py index 19ad7c14e5..6eab229d02 100644 --- a/cve_bin_tool/cve_scanner.py +++ b/cve_bin_tool/cve_scanner.py @@ -82,12 +82,42 @@ def get_cves(self, product_info: ProductInfo, triage_data: TriageData): return if product_info in self.all_cve_data: - # If product_info already in all_cve_data no need to fetch cves from database again - # We just need to update paths. + # If product_info already in all_cve_data, no need to fetch CVEs from the database again. + # We just need to update paths and triage data. self.logger.debug( - f"{product_info} already processed. Update path {triage_data['paths']}" + f"{product_info} already processed. Update paths {triage_data['paths']}" ) - # self.products_with_cve += 1 + + # Update the triage data + cve_data = self.all_cve_data[product_info]["cves"] + new_cve_data = [] + + for cve in cve_data: + cve_number = cve.cve_number + if cve_number in triage_data: + for key in [ + "remarks", + "comments", + "response", + "justification", + "severity", + ]: + data = triage_data[cve_number].get(key) + if data: + if ( + key == "severity" + and self.check_exploits + and cve_number in self.exploits_list + ): + data += "-EXPLOIT" + + self.logger.debug(f"Setting field {key} to: {data}") + cve = cve._replace(**{key: data}) + new_cve_data.append(cve) + + self.all_cve_data[product_info]["cves"] = new_cve_data + + # Update paths self.all_cve_data[product_info]["paths"] |= set(triage_data["paths"]) return From a9e360b0a530abb1f91ce068c0ccd4630a53e953 Mon Sep 17 00:00:00 2001 From: "sanskarsharma3110@gmail.com" Date: Tue, 30 Jul 2024 17:44:47 +0530 Subject: [PATCH 6/8] fix: test_requirement failure --- test/test_requirements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_requirements.py b/test/test_requirements.py index dc11fd3ad3..e3d287051f 100644 --- a/test/test_requirements.py +++ b/test/test_requirements.py @@ -132,7 +132,7 @@ def test_requirements(): "cve_bin_tool.cli", "--input-file", SCAN_CSV, - "--triage-input-file", + "--vex-file", TRIAGE_JSON, "--format", "json", From 0171b4ba9b82669b11dc477b70d8a9186ce65ca1 Mon Sep 17 00:00:00 2001 From: "sanskarsharma3110@gmail.com" Date: Wed, 7 Aug 2024 22:52:21 +0530 Subject: [PATCH 7/8] fix: bugs and improve functionality Signed-off-by: sanskarsharma3110@gmail.com --- cve_bin_tool/cli.py | 15 +++- cve_bin_tool/output_engine/__init__.py | 2 +- cve_bin_tool/util.py | 2 +- cve_bin_tool/vex_manager/generate.py | 46 +++++++----- cve_bin_tool/vex_manager/parse.py | 34 +++++++-- test/sbom/test_triage_cyclonedx_sbom.json | 87 +++++++++++++++++++++ test/test_vex.py | 71 +++++++++++++++++ test/vex/test_triage_cyclonedx_vex.json | 92 +++++++++++++++++++++++ 8 files changed, 318 insertions(+), 31 deletions(-) create mode 100644 test/sbom/test_triage_cyclonedx_sbom.json create mode 100644 test/vex/test_triage_cyclonedx_vex.json diff --git a/cve_bin_tool/cli.py b/cve_bin_tool/cli.py index ecf13b2ff3..00414569be 100644 --- a/cve_bin_tool/cli.py +++ b/cve_bin_tool/cli.py @@ -353,7 +353,7 @@ def main(argv=None): output_group.add_argument( "--vex-type", action="store", - default="cyclonedx", + default="", choices=["cyclonedx", "csaf", "openvex"], help="specify type of vulnerability exchange (vex) to generate (default: cyclonedx)", ) @@ -1018,7 +1018,7 @@ def main(argv=None): triage_data: TriageData total_files: int = 0 parsed_data: dict[ProductInfo, TriageData] = {} - + vex_product_info: dict[str, str] = {} # Package List parsing if args["package_list"]: sbom_root = args["package_list"] @@ -1097,6 +1097,7 @@ def main(argv=None): logger=LOGGER, ) parsed_vex_data = vexdata.parse_vex() + vex_product_info = vexdata.vex_product_info if not parsed_data: # assume the vex file being scanned is a standalone file args["filter_triage"] = False @@ -1136,8 +1137,12 @@ def main(argv=None): ) ) LOGGER.info(f"Known CVEs in {affected_string}:") - vex_product_info: dict[str, str] = {} - if args["vex_output"]: + if args["vex_type"] or args["vex_output"]: + # If vex_type is provided, then use it, else use cyclonedx as default vex_output should be provide in this case + # If vex_output is provided, then use it, else use product, release and vendor to generate the vex file. + if args["vex_output"] and not args["vex_type"]: + # default vex_type is cyclonedx + args["vex_type"] = "cyclonedx" if args["product"] and args["release"] and args["vendor"]: vex_product_info = { "product": args["product"], @@ -1145,6 +1150,8 @@ def main(argv=None): "vendor": args["vendor"], "revision_reason": args["revision_reason"], } + elif args["vex_file"]: + vex_product_info["revision_reason"] = args["revision_reason"] else: LOGGER.error( "Please provide --product, --release and --vendor for VEX generation" diff --git a/cve_bin_tool/output_engine/__init__.py b/cve_bin_tool/output_engine/__init__.py index 0bb6188b42..63a2c9fd39 100644 --- a/cve_bin_tool/output_engine/__init__.py +++ b/cve_bin_tool/output_engine/__init__.py @@ -792,7 +792,7 @@ def output_cves(self, outfile, output_type="console"): ) self.logger.info(f"Output stored at {self.append}") - if self.vex_filename != "": + if self.vex_filename != "" or self.vex_type != "": vexgen = VEXGenerate( self.vex_product_info["product"], self.vex_product_info["release"], diff --git a/cve_bin_tool/util.py b/cve_bin_tool/util.py index 0a9d2d2797..7b590f10a9 100644 --- a/cve_bin_tool/util.py +++ b/cve_bin_tool/util.py @@ -565,7 +565,7 @@ def decode_cpe23(cpe23) -> list: return [vendor or None, product or None, version or None] -def decode_cpe22(self, cpe22) -> list: +def decode_cpe22(cpe22) -> list: """ Decode a CPE 2.2 formatted string to extract vendor, product, and version information. diff --git a/cve_bin_tool/vex_manager/generate.py b/cve_bin_tool/vex_manager/generate.py index 0a4cddbd1e..c3441cd497 100644 --- a/cve_bin_tool/vex_manager/generate.py +++ b/cve_bin_tool/vex_manager/generate.py @@ -1,6 +1,5 @@ # Copyright (C) 2024 Intel Corporation # SPDX-License-Identifier: GPL-3.0-or-later -import os from logging import Logger from pathlib import Path from typing import Dict, List, Optional @@ -71,7 +70,10 @@ def generate_vex(self) -> None: Returns: None """ - vexgen = VEXGenerator(vex_type=self.vextype) + author = "Unknown Author" + if self.vendor: + author = self.vendor + vexgen = VEXGenerator(vex_type=self.vextype, author=author) kwargs = {"name": self.product, "release": self.release} if self.sbom: kwargs["sbom"] = self.sbom @@ -80,43 +82,49 @@ def generate_vex(self) -> None: self.logger.info( "No filename defined, Generating a new filename with Default Naming Convention" ) - self.filename = self.generate_vex_filename() + self.filename = self.__generate_vex_filename() if Path(self.filename).is_file(): self.logger.info(f"Updating the vex file: {self.filename}") vexgen.generate( project_name=self.product, - vex_data=self.get_vulnerabilities(), - metadata=self.get_metadata(), + vex_data=self.__get_vulnerabilities(), + metadata=self.__get_metadata(), filename=self.filename, ) - def generate_vex_filename(self) -> str: + def __generate_vex_filename(self) -> str: """ Generates a VEX filename based on the current date and time. Returns: str: The generated VEX filename. """ - filename = os.path.abspath( - os.path.join( - os.getcwd(), - f"{self.product}_{self.release}_{self.vendor}_{self.vextype}.json", - ) + filename = ( + Path.cwd() + / f"{self.product}_{self.release}_{self.vendor}_{self.vextype}.json" ) - return filename + return str(filename) - def get_metadata(self) -> Dict: - metadata = { - "id": f"{self.product.upper()}-{self.release}-VEX", - "supplier": self.vendor, - } + def __get_metadata(self) -> Dict: + metadata = {} + if self.vextype == "cyclonedx": + if self.product: + metadata["id"] = f"{self.product.upper()}-VEX" + elif self.vextype == "csaf": + if self.product and self.release and self.vendor: + metadata["id"] = f"{self.product.upper()}-{self.release}-VEX" + metadata["supplier"] = self.vendor + elif self.vextype == "openvex": + if self.vendor: + metadata["author"] = self.vendor + metadata["supplier"] = self.vendor if self.revision_reason: metadata["revision_reason"] = self.revision_reason - # other metadata can be added here + return metadata - def get_vulnerabilities(self) -> List[Vulnerability]: + def __get_vulnerabilities(self) -> List[Vulnerability]: """ Retrieves a list of vulnerabilities. diff --git a/cve_bin_tool/vex_manager/parse.py b/cve_bin_tool/vex_manager/parse.py index 0d82a000b6..6c4136a2f1 100644 --- a/cve_bin_tool/vex_manager/parse.py +++ b/cve_bin_tool/vex_manager/parse.py @@ -66,18 +66,40 @@ def parse_vex(self) -> DefaultDict[ProductInfo, TriageData]: vexparse = VEXParser(vex_type=self.vextype) vexparse.parse(self.filename) self.logger.debug(f"VEX Vulnerabilities: {vexparse.get_vulnerabilities()}") - self.process_vulnerabilities(vexparse.get_vulnerabilities()) - self.process_metadata(vexparse.get_metadata()) - self.process_product(vexparse.get_product()) + self.__process_vulnerabilities(vexparse.get_vulnerabilities()) + self.__process_metadata(vexparse.get_metadata()) + self.__process_product(vexparse.get_product()) + self.__extract_product_info() return self.parsed_data - def process_metadata(self, metadata) -> None: + def __extract_product_info(self): + """Extracts the product information from the parsed vex file""" + product_info = {} + if self.vextype == "cyclonedx": + # release and vendor is not available in cyclonedx + product_info["product"] = self.parsed_metadata.get("name") + product_info["release"] = "" + product_info["vendor"] = "" + elif self.vextype == "csaf": + csaf_product = self.parsed_product.get("CSAFPID_0001", {}) + if csaf_product: + product_info["product"] = csaf_product.get("product") + product_info["release"] = csaf_product.get("version") + product_info["vendor"] = csaf_product.get("vendor") + elif self.vextype == "openvex": + # product and release is not available in openvex + product_info["product"] = "" + product_info["release"] = "" + product_info["vendor"] = self.parsed_metadata.get("author") + self.vex_product_info = product_info + + def __process_metadata(self, metadata) -> None: self.parsed_metadata = metadata - def process_product(self, product) -> None: + def __process_product(self, product) -> None: self.parsed_product = product - def process_vulnerabilities(self, vulnerabilities) -> None: + def __process_vulnerabilities(self, vulnerabilities) -> None: """ "processes the vulnerabilities and extracts the necessary fields from the vulnerability.""" # for now cyclonedx is supported with minor tweaks other will be supported later for vuln in vulnerabilities: diff --git a/test/sbom/test_triage_cyclonedx_sbom.json b/test/sbom/test_triage_cyclonedx_sbom.json new file mode 100644 index 0000000000..15f7c7d1bd --- /dev/null +++ b/test/sbom/test_triage_cyclonedx_sbom.json @@ -0,0 +1,87 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.6", + "serialNumber": "urn:uuid:614e1a9d-616f-4f18-88ee-069127a2c271", + "version": 1, + "metadata": { + "timestamp": "2024-08-07T20:26:22Z", + "tools": { + "components": [ + { + "name": "cve-bin-tool", + "version": "3.3.1dev0", + "type": "application" + } + ] + }, + "component": { + "type": "application", + "bom-ref": "CDXRef-DOCUMENT", + "name": "SBOM_CVEBINTOOL-pubspec-lock" + } + }, + "components": [ + { + "type": "application", + "bom-ref": "1-CVEBINTOOL-pubspec-lock", + "name": "CVEBINTOOL-pubspec-lock", + "externalReferences": [ + { + "url": "pubspec.lock", + "type": "distribution", + "comment": "Download location for component" + } + ] + }, + { + "type": "library", + "bom-ref": "2-archive", + "name": "archive", + "version": "3.3.7", + "supplier": { + "name": "archive project" + }, + "cpe": "cpe:/a:archive_project:archive:3.3.7", + "evidence": { + "occurrences": [ + { + "location": "pubspec.lock" + } + ] + } + }, + { + "type": "library", + "bom-ref": "3-dio", + "name": "dio", + "version": "4.0.0", + "supplier": { + "name": "flutterchina" + }, + "cpe": "cpe:/a:flutterchina:dio:4.0.0", + "evidence": { + "occurrences": [ + { + "location": "pubspec.lock" + } + ] + } + } + ], + "dependencies": [ + { + "ref": "CDXRef-DOCUMENT", + "dependsOn": [ + "1-CVEBINTOOL-pubspec-lock" + ] + }, + { + "ref": "1-CVEBINTOOL-pubspec-lock", + "dependsOn": [ + "2-archive", + "3-dio" + ] + } + ] +} diff --git a/test/test_vex.py b/test/test_vex.py index aec1910fa6..27fbbc68b9 100644 --- a/test/test_vex.py +++ b/test/test_vex.py @@ -1,6 +1,8 @@ # Copyright (C) 2021 Intel Corporation # SPDX-License-Identifier: GPL-3.0-or-later import json +import subprocess +import tempfile import unittest from pathlib import Path @@ -10,8 +12,11 @@ from cve_bin_tool.vex_manager.generate import VEXGenerate from cve_bin_tool.vex_manager.parse import VEXParse +TEMP_DIR = Path(tempfile.mkdtemp(prefix="test_triage-")) TEST_DIR = Path(__file__).parent.resolve() VEX_PATH = TEST_DIR / "vex" +SBOM_PATH = TEST_DIR / "sbom" +OUTPUT_JSON = str(TEMP_DIR / "test_triage_output.json") class TestVexGeneration(unittest.TestCase): @@ -258,5 +263,71 @@ def test_parse_openvex(self, vex_format, vex_filename, expected_parsed_data): assert parsed_data == expected_parsed_data +class TestTriage: + """Test triage functionality""" + + TEST_SBOM = str(SBOM_PATH / "test_triage_cyclonedx_sbom.json") + TEST_VEX = str(VEX_PATH / "test_triage_cyclonedx_vex.json") + + def test_triage(self): + """Test triage functionality""" + subprocess.run( + [ + "python", + "-m", + "cve_bin_tool.cli", + "--sbom", + "cyclonedx", + "--sbom-file", + self.TEST_SBOM, + "--vex-file", + self.TEST_VEX, + "--format", + "json", + "--output-file", + OUTPUT_JSON, + ] + ) + + with open(OUTPUT_JSON) as f: + output_json = json.load(f) + assert len(output_json) >= 1 + for output in output_json: + if output.get("cve_number", "") == "CVE-2023-39137": + assert output["remarks"] == "NotAffected" + else: + assert output["remarks"] == "NewFound" + Path(OUTPUT_JSON).unlink() + + def test_filter_triage(self): + """Test filter triage functionality""" + subprocess.run( + [ + "python", + "-m", + "cve_bin_tool.cli", + "--filter-triage", + "--sbom", + "cyclonedx", + "--sbom-file", + self.TEST_SBOM, + "--vex-file", + self.TEST_VEX, + "--format", + "json", + "--output-file", + OUTPUT_JSON, + ] + ) + + with open(OUTPUT_JSON) as f: + output_json = json.load(f) + assert len(output_json) >= 1 + print("Output JSON:", output_json) + for output in output_json: + assert output.get("cve_number", "") != "CVE-2023-39137" + Path(OUTPUT_JSON).unlink() + + if __name__ == "__main__": unittest.main() diff --git a/test/vex/test_triage_cyclonedx_vex.json b/test/vex/test_triage_cyclonedx_vex.json new file mode 100644 index 0000000000..f7de006003 --- /dev/null +++ b/test/vex/test_triage_cyclonedx_vex.json @@ -0,0 +1,92 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.6", + "serialNumber": "urn:uuid:b54e9152-1a62-464d-be3f-c0149f07bf51", + "version": 1, + "metadata": { + "timestamp": "2024-08-07T20:41:52Z", + "tools": { + "components": [ + { + "name": "lib4vex", + "version": "0.1.0", + "type": "application" + } + ] + }, + "properties": [ + { + "name": "Revision_1", + "value": "Initial version" + } + ], + "component": { + "type": "application", + "bom-ref": "CDXRef-DOCUMENT", + "name": "myapp" + } + }, + "vulnerabilities": [ + { + "bom-ref": "archive@3.3.7", + "id": "CVE-2023-39137", + "source": { + "name": "NVD", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-39137" + }, + "description": "An issue in Archive v3.3.7 allows attackers to spoof zip filenames which can lead to inconsistent filename parsing.", + "published": "2024-08-07T20:41:52Z", + "updated": "2024-08-07T20:41:52Z", + "analysis": { + "state": "not_affected", + "detail": "" + }, + "affects": [ + { + "ref": "urn:cbt:1/archive_project#archive:3.3.7" + } + ] + }, + { + "bom-ref": "archive@3.3.7", + "id": "CVE-2023-39139", + "source": { + "name": "NVD", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-39139" + }, + "description": "An issue in Archive v3.3.7 allows attackers to execute a path traversal via extracting a crafted zip file.", + "published": "2024-08-07T20:41:52Z", + "updated": "2024-08-07T20:41:52Z", + "analysis": { + "state": "in_triage", + "detail": "" + }, + "affects": [ + { + "ref": "urn:cbt:1/archive_project#archive:3.3.7" + } + ] + }, + { + "bom-ref": "dio@4.0.0", + "id": "CVE-2021-31402", + "source": { + "name": "NVD", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-31402" + }, + "description": "The dio package 4.0.0 for Dart allows CRLF injection if the attacker controls the HTTP method string, a different vulnerability than CVE-2020-35669.", + "published": "2024-08-07T20:41:52Z", + "updated": "2024-08-07T20:41:52Z", + "analysis": { + "state": "in_triage", + "detail": "" + }, + "affects": [ + { + "ref": "urn:cbt:1/flutterchina#dio:4.0.0" + } + ] + } + ] +} From 176b1eca9cd8de3d7f799d614aa97a8b7367bf7b Mon Sep 17 00:00:00 2001 From: "sanskarsharma3110@gmail.com" Date: Thu, 8 Aug 2024 14:06:02 +0530 Subject: [PATCH 8/8] fix: output_engine test error Signed-off-by: sanskarsharma3110@gmail.com --- cve_bin_tool/output_engine/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cve_bin_tool/output_engine/__init__.py b/cve_bin_tool/output_engine/__init__.py index 63a2c9fd39..4fecb554fa 100644 --- a/cve_bin_tool/output_engine/__init__.py +++ b/cve_bin_tool/output_engine/__init__.py @@ -681,7 +681,7 @@ def __init__( sbom_format: str = "tag", sbom_root: str = "CVE_SBOM", vex_filename: str = "", - vex_type: str = "cyclonedx", + vex_type: str = "", vex_product_info: dict[str, str] = {}, offline: bool = False, ):