Skip to content

Commit ca80b76

Browse files
authored
Merge pull request #12829 from DefectDojo/release/2.48.3
Release: Merge release into master from: release/2.48.3
2 parents d005afe + ddd8df4 commit ca80b76

File tree

15 files changed

+582
-68
lines changed

15 files changed

+582
-68
lines changed

components/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "defectdojo",
3-
"version": "2.48.2",
3+
"version": "2.48.3",
44
"license" : "BSD-3-Clause",
55
"private": true,
66
"dependencies": {

dojo/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
# Django starts so that shared_task will use this app.
55
from .celery import app as celery_app # noqa: F401
66

7-
__version__ = "2.48.2"
7+
__version__ = "2.48.3"
88
__url__ = "https://github.com/DefectDojo/django-DefectDojo"
99
__docs__ = "https://documentation.defectdojo.com"

dojo/api_v2/serializers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2062,7 +2062,7 @@ def get_findings_count(self, obj) -> int:
20622062

20632063
# TODO: maybe extend_schema_field is needed here?
20642064
def get_findings_list(self, obj) -> list[int]:
2065-
return obj.open_findings_list
2065+
return obj.open_findings_list()
20662066

20672067

20682068
class CommonImportScanSerializer(serializers.Serializer):

dojo/models.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,12 +1322,10 @@ def get_breadcrumbs(self):
13221322
def get_product_type(self):
13231323
return self.prod_type if self.prod_type is not None else "unknown"
13241324

1325-
# only used in APIv2 serializers.py, query should be aligned with findings_count
1326-
@cached_property
1325+
# only used in APIv2 serializers.py, should be deprecated or at least prefetched
13271326
def open_findings_list(self):
1328-
findings = Finding.objects.filter(test__engagement__product=self,
1329-
active=True)
1330-
return [i.id for i in findings]
1327+
findings = Finding.objects.filter(test__engagement__product=self, active=True).values_list("id", flat=True)
1328+
return list(findings)
13311329

13321330
@property
13331331
def has_jira_configured(self):

dojo/templatetags/display_tags.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,10 +185,13 @@ def percentage(fraction, value):
185185

186186
@register.filter
187187
def format_epss(value):
188+
if value is None:
189+
return "N.A."
190+
188191
try:
189192
return f"{value:.2%}"
190193
except (ValueError, TypeError):
191-
return "N.A."
194+
return "error"
192195

193196

194197
def asvs_calc_level(benchmark_score):

dojo/tools/anchore_grype/parser.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import json
2+
import logging
23

34
from cvss import parser as cvss_parser
45
from cvss.cvss3 import CVSS3
56

67
from dojo.models import Finding
78

9+
logger = logging.getLogger(__name__)
10+
811

912
class AnchoreGrypeParser:
1013

@@ -27,7 +30,9 @@ def get_description_for_scan_types(self, scan_type):
2730
)
2831

2932
def get_findings(self, file, test):
33+
logger.debug(f"file: {file}")
3034
data = json.load(file)
35+
logger.debug(f"data: {data}")
3136
dupes = {}
3237
for item in data.get("matches", []):
3338
vulnerability = item["vulnerability"]
@@ -57,6 +62,7 @@ def get_findings(self, file, test):
5762
rel_description = related_vulnerability.get("description")
5863
rel_cvss = related_vulnerability.get("cvss")
5964
rel_epss = related_vulnerability.get("epss")
65+
rel_vuln_id = related_vulnerability.get("id")
6066
vulnerability_ids = self.get_vulnerability_ids(
6167
vuln_id, related_vulnerabilities,
6268
)
@@ -162,10 +168,15 @@ def get_findings(self, file, test):
162168
finding_cvss3 = self.get_cvss(vuln_cvss)
163169
if not finding_cvss3 and rel_cvss:
164170
finding_cvss3 = self.get_cvss(rel_cvss)
165-
171+
# https://github.com/DefectDojo/django-DefectDojo/issues/12819
172+
# the parser seems focues on only parsing the first related vulnerability
173+
# this fixes the mentioned github issue, but a more thorough rewrite might be needed
174+
# if the problem persists / we get more real world sample reports.
166175
finding_epss_score, finding_epss_percentile = self.get_epss_values(vuln_id, vuln_epss)
167176
if finding_epss_score is None and rel_epss:
168-
finding_epss_score, finding_epss_percentile = self.get_epss_values(vuln_id, rel_epss)
177+
finding_epss_score, finding_epss_percentile = self.get_epss_values(rel_vuln_id, rel_epss)
178+
if finding_epss_score is None and rel_vuln_id:
179+
finding_epss_score, finding_epss_percentile = self.get_epss_values(vuln_id, vuln_epss)
169180

