Skip to content

Commit 872ec2b

Browse files
committed
🔨 Rework RustyHog to fix #10584
1 parent 45b5383 commit 872ec2b

3 files changed

Lines changed: 71 additions & 64 deletions

File tree

docs/content/en/connecting_your_tools/parsers/file/rusty_hog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,7 @@ DefectDojo currently supports the parsing of the following Rusty Hog JSON output
1414
RustyHog scans only one target at a time. This is not efficient if you want to scan all targets (e.g. all JIRA tickets) and upload each single report to DefectDojo.
1515
[Rusty-Hog-Wrapper](https://github.com/manuel-sommer/Rusty-Hog-Wrapper) deals with this and scans a whole JIRA Project or Confluence Space, merges the findings into a valid file which can be uploaded to DefectDojo. (This is no official recommendation from DefectDojo, but rather a pointer in a direction on how to use this vulnerability scanner in a more efficient way.)
1616

17+
You can either select "Rusty Hog Scan" or directly the specific sub scanner (e.g. "Duroc Hog Scan"). If you choose "Rusty Hog Scan", we reommend to reupload scans into the same test. For more information look at [this issue](https://github.com/DefectDojo/django-DefectDojo/issues/10584).
18+
1719
### Sample Scan Data
1820
Sample Rusty Hog parser scans can be found [here](https://github.com/DefectDojo/django-DefectDojo/tree/master/unittests/scans/rusty_hog).

dojo/tools/rusty_hog/parser.py

Lines changed: 53 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -6,68 +6,66 @@
66

77
class RustyhogParser:
88
def get_scan_types(self):
9-
return ["Rusty Hog Scan"]
9+
return ["Rusty Hog Scan", "Choctaw Hog Scan", "Duroc Hog Scan", "Gottingen Hog Scan", "Essex Hog Scan"]
1010

1111
def get_label_for_scan_types(self, scan_type):
1212
return scan_type # no custom label for now
1313

1414
def get_description_for_scan_types(self, scan_type):
1515
return "Rusty Hog Scan - JSON Report"
1616

17-
def get_findings(self, json_output, test):
18-
tree = json.load(json_output)
19-
return self.get_items(tree, test)
20-
21-
def parse_json(self, json_output):
22-
return json.load(json_output)
23-
24-
def get_items(self, json_output, scanner, test):
17+
def get_findings(self, filename, test):
18+
if filename:
19+
tree = filename.read()
20+
try:
21+
json_output = json.loads(str(tree, "utf-8"))
22+
except Exception:
23+
json_output = json.loads(tree)
24+
if not json_output:
25+
json_output = []
2526
items = {}
26-
findings = self.__getitem(
27-
vulnerabilities=self.parse_json(json_output), scanner=scanner,
28-
)
27+
findings = self.__getitem(vulnerabilities=json_output, scanner=test)
2928
for finding in findings:
3029
unique_key = f"Finding {finding}"
3130
items[unique_key] = finding
3231
return list(items.values())
3332

3433
def get_tests(self, scan_type, handle):
35-
tree = self.parse_json(handle)
34+
tree = json.load(handle)
3635
tests = []
37-
parsername = "Rusty Hog"
38-
for node in tree:
39-
if (
40-
"commit" in node
41-
or "commitHash" in node
42-
or "parent_commit_hash" in node
43-
or "old_file_id" in node
44-
or "new_file_id" in node
45-
):
46-
parsername = "Choctaw Hog"
47-
break
48-
if "linenum" in node or "diff" in node:
49-
parsername = "Duroc Hog"
50-
break
51-
if "issue_id" in node or "location" in node:
52-
parsername = "Gottingen Hog"
53-
break
54-
if "page_id" in node:
55-
parsername = "Essex Hog"
56-
break
36+
if scan_type == "Rusty Hog Scan":
37+
parsername = "Rusty Hog"
38+
for node in tree:
39+
if (
40+
"commit" in node
41+
or "commitHash" in node
42+
or "parent_commit_hash" in node
43+
or "old_file_id" in node
44+
or "new_file_id" in node
45+
):
46+
parsername = "Choctaw Hog"
47+
break
48+
if "linenum" in node or "diff" in node:
49+
parsername = "Duroc Hog"
50+
break
51+
if "issue_id" in node or "location" in node:
52+
parsername = "Gottingen Hog"
53+
break
54+
if "page_id" in node:
55+
parsername = "Essex Hog"
56+
break
57+
else:
58+
parsername = scan_type.replace(" Scan", "")
5759
test = ParserTest(
5860
name=parsername,
5961
parser_type=parsername,
6062
version="",
6163
)
62-
if (
63-
parsername == "Rusty Hog"
64-
): # The outputfile is empty. A subscanner can't be classified
64+
if parsername == "Rusty Hog": # The outputfile is empty. A subscanner can't be classified
6565
test.description = "The exact scanner within Rusty Hog could not be determined due to missing information within the scan result."
6666
else:
6767
test.description = parsername
68-
test.findings = self.__getitem(
69-
vulnerabilities=tree, scanner=parsername,
70-
)
68+
test.findings = self.__getitem(vulnerabilities=tree, scanner=parsername)
7169
tests.append(test)
7270
return tests
7371

@@ -85,13 +83,15 @@ def __getitem(self, vulnerabilities, scanner):
8583
break
8684
if scanner == "Choctaw Hog":
8785
"""Choctaw Hog"""
86+
if vulnerability.get("commitHash") is None:
87+
raise ValueError("You chose the wrong scan type: " + scanner)
8888
found_secret_string = str(vulnerability.get("stringsFound") or "")
89-
description += f"**This string was found:** {found_secret_string}"
89+
description += f"\n**This string was found:** {found_secret_string}"
9090
if vulnerability.get("commit") is not None:
9191
description += "\n**Commit message:** {}".format(
9292
vulnerability.get("commit"),
9393
)
94-
if vulnerability.get("commitHash") is not None:
94+
if vulnerability.get("commitHash"):
9595
description += "\n**Commit hash:** {}".format(
9696
vulnerability.get("commitHash"),
9797
)
@@ -121,13 +121,15 @@ def __getitem(self, vulnerabilities, scanner):
121121
)
122122
elif scanner == "Duroc Hog":
123123
"""Duroc Hog"""
124+
if vulnerability.get("linenum") is None:
125+
raise ValueError("You chose the wrong scan type: " + scanner)
124126
found_secret_string = str(vulnerability.get("stringsFound") or "")
125-
description += f"**This string was found:** {found_secret_string}"
127+
description += f"\n**This string was found:** {found_secret_string}"
126128
if vulnerability.get("path") is not None:
127129
description += "\n**Path of Issue:** {}".format(
128130
vulnerability.get("path"),
129131
)
130-
if vulnerability.get("linenum") is not None:
132+
if vulnerability.get("linenum"):
131133
description += "\n**Linenum of Issue:** {}".format(
132134
vulnerability.get("linenum"),
133135
)
@@ -137,9 +139,11 @@ def __getitem(self, vulnerabilities, scanner):
137139
)
138140
elif scanner == "Gottingen Hog":
139141
"""Gottingen Hog"""
142+
if vulnerability.get("issue_id") is None:
143+
raise ValueError("You chose the wrong scan type: " + scanner)
140144
found_secret_string = str(vulnerability.get("stringsFound") or "")
141-
description += f"**This string was found:** {found_secret_string}"
142-
if vulnerability.get("issue_id") is not None:
145+
description += f"\n**This string was found:** {found_secret_string}"
146+
if vulnerability.get("issue_id"):
143147
description += "\n**JIRA Issue ID:** {}".format(
144148
vulnerability.get("issue_id"),
145149
)
@@ -152,9 +156,11 @@ def __getitem(self, vulnerabilities, scanner):
152156
vulnerability.get("url"), vulnerability.get("url"),
153157
)
154158
elif scanner == "Essex Hog":
159+
if vulnerability.get("page_id") is None:
160+
raise ValueError("You chose the wrong scan type: " + scanner)
155161
found_secret_string = str(vulnerability.get("stringsFound") or "")
156-
description += f"**This string was found:** {found_secret_string}"
157-
if vulnerability.get("page_id") is not None:
162+
description += f"\n**This string was found:** {found_secret_string}"
163+
if vulnerability.get("page_id"):
158164
description += "\n**Confluence URL:** [{}]({})".format(
159165
vulnerability.get("url"), vulnerability.get("url"),
160166
)

