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: generation of platform-specific cpe config #30

Merged
merged 1 commit into from
Jul 30, 2024
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
137 changes: 105 additions & 32 deletions scripts/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
from cpe import CPE


namespace = uuid.uuid5(uuid.NAMESPACE_URL, "https://github.com/anchore/nvd-data-overrides")
namespace = uuid.uuid5(
uuid.NAMESPACE_URL, "https://github.com/anchore/nvd-data-overrides"
)


class MatchCriteriaIdGenerator:
Expand All @@ -19,7 +21,9 @@ def __init__(self):
def _load_known_nvd_id_lookups(self):
self._nvd_known_id_lookup: dict[str, str] = {}

for nvd_file in glob("national-vulnerability-database/data/**/CVE-*.json", recursive=True):
for nvd_file in glob(
"national-vulnerability-database/data/**/CVE-*.json", recursive=True
):
with open(nvd_file) as f:
data = json.load(f)

Expand All @@ -31,16 +35,18 @@ def _load_known_nvd_id_lookups(self):
match_id = m["matchCriteriaId"]
del m["matchCriteriaId"]
del m["vulnerable"]
self._nvd_known_id_lookup[json.dumps(m, sort_keys=True)] = match_id
self._nvd_known_id_lookup[json.dumps(m, sort_keys=True)] = (
match_id
)

def generate(self, match_criteria: dict) -> str:
"""
Creates a stable UUID for a given set of match criteria. It will use any known values from the NVD
before attempting to create a new one. If in future we discover exactly how the NVD
generation works then in theory we should be able to exactly match any of their existing ids,
but I have not found any documentation around that so far. This will ensure they at least
match across the override dataset. We are not using the criteriaMatchId for anything, but others
might so we'll at least make them non-random
before attempting to create a new one. If in future we discover exactly how the NVD
generation works then in theory we should be able to exactly match any of their existing ids,
but I have not found any documentation around that so far. This will ensure they at least
match across the override dataset. We are not using the criteriaMatchId for anything, but others
might so we'll at least make them non-random
"""
data = deepcopy(match_criteria)
if self._nvd_known_id_lookup is None:
Expand Down Expand Up @@ -89,21 +95,15 @@ def generate():
if not versions:
continue

configuration = {
"nodes": []
}

configuration = {"nodes": []}

for cpe in cpes:
node = {
"cpeMatch": [],
"negate": False,
"operator": "OR"
}
node = {"cpeMatch": [], "negate": False, "operator": "OR"}

for version in versions:
match = {
cpe_match = {
"criteria": cpe,
"vulnerable": version["status"] == "affected"
"vulnerable": version["status"] == "affected",
}

less_than = version.get("lessThan")
Expand All @@ -115,27 +115,100 @@ def generate():
c = CPE(cpe)

if c.is_application():
c.get("app")[0]["version"] = CPEComponent2_3_FS(v, "version")
match["criteria"] = c.as_fs()
c.get("app")[0]["version"] = CPEComponent2_3_FS(
v, "version"
)
cpe_match["criteria"] = c.as_fs()
elif c.is_operating_system():
c.get("os")[0]["version"] = CPEComponent2_3_FS(v, "version")
match["criteria"] = c.as_fs()
c.get("os")[0]["version"] = CPEComponent2_3_FS(
v, "version"
)
cpe_match["criteria"] = c.as_fs()
elif c.is_hardware():
c.get("hw")[0]["version"] = CPEComponent2_3_FS(v, "version")
match["criteria"] = c.as_fs()
c.get("hw")[0]["version"] = CPEComponent2_3_FS(
v, "version"
)
cpe_match["criteria"] = c.as_fs()
elif v != "0":
match["versionStartIncluding"] = v
cpe_match["versionStartIncluding"] = v

if less_than and less_than.strip() != "*":
match["versionEndExcluding"] = less_than.strip()
cpe_match["versionEndExcluding"] = less_than.strip()
elif less_than_or_equal and less_than_or_equal.strip() != "*":
match["versionEndIncluding"] = less_than_or_equal.strip()
cpe_match["versionEndIncluding"] = (
less_than_or_equal.strip()
)

cpe_match["matchCriteriaId"] = generator.generate(cpe_match)
node["cpeMatch"].append(cpe_match)

match["matchCriteriaId"] = generator.generate(match)
node["cpeMatch"].append(match)

configuration["nodes"].append(node)

# Handle creating platform cpe config for specific cases. This won't handle multi-node configs,
# but that isn't necessary for the current dataset and we can always expand it later if needed.
platforms = affected.get("platforms")
match platforms:
case ["Android"]:
configuration["operator"] = "AND"
configuration["nodes"].append(
{
"cpeMatch": [
{
"vulnerable": False,
"criteria": "cpe:2.3:o:google:android:-:*:*:*:*:*:*:*",
"matchCriteriaId": "F8B9FEC8-73B6-43B8-B24E-1F7C20D91D26",
}
],
"negate": False,
"operator": "OR",
}
)
case ["iOS"]:
configuration["operator"] = "AND"
configuration["nodes"].append(
{
"cpeMatch": [
{
"vulnerable": False,
"criteria": "cpe:2.3:o:apple:iphone_os:-:*:*:*:*:*:*:*",
"matchCriteriaId": "B5415705-33E5-46D5-8E4D-9EBADC8C5705",
}
],
"negate": False,
"operator": "OR",
}
)
case ["MacOS"]:
configuration["operator"] = "AND"
configuration["nodes"].append(
{
"cpeMatch": [
{
"vulnerable": False,
"criteria": "cpe:2.3:o:apple:macos:-:*:*:*:*:*:*:*",
"matchCriteriaId": "387021A0-AF36-463C-A605-32EA7DAC172E",
}
],
"negate": False,
"operator": "OR",
}
)
case ["Windows"]:
configuration["operator"] = "AND"
configuration["nodes"].append(
{
"cpeMatch": [
{
"vulnerable": False,
"criteria": "cpe:2.3:o:microsoft:windows:-:*:*:*:*:*:*:*",
"matchCriteriaId": "A2572D17-1DE6-457B-99CC-64AFD54487EA",
}
],
"negate": False,
"operator": "OR",
}
)

override["cve"]["configurations"].append(configuration)

override_path = f"data/{year}"
Expand All @@ -148,4 +221,4 @@ def generate():


if __name__ == "__main__":
generate()
generate()
4 changes: 2 additions & 2 deletions scripts/generate.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/bash -l
set -euxo pipefail

git clone --depth 1 https://github.com/anchore/cve-data-enrichment
git clone --depth 1 https://github.com/westonsteimel/national-vulnerability-database
git -C "cve-data-enrichment" pull || git clone --depth 1 https://github.com/anchore/cve-data-enrichment cve-data-enrichment
git -C "national-vulnerability-database" pull || git clone --depth 1 https://github.com/westonsteimel/national-vulnerability-database national-vulnerability-database

python scripts/generate.py