99from packageurl import PackageURL
1010
1111from dojo .models import Finding
12+ from dojo .utils import parse_cvss_data
1213
1314logger = logging .getLogger (__name__ )
1415
@@ -23,6 +24,54 @@ class DependencyCheckParser:
2324 "critical" : "Critical" ,
2425 }
2526
27+ CVSS_V3_MAPPINGS = {
28+ "attackVector" : {
29+ "NETWORK" : "N" ,
30+ "ADJACENT" : "A" ,
31+ "LOCAL" : "L" ,
32+ "PHYSICAL" : "P" ,
33+ "N" : "N" ,
34+ "A" : "A" ,
35+ "L" : "L" ,
36+ "P" : "P" ,
37+ },
38+ "attackComplexity" : {"LOW" : "L" , "HIGH" : "H" , "L" : "L" , "H" : "H" },
39+ "privilegesRequired" : {
40+ "NONE" : "N" ,
41+ "LOW" : "L" ,
42+ "HIGH" : "H" ,
43+ "N" : "N" ,
44+ "L" : "L" ,
45+ "H" : "H" ,
46+ },
47+ "userInteraction" : {"NONE" : "N" , "REQUIRED" : "R" , "N" : "N" , "R" : "R" },
48+ "scope" : {"UNCHANGED" : "U" , "CHANGED" : "C" , "U" : "U" , "C" : "C" },
49+ "confidentialityImpact" : {
50+ "NONE" : "N" ,
51+ "LOW" : "L" ,
52+ "HIGH" : "H" ,
53+ "N" : "N" ,
54+ "L" : "L" ,
55+ "H" : "H" ,
56+ },
57+ "integrityImpact" : {
58+ "NONE" : "N" ,
59+ "LOW" : "L" ,
60+ "HIGH" : "H" ,
61+ "N" : "N" ,
62+ "L" : "L" ,
63+ "H" : "H" ,
64+ },
65+ "availabilityImpact" : {
66+ "NONE" : "N" ,
67+ "LOW" : "L" ,
68+ "HIGH" : "H" ,
69+ "N" : "N" ,
70+ "L" : "L" ,
71+ "H" : "H" ,
72+ },
73+ }
74+
2675 def add_finding (self , finding , dupes ):
2776 key_str = "|" .join (
2877 [
@@ -164,6 +213,55 @@ def get_component_name_and_version_from_dependency(
164213
165214 return None , None
166215
216+ def get_severity_and_cvss_meta (self , vulnerability , namespace ) -> dict :
217+ # Get the base severity from the report
218+ severity = vulnerability .findtext (f"{ namespace } severity" )
219+ cvssv3 = None
220+ cvssv3_score = None
221+ # Attempt to add the CVSSv3 score, and update the severity accordingly
222+ if (cvssv3_node := vulnerability .find (namespace + "cvssV3" )) is not None :
223+ try :
224+ vector_parts = [
225+ f"AV:{ self .CVSS_V3_MAPPINGS ['attackVector' ][cvssv3_node .findtext (f'{ namespace } attackVector' )]} " ,
226+ f"AC:{ self .CVSS_V3_MAPPINGS ['attackComplexity' ][cvssv3_node .findtext (f'{ namespace } attackComplexity' )]} " ,
227+ f"PR:{ self .CVSS_V3_MAPPINGS ['privilegesRequired' ][cvssv3_node .findtext (f'{ namespace } privilegesRequired' )]} " ,
228+ f"UI:{ self .CVSS_V3_MAPPINGS ['userInteraction' ][cvssv3_node .findtext (f'{ namespace } userInteraction' )]} " ,
229+ f"S:{ self .CVSS_V3_MAPPINGS ['scope' ][cvssv3_node .findtext (f'{ namespace } scope' )]} " ,
230+ f"C:{ self .CVSS_V3_MAPPINGS ['confidentialityImpact' ][cvssv3_node .findtext (f'{ namespace } confidentialityImpact' )]} " ,
231+ f"I:{ self .CVSS_V3_MAPPINGS ['integrityImpact' ][cvssv3_node .findtext (f'{ namespace } integrityImpact' )]} " ,
232+ f"A:{ self .CVSS_V3_MAPPINGS ['availabilityImpact' ][cvssv3_node .findtext (f'{ namespace } availabilityImpact' )]} " ,
233+ ]
234+ version = cvssv3_node .findtext ("version" ) or "3.1"
235+ vector = f"CVSS:{ version } /" + "/" .join (vector_parts )
236+ if cvss_data := parse_cvss_data (vector ):
237+ cvssv3 = cvss_data .get ("vector" )
238+ cvssv3_score = cvss_data .get ("score" )
239+ severity = cvss_data .get ("severity" )
240+ except Exception as e :
241+ # Only log the error - there is not much we can do to recover from this
242+ logger .debug (e )
243+ elif (cvssv2_node := vulnerability .find (namespace + "cvssV2" )) is not None :
244+ severity = cvssv2_node .findtext (f"{ namespace } severity" ).lower ().capitalize ()
245+
246+ # handle if the severity have something not in the mapping
247+ # default to 'Medium' and produce warnings in logs
248+ if severity :
249+ if severity .strip ().lower () not in self .SEVERITY_MAPPING :
250+ logger .warning (
251+ f"Warning: Unknow severity value detected '{ severity } '. Bypass to 'Medium' value" ,
252+ )
253+ severity = "Medium"
254+ else :
255+ severity = self .SEVERITY_MAPPING [severity .strip ().lower ()]
256+ else :
257+ severity = "Medium"
258+
259+ return {
260+ "severity" : severity ,
261+ "cvssv3" : cvssv3 ,
262+ "cvssv3_score" : cvssv3_score ,
263+ }
264+
167265 def get_finding_from_vulnerability (
168266 self , dependency , related_dependency , vulnerability , test , namespace ,
169267 ):
@@ -238,36 +336,6 @@ def get_finding_from_vulnerability(
238336 # some changes in v6.0.0 around CVSS version information
239337 # https://github.com/jeremylong/DependencyCheck/pull/2781
240338
241- cvssv2_node = vulnerability .find (namespace + "cvssV2" )
242- cvssv3_node = vulnerability .find (namespace + "cvssV3" )
243- severity = vulnerability .findtext (f"{ namespace } severity" )
244- if not severity :
245- if cvssv3_node is not None :
246- severity = (
247- cvssv3_node .findtext (f"{ namespace } baseSeverity" )
248- .lower ()
249- .capitalize ()
250- )
251- elif cvssv2_node is not None :
252- severity = (
253- cvssv2_node .findtext (f"{ namespace } severity" )
254- .lower ()
255- .capitalize ()
256- )
257-
258- # handle if the severity have something not in the mapping
259- # default to 'Medium' and produce warnings in logs
260- if severity :
261- if severity .strip ().lower () not in self .SEVERITY_MAPPING :
262- logger .warning (
263- f"Warning: Unknow severity value detected '{ severity } '. Bypass to 'Medium' value" ,
264- )
265- severity = "Medium"
266- else :
267- severity = self .SEVERITY_MAPPING [severity .strip ().lower ()]
268- else :
269- severity = "Medium"
270-
271339 reference_detail = None
272340 references_node = vulnerability .find (namespace + "references" )
273341
@@ -315,7 +383,6 @@ def get_finding_from_vulnerability(
315383 test = test ,
316384 cwe = cwe ,
317385 description = description ,
318- severity = severity ,
319386 mitigation = mitigation ,
320387 mitigated = mitigated ,
321388 is_mitigated = is_Mitigated ,
@@ -326,6 +393,7 @@ def get_finding_from_vulnerability(
326393 references = reference_detail ,
327394 component_name = component_name ,
328395 component_version = component_version ,
396+ ** self .get_severity_and_cvss_meta (vulnerability , namespace ),
329397 )
330398
331399 if vulnerability_id :
0 commit comments