Skip to content

Commit 2ed6280

Browse files
fix: missing CVEs by switching to cve 2.0 (#5172) (#5265)
1 parent 47f30a7 commit 2ed6280

File tree

5 files changed

+31
-18
lines changed

5 files changed

+31
-18
lines changed

cve_bin_tool/data_sources/nvd_source.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
DISK_LOCATION_BACKUP,
2525
DISK_LOCATION_DEFAULT,
2626
NVD_FILENAME_TEMPLATE,
27+
NVD_VERSION,
2728
)
2829
from cve_bin_tool.error_handler import (
2930
AttemptedToWriteOutsideCachedir,
@@ -49,11 +50,13 @@ class NVD_Source(Data_Source):
4950
CACHEDIR = DISK_LOCATION_DEFAULT
5051
BACKUPCACHEDIR = DISK_LOCATION_BACKUP
5152
FEED_NVD = "https://nvd.nist.gov/vuln/data-feeds"
52-
FEED_MIRROR = "https://v4.mirror.cveb.in/nvd/json/cve/1.1"
53+
FEED_MIRROR = f"https://v4.mirror.cveb.in/nvd/json/cve/{NVD_VERSION}"
5354
LOGGER = LOGGER.getChild("CVEDB")
5455
NVDCVE_FILENAME_TEMPLATE = NVD_FILENAME_TEMPLATE
56+
NVDCVE_VERSION = NVD_VERSION
57+
NVDCVE_TOP_LIST_TAG = "CVE_Items" if NVD_VERSION == "1.1" else "vulnerabilities"
5558
META_LINK_NVD = "https://nvd.nist.gov"
56-
META_LINK_MIRROR = "https://v4.mirror.cveb.in/nvd/json/cve/1.1"
59+
META_LINK_MIRROR = f"https://v4.mirror.cveb.in/nvd/json/cve/{NVD_VERSION}"
5760
META_REGEX_NVD = re.compile(r"feeds\/json\/.*-[0-9]*\.[0-9]*-[0-9]*\.meta")
5861
META_REGEX_MIRROR = re.compile(r"nvdcve-[0-9]*\.[0-9]*-[0-9]*\.meta")
5962
RANGE_UNSET = ""
@@ -107,9 +110,14 @@ async def get_cve_data(self):
107110
severity_data = []
108111
affected_data = []
109112
years = self.nvd_years()
113+
formatter = (
114+
self.format_data
115+
if self.NVDCVE_VERSION == "1.1"
116+
else self.format_data_api2
117+
)
110118
for year in years:
111-
severity, affected = self.format_data(
112-
self.load_nvd_year(year)["CVE_Items"]
119+
severity, affected = formatter(
120+
self.load_nvd_year(year)[self.NVDCVE_TOP_LIST_TAG]
113121
)
114122
severity_data.extend(severity)
115123
affected_data.extend(affected)
@@ -239,6 +247,10 @@ def format_data_api2(self, all_cve_entries):
239247

240248
cve_item = cve_element["cve"]
241249

250+
if cve_item["vulnStatus"] == "Rejected":
251+
# Skip this CVE if it's marked as 'REJECT'
252+
continue
253+
242254
cve = {
243255
"ID": cve_item["id"],
244256
"description": cve_item["descriptions"][0]["value"],
@@ -252,9 +264,6 @@ def format_data_api2(self, all_cve_entries):
252264
else cve_item["published"]
253265
),
254266
}
255-
if cve["description"].startswith("** REJECT **"):
256-
# Skip this CVE if it's marked as 'REJECT'
257-
continue
258267

259268
# Multiple ways of including CVSS metrics.
260269
# Newer data uses "impact" -- we may wish to delete the old below
@@ -612,17 +621,18 @@ def load_nvd_year(self, year: int) -> dict[str, str | object]:
612621
with gzip.open(filename, "rb") as fileobj:
613622
cves_for_year = json.load(fileobj)
614623
self.LOGGER.debug(
615-
f'Year {year} has {len(cves_for_year["CVE_Items"])} CVEs in dataset'
624+
f"Year {year} has {len(cves_for_year[self.NVDCVE_TOP_LIST_TAG])} CVEs in dataset"
616625
)
617626
return cves_for_year
618627

619628
def nvd_years(self) -> list[int]:
620629
"""
621630
Return the years we have NVD data for.
622631
"""
632+
any_year_file = self.NVDCVE_FILENAME_TEMPLATE.format("*")
623633
return sorted(
624634
int(filename.split(".")[-3].split("-")[-1])
625-
for filename in glob.glob(str(Path(self.cachedir) / "nvdcve-1.1-*.json.gz"))
635+
for filename in glob.glob(str(Path(self.cachedir) / any_year_file))
626636
)
627637
# FIXME: temporary workaround so we don't try to load bad year data
628638
# return list(range(2020, 2025))

cve_bin_tool/database_defaults.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@
99
DISK_LOCATION_BACKUP = CACHE_DIR / "cve-bin-tool-backup"
1010
OLD_CACHE_DIR = Path.home() / ".cache" / "cvedb"
1111
DBNAME = "cve.db"
12-
NVD_FILENAME_TEMPLATE = "nvdcve-1.1-{}.json.gz"
12+
NVD_VERSION = "2.0"
13+
NVD_FILENAME_TEMPLATE = "nvdcve-" + NVD_VERSION + "-{}.json.gz"

test/test_database_defaults.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ def test_cache_paths_with_xdg(patch_env, tmp_path):
3737
assert db_defaults.DISK_LOCATION_BACKUP == tmp_path / "cve-bin-tool-backup"
3838
assert db_defaults.OLD_CACHE_DIR == Path.home() / ".cache" / "cvedb"
3939
assert db_defaults.DBNAME == "cve.db"
40-
assert db_defaults.NVD_FILENAME_TEMPLATE.format("2024") == "nvdcve-1.1-2024.json.gz"
40+
assert db_defaults.NVD_VERSION == "2.0"
41+
assert db_defaults.NVD_FILENAME_TEMPLATE.format("2024") == "nvdcve-2.0-2024.json.gz"
4142

4243

4344
def test_cache_paths_fallback_to_home(patch_env):
@@ -53,4 +54,5 @@ def test_cache_paths_fallback_to_home(patch_env):
5354
assert db_defaults.DISK_LOCATION_BACKUP == expected_cache / "cve-bin-tool-backup"
5455
assert db_defaults.OLD_CACHE_DIR == Path.home() / ".cache" / "cvedb"
5556
assert db_defaults.DBNAME == "cve.db"
56-
assert db_defaults.NVD_FILENAME_TEMPLATE.format("2023") == "nvdcve-1.1-2023.json.gz"
57+
assert db_defaults.NVD_VERSION == "2.0"
58+
assert db_defaults.NVD_FILENAME_TEMPLATE.format("2023") == "nvdcve-2.0-2023.json.gz"

test/test_json.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
from cve_bin_tool.log import LOGGER
2121
from cve_bin_tool.util import make_http_requests
2222

23-
NVD_SCHEMA = "https://scap.nist.gov/schema/nvd/feed/1.1/nvd_cve_feed_json_1.1.schema"
24-
# NVD feeds from "https://nvd.nist.gov/vuln/data-feeds#JSON_FEED" but stored locally
23+
NVD_SCHEMA = "https://csrc.nist.gov/schema/nvd/api/2.0/cve_api_json_2.0.schema"
24+
# NVD feeds from "https://nvd.nist.gov/vuln/data-feeds#divJson20Feeds" but stored locally
2525

2626

2727
@pytest.mark.skipif(
@@ -43,11 +43,11 @@ def test_json_validation(self, year):
4343
"""Validate latest nvd json file against their published schema"""
4444
# Open the latest nvd file on disk
4545
with gzip.open(
46-
Path(DISK_LOCATION_DEFAULT) / f"nvdcve-1.1-{year}.json.gz",
46+
Path(DISK_LOCATION_DEFAULT) / f"nvdcve-2.0-{year}.json.gz",
4747
"rb",
4848
) as json_file:
4949
nvd_json = json.loads(json_file.read())
50-
LOGGER.info(f"Loaded json for year {year}: nvdcve-1.1-{year}.json.gz")
50+
LOGGER.info(f"Loaded json for year {year}: nvdcve-2.0-{year}.json.gz")
5151

5252
# Validate -- will raise a ValidationError if not valid
5353
try:

test/test_source_nvd.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ async def test_00_getmeta(self):
3232
) as session:
3333
_jsonurl, meta = await self.nvd.getmeta(
3434
session,
35-
"https://nvd.nist.gov/feeds/json/cve/1.1/nvdcve-1.1-modified.meta",
35+
"https://nvd.nist.gov/feeds/json/cve/2.0/nvdcve-2.0-modified.meta",
3636
)
3737
assert "sha256" in meta
3838

@@ -43,7 +43,7 @@ async def test_00_getmeta(self):
4343
async def test_01_cache_update(self):
4444
async with aiohttp.ClientSession(trust_env=True) as session:
4545
jsonurl, meta = await self.nvd.getmeta(
46-
session, "https://nvd.nist.gov/feeds/json/cve/1.1/nvdcve-1.1-2015.meta"
46+
session, "https://nvd.nist.gov/feeds/json/cve/2.0/nvdcve-2.0-2015.meta"
4747
)
4848
assert "sha256" in meta
4949
await self.nvd.cache_update(session, jsonurl, meta["sha256"])

0 commit comments

Comments
 (0)