170181
dupe_key = finding_title
171182
if dupe_key in dupes:
@@ -211,17 +222,23 @@ def get_cvss(self, cvss):
211222
return None
212223

213224
def get_epss_values(self, vuln_id, epss_list):
225+
if not isinstance(epss_list, list):
226+
logger.debug(f"epss_list is not a list: {epss_list}")
227+
return None, None
228+
214229
if isinstance(epss_list, list):
230+
logger.debug(f"epss_list: {epss_list}")
215231
for epss_data in epss_list:
216232
if epss_data.get("cve") != vuln_id:
217233
continue
218234
try:
219235
epss_score = float(epss_data.get("epss"))
220236
epss_percentile = float(epss_data.get("percentile"))
221237
except (TypeError, ValueError):
222-
pass
238+
logger.debug(f"epss_data is not a float: {epss_data}")
223239
else:
224240
return epss_score, epss_percentile
241+
logger.debug(f"epss not found for vuln_id: {vuln_id} in epss_list: {epss_list}")
225242
return None, None
226243

227244
def get_vulnerability_ids(self, vuln_id, related_vulnerabilities):

dojo/tools/dependency_check/parser.py

Lines changed: 99 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from packageurl import PackageURL
1010

1111
from dojo.models import Finding
12+
from dojo.utils import parse_cvss_data
1213

1314
logger = 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:

dojo/tools/trivy/parser.py

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@
1717
"UNKNOWN": "Info",
1818
}
1919

20+
CVSS_SEVERITY_SOURCES = [
21+
"nvd",
22+
"ghsa",
23+
"redhat",
24+
"bitnami",
25+
]
26+
2027
DESCRIPTION_TEMPLATE = """{title}
2128
**Target:** {target}
2229
**Type:** {type}
@@ -243,28 +250,29 @@ def get_result_items(self, test, results, service_name=None, artifact_name=""):
243250
try:
244251
vuln_id = vuln.get("VulnerabilityID", "0")
245252
package_name = vuln["PkgName"]
246-
severity_source = vuln.get("SeveritySource", None)
247-
cvss = vuln.get("CVSS", None)
253+
detected_severity_source = vuln.get("SeveritySource", None)
254+
cvss = vuln.get("CVSS", {})
255+
cvssclass = None
248256
cvssv3 = None
249257
cvssv3_score = None
250-
if severity_source is not None and cvss is not None:
258+
# Iterate over the possible severity sources tom find the first match
259+
for severity_source in [detected_severity_source, *CVSS_SEVERITY_SOURCES]:
251260
cvssclass = cvss.get(severity_source, None)
252261
if cvssclass is not None:
253-
if cvssclass.get("V3Score") is not None:
254-
severity = self.convert_cvss_score(cvssclass.get("V3Score"))
255-
cvssv3_string = dict(cvssclass).get("V3Vector")
256-
cvss_data = parse_cvss_data(cvssv3_string)
257-
if cvss_data:
258-
cvssv3 = cvss_data.get("vector")
259-
cvssv3_score = cvss_data.get("score")
260-
elif cvssclass.get("V3Score") is not None:
261-
cvssv3_score = cvssclass.get("V3Score")
262-
elif cvssclass.get("V2Score") is not None:
263-
severity = self.convert_cvss_score(cvssclass.get("V2Score"))
264-
else:
265-
severity = self.convert_cvss_score(None)
262+
break
263+
# Parse the CVSS class if it is not None
264+
if cvssclass is not None:
265+
if cvss_data := parse_cvss_data(cvssclass.get("V3Vector", "")):
266+
cvssv3 = cvss_data.get("vector")
267+
cvssv3_score = cvss_data.get("score")
268+
severity = cvss_data.get("severity")
269+
elif (cvss_v3_score := cvssclass.get("V3Score")) is not None:
270+
cvssv3_score = cvss_v3_score
271+
severity = self.convert_cvss_score(cvss_v3_score)
272+
elif (cvss_v2_score := cvssclass.get("V2Score")) is not None:
273+
severity = self.convert_cvss_score(cvss_v2_score)
266274
else:
267-
severity = TRIVY_SEVERITIES[vuln["Severity"]]
275+
severity = self.convert_cvss_score(None)
268276
else:
269277
severity = TRIVY_SEVERITIES[vuln["Severity"]]
270278
if target_class == "os-pkgs" or target_class == "lang-pkgs":

helm/defectdojo/Chart.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
apiVersion: v2
2-
appVersion: "2.48.2"
2+
appVersion: "2.48.3"
33
description: A Helm chart for Kubernetes to install DefectDojo
44
name: defectdojo
5-
version: 1.6.197
5+
version: 1.6.198
66
icon: https://www.defectdojo.org/img/favicon.ico
77
maintainers:
88
- name: madchap

0 commit comments

Comments
 (0)