unittests/tools/test_rusty_hog_parser.py

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from dojo.models import Test
21
from dojo.tools.rusty_hog.parser import RustyhogParser
32
from unittests.dojo_test_case import DojoTestCase, get_unit_tests_scans_path
43

@@ -7,25 +6,25 @@ class TestRustyhogParser(DojoTestCase):
76
def test_parse_file_with_no_vuln_has_no_finding_choctawhog(self):
87
with open(get_unit_tests_scans_path("rusty_hog") / "choctawhog_no_vuln.json", encoding="utf-8") as testfile:
98
parser = RustyhogParser()
10-
findings = parser.get_items(testfile, "Rusty Hog", Test()) # The outputfile is empty. A subscanner can't be classified
9+
findings = parser.get_findings(testfile, "Rusty Hog") # The outputfile is empty. A subscanner can't be classified
1110
self.assertEqual(0, len(findings))
1211

1312
def test_parse_file_with_one_vuln_has_one_finding_choctawhog(self):
1413
with open(get_unit_tests_scans_path("rusty_hog") / "choctawhog_one_vuln.json", encoding="utf-8") as testfile:
1514
parser = RustyhogParser()
16-
findings = parser.get_items(testfile, "Choctaw Hog", Test())
15+
findings = parser.get_findings(testfile, "Choctaw Hog")
1716
self.assertEqual(1, len(findings))
1817

