Skip to content

Commit cdee0ec

Browse files
update parsers that didn't parse cvss correctly yet
1 parent d9ebb93 commit cdee0ec

13 files changed

Lines changed: 90 additions & 38 deletions

File tree

docs/content/en/open_source/contributing/how-to-write-a-parser.md

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,22 @@ Good example:
169169
### Do not parse CVSS by hand (vector, score or severity)
170170

171171
Data can have `CVSS` vectors or scores. Don't write your own CVSS score algorithm.
172-
For parser, we rely on module `cvss`.
172+
For parser, we rely on module `cvss`. But we also have a helper method to validate the vector and extract the base score and severity from it.
173173

174-
It's easy to use and will make the parser aligned with the rest of the code.
174+
```python
175+
cvss_data = parse_cvss_data("CVSS:3.0/S:C/C:H/I:H/A:N/AV:P/AC:H/PR:H/UI:R/E:H/RL:O/RC:R/CR:H/IR:X/AR:X/MAC:H/MPR:X/MUI:X/MC:L/MA:X")
176+
if cvss_data:
177+
finding.cvss3 = cvss_data.get("vector")
178+
finding.cvssv3_score = cvss_data.get("score")
179+
finding.severity = cvss_data.get("severity") # if your tool does generate severity
180+
```
181+
182+
If you need more manual processing, you can parse the `CVSS` vector directly.
175183

176184
Example of use:
177185

178186
```python
179-
from cvss.cvss3 import CVSS3
187+
from dojo.utils import cvss.cvss3 import CVSS3
180188
import cvss.parser
181189
vectors = cvss.parser.parse_cvss_from_text("CVSS:3.0/S:C/C:H/I:H/A:N/AV:P/AC:H/PR:H/UI:R/E:H/RL:O/RC:R/CR:H/IR:X/AR:X/MAC:H/MPR:X/MUI:X/MC:L/MA:X")
182190
if len(vectors) > 0 and type(vectors[0]) is CVSS3:
@@ -186,17 +194,8 @@ if len(vectors) > 0 and type(vectors[0]) is CVSS3:
186194
severity = vectors[0].severities()[0]
187195
vectors[0].compute_base_score()
188196
cvssv3_score = vectors[0].scores()[0]
189-
print(severity)
190-
print(cvssv3_score)
191-
```
192-
193-
Good example:
194-
195-
```python
196-
vectors = cvss.parser.parse_cvss_from_text(item['cvss_vect'])
197-
if len(vectors) > 0 and type(vectors[0]) is CVSS3:
198-
finding.cvss = vectors[0].clean_vector()
199-
finding.severity = vectors[0].severities()[0] # if your tool does generate severity
197+
finding.severity = severity
198+
finding.cvssv3_score = cvssv3_score
200199
```
201200

202201
Bad example (DIY):

dojo/models.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@
1010
from pathlib import Path
1111
from uuid import uuid4
1212

