Skip to content

Commit d5c8dff

Browse files
valentijnscholtenMaffoochdogboat
authored
Add CVSS4 support (#12751)
* cvss4: model + parsers * cvss4: UI + rest tests + fixes * add cvssv4 to forms, fix test * add cvssv4 to forms, fix test * simplify test_metrics_queries * add cvssv4 to forms, fix test * update how to write a parser guide * fix one more test * cvss4: add links to extnernal calculators * cvss4: add links to extnernal calculators * fix one more test * fix one more test * fix one more test * fix and add tests * show CVSS vector together in tooltip * cleanup maxDiff * fix tests * update model help_text and verbose_name * fix tests * fix migration * fix tests * allow toggling of CVSS fields * revert back to overriding the score field always * fix tests * fix tests * Update docs/content/en/open_source/contributing/how-to-write-a-parser.md * Update dojo/forms.py Co-authored-by: Sean Reid <dogboat@users.noreply.github.com> * Update dojo/forms.py * Update dojo/models.py Co-authored-by: Sean Reid <dogboat@users.noreply.github.com> --------- Co-authored-by: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> Co-authored-by: Sean Reid <dogboat@users.noreply.github.com>
1 parent d38dfdf commit d5c8dff

File tree

28 files changed

+744
-294
lines changed

28 files changed

+744
-294
lines changed

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

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -166,43 +166,52 @@ Good example:
166166
finding.cwe = data["mykey"]
167167
```
168168

169-
### Do not parse CVSS by hand (vector, score or severity)
169+
### Parsing of CVSS vectors
170170

171-
Data can have `CVSS` vectors or scores. Don't write your own CVSS score algorithm.
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.
171+
Data can have `CVSS` vectors or scores. Defect Dojo use the `cvss` module provided by RedHat Security.
172+
There's also a helper method to validate the vector and extract the base score and severity from it.
173173

174174
```python
175-
from dojo.utils import parse_cvss_data
176-
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")
177-
if cvss_data:
178-
finding.cvssv3 = cvss_data.get("vector")
179-
finding.cvssv3_score = cvss_data.get("score")
180-
finding.severity = cvss_data.get("severity") # if your tool does generate severity
175+
from dojo.utils import parse_cvss_data
176+
177+
cvss_vector = <get CVSS3 or CVSS4 vector from the report>
178+
cvss_data = parse_cvss_data(cvss_vector)
179+
if cvss_data:
180+
finding.severity = cvss_data["severity"]
181+
finding.cvssv3 = cvss_data["cvssv3"]
182+
finding.cvssv4 = cvss_data["cvssv4"]
183+
# we don't set any score fields as those will be overwritten by Defect Dojo
181184
```
185+
Not all values have to be used as scan reports usually provide their own value for `severity`.
186+
And sometimes also for `cvss_score`. Defect Dojo will not overwrite any `cvss3_score` or `cvss4_score`.
187+
If no score is set, Defect Dojo will use the `cvss` library to calculate the score.
188+
The response also has the detected major version of the CVSS vector in `cvss_data["major_version"]`.
182189

183-
If you need more manual processing, you can parse the `CVSS3` vector directly.
190+
191+
If you need more manual processing, you can parse the `CVSS` vector directly.
184192

185193
Example of use:
186194

187195
```python
188-
import cvss.parser
189-
from cvss import CVSS2, CVSS3
190-
191-
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")
192-
if len(vectors) > 0 and type(vectors[0]) is CVSS3:
193-
print(vectors[0].severities()) # this is the 3 severities
194-
195-
cvssv3 = vectors[0].clean_vector()
196-
severity = vectors[0].severities()[0]
197-
vectors[0].compute_base_score()
198-
cvssv3_score = vectors[0].scores()[0]
199-
finding.severity = severity
200-
finding.cvssv3_score = cvssv3_score
196+
import cvss.parser
197+
from cvss import CVSS2, CVSS3, CVSS4
198+
199+
# TEMPORARY: Use Defect Dojo implementation of `parse_cvss_from_text` white waiting for https://github.com/RedHatProductSecurity/cvss/pull/75 to be released
200+
vectors = dojo.utils.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")
201+
if len(vectors) > 0 and type(vectors[0]) is CVSS3:
202+
print(vectors[0].severities()) # this is the 3 severities
203+
204+
cvssv3 = vectors[0].clean_vector()
205+
severity = vectors[0].severities()[0]
206+
vectors[0].compute_base_score()
207+
cvssv3_score = vectors[0].scores()[0]
208+
finding.severity = severity
209+
finding.cvssv3_score = cvssv3_score
201210
```
202211

203-
Bad example (DIY):
212+
Do not do something like this:
204213

205-
```python
214+
```
206215
def get_severity(self, cvss, cvss_version="2.0"):
207216
cvss = float(cvss)
208217
cvss_version = float(cvss_version[:1])
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Generated by Django 5.1.8 on 2025-07-08 17:21
2+
3+
import django.core.validators
4+
import dojo.validators
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
('dojo', '0233_remove_test_actual_time_remove_test_estimated_time'),
12+
]
13+
14+
operations = [
15+
migrations.AlterField(
16+
model_name='finding',
17+
name='cvssv3',
18+
field=models.TextField(help_text='Common Vulnerability Scoring System version 3 (CVSS3) score associated with this finding.', max_length=117, null=True, validators=[dojo.validators.cvss3_validator], verbose_name='CVSS3 Vector'),
19+
),
20+
migrations.AlterField(
21+
model_name='finding',
22+
name='cvssv3_score',
23+
field=models.FloatField(blank=True, help_text='Numerical CVSSv3 score for the vulnerability. If the vector is given, the score is updated while saving the finding. The value must be between 0-10.', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(10.0)], verbose_name='CVSS3 Score'),
24+
),
25+
migrations.AddField(
26+
model_name='finding',
27+
name='cvssv4',
28+
field=models.TextField(help_text='Common Vulnerability Scoring System version 4 (CVSS4) score associated with this finding.', max_length=255, null=True, validators=[dojo.validators.cvss4_validator], verbose_name='CVSS4 vector'),
29+
),
30+
migrations.AddField(
31+
model_name='finding',
32+
name='cvssv4_score',
33+
field=models.FloatField(blank=True, help_text='Numerical CVSSv4 score for the vulnerability. If the vector is given, the score is updated while saving the finding. The value must be between 0-10.', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(10.0)], verbose_name='CVSSv4 Score'),
34+
),
35+
migrations.AddField(
36+
model_name='system_settings',
37+
name='enable_cvss3_display',
38+
field=models.BooleanField(blank=False, default=True, help_text='With this setting turned off, CVSS3 fields will be hidden in the user interface.', verbose_name='Enable CVSS3 Display'),
39+
),
40+
migrations.AddField(
41+
model_name='system_settings',
42+
name='enable_cvss4_display',
43+
field=models.BooleanField(blank=False, default=True, help_text='With this setting turned off, CVSS4 fields will be hidden in the user interface.', verbose_name='Enable CVSS4 Display'),
44+
),
45+
]

0 commit comments

Comments
 (0)