Skip to content

Commit fdcdc34

Browse files
authored
update invicti parser to use FirstSeenDate (#14610)
* update invicti parser to use FirstSeenDate * invicti: respect USE_FIRST_SEEN * update invicti test * ruff * update invicti test to use Generated scan date
1 parent 701d5cd commit fdcdc34

2 files changed

Lines changed: 73 additions & 15 deletions

File tree

dojo/tools/netsparker/parser.py

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,25 @@
1010
from dojo.tools.locations import LocationData
1111

1212

13+
def _parse_date(date_str):
14+
"""Parse a Netsparker/Invicti date string into a date object."""
15+
if not date_str:
16+
return None
17+
try:
18+
if "UTC" in date_str:
19+
return datetime.datetime.strptime(
20+
date_str.split(" ")[0], "%d/%m/%Y",
21+
).date()
22+
return datetime.datetime.strptime(
23+
date_str, "%d/%m/%Y %H:%M %p",
24+
).date()
25+
except ValueError:
26+
try:
27+
return date_parser.parse(date_str).date()
28+
except (ValueError, date_parser.ParserError):
29+
return None
30+
31+
1332
class NetsparkerParser:
1433
def get_scan_types(self):
1534
return ["Netsparker Scan"]
@@ -27,20 +46,7 @@ def get_findings(self, filename, test):
2746
except Exception:
2847
data = json.loads(tree)
2948
dupes = {}
30-
try:
31-
if "UTC" in data["Generated"]:
32-
scan_date = datetime.datetime.strptime(
33-
data["Generated"].split(" ")[0], "%d/%m/%Y",
34-
).date()
35-
else:
36-
scan_date = datetime.datetime.strptime(
37-
data["Generated"], "%d/%m/%Y %H:%M %p",
38-
).date()
39-
except ValueError:
40-
try:
41-
scan_date = date_parser.parse(data["Generated"])
42-
except date_parser.ParserError:
43-
scan_date = None
49+
scan_date = _parse_date(data.get("Generated"))
4450

4551
for item in data["Vulnerabilities"]:
4652
title = item["Name"]
@@ -62,6 +68,7 @@ def get_findings(self, filename, test):
6268
dupe_key = title
6369
request = item["HttpRequest"].get("Content", None)
6470
response = item["HttpResponse"].get("Content", None)
71+
finding_date = (_parse_date(item.get("FirstSeenDate")) or scan_date) if settings.USE_FIRST_SEEN else scan_date
6572

6673
finding = Finding(
6774
title=title,
@@ -70,7 +77,7 @@ def get_findings(self, filename, test):
7077
severity=sev.title(),
7178
mitigation=mitigation,
7279
impact=impact,
73-
date=scan_date,
80+
date=finding_date,
7481
references=references,
7582
cwe=cwe,
7683
static_finding=True,

unittests/tools/test_netsparker_parser.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11

2+
from django.test import override_settings
3+
24
from dojo.models import Test
35
from dojo.tools.netsparker.parser import NetsparkerParser
46
from unittests.dojo_test_case import DojoTestCase, get_unit_tests_scans_path
@@ -7,6 +9,7 @@
79
class TestNetsparkerParser(DojoTestCase):
810

911
def test_parse_file_with_one_finding(self):
12+
"""With USE_FIRST_SEEN=False (default), date should come from Generated (scan date)."""
1013
with (get_unit_tests_scans_path("netsparker") / "netsparker_one_finding.json").open(encoding="utf-8") as testfile:
1114
parser = NetsparkerParser()
1215
findings = parser.get_findings(testfile, Test())
@@ -16,6 +19,7 @@ def test_parse_file_with_one_finding(self):
1619
finding = findings[0]
1720
self.assertEqual("Medium", finding.severity)
1821
self.assertEqual(16, finding.cwe)
22+
# Generated date is "25/06/2021 09:59 AM"
1923
self.assertEqual("25/06/2021", finding.date.strftime("%d/%m/%Y"))
2024
self.assertIsNotNone(finding.description)
2125
self.assertGreater(len(finding.description), 0)
@@ -24,7 +28,23 @@ def test_parse_file_with_one_finding(self):
2428
location = self.get_unsaved_locations(finding)[0]
2529
self.assertEqual(str(location), "http://php.testsparker.com/auth/login.php")
2630

31+
@override_settings(USE_FIRST_SEEN=True)
32+
def test_parse_file_with_one_finding_first_seen(self):
33+
"""With USE_FIRST_SEEN=True, date should come from FirstSeenDate."""
34+
with (get_unit_tests_scans_path("netsparker") / "netsparker_one_finding.json").open(encoding="utf-8") as testfile:
35+
parser = NetsparkerParser()
36+
findings = parser.get_findings(testfile, Test())
37+
self.assertEqual(1, len(findings))
38+
self.validate_locations(findings)
39+
with self.subTest(i=0):
40+
finding = findings[0]
41+
self.assertEqual("Medium", finding.severity)
42+
self.assertEqual(16, finding.cwe)
43+
# FirstSeenDate is "16/06/2021 12:30 PM"
44+
self.assertEqual("16/06/2021", finding.date.strftime("%d/%m/%Y"))
45+
2746
def test_parse_file_with_multiple_finding(self):
47+
"""With USE_FIRST_SEEN=False (default), dates should come from Generated (scan date)."""
2848
with (get_unit_tests_scans_path("netsparker") / "netsparker_many_findings.json").open(encoding="utf-8") as testfile:
2949
parser = NetsparkerParser()
3050
findings = parser.get_findings(testfile, Test())
@@ -34,6 +54,7 @@ def test_parse_file_with_multiple_finding(self):
3454
finding = findings[0]
3555
self.assertEqual("Medium", finding.severity)
3656
self.assertEqual(16, finding.cwe)
57+
# Generated date is "25/06/2021 10:00 AM"
3758
self.assertEqual("25/06/2021", finding.date.strftime("%d/%m/%Y"))
3859
self.assertIsNotNone(finding.description)
3960
self.assertGreater(len(finding.description), 0)
@@ -66,6 +87,22 @@ def test_parse_file_with_multiple_finding(self):
6687
location = self.get_unsaved_locations(finding)[0]
6788
self.assertEqual(str(location), "http://php.testsparker.com")
6889

90+
@override_settings(USE_FIRST_SEEN=True)
91+
def test_parse_file_with_multiple_finding_first_seen(self):
92+
"""With USE_FIRST_SEEN=True, dates should come from FirstSeenDate."""
93+
with (get_unit_tests_scans_path("netsparker") / "netsparker_many_findings.json").open(encoding="utf-8") as testfile:
94+
parser = NetsparkerParser()
95+
findings = parser.get_findings(testfile, Test())
96+
self.assertEqual(16, len(findings))
97+
with self.subTest(i=0):
98+
finding = findings[0]
99+
# FirstSeenDate is "16/06/2021 12:30 PM"
100+
self.assertEqual("16/06/2021", finding.date.strftime("%d/%m/%Y"))
101+
with self.subTest(i=2):
102+
finding = findings[2]
103+
# FirstSeenDate is "15/06/2021 01:44 PM"
104+
self.assertEqual("15/06/2021", finding.date.strftime("%d/%m/%Y"))
105+
69106
def test_parse_file_issue_9816(self):
70107
with (get_unit_tests_scans_path("netsparker") / "issue_9816.json").open(encoding="utf-8") as testfile:
71108
parser = NetsparkerParser()
@@ -91,6 +128,7 @@ def test_parse_file_issue_10311(self):
91128
self.assertEqual("03/02/2019", finding.date.strftime("%d/%m/%Y"))
92129

93130
def test_parse_file_issue_11020(self):
131+
"""With USE_FIRST_SEEN=False (default), date should come from Generated (scan date)."""
94132
with (get_unit_tests_scans_path("netsparker") / "issue_11020.json").open(encoding="utf-8") as testfile:
95133
parser = NetsparkerParser()
96134
findings = parser.get_findings(testfile, Test())
@@ -100,4 +138,17 @@ def test_parse_file_issue_11020(self):
100138
finding = findings[0]
101139
self.assertEqual("Low", finding.severity)
102140
self.assertEqual(205, finding.cwe)
141+
# Generated date is "2024-10-08 02:33 PM"
103142
self.assertEqual("08/10/2024", finding.date.strftime("%d/%m/%Y"))
143+
144+
@override_settings(USE_FIRST_SEEN=True)
145+
def test_parse_file_issue_11020_first_seen(self):
146+
"""With USE_FIRST_SEEN=True, date should come from FirstSeenDate."""
147+
with (get_unit_tests_scans_path("netsparker") / "issue_11020.json").open(encoding="utf-8") as testfile:
148+
parser = NetsparkerParser()
149+
findings = parser.get_findings(testfile, Test())
150+
self.assertEqual(3, len(findings))
151+
with self.subTest(i=0):
152+
finding = findings[0]
153+
# FirstSeenDate is "2024-07-23 05:32 PM"
154+
self.assertEqual("23/07/2024", finding.date.strftime("%d/%m/%Y"))

0 commit comments

Comments
 (0)