diff --git a/docs/content/releases/os_upgrading/2.59.md b/docs/content/releases/os_upgrading/2.59.md index c9921cf6be8..a496db52ae2 100644 --- a/docs/content/releases/os_upgrading/2.59.md +++ b/docs/content/releases/os_upgrading/2.59.md @@ -41,3 +41,7 @@ As announced in DefectDojo 2.57.0, the Stub Findings feature has been removed. T Any requests to this endpoint will now return a 404 Not Found error. The Stub Findings UI is no longer available. For more information, check the [Release Notes](https://github.com/DefectDojo/django-DefectDojo/releases/tag/2.59.0). + +## Bug Fixes + +- **Qualys Parser**: Fixed an issue where findings with the same QID but different ports were being collapsed into a single finding. Each QID+port combination now correctly gets its own endpoint, preserving port-level granularity without affecting finding titles or deduplication. ([#13682](https://github.com/DefectDojo/django-DefectDojo/issues/13682)) diff --git a/dojo/tools/qualys/parser.py b/dojo/tools/qualys/parser.py index d1c5f7c1dd4..2030ae7b124 100644 --- a/dojo/tools/qualys/parser.py +++ b/dojo/tools/qualys/parser.py @@ -354,12 +354,14 @@ def parse_finding(host, tree): finding.cvssv3_score = temp.get("CVSS_value") finding.verified = True # manage endpoint/location + host = issue_row["fqdn"] or issue_row["ip_address"] + port = temp.get("port_status") if settings.V3_FEATURE_LOCATIONS: - location = LocationData.url(host=issue_row["fqdn"]) if issue_row["fqdn"] else LocationData.url(host=issue_row["ip_address"]) + location = LocationData.url(host=host, port=int(port) if port else None) finding.unsaved_locations = [location] else: # TODO: Delete this after the move to Locations - location = Endpoint(host=issue_row["fqdn"]) if issue_row["fqdn"] else Endpoint(host=issue_row["ip_address"]) + location = Endpoint(host=host, port=int(port) if port else None) finding.unsaved_endpoints = [location] finding.unsaved_vulnerability_ids = temp.get("cve_list", []) ret_rows.append(finding) diff --git a/unittests/scans/qualys/qualys_same_qid_different_ports.xml b/unittests/scans/qualys/qualys_same_qid_different_ports.xml new file mode 100644 index 00000000000..9e4c7fe29d1 --- /dev/null +++ b/unittests/scans/qualys/qualys_same_qid_different_ports.xml @@ -0,0 +1,68 @@ + + +
+ + + + + 192.168.1.1 + 192.168.1.1 + + + +
+ + + + 12345 + <![CDATA[Test Vulnerability]]> + 3 + + 2024-01-01T00:00:00Z + + + + + + + + 192.168.1.1 + IP + + + 2024-01-01T00:00:00Z + + + 12345 + Practice + 80 + false + + 2024-01-01T00:00:00Z + 2024-01-01T00:00:00Z + 1 + + + 12345 + Practice + 443 + true + + 2024-01-01T00:00:00Z + 2024-01-01T00:00:00Z + 1 + + + 12345 + Practice + 8080 + false + + 2024-01-01T00:00:00Z + 2024-01-01T00:00:00Z + 1 + + + + +
\ No newline at end of file diff --git a/unittests/tools/test_qualys_parser.py b/unittests/tools/test_qualys_parser.py index 060b6b9fcc0..e8e6d838a78 100644 --- a/unittests/tools/test_qualys_parser.py +++ b/unittests/tools/test_qualys_parser.py @@ -239,3 +239,29 @@ def test_get_severity(self): } self.assertEqual(expected_counts, counts) + + def test_parse_file_same_qid_different_ports_has_separate_endpoints(self): + """Test that findings with same QID but different ports get separate endpoints. + Regression test for https://github.com/DefectDojo/django-DefectDojo/issues/13682 + """ + with ( + get_unit_tests_scans_path("qualys") / "qualys_same_qid_different_ports.xml").open(encoding="utf-8", + ) as testfile: + parser = QualysParser() + findings = parser.get_findings(testfile, Test()) + self.validate_locations(findings) + # Same QID on 3 different ports should produce 3 separate findings + self.assertEqual(3, len(findings)) + # All findings should have the same title (QID unchanged) + for finding in findings: + self.assertEqual(finding.title, "QID-12345 | Test Vulnerability") + # Each finding should have a different port on its endpoint + ports = set() + for finding in findings: + locations = self.get_unsaved_locations(finding) + self.assertEqual(1, len(locations)) + self.assertEqual(locations[0].host, "testhost.example.com") + ports.add(locations[0].port) + # All 3 ports should be present + self.assertEqual({80, 443, 8080}, ports) +