Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
13 changes: 11 additions & 2 deletions cve_bin_tool/output_engine/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from __future__ import annotations

import re
from collections import Counter, defaultdict
from datetime import datetime
from logging import Logger
Expand All @@ -28,6 +29,12 @@
}


def normalize_id(text):
"""Normalize text for use as HTML ID by replacing problematic characters."""
# Replace slashes, backslashes, spaces, and other problematic characters
return re.sub(r"[\/\\\s.,:;?!@#$%^&*()+=]", "_", text)


def normalize_severity(severity: str) -> str:
"""Normalize severity values to standard format.

Expand Down Expand Up @@ -306,9 +313,11 @@ def output_html(

# hid is unique for each product
if product_info.vendor != "UNKNOWN":
hid = f"{product_info.vendor}{product_info.product}{''.join(product_info.version.split('.'))}"
hid = normalize_id(
f"{product_info.vendor}{product_info.product}{''.join(product_info.version.split('.'))}"
)
else:
hid = (
hid = normalize_id(
f"{product_info.product}{''.join(product_info.version.split('.'))}"
)
new_cves = render_cves(
Expand Down
2 changes: 1 addition & 1 deletion cve_bin_tool/output_engine/html_reports/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function handleActive(key, id) {

function filterCVEs(remark, id) {
const classes = ['new', 'confirmed', 'mitigated', 'unexplored', 'false_positive', 'not_affected']
for (let i = 0; i < 6; i++) {
for (let i = 0; i < classes.length; i++) {
let ele = document
.getElementById(`listCVE${id}`)
.getElementsByClassName(classes[i])[0]
Expand Down
3 changes: 2 additions & 1 deletion cve_bin_tool/output_engine/html_reports/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,8 @@ <h5 class="fw-light p-t-5">Paths of Scanned Files</h5>
<li class="list-group-item">
<h5 class="fw-normal">{{path}}</h5>
{% for product in all_paths[path]%}
<a id="vendorProductPill" data-bs-toggle="modal" data-bs-target="#modal{{ product }}">
<a id="vendorProductPill" data-bs-toggle="modal"
data-bs-target="#modal{{ product | replace('/', '_') | replace('\\', '_') | replace('.', '_') | replace(':', '_') }}">
<span class="badge rounded-pill bg-info">{{product}}</span>
</a>
{% endfor %}
Expand Down
12 changes: 9 additions & 3 deletions cve_bin_tool/output_engine/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,12 @@ def format_output(
for cve in cve_data["cves"]:
if isinstance(cve, str):
continue

# Ensure proper remarks string value is used
remarks_value = (
cve.remarks.name if hasattr(cve.remarks, "name") else str(cve.remarks)
)

# If EPSS values are not available for a given CVE, assign them a value of "-"
probability = "-"
percentile = "-"
Expand All @@ -206,7 +212,7 @@ def format_output(
"cvss_version": str(cve.cvss_version),
"cvss_vector": cve.cvss_vector,
"paths": paths,
"remarks": cve.remarks.name,
"remarks": remarks_value,
"comments": cve.comments,
}
if metrics:
Expand Down Expand Up @@ -312,12 +318,12 @@ def group_cve_by_remark(
"""Return a dict containing CVE details dict mapped to Remark as Key.