1918
def test_parse_file_with_multiple_vuln_has_multiple_finding_choctawhog(self):
2019
with open(get_unit_tests_scans_path("rusty_hog") / "choctawhog_many_vulns.json", encoding="utf-8") as testfile:
2120
parser = RustyhogParser()
22-
findings = parser.get_items(testfile, "Choctaw Hog", Test())
21+
findings = parser.get_findings(testfile, "Choctaw Hog")
2322
self.assertEqual(13, len(findings))
2423

2524
def test_parse_file_with_multiple_vuln_has_multiple_finding_choctawhog_content(self):
2625
with open(get_unit_tests_scans_path("rusty_hog") / "choctawhog_many_vulns.json", encoding="utf-8") as testfile:
2726
parser = RustyhogParser()
28-
findings = parser.get_items(testfile, "Choctaw Hog", Test())
27+
findings = parser.get_findings(testfile, "Choctaw Hog")
2928
self.assertEqual(findings[0].title, "Email address found in Git path .github/workflows/main.yml (a7bce96377c4ff2ac16cd51fb0da7fe7ea678829)")
3029
self.assertIn("**This string was found:** ['dojo-helpers@this-repo.com']", findings[0].description)
3130
self.assertIn("**Commit message:** removing action", findings[0].description)
@@ -38,25 +37,25 @@ def test_parse_file_with_multiple_vuln_has_multiple_finding_choctawhog_content(s
3837
def test_parse_file_with_no_vuln_has_no_finding_duorchog(self):
3938
with open(get_unit_tests_scans_path("rusty_hog") / "durochog_no_vuln.json", encoding="utf-8") as testfile:
4039
parser = RustyhogParser()
41-
findings = parser.get_items(testfile, "Rusty Hog", Test()) # The outputfile is empty. A subscanner can't be classified
40+
findings = parser.get_findings(testfile, "Rusty Hog") # The outputfile is empty. A subscanner can't be classified
4241
self.assertEqual(0, len(findings))
4342

4443
def test_parse_file_with_one_vuln_has_one_finding_durochog(self):
4544
with open(get_unit_tests_scans_path("rusty_hog") / "durochog_one_vuln.json", encoding="utf-8") as testfile:
4645
parser = RustyhogParser()
47-
findings = parser.get_items(testfile, "Duroc Hog", Test())
46+
findings = parser.get_findings(testfile, "Duroc Hog")
4847
self.assertEqual(1, len(findings))
4948

5049
def test_parse_file_with_multiple_vuln_has_multiple_finding_durochog(self):
5150
with open(get_unit_tests_scans_path("rusty_hog") / "durochog_many_vulns.json", encoding="utf-8") as testfile:
5251
parser = RustyhogParser()
53-
findings = parser.get_items(testfile, "Duroc Hog", Test())
52+
findings = parser.get_findings(testfile, "Duroc Hog")
5453
self.assertEqual(4, len(findings))
5554

5655
def test_parse_file_with_multiple_vuln_has_multiple_finding_durochog_content(self):
5756
with open(get_unit_tests_scans_path("rusty_hog") / "durochog_many_vulns.json", encoding="utf-8") as testfile:
5857
parser = RustyhogParser()
59-
findings = parser.get_items(testfile, "Duroc Hog", Test())
58+
findings = parser.get_findings(testfile, "Duroc Hog")
6059
self.assertEqual(findings[0].title, "password (Password) found in path /scan_folder/unittests/scans/sonarqube/sonar-no-finding.html")
6160
self.assertIn("**This string was found:** ['password = getEncryptedPass()']", findings[0].description)
6261
self.assertIn("**Path of Issue:** /scan_folder/unittests/scans/sonarqube/sonar-no-finding.html", findings[0].description)
@@ -67,25 +66,25 @@ def test_parse_file_with_multiple_vuln_has_multiple_finding_durochog_content(sel
6766
def test_parse_file_with_no_vuln_has_no_finding_gottingenhog(self):
6867
with open(get_unit_tests_scans_path("rusty_hog") / "gottingenhog_no_vuln.json", encoding="utf-8") as testfile:
6968
parser = RustyhogParser()
70-
findings = parser.get_items(testfile, "Rusty Hog", Test()) # The outputfile is empty. A subscanner can't be classified
69+
findings = parser.get_findings(testfile, "Rusty Hog") # The outputfile is empty. A subscanner can't be classified
7170
self.assertEqual(0, len(findings))
7271

7372
def test_parse_file_with_one_vuln_has_one_finding_gottingenhog(self):
7473
with open(get_unit_tests_scans_path("rusty_hog") / "gottingenhog_one_vuln.json", encoding="utf-8") as testfile:
7574
parser = RustyhogParser()
76-
findings = parser.get_items(testfile, "Gottingen Hog", Test())
75+
findings = parser.get_findings(testfile, "Gottingen Hog")
7776
self.assertEqual(1, len(findings))
7877

7978
def test_parse_file_with_multiple_vuln_has_multiple_finding_gottingenhog(self):
8079
with open(get_unit_tests_scans_path("rusty_hog") / "gottingenhog_many_vulns.json", encoding="utf-8") as testfile:
8180
parser = RustyhogParser()
82-
findings = parser.get_items(testfile, "Gottingen Hog", Test())
81+
findings = parser.get_findings(testfile, "Gottingen Hog")
8382
self.assertEqual(10, len(findings))
8483

8584
def test_parse_file_with_multiple_vuln_has_multiple_finding_gottingenhog_content(self):
8685
with open(get_unit_tests_scans_path("rusty_hog") / "gottingenhog_many_vulns.json", encoding="utf-8") as testfile:
8786
parser = RustyhogParser()
88-
findings = parser.get_items(testfile, "Gottingen Hog", Test())
87+
findings = parser.get_findings(testfile, "Gottingen Hog")
8988
self.assertEqual(findings[0].title, "password found in Jira ID TEST-123 (Issue Description)")
9089
self.assertIn("**This string was found:** ['password: jeans']", findings[0].description)
9190
self.assertIn("**JIRA Issue ID:** TEST-123", findings[0].description)
@@ -96,19 +95,19 @@ def test_parse_file_with_multiple_vuln_has_multiple_finding_gottingenhog_content
9695
def test_parse_file_with_no_vuln_has_no_finding_essexhog(self):
9796
with open(get_unit_tests_scans_path("rusty_hog") / "essexhog_no_vuln.json", encoding="utf-8") as testfile:
9897
parser = RustyhogParser()
99-
findings = parser.get_items(testfile, "Rusty Hog", Test()) # The outputfile is empty. A subscanner can't be classified
98+
findings = parser.get_findings(testfile, "Rusty Hog") # The outputfile is empty. A subscanner can't be classified
10099
self.assertEqual(0, len(findings))
101100

102101
def test_parse_file_with_one_vuln_has_one_finding_essexhog(self):
103102
with open(get_unit_tests_scans_path("rusty_hog") / "essexhog_one_vuln.json", encoding="utf-8") as testfile:
104103
parser = RustyhogParser()
105-
findings = parser.get_items(testfile, "Essex Hog", Test())
104+
findings = parser.get_findings(testfile, "Essex Hog")
106105
self.assertEqual(1, len(findings))
107106

108107
def test_parse_file_with_multiple_vuln_has_multiple_finding_essexhog(self):
109108
with open(get_unit_tests_scans_path("rusty_hog") / "essexhog_many_vulns.json", encoding="utf-8") as testfile:
110109
parser = RustyhogParser()
111-
findings = parser.get_items(testfile, "Essex Hog", Test())
110+
findings = parser.get_findings(testfile, "Essex Hog")
112111
self.assertEqual(3, len(findings))
113112
self.assertEqual("https://confluence.com/pages/viewpage.action?pageId=12345", findings[0].file_path)
114113
self.assertEqual("['-----BEGIN EC PRIVATE KEY-----']", findings[0].payload)
@@ -117,7 +116,7 @@ def test_parse_file_with_multiple_vuln_has_multiple_finding_essexhog(self):
117116
def test_parse_file_with_multiple_vuln_has_multiple_finding_essexhog_content(self):
118117
with open(get_unit_tests_scans_path("rusty_hog") / "essexhog_many_vulns.json", encoding="utf-8") as testfile:
119118
parser = RustyhogParser()
120-
findings = parser.get_items(testfile, "Essex Hog", Test())
119+
findings = parser.get_findings(testfile, "Essex Hog")
121120
self.assertEqual(findings[0].title, "SSH (EC) private key found in Confluence Page ID 12345")
122121
self.assertIn("-----BEGIN EC PRIVATE KEY-----", findings[0].description)
123122
self.assertIn("**Confluence URL:** [https://confluence.com/pages/viewpage.action?pageId=12345](https://confluence.com/pages/viewpage.action?pageId=12345)", findings[0].description)

0 commit comments

Comments
 (0)