Skip to content

feat: add support for CPE field in SPDX document #3683

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

Closed
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
72 changes: 46 additions & 26 deletions cve_bin_tool/cve_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,40 +62,25 @@ def __init__(
self.disabled_sources = disabled_sources
self.all_product_data = dict()

def get_cves(self, product_info: ProductInfo, triage_data: TriageData):
"""Get CVEs against a specific version of a product.
Example:
nvd.get_cves('haxx', 'curl', '7.34.0')
"""

# Prevent any queries resulting in CVEs with UNKNOWN score value
# being reported
if self.score > 10 or self.epss_probability > 1.0 or self.epss_percentile > 1.0:
return
def _get_all_cves_for_product(self, product_info):
query = """
SELECT CVE_number FROM cve_range
WHERE vendor=? AND product=?
"""

if product_info.vendor == "UNKNOWN":
# Add product
if product_info not in self.all_product_data:
self.logger.debug(f"Add product {product_info}")
# Number of CVEs is 0
self.all_product_data[product_info] = 0
return
# Removing * from vendors that are guessed by the package list parser
vendor = product_info.vendor.replace("*", "")

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.
self.logger.debug(
f"{product_info} already processed. Update path {triage_data['paths']}"
)
# self.products_with_cve += 1
self.all_cve_data[product_info]["paths"] |= set(triage_data["paths"])
return
self.cursor.execute(query, [vendor, product_info.product])
return list(map(lambda x: x[0], self.cursor.fetchall()))

def _get_cves_for_product_version(self, product_info):
# Check for anything directly marked
query = """
SELECT CVE_number FROM cve_range
WHERE vendor=? AND product=? AND version=?
"""

# Removing * from vendors that are guessed by the package list parser
vendor = product_info.vendor.replace("*", "")

Expand Down Expand Up @@ -180,6 +165,41 @@ def get_cves(self, product_info: ProductInfo, triage_data: TriageData):
end_excluding=version_end_excluding,
)

return cve_list

def get_cves(self, product_info: ProductInfo, triage_data: TriageData):
"""Get CVEs against a specific version of a product.
Example:
nvd.get_cves('haxx', 'curl', '7.34.0')
"""
# Prevent any queries resulting in CVEs with UNKNOWN score value
# being reported
if self.score > 10 or self.epss_probability > 1.0 or self.epss_percentile > 1.0:
return

if product_info.vendor == "UNKNOWN":
# Add product
if product_info not in self.all_product_data:
self.logger.debug(f"Add product {product_info}")
# Number of CVEs is 0
self.all_product_data[product_info] = 0
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.
self.logger.debug(
f"{product_info} already processed. Update path {triage_data['paths']}"
)
# self.products_with_cve += 1
self.all_cve_data[product_info]["paths"] |= set(triage_data["paths"])
return

if product_info.version == "*":
cve_list = self._get_all_cves_for_product(product_info)
else:
cve_list = self._get_cves_for_product_version(product_info)

# Go through and get all the severities
cves: List[CVE] = []
if cve_list:
Expand Down
21 changes: 18 additions & 3 deletions cve_bin_tool/sbom_manager/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from pathlib import Path

import defusedxml.ElementTree as ET
from cpeparser import CpeParser
from lib4sbom.parser import SBOMParser
from packageurl import PackageURL

