1111import json
1212import logging
1313from datetime import date
14+ from datetime import timezone
1415from traceback import format_exc as traceback_format_exc
1516from typing import Iterable
1617
@@ -94,7 +95,7 @@ def advisories_count(self):
9495 return advisory_count
9596
9697 def collect_advisories (self ) -> Iterable [AdvisoryData ]:
97- for _year , cve_data in fetch_cve_data_1_1 (logger = self .log ):
98+ for _year , cve_data in fetch_cve_data_2_0 (logger = self .log ):
9899 yield from to_advisories (cve_data = cve_data )
99100
100101
@@ -107,15 +108,15 @@ def fetch(url, logger=None):
107108 return json .loads (data )
108109
109110
110- def fetch_cve_data_1_1 (starting_year = 2002 , logger = None ):
111+ def fetch_cve_data_2_0 (starting_year = 2002 , logger = None ):
111112 """
112113 Yield tuples of (year, lists of CVE mappings) from the NVD, one for each
113114 year since ``starting_year`` defaulting to 2002.
114115 """
115116 current_year = date .today ().year
116117 # NVD json feeds start from 2002.
117118 for year in range (starting_year , current_year + 1 ):
118- download_url = f"https://nvd.nist.gov/feeds/json/cve/1.1 /nvdcve-1.1 -{ year } .json.gz"
119+ download_url = f"https://nvd.nist.gov/feeds/json/cve/2.0 /nvdcve-2.0 -{ year } .json.gz"
119120 yield year , fetch (url = download_url , logger = logger )
120121
121122
@@ -134,20 +135,22 @@ class CveItem:
134135 cve_item = attr .attrib (default = attr .Factory (dict ), type = dict )
135136
136137 @classmethod
137- def to_advisories (cls , cve_data , skip_hardware = True ):
138+ def to_advisories (cls , vulnerabilities , skip_hardware = True ):
138139 """
139140 Yield AdvisoryData objects from ``cve_data`` data for CVE JSON 1.1feed.
140141 Skip hardware
141142 """
142- for cve_item in CveItem .from_cve_data (cve_data = cve_data , skip_hardware = skip_hardware ):
143+ for cve_item in CveItem .from_cve_data (
144+ cve_data = vulnerabilities , skip_hardware = skip_hardware
145+ ):
143146 yield cve_item .to_advisory ()
144147
145148 @classmethod
146149 def from_cve_data (cls , cve_data , skip_hardware = True ):
147150 """
148151 Yield CVE items mapping from a cve_data list of CVE mappings from the NVD.
149152 """
150- for cve_item in cve_data .get ("CVE_Items " ) or []:
153+ for cve_item in cve_data .get ("vulnerabilities " ) or []:
151154 if not cve_item :
152155 continue
153156 if not isinstance (cve_item , dict ):
@@ -159,20 +162,20 @@ def from_cve_data(cls, cve_data, skip_hardware=True):
159162
160163 @property
161164 def cve_id (self ):
162- return self .cve_item ["cve" ]["CVE_data_meta" ][ "ID " ]
165+ return self .cve_item ["cve" ]["id " ]
163166
164167 @property
165168 def summary (self ):
166169 """
167170 Return a descriptive summary.
168171 """
169- # In 99% of cases len(cve_item['cve']['description']['description_data'] ) == 1 , so
170- # this usually returns cve_item['cve']['description']['description_data'][ 0]['value']
172+ # In 99% of cases len(cve_item['cve']['description']) == 1 , so
173+ # this usually returns cve_item['cve']['description'][0]['value']
171174 # In the remaining 1% cases this returns the longest summary.
172- # FIXME: we should retun the full description WITH the summry as the first line instead
175+ # FIXME: we should return the full description WITH the summary as the first line instead
173176 summaries = []
174- for desc in get_item (self .cve_item , "cve" , "description" , "description_data " ) or []:
175- if desc .get ("value" ):
177+ for desc in get_item (self .cve_item , "cve" , "descriptions " ) or []:
178+ if desc .get ("value" ) and desc . get ( "lang" ) == "en" :
176179 summaries .append (desc ["value" ])
177180 return max (summaries , key = len ) if summaries else None
178181
@@ -183,11 +186,12 @@ def cpes(self):
183186 """
184187 # FIXME: we completely ignore the configurations here
185188 cpes = []
186- for node in get_item (self .cve_item , "configurations" , "nodes" ) or []:
187- for cpe_data in node .get ("cpe_match" ) or []:
188- cpe23_uri = cpe_data .get ("cpe23Uri" )
189- if cpe23_uri and cpe23_uri not in cpes :
190- cpes .append (cpe23_uri )
189+ for nodes in get_item (self .cve_item , "cve" , "configurations" ) or []:
190+ for node in nodes .get ("nodes" ) or []:
191+ for cpe_data in node .get ("cpeMatch" ) or []:
192+ cpe23_uri = cpe_data .get ("criteria" )
193+ if cpe23_uri and cpe23_uri not in cpes :
194+ cpes .append (cpe23_uri )
191195 return cpes
192196
193197 @property
@@ -196,43 +200,32 @@ def severities(self):
196200 Return a list of VulnerabilitySeverity for this CVE.
197201 """
198202 severities = []
199- impact = self .cve_item .get ("impact" ) or {}
200- base_metric_v4 = impact .get ("baseMetricV4" ) or {}
201- if base_metric_v4 :
202- cvss_v4 = base_metric_v4 .get ("cvssV4" ) or {}
203- vs = VulnerabilitySeverity (
204- system = severity_systems .CVSSV4 ,
205- value = str (cvss_v4 .get ("baseScore" ) or "" ),
206- scoring_elements = str (cvss_v4 .get ("vectorString" ) or "" ),
207- )
208- severities .append (vs )
209-
210- base_metric_v3 = impact .get ("baseMetricV3" ) or {}
211- if base_metric_v3 :
212- cvss_v3 = get_item (base_metric_v3 , "cvssV3" )
213- version = cvss_v3 .get ("version" )
214- system = None
215- if version == "3.1" :
216- system = severity_systems .CVSSV31
217- else :
218- system = severity_systems .CVSSV3
219- vs = VulnerabilitySeverity (
220- system = system ,
221- value = str (cvss_v3 .get ("baseScore" ) or "" ),
222- scoring_elements = str (cvss_v3 .get ("vectorString" ) or "" ),
223- )
224- severities .append (vs )
225-
226- base_metric_v2 = impact .get ("baseMetricV2" ) or {}
227- if base_metric_v2 :
228- cvss_v2 = base_metric_v2 .get ("cvssV2" ) or {}
229- vs = VulnerabilitySeverity (
230- system = severity_systems .CVSSV2 ,
231- value = str (cvss_v2 .get ("baseScore" ) or "" ),
232- scoring_elements = str (cvss_v2 .get ("vectorString" ) or "" ),
233- )
234- severities .append (vs )
203+ metrics = get_item (self .cve_item , "cve" , "metrics" ) or {}
204+ url = f"https://nvd.nist.gov/vuln/detail/{ self .cve_id } "
205+ metric_configs = [
206+ ("cvssMetricV40" , severity_systems .CVSSV4 ),
207+ ("cvssMetricV31" , severity_systems .CVSSV31 ),
208+ ("cvssMetricV30" , severity_systems .CVSSV3 ),
209+ ("cvssMetricV2" , severity_systems .CVSSV2 ),
210+ ]
235211
212+ for key , default_system in metric_configs :
213+ items = metrics .get (key ) or []
214+
215+ for item in items :
216+ cvss_data = item .get ("cvssData" ) or {}
217+ system = default_system
218+ if key == "cvssMetricV31" and cvss_data .get ("version" ) != "3.1" :
219+ system = severity_systems .CVSSV3
220+
221+ severities .append (
222+ VulnerabilitySeverity (
223+ system = system ,
224+ value = str (cvss_data .get ("baseScore" ) or "" ),
225+ scoring_elements = str (cvss_data .get ("vectorString" ) or "" ),
226+ url = url ,
227+ )
228+ )
236229 return severities
237230
238231 @property
@@ -243,7 +236,7 @@ def reference_urls(self):
243236 # FIXME: we should also collect additional data from the references such as tags and ids
244237
245238 urls = []
246- for reference in get_item (self .cve_item , "cve" , "references" , "reference_data" ) or []:
239+ for reference in get_item (self .cve_item , "cve" , "references" ) or []:
247240 ref_url = reference .get ("url" )
248241 if ref_url and ref_url .startswith (("http" , "ftp" )) and ref_url not in urls :
249242 urls .append (ref_url )
@@ -294,9 +287,7 @@ def weaknesses(self):
294287 Return a list of CWE IDs like: [119, 189]
295288 """
296289 weaknesses = []
297- for weaknesses_item in (
298- get_item (self .cve_item , "cve" , "problemtype" , "problemtype_data" ) or []
299- ):
290+ for weaknesses_item in get_item (self .cve_item , "cve" , "weaknesses" ) or []:
300291 weaknesses_description = weaknesses_item .get ("description" ) or []
301292 for weaknesses_value in weaknesses_description :
302293 cwe_id = (
@@ -315,7 +306,9 @@ def to_advisory(self):
315306 aliases = [self .cve_id ],
316307 summary = self .summary ,
317308 references = self .references ,
318- date_published = dateparser .parse (self .cve_item .get ("publishedDate" )),
309+ date_published = dateparser .parse (self .cve_item ["cve" ].get ("published" )).replace (
310+ tzinfo = timezone .utc
311+ ),
319312 weaknesses = self .weaknesses ,
320313 url = f"https://nvd.nist.gov/vuln/detail/{ self .cve_id } " ,
321314 )
0 commit comments