13+
import cvss.parser
1314
import dateutil
1415
import hyperlink
1516
import tagulous.admin
1617
from auditlog.registry import auditlog
17-
from cvss import CVSS3
1818
from dateutil.relativedelta import relativedelta
1919
from django import forms
2020
from django.conf import settings
@@ -2333,8 +2333,6 @@ class Finding(models.Model):
23332333
verbose_name=_("EPSS percentile"),
23342334
help_text=_("EPSS percentile for the CVE. Describes how many CVEs are scored at or below this one."),
23352335
validators=[MinValueValidator(0.0), MaxValueValidator(1.0)])
2336-
# cvssv3_regex = RegexValidator(regex=r"^AV:[NALP]|AC:[LH]|PR:[UNLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]", message="CVSS must be entered in format: 'AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H'")
2337-
# cvssv3 = models.TextField(validators=[cvssv3_regex],
23382336
cvssv3 = models.TextField(validators=[cvss3_validator],
23392337
max_length=117,
23402338
null=True,
@@ -2702,11 +2700,12 @@ def save(self, dedupe_option=True, rules_option=True, product_grading_option=Tru
27022700
# Synchronize cvssv3 score using cvssv3 vector
27032701
if self.cvssv3:
27042702
try:
2705-
cvss_object = CVSS3(self.cvssv3)
2703+
cvss_vector = cvss.parser.parse_cvss_from_text(self.cvssv3)
27062704
# use the environmental score, which is the most refined score
2707-
self.cvssv3_score = cvss_object.scores()[2]
2705+
self.cvssv3_score = cvss_vector.scores()[2]
27082706
except Exception as ex:
2709-
logger.error("Can't compute cvssv3 score for finding id %i. Invalid cvssv3 vector found: '%s'. Exception: %s", self.id, self.cvssv3, ex)
2707+
logger.warning("Can't compute cvssv3 score for finding id %i. Invalid cvssv3 vector found: '%s'. Exception: %s.", self.id, self.cvssv3, ex)
2708+
# should we set self.cvssv3 to None here to avoid storing invalid vectors? it would also remove invalid vectors on existing findings...
27102709

27112710
self.set_hash_code(dedupe_option)
27122711

@@ -3519,8 +3518,6 @@ class Finding_Template(models.Model):
35193518
blank=False,
35203519
verbose_name="Vulnerability Id",
35213520
help_text="An id of a vulnerability in a security advisory associated with this finding. Can be a Common Vulnerabilities and Exposures (CVE) or from other sources.")
3522-
# cvssv3_regex = RegexValidator(regex=r"^AV:[NALP]|AC:[LH]|PR:[UNLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]", message="CVSS must be entered in format: 'AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H'")
3523-
# cvssv3 = models.TextField(validators=[cvssv3_regex], max_length=117, null=True)
35243521
cvssv3 = models.TextField(help_text=_("Common Vulnerability Scoring System version 3 (CVSSv3) score associated with this finding."), validators=[cvss3_validator], max_length=117, null=True, verbose_name=_("CVSS v3 vector"))
35253522

35263523
severity = models.CharField(max_length=200, null=True, blank=True)

dojo/tools/aqua/parser.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import json
22

33
from dojo.models import Finding
4+
from dojo.utils import parse_cvss_data
45

56

67
class AquaParser:
@@ -170,7 +171,7 @@ def get_item(self, resource, vuln, test):
170171
severity_justification += "\nAqua severity classification: {}".format(vuln.get("aqua_severity_classification"))
171172
severity_justification += "\nAqua scoring system: {}".format(vuln.get("aqua_scoring_system"))
172173
if "nvd_score_v3" in vuln:
173-
cvssv3 = vuln.get("nvd_vectors_v3") # TODO: VECTOR
174+
cvssv3 = vuln.get("nvd_vectors_v3")
174175
if "aqua_score" in vuln:
175176
if score is None:
176177
score = vuln.get("aqua_score")
@@ -193,14 +194,15 @@ def get_item(self, resource, vuln, test):
193194
)
194195
severity_justification += "\nNVD v3 vectors: {}".format(vuln.get("nvd_vectors_v3"))
195196
# Add the CVSS3 to Finding
196-
cvssv3 = vuln.get("nvd_vectors_v3") # TODO: VECTOR
197+
cvssv3 = vuln.get("nvd_vectors_v3")
197198
if "nvd_score" in vuln:
198199
if score is None:
199200
score = vuln.get("nvd_score")
200201
used_for_classification = (
201202
f"NVD score v2 ({score}) used for classification.\n"
202203
)
203204
severity_justification += "\nNVD v2 vectors: {}".format(vuln.get("nvd_vectors"))
205+
204206
severity_justification += f"\n{used_for_classification}"
205207
severity = self.severity_of(score)
206208
finding = Finding(
@@ -214,14 +216,19 @@ def get_item(self, resource, vuln, test):
214216
severity=severity,
215217
severity_justification=severity_justification,
216218
cwe=0,
217-
cvssv3=cvssv3,
218219
description=description.strip(),
219220
mitigation=fix_version,
220221
references=url,
221222
component_name=resource.get("name"),
222223
component_version=resource.get("version"),
223224
impact=severity,
224225
)
226+
227+
cvss_data = parse_cvss_data(cvssv3)
228+
if cvss_data:
229+
finding.cvss3 = cvss_data.get("vector")
230+
finding.cvssv3_score = cvss_data.get("score")
231+
225232
if vulnerability_id != "No CVE":
226233
finding.unsaved_vulnerability_ids = [vulnerability_id]
227234
if vuln.get("epss_score"):

