Skip to content

Commit 5eda8d3

Browse files
update parsers that didn't parse cvss correctly yet
1 parent b0dda81 commit 5eda8d3

13 files changed

Lines changed: 93 additions & 42 deletions

File tree

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

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ $ docker compose build --build-arg uid=1000
3737
|`unittests/scans/<parser_dir>/{many_vulns,no_vuln,one_vuln}.json` | Sample files containing meaningful data for unit tests. The minimal set.
3838
|`unittests/tools/test_<parser_name>_parser.py` | Unit tests of the parser.
3939
|`dojo/settings/settings.dist.py` | If you want to use a modern hashcode based deduplication algorithm
40-
|`docs/content/en/connecting_your_tools/parsers/<file/api>/<parser_file>.md` | Documentation, what kind of file format is required and how it should be obtained
41-
40+
|`docs/content/en/connecting_your_tools/parsers/<file/api>/<parser_file>.md` | Documentation, what kind of file format is required and how it should be obtained
41+
4242

4343
## Factory contract
4444

@@ -145,7 +145,7 @@ Very bad example:
145145
Various file formats are handled through libraries. In order to keep DefectDojo slim and also don't extend the attack surface, keep the number of libraries used minimal and take other parsers as an example.
146146

147147
#### defusedXML in favour of lxml
148-
As xml is by default an unsecure format, the information parsed from various xml output has to be parsed in a secure way. Within an evaluation, we determined that defusedXML is the library which we will use in the future to parse xml files in parsers as this library is rated more secure. Thus, we will only accept PRs with the defusedxml library.
148+
As xml is by default an unsecure format, the information parsed from various xml output has to be parsed in a secure way. Within an evaluation, we determined that defusedXML is the library which we will use in the future to parse xml files in parsers as this library is rated more secure. Thus, we will only accept PRs with the defusedxml library.
149149

150150
### Not all attributes are mandatory
151151

@@ -168,14 +168,22 @@ Good example:
168168
### Do not parse CVSS by hand (vector, score or severity)
169169

170170
Data can have `CVSS` vectors or scores. Don't write your own CVSS score algorithm.
171-
For parser, we rely on module `cvss`.
171+
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.
172+
173+
```python
174+
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")
175+
if cvss_data:
176+
finding.cvss3 = cvss_data.get("vector")
177+
finding.cvssv3_score = cvss_data.get("score")
178+
finding.severity = cvss_data.get("severity") # if your tool does generate severity
179+
```
172180

173-
It's easy to use and will make the parser aligned with the rest of the code.
181+
If you need more manual processing, you can parse the `CVSS` vector directly.
174182

175183
Example of use:
176184

177185
```python
178-
from cvss.cvss3 import CVSS3
186+
from dojo.utils import cvss.cvss3 import CVSS3
179187
import cvss.parser
180188
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")
181189
if len(vectors) > 0 and type(vectors[0]) is CVSS3:
@@ -185,17 +193,8 @@ if len(vectors) > 0 and type(vectors[0]) is CVSS3:
185193
severity = vectors[0].severities()[0]
186194
vectors[0].compute_base_score()
187195
cvssv3_score = vectors[0].scores()[0]
188-
print(severity)
189-
print(cvssv3_score)
190-
```
191-
192-
Good example:
193-
194-
```python
195-
vectors = cvss.parser.parse_cvss_from_text(item['cvss_vect'])
196-
if len(vectors) > 0 and type(vectors[0]) is CVSS3:
197-
finding.cvss = vectors[0].clean_vector()
198-
finding.severity = vectors[0].severities()[0] # if your tool does generate severity
196+
finding.severity = severity
197+
finding.cvssv3_score = cvssv3_score
199198
```
200199

201200
Bad example (DIY):
@@ -366,4 +365,3 @@ Please add a new .md file in [`docs/content/en/connecting_your_tools/parsers`] w
366365
* A link to the scanner itself - (e.g. GitHub or vendor link)
367366

368367
Here is an example of a completed Parser documentation page: [https://github.com/DefectDojo/django-DefectDojo/blob/master/docs/content/en/connecting_your_tools/parsers/file/acunetix.md](https://github.com/DefectDojo/django-DefectDojo/blob/master/docs/content/en/connecting_your_tools/parsers/file/acunetix.md)
369-

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)