Expand Down Expand Up @@ -43,6 +44,11 @@ def __init__(
# Connect to the database
self.cvedb = CVEDB(version_check=False)

def _extract_info_from_cpe23(self, cpe: str):
cpe_parser = CpeParser()
result = cpe_parser.parser(cpe)
return result["product"], result["version"]

def scan_file(self) -> dict[ProductInfo, TriageData]:
LOGGER.info(f"Processing SBOM {self.filename} of type {self.type.upper()}")
modules = []
Expand Down Expand Up @@ -115,7 +121,7 @@ def parse_sbom(self):
packages = [x for x in sbom_parser.get_sbom()["packages"].values()]
LOGGER.debug(f"Parsed SBOM {self.filename} {packages}")
for package in packages:
purl_found = False
extra_id_found = False
# If PURL record found, use this data in preference to package data
ext_ref = package.get("externalreference")
if ext_ref is not None:
Expand All @@ -125,8 +131,17 @@ def parse_sbom(self):
purl_info = PackageURL.from_string(ref[2]).to_dict()
if purl_info["name"] and purl_info["version"]:
modules.append([purl_info["name"], purl_info["version"]])
purl_found = True
if not purl_found:
extra_id_found = True
elif ref[1] == "cpe23Type":
# https://github.com/anthonyharrison/lib4sbom/issues/28
if ref[2].startswith("cpe:2.3"):
(
package_name,
package_version,
) = self._extract_info_from_cpe23(ref[2])
modules.append([package_name, package_version])
extra_id_found = True
if not extra_id_found:
if package.get("version") is not None:
modules.append([package["name"], package["version"]])
else:
Expand Down
1 change: 1 addition & 0 deletions requirements.csv
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ python_not_in_db,importlib_resources
vsajip_not_in_db,python-gnupg
anthonyharrison_not_in_db,lib4sbom
the_purl_authors_not_in_db,packageurl-python
sabuhish_not_in_db,cpeparser
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ toml; python_version < "3.11"
urllib3>=1.26.5 # dependency of requests added explictly to avoid CVEs
xmlschema
zstandard; python_version >= "3.4"
cpeparser==0.0.2
6 changes: 3 additions & 3 deletions test/sbom/spdx_mixed_test.spdx.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
"downloadLocation" : "http://ftp.gnu.org/gnu/glibc/glibc-ports-2.15.tar.gz",
"externalRefs" : [ {
"referenceCategory" : "SECURITY",
"referenceLocator" : "cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*",
"referenceLocator" : "cpe:2.3:a:gnu:glibc:2.11.1:*:*:*:*:*:*:*",
"referenceType" : "cpe23Type"
}, {
"comment" : "This is the external ref for Acme",
Expand All @@ -97,7 +97,7 @@
"licenseConcluded" : "(LGPL-2.0-only OR LicenseRef-3)",
"licenseDeclared" : "(LGPL-2.0-only AND LicenseRef-3)",
"licenseInfoFromFiles" : [ "GPL-2.0-only", "LicenseRef-2", "LicenseRef-1" ],
"name" : "GLIBC",
"name" : "spring_framework",
"originator" : "Organization: ExampleCodeInspect (contact@example.com)",
"packageFileName" : "glibc-2.11.1.tar.gz",
"packageVerificationCode" : {
Expand All @@ -107,7 +107,7 @@
"sourceInfo" : "uses glibc-2_11-branch from git://sourceware.org/git/glibc.git.",
"summary" : "GNU C library.",
"supplier" : "Person: Jane Doe (jane.doe@example.com)",
"versionInfo" : "2.11.1"
"versionInfo" : "4.1.0"
}, {
"SPDXID" : "SPDXRef-fromDoap-1",
"copyrightText" : "NOASSERTION",
Expand Down
6 changes: 3 additions & 3 deletions test/sbom/spdx_test.spdx
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ SPDXREF: SPDXRef-File
## Relationships
Relationship: SPDXRef-File GENERATED_FROM SPDXRef-fromDoap-0
## Package Information
PackageName: glibc
PackageName: spring_framework
SPDXID: SPDXRef-Package
PackageVersion: 2.11.1
PackageVersion: 4.1.0
PackageFileName: glibc-2.11.1.tar.gz
PackageSupplier: Person: Jane Doe (jane.doe@example.com)
PackageOriginator: Organization: ExampleCodeInspect (contact@example.com)
Expand All @@ -92,7 +92,7 @@ PackageCopyrightText: <text>Copyright 2008-2010 John Smith</text>
PackageSummary: <text>GNU C library.</text>
PackageDescription: <text>The GNU C Library defines functions that are specified by the ISO C standard, as well as additional features specific to POSIX and other derivatives of the Unix operating system, and extensions specific to GNU systems.</text>
PackageAttributionText: <text>The GNU C Library is free software. See the file COPYING.LIB for copying conditions, and LICENSES for notices about a few contributions that require these additional notices to be distributed. License copyright years may be listed using range notation, e.g., 1996-2015, indicating that every year in the range, inclusive, is a copyrightable year that would otherwise be listed individually.</text>
ExternalRef: SECURITY cpe23Type cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*
ExternalRef: SECURITY cpe23Type cpe:2.3:a:gnu:glibc:2.11.1:*:*:*:*:*:*:*
ExternalRef: OTHER LocationRef-acmeforge acmecorp/acmenator/4.1.3-alpha
ExternalRefComment: This is the external ref for Acme
## Annotations
Expand Down
6 changes: 3 additions & 3 deletions test/sbom/spdx_test.spdx.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
"downloadLocation" : "http://ftp.gnu.org/gnu/glibc/glibc-ports-2.15.tar.gz",
"externalRefs" : [ {
"referenceCategory" : "SECURITY",
"referenceLocator" : "cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*",
"referenceLocator" : "cpe:2.3:a:gnu:glibc:2.11.1:*:*:*:*:*:*:*",
"referenceType" : "cpe23Type"
}, {
"comment" : "This is the external ref for Acme",
Expand All @@ -97,7 +97,7 @@
"licenseConcluded" : "(LGPL-2.0-only OR LicenseRef-3)",
"licenseDeclared" : "(LGPL-2.0-only AND LicenseRef-3)",
"licenseInfoFromFiles" : [ "GPL-2.0-only", "LicenseRef-2", "LicenseRef-1" ],
"name" : "glibc",
"name" : "spring_framework",
"originator" : "Organization: ExampleCodeInspect (contact@example.com)",
"packageFileName" : "glibc-2.11.1.tar.gz",
"packageVerificationCode" : {
Expand All @@ -107,7 +107,7 @@
"sourceInfo" : "uses glibc-2_11-branch from git://sourceware.org/git/glibc.git.",
"summary" : "GNU C library.",
"supplier" : "Person: Jane Doe (jane.doe@example.com)",
"versionInfo" : "2.11.1"
"versionInfo" : "4.1.0"
}, {
"SPDXID" : "SPDXRef-fromDoap-1",
"copyrightText" : "NOASSERTION",
Expand Down
6 changes: 3 additions & 3 deletions test/sbom/spdx_test.spdx.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ packages:
downloadLocation: "http://ftp.gnu.org/gnu/glibc/glibc-ports-2.15.tar.gz"
externalRefs:
- referenceCategory: "SECURITY"
referenceLocator: "cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*"
referenceLocator: "cpe:2.3:a:gnu:glibc:2.11.1:*:*:*:*:*:*:*"
referenceType: "cpe23Type"
- comment: "This is the external ref for Acme"
referenceCategory: "OTHER"
Expand All @@ -191,7 +191,7 @@ packages:
- "GPL-2.0-only"
- "LicenseRef-2"
- "LicenseRef-1"
name: "glibc"
name: "spring_framework"
originator: "Organization: ExampleCodeInspect (contact@example.com)"
packageFileName: "glibc-2.11.1.tar.gz"
packageVerificationCode:
Expand All @@ -201,7 +201,7 @@ packages:
sourceInfo: "uses glibc-2_11-branch from git://sourceware.org/git/glibc.git."
summary: "GNU C library."
supplier: "Person: Jane Doe (jane.doe@example.com)"
versionInfo: "2.11.1"
versionInfo: "4.1.0"
- SPDXID: "SPDXRef-fromDoap-1"
copyrightText: "NOASSERTION"
downloadLocation: "NOASSERTION"
Expand Down
6 changes: 3 additions & 3 deletions test/sbom/spdx_test.spdx.yml
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ packages:
downloadLocation: "http://ftp.gnu.org/gnu/glibc/glibc-ports-2.15.tar.gz"
externalRefs:
- referenceCategory: "SECURITY"
referenceLocator: "cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*"
referenceLocator: "cpe:2.3:a:gnu:glibc:2.11.1:*:*:*:*:*:*:*"
referenceType: "cpe23Type"
- comment: "This is the external ref for Acme"
referenceCategory: "OTHER"
Expand All @@ -191,7 +191,7 @@ packages:
- "GPL-2.0-only"
- "LicenseRef-2"
- "LicenseRef-1"
name: "glibc"
name: "spring_framework"
originator: "Organization: ExampleCodeInspect (contact@example.com)"
packageFileName: "glibc-2.11.1.tar.gz"
packageVerificationCode:
Expand All @@ -201,7 +201,7 @@ packages:
sourceInfo: "uses glibc-2_11-branch from git://sourceware.org/git/glibc.git."
summary: "GNU C library."
supplier: "Person: Jane Doe (jane.doe@example.com)"
versionInfo: "2.11.1"
versionInfo: "4.1.0"
- SPDXID: "SPDXRef-fromDoap-1"
copyrightText: "NOASSERTION"
downloadLocation: "NOASSERTION"
Expand Down
18 changes: 18 additions & 0 deletions test/test_exploits.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,24 @@ class TestExploitScanner:
@pytest.mark.parametrize(
"check_exploits, exploits_list, product_info, triage_info, expected_result",
(
(
True,
["CVE-2020-24658", "CVE-2022-43701", "CVE-2022-43702"],
ProductInfo(vendor="arm", product="arm_compiler", version="*"),
{
"CVE-2020-24658": {
"severity": "HIGH",
},
"CVE-2022-43701": {
"severity": "HIGH",
},
"CVE-2022-43702": {
"severity": "HIGH",
},
"paths": {""},
},
"HIGH-EXPLOIT",
),
(
True,
["CVE-2018-19664"],
Expand Down