Skip to content

Commit

Permalink
fix: fix severity lookup in Qualys parser (#10205)
Browse files Browse the repository at this point in the history
* fix: fix severity lookup in Qualys parser

Fixes a bug in the Qualys parser where the severity lookup was not correctly handling integer values. This caused incorrect severity values to be returned. The fix updates the severity lookup to convert the severity value to an integer before performing the lookup.

* Add unittest for qualys parser get_severity

* Add non-legacy severity parsing unittest

* Fix severity lookup order: first 'SEVERITY' then fall back to 'CVSS_VALUE'

* Remove fallback to cvss_value from 'severity' extraction
  • Loading branch information
nv-pipo authored May 31, 2024
1 parent b14fdc1 commit 20176f2
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 38 deletions.
74 changes: 37 additions & 37 deletions dojo/tools/qualys/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,41 +56,42 @@
"Vuln": "CONFIRMED",
}

# Severity mapping taken from
# https://qualysguard.qg2.apps.qualys.com/portal-help/en/malware/knowledgebase/severity_levels.htm
LEGACY_SEVERITY_LOOKUP = {
1: "Informational",
2: "Low",
3: "Medium",
4: "High",
5: "Critical",
}
NON_LEGACY_SEVERITY_LOOKUP = {
"Informational": "Low",
"Low": "Low",
"Medium": "Medium",
"High": "High",
"Critical": "High",
}


def get_severity(severity_value_str: str | None) -> str:
severity_value: int = int(severity_value_str or -1)

sev: str = LEGACY_SEVERITY_LOOKUP.get(severity_value, "Unknown")

def get_severity(severity_value: int, cvss_value: float) -> str:
legacy_severity_lookup = {
1: "Informational",
2: "Low",
3: "Medium",
4: "High",
5: "Critical",
}
# Severity mapping taken from
# https://qualysguard.qg2.apps.qualys.com/portal-help/en/malware/knowledgebase/severity_levels.htm
qualys_severity_lookup = {
1: "Low",
2: "Low",
3: "Medium",
4: "High",
5: "High",
}

if settings.USE_QUALYS_LEGACY_SEVERITY_PARSING:
# Non legacy severity is a subset of legacy severity, retrieve it from lookup
if not settings.USE_QUALYS_LEGACY_SEVERITY_PARSING:
sev: str = NON_LEGACY_SEVERITY_LOOKUP.get(sev, "Unknown")

# If we still don't have a severity, default to Informational
if sev == "Unknown":
logger.warning(
"Could not determine severity from severity_value_str: %s",
severity_value_str,
)
sev = "Informational"
if cvss_value is not None and cvss_value > 0:
if 0.1 <= float(cvss_value) <= 3.9:
sev = "Low"
elif 4.0 <= float(cvss_value) <= 6.9:
sev = "Medium"
elif 7.0 <= float(cvss_value) <= 8.9:
sev = "High"
elif float(cvss_value) >= 9.0:
sev = "Critical"
elif severity_value is not None:
sev = legacy_severity_lookup.get(severity_value, "Informational")
return sev
else:
return qualys_severity_lookup.get(severity_value, "Informational")

return sev


def htmltext(blob):
Expand Down Expand Up @@ -243,10 +244,9 @@ def parse_finding(host, tree):
}
_temp["cve"] = "\n".join(list(_cl.keys()))
_temp["links"] = "\n".join(list(_cl.values()))
# The CVE in Qualys report might not have a CVSS score, so findings are informational by default
# unless we can find map to a Severity OR a CVSS score from the
# findings detail.
sev = get_severity(vuln_item.findtext("SEVERITY"), _temp.get("CVSS_value", None))

# Generate severity from number in XML's 'SEVERITY' field, if not present default to 'Informational'
sev = get_severity(vuln_item.findtext("SEVERITY"))
finding = None
if _temp_cve_details:
refs = "\n".join(list(_cl.values()))
Expand Down
35 changes: 34 additions & 1 deletion unittests/tools/test_qualys_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def parse_file_with_multiple_vuln_has_multiple_findings(self):
self.assertEqual(finding_cvssv3_vector.cvssv3,
"CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H")
self.assertEqual(
finding_cvssv3_vector.severity, "Critical"
finding_cvssv3_vector.severity, "High"
)
return finding

Expand Down Expand Up @@ -178,3 +178,36 @@ def test_parse_file_with_cvss_values_and_scores(self):
self.assertEqual(
finding_no_cvssv3_at_detection.cvssv3_score, 9.0
)

def test_get_severity_legacy(self):
with open(get_unit_tests_path() + "/scans/qualys/Qualys_Sample_Report.xml") as testfile:
parser = QualysParser()
findings = parser.get_findings(testfile, Test())
counts = {}
for finding in findings:
counts[finding.severity] = counts.get(finding.severity, 0) + 1
expected_counts = {
"Informational": 177,
"Low": 65,
"Medium": 46,
"High": 6,
"Critical": 7,
}

self.assertEqual(expected_counts, counts)

@override_settings(USE_QUALYS_LEGACY_SEVERITY_PARSING=False)
def test_get_severity(self):
with open(get_unit_tests_path() + "/scans/qualys/Qualys_Sample_Report.xml") as testfile:
parser = QualysParser()
findings = parser.get_findings(testfile, Test())
counts = {}
for finding in findings:
counts[finding.severity] = counts.get(finding.severity, 0) + 1
expected_counts = {
"Low": 242,
"Medium": 46,
"High": 13,
}

self.assertEqual(expected_counts, counts)

0 comments on commit 20176f2

Please sign in to comment.