Example:
cve_by_remark = {
cve_by_remarks = {
"NEW":[
{
"cve_number": "CVE-XXX-XXX",
"severity": "High",
"decription: "Lorem Ipsm",
"description": "Lorem Ipsum",
},
{...}
],
Expand Down
39 changes: 39 additions & 0 deletions cve_bin_tool/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,20 @@ def decode_bom_ref(ref: str):
or None if the reference cannot be decoded.

"""
# If the reference starts with urn:cbt:, use parse_urn to properly handle special characters
if ref.startswith("urn:cbt:"):
try:
vendor, product, version = parse_urn(ref)
location = "location/to/product"
return ProductInfo(
vendor.strip(), product.strip(), version.strip(), location
)
except (ValueError, AttributeError) as e:
LOGGER.debug(f"Failed to parse URN: {ref} - Error: {e}")
# Don't return None here, continue to try other parsing methods
pass

# If the reference couldn't be handled by parse_urn, fall back to regex patterns
# urn:cbt:{bom_version}/{vendor}#{product}-{version}
urn_cbt_ref = re.compile(
r"urn:cbt:(?P<bom_version>.*?)\/(?P<vendor>.*?)#(?P<product>.*?)-(?P<version>.*)"
Expand Down Expand Up @@ -608,6 +622,31 @@ def windows_fixup(filename):
return filename.replace(":", "_").replace("\\", "_")


def generate_urn(vendor, product, version):
"""Generates a URN for a given vendor, product, version combo."""
return f"urn:cbt:1/{vendor}#{product}:{version}"


def parse_urn(urn):
"""Parse a URN and return vendor, product, version tuple.

Properly handles product names with special characters like slashes.
"""
# Remove the prefix
urn = urn.replace("urn:cbt:1/", "")

# Split vendor and the rest
vendor, rest = urn.split("#", 1)

# Split product and version, being careful with the first colon only
# This preserves any colons in the version part
product_version_parts = rest.split(":", 1)
product = product_version_parts[0]
version = product_version_parts[1] if len(product_version_parts) > 1 else ""

return vendor, product, version


def strip_path(path_element: str, scanned_dir: str) -> str:
path = Path(path_element)
return path.drive + path.root + os.path.relpath(path_element, scanned_dir)
49 changes: 41 additions & 8 deletions cve_bin_tool/vex_manager/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@
from lib4vex.parser import VEXParser

from cve_bin_tool.log import LOGGER
from cve_bin_tool.util import ProductInfo, Remarks, decode_bom_ref, decode_purl
from cve_bin_tool.util import (
ProductInfo,
Remarks,
decode_bom_ref,
decode_purl,
parse_urn,
)

TriageData = Dict[str, Union[Dict[str, Any], Set[str]]]

Expand Down Expand Up @@ -124,16 +130,43 @@ def __process_vulnerabilities(self, vulnerabilities) -> None:
product_info = None
serialNumber = ""
if self.vextype == "cyclonedx":
decoded_ref = decode_bom_ref(vuln.get("bom_link"))
if isinstance(decoded_ref, tuple) and not isinstance(
decoded_ref, ProductInfo
):
product_info, serialNumber = decoded_ref
self.serialNumbers.add(serialNumber)
# First try with the custom parse_urn function to handle slashes
if vuln.get("bom_link") and vuln.get("bom_link").startswith("urn:cbt:"):
try:
vendor, product, version = parse_urn(vuln.get("bom_link"))
product_info = ProductInfo(
vendor=vendor.strip(),
product=product.strip(),
version=version.strip(),
location="NotFound",
)
except (ValueError, AttributeError):
# If the custom parse fails, fall back to decode_bom_ref
product_info = decode_bom_ref(vuln.get("bom_link"))
else:
product_info = decoded_ref
# Fall back to decode_bom_ref for other formats
decoded_ref = decode_bom_ref(vuln.get("bom_link"))
if isinstance(decoded_ref, tuple) and not isinstance(
decoded_ref, ProductInfo
):
product_info, serialNumber = decoded_ref
self.serialNumbers.add(serialNumber)
else:
product_info = decoded_ref
elif self.vextype in ["openvex", "csaf"]:
product_info = decode_purl(vuln.get("purl"))
if product_info and not hasattr(product_info, "location"):
# Create a new ProductInfo with the location field
product_info = ProductInfo(
vendor=product_info.vendor,
product=product_info.product,
version=product_info.version,
location="NotFound",
purl=(
product_info.purl if hasattr(product_info, "purl") else None
),
)

if product_info:
cve_data = {
"remarks": remarks,
Expand Down
Loading
Loading