dojo/tools/blackduck_binary_analysis/parser.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def ingest_findings(self, sorted_findings, test):
4343
cwe = 1357
4444
title = self.format_title(i)
4545
description = self.format_description(i)
46-
cvss_v3 = True # TODO: VECTOR
46+
cvss_v3 = True
4747
if str(i.cvss_vector_v3) != "":
4848
cvss_vectors = "{}{}".format(
4949
"CVSS:3.1/",

dojo/tools/cyberwatch_galeax/parser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ def build_findings_for_cve(self, cve_code, c_data, test):
197197
description = c_data["description"]
198198
impact = c_data["impact"]
199199
references = c_data["references"]
200-
cvssv3 = c_data["cvssv3"] # TODO: VECTOR
200+
cvssv3 = c_data["cvssv3"]
201201
cvssv3_score = c_data["cvssv3_score"]
202202
products = c_data["products"]
203203

@@ -515,7 +515,7 @@ def parse_cvss(self, cvss_v3_vector, json_data):
515515
if cvss_v3_vector:
516516
vectors = cvss.parser.parse_cvss_from_text(cvss_v3_vector)
517517
if vectors and isinstance(vectors[0], CVSS3):
518-
cvssv3 = vectors[0].clean_vector() # TODO: VECTOR
518+
cvssv3 = vectors[0].clean_vector()
519519
cvssv3_score = vectors[0].scores()[0]
520520
severity = vectors[0].severities()[0]
521521
return cvssv3, cvssv3_score, severity

dojo/tools/jfrog_xray_unified/parser.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from datetime import datetime
33

44
from dojo.models import Finding
5+
from dojo.utils import parse_cvss_data
56

67

78
class JFrogXrayUnifiedParser:
@@ -75,7 +76,7 @@ def get_item(vulnerability, test):
7576
vulnerability_id = worstCve["cve"]
7677
if "cvss_v3_vector" in worstCve:
7778
cvss_v3 = worstCve["cvss_v3_vector"]
78-
cvssv3 = cvss_v3 # TODO: VECTOR
79+
cvssv3 = cvss_v3
7980
if "cvss_v2_vector" in worstCve:
8081
cvss_v2 = worstCve["cvss_v2_vector"]
8182

@@ -134,12 +135,16 @@ def get_item(vulnerability, test):
134135
dynamic_finding=False,
135136
references=references,
136137
impact=severity,
137-
cvssv3=cvssv3,
138138
date=scan_time,
139139
unique_id_from_tool=vulnerability["issue_id"],
140140
tags=tags,
141141
)
142142

143+
cvss_data = parse_cvss_data(cvssv3)
144+
if cvss_data:
145+
finding.cvss3 = cvss_data.get("vector")
146+
finding.cvssv3_score = cvss_data.get("score")
147+
143148
if vulnerability_id:
144149
finding.unsaved_vulnerability_ids = [vulnerability_id]
145150

dojo/tools/npm_audit_7_plus/parser.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,12 @@ def get_item(item_node, tree, test):
165165
cwe = int(cwe.split("-")[1])
166166
dojo_finding.cwe = cwe
167167

