diff --git a/docs/content/en/open_source/contributing/how-to-write-a-parser.md b/docs/content/en/open_source/contributing/how-to-write-a-parser.md index 61dd7cdf179..6dee4602c29 100644 --- a/docs/content/en/open_source/contributing/how-to-write-a-parser.md +++ b/docs/content/en/open_source/contributing/how-to-write-a-parser.md @@ -166,43 +166,52 @@ Good example: finding.cwe = data["mykey"] ``` -### Do not parse CVSS by hand (vector, score or severity) +### Parsing of CVSS vectors -Data can have `CVSS` vectors or scores. Don't write your own CVSS score algorithm. -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. +Data can have `CVSS` vectors or scores. Defect Dojo use the `cvss` module provided by RedHat Security. +There's also a helper method to validate the vector and extract the base score and severity from it. ```python -from dojo.utils import parse_cvss_data -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") -if cvss_data: - finding.cvssv3 = cvss_data.get("vector") - finding.cvssv3_score = cvss_data.get("score") - finding.severity = cvss_data.get("severity") # if your tool does generate severity + from dojo.utils import parse_cvss_data + + cvss_vector = + cvss_data = parse_cvss_data(cvss_vector) + if cvss_data: + finding.severity = cvss_data["severity"] + finding.cvssv3 = cvss_data["cvssv3"] + finding.cvssv4 = cvss_data["cvssv4"] + # we don't set any score fields as those will be overwritten by Defect Dojo ``` +Not all values have to be used as scan reports usually provide their own value for `severity`. +And sometimes also for `cvss_score`. Defect Dojo will not overwrite any `cvss3_score` or `cvss4_score`. +If no score is set, Defect Dojo will use the `cvss` library to calculate the score. +The response also has the detected major version of the CVSS vector in `cvss_data["major_version"]`. -If you need more manual processing, you can parse the `CVSS3` vector directly. + +If you need more manual processing, you can parse the `CVSS` vector directly. Example of use: ```python -import cvss.parser -from cvss import CVSS2, CVSS3 - -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") -if len(vectors) > 0 and type(vectors[0]) is CVSS3: - print(vectors[0].severities()) # this is the 3 severities - - cvssv3 = vectors[0].clean_vector() - severity = vectors[0].severities()[0] - vectors[0].compute_base_score() - cvssv3_score = vectors[0].scores()[0] - finding.severity = severity - finding.cvssv3_score = cvssv3_score + import cvss.parser + from cvss import CVSS2, CVSS3, CVSS4 + + # TEMPORARY: Use Defect Dojo implementation of `parse_cvss_from_text` white waiting for https://github.com/RedHatProductSecurity/cvss/pull/75 to be released + 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") + if len(vectors) > 0 and type(vectors[0]) is CVSS3: + print(vectors[0].severities()) # this is the 3 severities + + cvssv3 = vectors[0].clean_vector() + severity = vectors[0].severities()[0] + vectors[0].compute_base_score() + cvssv3_score = vectors[0].scores()[0] + finding.severity = severity + finding.cvssv3_score = cvssv3_score ``` -Bad example (DIY): +Do not do something like this: -```python +``` def get_severity(self, cvss, cvss_version="2.0"): cvss = float(cvss) cvss_version = float(cvss_version[:1]) diff --git a/dojo/db_migrations/0234_finding_cvssv4_finding_cvssv4_score.py b/dojo/db_migrations/0234_finding_cvssv4_finding_cvssv4_score.py new file mode 100644 index 00000000000..54285902cfb --- /dev/null +++ b/dojo/db_migrations/0234_finding_cvssv4_finding_cvssv4_score.py @@ -0,0 +1,45 @@ +# Generated by Django 5.1.8 on 2025-07-08 17:21 + +import django.core.validators +import dojo.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dojo', '0233_remove_test_actual_time_remove_test_estimated_time'), + ] + + operations = [ + migrations.AlterField( + model_name='finding', + name='cvssv3', + 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'), + ), + migrations.AlterField( + model_name='finding', + name='cvssv3_score', + 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'), + ), + migrations.AddField( + model_name='finding', + name='cvssv4', + 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'), + ), + migrations.AddField( + model_name='finding', + name='cvssv4_score', + 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'), + ), + migrations.AddField( + model_name='system_settings', + name='enable_cvss3_display', + 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'), + ), + migrations.AddField( + model_name='system_settings', + name='enable_cvss4_display', + 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'), + ), + ] diff --git a/dojo/forms.py b/dojo/forms.py index 19f1fe6962d..59825989f0e 100644 --- a/dojo/forms.py +++ b/dojo/forms.py @@ -123,6 +123,13 @@ ("duplicate", "Duplicate"), ("out_of_scope", "Out of Scope")) +CVSS_CALCULATOR_URLS = { + "https://www.first.org/cvss/calculator/3-0": "CVSS3 Calculator by FIRST", + "https://www.first.org/cvss/calculator/4-0": "CVSS4 Calculator by FIRST", + "https://www.metaeffekt.com/security/cvss/calculator/": "CVSS2/3/4 Calculator by Metaeffekt", + } + + vulnerability_ids_field = forms.CharField(max_length=5000, required=False, label="Vulnerability Ids", @@ -133,6 +140,22 @@ EFFORT_FOR_FIXING_INVALID_CHOICE = _("Select valid choice: Low,Medium,High") +class BulletListDisplayWidget(forms.Widget): + def __init__(self, urls_dict=None, *args, **kwargs): + self.urls_dict = urls_dict or {} + super().__init__(*args, **kwargs) + + def render(self, name, value, attrs=None, renderer=None): + if not self.urls_dict: + return "" + + html = '" + return mark_safe(html) + + class MultipleSelectWithPop(forms.SelectMultiple): def render(self, name, *args, **kwargs): html = super().render(name, *args, **kwargs) @@ -1125,7 +1148,10 @@ class AddFindingForm(forms.ModelForm): widget=forms.TextInput(attrs={"class": "datepicker", "autocomplete": "off"})) cwe = forms.IntegerField(required=False) vulnerability_ids = vulnerability_ids_field - cvssv3 = forms.CharField(max_length=117, required=False, widget=forms.TextInput(attrs={"class": "cvsscalculator", "data-toggle": "dropdown", "aria-haspopup": "true", "aria-expanded": "false"})) + cvssv3 = forms.CharField(label="CVSS3 Vector", max_length=117, required=False, widget=forms.TextInput(attrs={"class": "cvsscalculator", "data-toggle": "dropdown", "aria-haspopup": "true", "aria-expanded": "false"})) + cvssv3_score = forms.FloatField(label="CVSS3 Score", required=False, max_value=10.0, min_value=0.0) + cvssv4 = forms.CharField(label="CVSS4 Vector", max_length=255, required=False) + cvssv4_score = forms.FloatField(label="CVSS4 Score", required=False, max_value=10.0, min_value=0.0) description = forms.CharField(widget=forms.Textarea) severity = forms.ChoiceField( choices=SEVERITY_CHOICES, @@ -1152,7 +1178,7 @@ class AddFindingForm(forms.ModelForm): "invalid_choice": EFFORT_FOR_FIXING_INVALID_CHOICE}) # the only reliable way without hacking internal fields to get predicatble ordering is to make it explicit - field_order = ("title", "date", "cwe", "vulnerability_ids", "severity", "cvssv3", "description", "mitigation", "impact", "request", "response", "steps_to_reproduce", + field_order = ("title", "date", "cwe", "vulnerability_ids", "severity", "cvssv3", "cvssv3_Score", "cvssv4", "cvssv4_score", "description", "mitigation", "impact", "request", "response", "steps_to_reproduce", "severity_justification", "endpoints", "endpoints_to_add", "references", "active", "verified", "false_p", "duplicate", "out_of_scope", "risk_accepted", "under_defect_review") @@ -1174,6 +1200,9 @@ def __init__(self, *args, **kwargs): self.endpoints_to_add_list = [] + # Hide CVSS fields based on system settings + hide_cvss_fields_if_disabled(self) + def clean(self): cleaned_data = super().clean() if ((cleaned_data["active"] or cleaned_data["verified"]) and cleaned_data["duplicate"]): @@ -1209,7 +1238,17 @@ class AdHocFindingForm(forms.ModelForm): widget=forms.TextInput(attrs={"class": "datepicker", "autocomplete": "off"})) cwe = forms.IntegerField(required=False) vulnerability_ids = vulnerability_ids_field - cvssv3 = forms.CharField(max_length=117, required=False, widget=forms.TextInput(attrs={"class": "cvsscalculator", "data-toggle": "dropdown", "aria-haspopup": "true", "aria-expanded": "false"})) + + cvss_info = forms.CharField( + label="CVSS", + widget=BulletListDisplayWidget(CVSS_CALCULATOR_URLS), + required=False, + disabled=True) + + cvssv3 = forms.CharField(label="CVSS3 Vector", max_length=117, required=False, widget=forms.TextInput(attrs={"class": "cvsscalculator", "data-toggle": "dropdown", "aria-haspopup": "true", "aria-expanded": "false"})) + cvssv3_score = forms.FloatField(label="CVSS3 Score", required=False, max_value=10.0, min_value=0.0) + cvssv4 = forms.CharField(label="CVSS4 Vector", max_length=255, required=False) + cvssv4_score = forms.FloatField(label="CVSS4 Score", required=False, max_value=10.0, min_value=0.0) description = forms.CharField(widget=forms.Textarea) severity = forms.ChoiceField( choices=SEVERITY_CHOICES, @@ -1236,9 +1275,9 @@ class AdHocFindingForm(forms.ModelForm): "invalid_choice": EFFORT_FOR_FIXING_INVALID_CHOICE}) # the only reliable way without hacking internal fields to get predicatble ordering is to make it explicit - field_order = ("title", "date", "cwe", "vulnerability_ids", "severity", "cvssv3", "description", "mitigation", "impact", "request", "response", "steps_to_reproduce", - "severity_justification", "endpoints", "endpoints_to_add", "references", "active", "verified", "false_p", "duplicate", "out_of_scope", - "risk_accepted", "under_defect_review", "sla_start_date", "sla_expiration_date") + field_order = ("title", "date", "cwe", "vulnerability_ids", "severity", "cvss_info", "cvssv3", "cvssv3_score", "cvssv4", "cvssv4_score", "description", "mitigation", + "impact", "request", "response", "steps_to_reproduce", "severity_justification", "endpoints", "endpoints_to_add", "references", + "active", "verified", "false_p", "duplicate", "out_of_scope", "risk_accepted", "under_defect_review", "sla_start_date", "sla_expiration_date") def __init__(self, *args, **kwargs): req_resp = kwargs.pop("req_resp") @@ -1258,6 +1297,9 @@ def __init__(self, *args, **kwargs): self.endpoints_to_add_list = [] + # Hide CVSS fields based on system settings + hide_cvss_fields_if_disabled(self) + def clean(self): cleaned_data = super().clean() if ((cleaned_data["active"] or cleaned_data["verified"]) and cleaned_data["duplicate"]): @@ -1291,7 +1333,17 @@ class PromoteFindingForm(forms.ModelForm): widget=forms.TextInput(attrs={"class": "datepicker", "autocomplete": "off"})) cwe = forms.IntegerField(required=False) vulnerability_ids = vulnerability_ids_field - cvssv3 = forms.CharField(max_length=117, required=False, widget=forms.TextInput(attrs={"class": "cvsscalculator", "data-toggle": "dropdown", "aria-haspopup": "true", "aria-expanded": "false"})) + + cvss_info = forms.CharField( + label="CVSS", + widget=BulletListDisplayWidget(CVSS_CALCULATOR_URLS), + required=False, + disabled=True) + + cvssv3 = forms.CharField(label="CVSS3 Vector", max_length=117, required=False, widget=forms.TextInput(attrs={"class": "cvsscalculator", "data-toggle": "dropdown", "aria-haspopup": "true", "aria-expanded": "false"})) + cvssv3_score = forms.FloatField(label="CVSS3 Score", required=False, max_value=10.0, min_value=0.0) + cvssv4 = forms.CharField(label="CVSS4 Vector", max_length=255, required=False) + cvssv4_score = forms.FloatField(label="CVSS4 Score", required=False, max_value=10.0, min_value=0.0) description = forms.CharField(widget=forms.Textarea) severity = forms.ChoiceField( choices=SEVERITY_CHOICES, @@ -1308,10 +1360,10 @@ class PromoteFindingForm(forms.ModelForm): references = forms.CharField(widget=forms.Textarea, required=False) # the onyl reliable way without hacking internal fields to get predicatble ordering is to make it explicit - field_order = ("title", "group", "date", "sla_start_date", "sla_expiration_date", "cwe", "vulnerability_ids", "severity", "cvssv3", - "cvssv3_score", "description", "mitigation", "impact", "request", "response", "steps_to_reproduce", "severity_justification", - "endpoints", "endpoints_to_add", "references", "active", "mitigated", "mitigated_by", "verified", "false_p", "duplicate", - "out_of_scope", "risk_accept", "under_defect_review") + field_order = ("title", "group", "date", "sla_start_date", "sla_expiration_date", "cwe", "vulnerability_ids", "severity", "cvss_info", "cvssv3", + "cvssv3_score", "cvssv4", "cvssv4_score", "description", "mitigation", "impact", "request", "response", "steps_to_reproduce", + "severity_justification", "endpoints", "endpoints_to_add", "references", "active", "mitigated", "mitigated_by", "verified", + "false_p", "duplicate", "out_of_scope", "risk_accept", "under_defect_review") def __init__(self, *args, **kwargs): product = None @@ -1325,6 +1377,9 @@ def __init__(self, *args, **kwargs): self.endpoints_to_add_list = [] + # Hide CVSS fields based on system settings + hide_cvss_fields_if_disabled(self) + def clean(self): cleaned_data = super().clean() @@ -1352,8 +1407,18 @@ class FindingForm(forms.ModelForm): widget=forms.TextInput(attrs={"class": "datepicker", "autocomplete": "off"})) cwe = forms.IntegerField(required=False) vulnerability_ids = vulnerability_ids_field - cvssv3 = forms.CharField(max_length=117, required=False, widget=forms.TextInput(attrs={"class": "cvsscalculator", "data-toggle": "dropdown", "aria-haspopup": "true", "aria-expanded": "false"})) - cvssv3_score = forms.FloatField(required=False, max_value=10.0, min_value=0.0) + + cvss_info = forms.CharField( + label="CVSS", + widget=BulletListDisplayWidget(CVSS_CALCULATOR_URLS), + required=False, + disabled=True) + + cvssv3 = forms.CharField(label="CVSS3 Vector", max_length=117, required=False, widget=forms.TextInput(attrs={"class": "cvsscalculator", "data-toggle": "dropdown", "aria-haspopup": "true", "aria-expanded": "false"})) + cvssv3_score = forms.FloatField(label="CVSS3 Score", required=False, max_value=10.0, min_value=0.0) + cvssv4 = forms.CharField(label="CVSS4 Vector", max_length=255, required=False) + cvssv4_score = forms.FloatField(label="CVSS4 Score", required=False, max_value=10.0, min_value=0.0) + description = forms.CharField(widget=forms.Textarea) severity = forms.ChoiceField( choices=SEVERITY_CHOICES, @@ -1384,8 +1449,8 @@ class FindingForm(forms.ModelForm): "invalid_choice": EFFORT_FOR_FIXING_INVALID_CHOICE}) # the only reliable way without hacking internal fields to get predicatble ordering is to make it explicit - field_order = ("title", "group", "date", "sla_start_date", "sla_expiration_date", "cwe", "vulnerability_ids", "severity", "cvssv3", - "cvssv3_score", "description", "mitigation", "impact", "request", "response", "steps_to_reproduce", "severity_justification", + field_order = ("title", "group", "date", "sla_start_date", "sla_expiration_date", "cwe", "vulnerability_ids", "severity", "cvss_info", "cvssv3", + "cvssv3_score", "cvssv4", "cvssv4_score", "description", "mitigation", "impact", "request", "response", "steps_to_reproduce", "severity_justification", "endpoints", "endpoints_to_add", "references", "active", "mitigated", "mitigated_by", "verified", "false_p", "duplicate", "out_of_scope", "risk_accept", "under_defect_review") @@ -1441,6 +1506,9 @@ def __init__(self, *args, **kwargs): self.endpoints_to_add_list = [] + # Hide CVSS fields based on system settings + hide_cvss_fields_if_disabled(self) + def clean(self): cleaned_data = super().clean() @@ -1511,6 +1579,7 @@ class ApplyFindingTemplateForm(forms.Form): cwe = forms.IntegerField(label="CWE", required=False) vulnerability_ids = vulnerability_ids_field cvssv3 = forms.CharField(label="CVSSv3", max_length=117, required=False, widget=forms.TextInput(attrs={"class": "btn btn-secondary dropdown-toggle", "data-toggle": "dropdown", "aria-haspopup": "true", "aria-expanded": "false"})) + cvssv4 = forms.CharField(label="CVSSv3", max_length=117, required=False) severity = forms.ChoiceField(required=False, choices=SEVERITY_CHOICES, error_messages={"required": "Select valid choice: In Progress, On Hold, Completed", "invalid_choice": "Select valid choice: Critical,High,Medium,Low"}) @@ -1528,6 +1597,9 @@ def __init__(self, template=None, *args, **kwargs): if template: self.template.vulnerability_ids = "\n".join(template.vulnerability_ids) + # Hide CVSS fields based on system settings + hide_cvss_fields_if_disabled(self) + def clean(self): cleaned_data = super().clean() @@ -1546,8 +1618,8 @@ def clean_tags(self): return self.cleaned_data.get("tags") class Meta: - fields = ["title", "cwe", "vulnerability_ids", "cvssv3", "severity", "description", "mitigation", "impact", "references", "tags"] - order = ("title", "cwe", "vulnerability_ids", "cvssv3", "severity", "description", "impact", "is_mitigated") + fields = ["title", "cwe", "vulnerability_ids", "cvssv3", "cvssv4", "severity", "description", "mitigation", "impact", "references", "tags"] + order = ("title", "cwe", "vulnerability_ids", "cvssv3", "cvssv4", "severity", "description", "impact", "is_mitigated") class FindingTemplateForm(forms.ModelForm): @@ -1556,7 +1628,7 @@ class FindingTemplateForm(forms.ModelForm): cwe = forms.IntegerField(label="CWE", required=False) vulnerability_ids = vulnerability_ids_field - cvssv3 = forms.CharField(max_length=117, required=False, widget=forms.TextInput(attrs={"class": "btn btn-secondary dropdown-toggle", "data-toggle": "dropdown", "aria-haspopup": "true", "aria-expanded": "false"})) + cvssv3 = forms.CharField(label="CVSS3 Vector", max_length=117, required=False, widget=forms.TextInput(attrs={"class": "btn btn-secondary dropdown-toggle", "data-toggle": "dropdown", "aria-haspopup": "true", "aria-expanded": "false"})) severity = forms.ChoiceField( required=False, choices=SEVERITY_CHOICES, @@ -1570,6 +1642,9 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields["tags"].autocomplete_tags = Finding.tags.tag_model.objects.all().order_by("name") + # Hide CVSS fields based on system settings + hide_cvss_fields_if_disabled(self) + class Meta: model = Finding_Template order = ("title", "cwe", "vulnerability_ids", "cvssv3", "severity", "description", "impact") @@ -3740,3 +3815,30 @@ def set_permission(self, codename): else: msg = "Neither user or group are set" raise Exception(msg) + + +def hide_cvss_fields_if_disabled(form_instance): + """Hide CVSS fields based on system settings.""" + enable_cvss3 = get_system_setting("enable_cvss3_display", True) + enable_cvss4 = get_system_setting("enable_cvss4_display", True) + + # Hide CVSS3 fields if disabled + if not enable_cvss3: + if "cvssv3" in form_instance.fields: + del form_instance.fields["cvssv3"] + if "cvssv3_score" in form_instance.fields: + del form_instance.fields["cvssv3_score"] + if "cvss_info" in form_instance.fields: + del form_instance.fields["cvss_info"] + + # Hide CVSS4 fields if disabled + if not enable_cvss4: + if "cvssv4" in form_instance.fields: + del form_instance.fields["cvssv4"] + if "cvssv4_score" in form_instance.fields: + del form_instance.fields["cvssv4_score"] + + # If both are disabled, hide all CVSS related fields + if not enable_cvss3 and not enable_cvss4: + if "cvss_info" in form_instance.fields: + del form_instance.fields["cvss_info"] diff --git a/dojo/models.py b/dojo/models.py index 68ab2092c3d..ae8dc38a5e4 100644 --- a/dojo/models.py +++ b/dojo/models.py @@ -43,7 +43,7 @@ from tagulous.models import TagField from tagulous.models.managers import FakeTagRelatedManager -from dojo.validators import cvss3_validator +from dojo.validators import cvss3_validator, cvss4_validator logger = logging.getLogger(__name__) deduplicationLogger = logging.getLogger("dojo.specific-loggers.deduplication") @@ -598,6 +598,16 @@ class System_Settings(models.Model): blank=False, verbose_name=_("Enable Calendar"), help_text=_("With this setting turned off, the Calendar will be disabled in the user interface.")) + enable_cvss3_display = models.BooleanField( + default=True, + blank=False, + verbose_name=_("Enable CVSS3 Display"), + help_text=_("With this setting turned off, CVSS3 fields will be hidden in the user interface.")) + enable_cvss4_display = models.BooleanField( + default=True, + blank=False, + verbose_name=_("Enable CVSS4 Display"), + help_text=_("With this setting turned off, CVSS4 fields will be hidden in the user interface.")) default_group = models.ForeignKey( Dojo_Group, null=True, @@ -816,7 +826,7 @@ def clean(self): class Product_Type(models.Model): """ - Product types represent the top level model, these can be business unit divisions, different offices or locations, development teams, or any other logical way of distinguishing “types” of products. + Product types represent the top level model, these can be business unit divisions, different offices or locations, development teams, or any other logical way of distinguishing "types" of products. ` Examples: * IAM Team @@ -2348,14 +2358,25 @@ class Finding(models.Model): cvssv3 = models.TextField(validators=[cvss3_validator], max_length=117, null=True, - verbose_name=_("CVSS v3 vector"), - help_text=_("Common Vulnerability Scoring System version 3 (CVSSv3) score associated with this finding.")) + verbose_name=_("CVSS3 Vector"), + help_text=_("Common Vulnerability Scoring System version 3 (CVSS3) score associated with this finding.")) cvssv3_score = models.FloatField(null=True, blank=True, - verbose_name=_("CVSSv3 score"), + verbose_name=_("CVSS3 Score"), 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."), validators=[MinValueValidator(0.0), MaxValueValidator(10.0)]) + cvssv4 = models.TextField(validators=[cvss4_validator], + max_length=255, + null=True, + verbose_name=_("CVSS4 vector"), + help_text=_("Common Vulnerability Scoring System version 4 (CVSS4) score associated with this finding.")) + cvssv4_score = models.FloatField(null=True, + blank=True, + verbose_name=_("CVSSv4 Score"), + 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."), + validators=[MinValueValidator(0.0), MaxValueValidator(10.0)]) + url = models.TextField(null=True, blank=True, editable=False, @@ -2712,13 +2733,13 @@ def save(self, dedupe_option=True, rules_option=True, product_grading_option=Tru self.numerical_severity = Finding.get_numerical_severity(self.severity) # Synchronize cvssv3 score using cvssv3 vector + if self.cvssv3: try: - cvss_data = parse_cvss_data(self.cvssv3) if cvss_data: - self.cvssv3 = cvss_data.get("vector") - self.cvssv3_score = cvss_data.get("score") + self.cvssv3 = cvss_data.get("cvssv3") + self.cvssv3_score = cvss_data.get("cvssv3_score") except Exception as ex: logger.warning("Can't compute cvssv3 score for finding id %i. Invalid cvssv3 vector found: '%s'. Exception: %s.", self.id, self.cvssv3, ex) @@ -2726,6 +2747,18 @@ def save(self, dedupe_option=True, rules_option=True, product_grading_option=Tru if self.pk is None: self.cvssv3 = None + # behaviour for CVVS4 is slightly different. Extracting thsi into a method would lead to probabl hard to read code + if self.cvssv4: + try: + cvss_data = parse_cvss_data(self.cvssv4) + if cvss_data: + self.cvssv4 = cvss_data.get("cvssv4") + self.cvssv4_score = cvss_data.get("cvssv4_score") + + except Exception as ex: + logger.warning("Can't compute cvssv4 score for finding id %i. Invalid cvssv4 vector found: '%s'. Exception: %s.", self.id, self.cvssv4, ex) + self.cvssv4 = None + self.set_hash_code(dedupe_option) if self.pk is None: diff --git a/dojo/templates/dojo/view_finding.html b/dojo/templates/dojo/view_finding.html index ba973b1ba34..fd4ffe9c8e1 100755 --- a/dojo/templates/dojo/view_finding.html +++ b/dojo/templates/dojo/view_finding.html @@ -8,9 +8,6 @@ {% load get_endpoint_status %} {% block add_styles %} {{ block.super }} - .tooltip-inner { - max-width: 650px; - } {% endblock %} {% block add_css_before %} {{ block.super }} @@ -292,16 +289,11 @@

{% if finding.severity %} - {% if finding.cvssv3 %} - - {% endif %} {{ finding.severity_display }} - {% if finding.cvssv3_score %} - ({{ finding.cvssv3_score }}) - {% endif %} - {% if finding.cvssv3 %} - + {% if system_settings.enable_cvss4_display and finding.cvssv4_score or system_settings.enable_cvss3_display and finding.cvssv3_score %} + + ({% if system_settings.enable_cvss4_display and finding.cvssv4_score %}{{ finding.cvssv4_score }}{% if system_settings.enable_cvss3_display and finding.cvssv3_score %}, {% endif %}{% endif %}{% if system_settings.enable_cvss3_display and finding.cvssv3_score %}{{ finding.cvssv3_score }}{% endif %}) {% endif %} {% else %} Unknown @@ -760,7 +752,7 @@

Similar Findings ({{ similar_findings.paginator.count }} {% endif %} - + {% endif %} {% comment %} Add a form to (ab)use to submit any actions related to similar/duplicates as POST requests {% endcomment %}