diff --git a/pyproject.toml b/pyproject.toml index de1a8d1..e9a6f55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "appthreat-vulnerability-db" -version = "5.7.8" +version = "5.8.0" description = "AppThreat's vulnerability database and package search library with a built-in file based storage. OSV, CVE, GitHub, npm are the primary sources of vulnerabilities." authors = [ {name = "Team AppThreat", email = "cloud@appthreat.com"}, diff --git a/test/data/osv-maven-cvss4.json b/test/data/osv-maven-cvss4.json new file mode 100644 index 0000000..4d3309b --- /dev/null +++ b/test/data/osv-maven-cvss4.json @@ -0,0 +1,156 @@ +{ + "schema_version": "1.4.0", + "id": "GHSA-gr3c-q7xf-47vh", + "modified": "2024-11-08T18:49:15Z", + "published": "2024-11-08T18:49:15Z", + "aliases": [ + "CVE-2024-52007" + ], + "summary": "XXE vulnerability in XSLT parsing in `org.hl7.fhir.core`", + "details": "### Summary\nXSLT parsing performed by various components are vulnerable to XML external entity injections. A processed XML file with a malicious DTD tag ( ]> could produce XML containing data from the host system. This impacts use cases where org.hl7.fhir.core is being used to within a host where external clients can submit XML.\n\n### Details\nThis is related to https://github.com/hapifhir/org.hl7.fhir.core/security/advisories/GHSA-6cr6-ph3p-f5rf, in which its fix ( https://github.com/hapifhir/org.hl7.fhir.core/issues/1571, https://github.com/hapifhir/org.hl7.fhir.core/pull/1717) was incomplete. \n\n### References\nhttps://cwe.mitre.org/data/definitions/611.html\nhttps://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#jaxp-documentbuilderfactory-saxparserfactory-and-dom4j", + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N" + }, + { + "type": "CVSS_V4", + "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:H/SI:N/SA:N" + } + ], + "affected": [ + { + "package": { + "ecosystem": "Maven", + "name": "ca.uhn.hapi.fhir:org.hl7.fhir.dstu3" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "6.4.0" + } + ] + } + ] + }, + { + "package": { + "ecosystem": "Maven", + "name": "ca.uhn.hapi.fhir:org.hl7.fhir.r4" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "6.4.0" + } + ] + } + ] + }, + { + "package": { + "ecosystem": "Maven", + "name": "ca.uhn.hapi.fhir:org.hl7.fhir.r4b" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "6.4.0" + } + ] + } + ] + }, + { + "package": { + "ecosystem": "Maven", + "name": "ca.uhn.hapi.fhir:org.hl7.fhir.r5" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "6.4.0" + } + ] + } + ] + }, + { + "package": { + "ecosystem": "Maven", + "name": "ca.uhn.hapi.fhir:org.hl7.fhir.utilities" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "6.4.0" + } + ] + } + ] + }, + { + "package": { + "ecosystem": "Maven", + "name": "ca.uhn.hapi.fhir:org.hl7.fhir.dstu2016may" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "6.4.0" + } + ] + } + ] + } + ], + "references": [ + { + "type": "WEB", + "url": "https://github.com/hapifhir/org.hl7.fhir.core/security/advisories/GHSA-gr3c-q7xf-47vh" + }, + { + "type": "PACKAGE", + "url": "https://github.com/hapifhir/org.hl7.fhir.core" + } + ], + "database_specific": { + "cwe_ids": [ + "CWE-611" + ], + "severity": "HIGH", + "github_reviewed": true, + "github_reviewed_at": "2024-11-08T18:49:15Z", + "nvd_published_at": null + } +} \ No newline at end of file diff --git a/test/test_source.py b/test/test_source.py index df1ee3c..132b38e 100644 --- a/test/test_source.py +++ b/test/test_source.py @@ -117,6 +117,15 @@ def test_osv_pypi3_json(): return json.loads(fp.read()) +@pytest.fixture +def test_osv_maven_cvss4_json(): + test_cve_data = os.path.join( + os.path.dirname(os.path.realpath(__file__)), "data", "osv-maven-cvss4.json" + ) + with open(test_cve_data, mode="r", encoding="utf-8") as fp: + return json.loads(fp.read()) + + @pytest.fixture def test_osv_mal_json(): test_cve_data = os.path.join( @@ -497,6 +506,13 @@ def test_osv_convert( assert not cve_data +def test_osv_convert4(test_osv_maven_cvss4_json): + osvlatest = OSVSource() + # maven cvss4 version + cve_data = osvlatest.convert(test_osv_maven_cvss4_json) + assert len(cve_data) == 6 + + def test_osv_mal_convert(test_osv_mal_json, test_osv_mal2_json): osvlatest = OSVSource() cve_data = osvlatest.convert(test_osv_mal2_json) diff --git a/vdb/cli.py b/vdb/cli.py index 75bb557..f4bc521 100644 --- a/vdb/cli.py +++ b/vdb/cli.py @@ -152,7 +152,7 @@ def print_results(results): package_issue.fixed_location, vuln_occ_dict.get("problem_type"), vuln_occ_dict.get("severity"), - vuln_occ_dict.get("cvss_score"), + vuln_occ_dict.get("cvss4_vector_string") + "\n" + vuln_occ_dict.get("cvss_score") if vuln_occ_dict.get("cvss4_vector_string") else vuln_occ_dict.get("cvss_score"), vuln_occ_dict.get("short_description"), ] ) diff --git a/vdb/lib/__init__.py b/vdb/lib/__init__.py index 43a9307..259b648 100644 --- a/vdb/lib/__init__.py +++ b/vdb/lib/__init__.py @@ -162,6 +162,7 @@ def convert_time(time_str): class Vulnerability(object): """Vulnerability""" + cvss4_vector_string: str = None def __init__( self, @@ -175,6 +176,7 @@ def __init__( cvss_v3, source_update_time, source_orig_time, + cvss4_vector_string=None ): self.id = vid self.problem_type = problem_type @@ -186,6 +188,7 @@ def __init__( self.cvss_v3 = cvss_v3 self.source_update_time: datetime = convert_time(source_update_time) self.source_orig_time: datetime = convert_time(source_orig_time) + self.cvss4_vector_string = cvss4_vector_string def __repr__(self): return json.dumps( @@ -204,6 +207,7 @@ def __repr__(self): "source_orig_time": convert_time(self.source_orig_time).strftime( "%Y-%m-%dT%H:%M:%S" ), + "cvss4_vector_string": self.cvss4_vector_string } ) @@ -550,6 +554,7 @@ class VulnerabilityOccurrence: source_update_time: datetime source_orig_time: datetime matched_by: str + cvss4_vector_string: str = None def __init__( self, @@ -567,6 +572,7 @@ def __init__( source_update_time, source_orig_time, matched_by, + cvss4_vector_string=None ): self.id = id self.problem_type = problem_type @@ -582,6 +588,7 @@ def __init__( self.source_update_time = source_update_time self.source_orig_time = source_orig_time self.matched_by = matched_by + self.cvss4_vector_string = cvss4_vector_string def to_dict(self): """Convert the object to dict""" @@ -622,6 +629,7 @@ def to_dict(self): "source_update_time": source_update_time, "source_orig_time": source_orig_time, "matched_by": self.matched_by, + "cvss4_vector_string": self.cvss4_vector_string } diff --git a/vdb/lib/osv.py b/vdb/lib/osv.py index 217a665..9cf0602 100644 --- a/vdb/lib/osv.py +++ b/vdb/lib/osv.py @@ -15,6 +15,7 @@ compress_str, convert_score_severity, get_cvss3_from_vector, + get_cvss4_from_vector, get_default_cve_data, parse_purl, ) @@ -141,6 +142,7 @@ def to_vuln(self, cve_data): break assigner = "OSV" vectorString = "" + cvss4_vector_string = "" if cve_id.startswith("GHSA"): assigner = "github_m" elif cve_id.startswith("CVE"): @@ -154,6 +156,8 @@ def to_vuln(self, cve_data): for sv in severity_list: if sv["type"] == "CVSS_V3": vectorString = sv["score"] + elif sv["type"] == "CVSS_V4": + cvss4_vector_string = sv["score"] # Issue 58 cve_database_specific = cve_data.get("database_specific") cve_ecosystem_specific = cve_data.get("ecosystem_specific") @@ -187,7 +191,13 @@ def to_vuln(self, cve_data): if cvss.get("score"): score = cvss.get("score") severity = convert_score_severity(score) - if vectorString: + if cvss4_vector_string: + cvss4_obj = get_cvss4_from_vector(cvss4_vector_string) + score = cvss4_obj.get("baseScore") + severity = cvss4_obj.get("baseSeverity") + exploitabilityScore = score + attackComplexity = cvss4_obj.get("attackComplexity") + elif vectorString: cvss3_obj = get_cvss3_from_vector(vectorString) score = cvss3_obj.get("baseScore") severity = cvss3_obj.get("baseSeverity") @@ -200,7 +210,6 @@ def to_vuln(self, cve_data): # Override the score for malware if cve_id.startswith("MAL"): score = 10.0 - user_interaction = "NONE" # Set the default vector string only if unavailable if not vectorString and dvectorString: vectorString = dvectorString @@ -304,6 +313,8 @@ def to_vuln(self, cve_data): try: vuln = NvdSource.convert_vuln(json_lib.loads(tdata)) vuln.description = compress_str(description) + if cvss4_vector_string: + vuln.cvss4_vector_string = cvss4_vector_string ret_data.append(vuln) except Exception: pass @@ -382,6 +393,8 @@ def to_vuln(self, cve_data): try: vuln = NvdSource.convert_vuln(json_lib.loads(tdata)) vuln.description = compress_str(description) + if cvss4_vector_string: + vuln.cvss4_vector_string = cvss4_vector_string ret_data.append(vuln) except Exception: pass diff --git a/vdb/lib/utils.py b/vdb/lib/utils.py index 0eb6d32..e7057f0 100644 --- a/vdb/lib/utils.py +++ b/vdb/lib/utils.py @@ -1,12 +1,11 @@ import codecs import importlib -import os import re import string from datetime import date, datetime from enum import Enum -from cvss import CVSS3 +from cvss import CVSS3, CVSS4 from packageurl import PackageURL from semver import VersionInfo @@ -872,6 +871,16 @@ def get_cvss3_from_vector(vector): return c.as_json() +def get_cvss4_from_vector(vector): + """ + Return CVE metadata for the given vector + :param vector: Vector + :return: CVSS4 parsed and converted to json + """ + c = CVSS4(vector) + return c.as_json() + + def convert_to_occurrence(datas): """Method to parse raw search result and convert to Vulnerability occurence @@ -934,6 +943,7 @@ def convert_to_occurrence(datas): source_update_time=vobj.get("source_update_time"), source_orig_time=vobj.get("source_orig_time"), matched_by=match, + cvss4_vector_string=vobj.get("cvss4_vector_string") ) id_list.append(unique_key) data_list.append(occ)