168-
if (cvssv3 is not None) and (len(cvssv3) > 0): # TODO: VECTOR
169-
dojo_finding.cvssv3 = cvssv3
168+
if (cvssv3 is not None) and (len(cvssv3) > 0):
169+
from dojo.utils import parse_cvss_data
170+
cvss_data = parse_cvss_data(cvssv3)
171+
if cvss_data:
172+
dojo_finding.cvss3 = cvss_data.get("vector")
173+
dojo_finding.cvssv3_score = cvss_data.get("score")
170174

171175
return dojo_finding
172176

dojo/tools/qualys/csv_parser.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from django.conf import settings
99

1010
from dojo.models import Endpoint, Finding
11+
from dojo.utils import parse_cvss_data
1112

1213
_logger = logging.getLogger(__name__)
1314

@@ -197,7 +198,7 @@ def build_findings_from_dict(report_findings: [dict]) -> [Finding]:
197198
# Clean up the CVE data appropriately
198199
cve_list = _clean_cve_data(cve_data)
199200

200-
if "CVSS3 Base" in report_finding: # TODO: VECTOR
201+
if "CVSS3 Base" in report_finding:
201202
cvssv3 = _extract_cvss_vectors(
202203
report_finding["CVSS3 Base"], report_finding["CVSS3 Temporal"],
203204
)
@@ -227,8 +228,13 @@ def build_findings_from_dict(report_findings: [dict]) -> [Finding]:
227228
impact=report_finding["Impact"],
228229
date=date,
229230
vuln_id_from_tool=report_finding["QID"],
230-
cvssv3=cvssv3,
231231
)
232+
# Make sure vector is valid
233+
cvss_data = parse_cvss_data(cvssv3)
234+
if cvss_data:
235+
finding.cvss3 = cvss_data.get("vector")
236+
finding.cvssv3_score = cvss_data.get("score")
237+
232238
# Qualys reports regression findings as active, but with a Date Last
233239
# Fixed.
234240
if report_finding["Date Last Fixed"]:

dojo/tools/qualys/parser.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from dojo.models import Endpoint, Finding
1010
from dojo.tools.qualys import csv_parser
11+
from dojo.utils import parse_cvss_data
1112

1213
logger = logging.getLogger(__name__)
1314

@@ -352,7 +353,11 @@ def parse_finding(host, tree):
352353
finding.is_mitigated = temp["mitigated"]
353354
finding.active = temp["active"]
354355
if temp.get("CVSS_vector") is not None:
355-
finding.cvssv3 = temp.get("CVSS_vector") # TODO: VECTOR
356+
cvss_data = parse_cvss_data(temp.get("CVSS_vector"))
357+
if cvss_data:
358+
finding.cvss3 = cvss_data.get("vector")
359+
finding.cvss3_score = cvss_data.get("base_score")
360+
356361
if temp.get("CVSS_value") is not None:
357362
finding.cvssv3_score = temp.get("CVSS_value")
358363
finding.verified = True

dojo/tools/sonatype/parser.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from dojo.models import Finding
44
from dojo.tools.sonatype.identifier import ComponentIdentifier
5+
from dojo.utils import parse_cvss_data
56

67

78
class SonatypeParser:
@@ -63,7 +64,10 @@ def get_finding(security_issue, component, test):
6364
finding.cwe = security_issue["cwe"]
6465

6566
if "cvssVector" in security_issue:
66-
finding.cvssv3 = security_issue["cvssVector"] # TODO: VECTOR
67+
cvss_data = parse_cvss_data(security_issue["cvssVector"])
68+
if cvss_data:
69+
finding.cvss3 = cvss_data.get("vector")
70+
finding.cvssv3_score = cvss_data.get("score")
6771

6872
if "pathnames" in component:
6973
finding.file_path = " ".join(component["pathnames"])[:1000]

0 commit comments

Comments
 (0)