Skip to content

Commit d8e440e

Browse files
authored
Adhere to new vulnerability scheme (#30)
* Secrets adheres to new vulnerability scheme * Tests for secrets scanning * SAST adheres to new vulnerability scheme * Generate empty testsuite on empty input report * Tests for SAST scanning * Fix last incorrect import
1 parent 7df8d24 commit d8e440e

12 files changed

Lines changed: 618 additions & 104 deletions

File tree

secscanner2junit/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77
from secscanner2junit.config import get_config, Config
88
from secscanner2junit.container_scanning import ContainerScanningParser
9-
from .sast import SastParser
10-
from .secrets import SecretsParser
9+
from secscanner2junit.sast import SastParser
10+
from secscanner2junit.secrets import SecretsParser
1111

1212

1313
class ScanType(enum.Enum):

secscanner2junit/sast.py

Lines changed: 23 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1-
from collections import defaultdict
2-
from random import randrange
3-
41
from junit_xml import TestSuite, TestCase
52

6-
# is it correct import changed from .parser ? I've get error ImportError: attempted relative import with no known parent package
73
from secscanner2junit.config import Config
84
from secscanner2junit.parser import Parser
5+
from secscanner2junit.vulnerability import SastVulnerability
96

107

118
# See following links to learn more about sast scanners and theirs output
@@ -16,90 +13,34 @@ def __init__(self, report, ts_name, config: Config):
1613
super().__init__(report, ts_name, config)
1714
self.p_type = "SAST"
1815

19-
def parse_findings(self, finding, time):
20-
output = ""
21-
properties = defaultdict(str)
22-
simple_props = ["id", "name", "message", "description", "severity", "confidence"]
23-
for prop in simple_props:
24-
try:
25-
prop_res = finding[prop]
26-
properties[prop] = prop_res
27-
output += "{prop}: {prop_res}\n".format(prop=prop, prop_res=prop_res)
28-
except KeyError:
29-
pass
30-
31-
try:
32-
url = finding['links'][0]['url']
33-
properties['url'] = url
34-
output += "url: {url}\n".format(url=url)
35-
except (KeyError, IndexError):
36-
pass
37-
38-
try:
39-
file = finding['location']['file']
40-
properties['file'] = file
41-
output += "file: {file}\n".format(file=file)
42-
except KeyError:
43-
pass
44-
45-
try:
46-
vclass = finding['location']['class']
47-
properties['class'] = vclass
48-
output += "class: {vclass}\n".format(vclass=vclass)
49-
except KeyError:
50-
pass
51-
52-
try:
53-
method = finding['location']['method']
54-
properties['method'] = method
55-
output += "method: {method}\n".format(method=method)
56-
except KeyError:
57-
pass
16+
def parse_vulnerability(self, raw_vulnerability):
17+
vulnerability = SastVulnerability(raw_vulnerability)
5818

59-
try:
60-
start_line = finding['location']['start_line']
61-
properties['start line'] = start_line
62-
output += "start line: {start_line}\n".format(start_line=start_line)
63-
except KeyError:
64-
pass
19+
tc = TestCase(name=vulnerability.get_testcase_name(),
20+
classname=self.p_type,
21+
file=vulnerability.get_location(),
22+
elapsed_sec=1)
6523

66-
try:
67-
end_line = finding['location']['end_line']
68-
properties['end line'] = end_line
69-
output += "end line: {end_line}\n".format(end_line=end_line)
70-
except KeyError:
71-
pass
72-
73-
try:
74-
output += "identifiers.name: {identifiers_name}\n".format(identifiers_name=finding['identifiers'][0]['name'])
75-
output += "identifiers.type: {identifiers_type}\n".format(identifiers_type=finding['identifiers'][0]['type'])
76-
output += "identifiers.value: {identifiers_value}\n".format(identifiers_value=finding['identifiers'][0]['value'])
77-
except KeyError:
78-
pass
79-
80-
f_type = finding['identifiers'][0]['name']
81-
82-
try:
83-
finding_id = finding['id']
84-
except KeyError:
85-
finding_id = str(randrange(1, 10000000))
86-
87-
if properties['name']:
88-
tc = TestCase(name=properties['name'] + " (ID: " + finding_id + ")", classname=self.p_type, file=properties['file'], elapsed_sec=time, line=properties['start_line'])
89-
else:
90-
tc = TestCase(name=f_type + " (ID: " + finding_id + ")", classname=self.p_type, file=properties['file'], elapsed_sec=time, line=properties['start_line'])
91-
tc.add_failure_info(message=properties['message'], output=output, failure_type=f_type)
24+
tc.add_failure_info(message=vulnerability.get_description(),
25+
output=vulnerability.get_output(),
26+
failure_type=vulnerability.get_failure_type())
9227
return tc
9328

9429
def parse(self):
95-
timing = 0
96-
findings = self.report['vulnerabilities']
97-
findings = self.config.suppress(findings)
98-
scanners = list(set(f['scanner']['name'] for f in findings))
30+
vulnerabilities = self.report['vulnerabilities']
31+
vulnerabilities = self.config.suppress(vulnerabilities)
32+
scanners = list(set(vuln['scanner']['name'] for vuln in vulnerabilities))
9933
testsuites = []
34+
10035
for scanner in scanners:
101-
rel_find = filter(lambda x: x['scanner']['name'] == scanner, findings)
102-
testcases = [self.parse_findings(finding, timing) for finding in rel_find]
36+
testcases = []
37+
relevant_vulns = filter(lambda x: x['scanner']['name'] == scanner, vulnerabilities)
38+
for vuln in relevant_vulns:
39+
testcases.append(self.parse_vulnerability(vuln))
40+
10341
testsuites.append(TestSuite(name=self.ts_name + scanner.replace(' ', '-'), test_cases=testcases))
104-
return testsuites
10542

43+
# If the report was empty, we generate an empty testsuite to return a valid Junit XML file
44+
if len(testsuites) == 0:
45+
testsuites.append(TestSuite(name=self.ts_name, test_cases=[]))
46+
return testsuites

secscanner2junit/secrets.py

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,36 @@
1-
from datetime import datetime as dt
2-
31
from junit_xml import TestSuite, TestCase
4-
5-
from .parser import Parser
2+
from secscanner2junit.config import Config
3+
from secscanner2junit.parser import Parser
4+
from secscanner2junit.vulnerability import SecretsVulnerability
65

76

87
class SecretsParser(Parser):
9-
def __init__(self, report, ts_name, config):
8+
def __init__(self, report, ts_name, config:Config):
109
super().__init__(report, ts_name, config)
1110
self.p_type = "Secrets"
1211

13-
def parse_findings(self, finding, time):
14-
name = finding['name']
15-
message = finding['message']
16-
location_file = finding['location']['file']
17-
location_line = finding['location']['start_line']
18-
tc = TestCase(name=name, classname=self.p_type, file=location_file, elapsed_sec=time, line=location_line)
19-
tc.add_failure_info(message=message, output=message)
12+
def parse_vulnerability(self, raw_vulnerability):
13+
vulnerability = SecretsVulnerability(raw_vulnerability)
14+
15+
tc = TestCase(name=vulnerability.get_testcase_name(),
16+
classname=self.p_type,
17+
file=vulnerability.get_location(),
18+
elapsed_sec=1)
19+
20+
tc.add_failure_info(message=vulnerability.get_description(),
21+
output=vulnerability.get_output(),
22+
failure_type=vulnerability.get_failure_type())
2023
return tc
2124

2225
def parse(self):
23-
version = self.report['scan']['scanner']['version']
24-
findings = self.report['vulnerabilities']
25-
start_time = self.report['scan']['start_time']
26-
end_time = self.report['scan']['end_time']
27-
timing = dt.strptime(end_time, '%Y-%m-%dT%H:%M:%S') - dt.strptime(start_time, '%Y-%m-%dT%H:%M:%S')
28-
testcases = [self.parse_findings(finding, timing.seconds) for finding in findings]
29-
ts = TestSuite(name=self.ts_name + '-' + version, test_cases=testcases, timestamp=start_time)
30-
return [ts]
26+
vulnerabilities = self.report['vulnerabilities']
27+
vulnerabilities = self.config.suppress(vulnerabilities)
28+
29+
testsuites = []
30+
testcases = []
31+
32+
for raw_vulnerability in vulnerabilities:
33+
testcases.append(self.parse_vulnerability(raw_vulnerability))
34+
35+
testsuites.append(TestSuite(name=self.ts_name, test_cases=testcases))
36+
return testsuites

secscanner2junit/vulnerability.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ def get_location(self):
120120
return self.__location.get_location()
121121

122122

123+
class SecretsVulnerability(SastVulnerability):
124+
def __init__(self, raw_vulnerability: dict):
125+
super().__init__(raw_vulnerability)
126+
127+
123128
class Identifier(_Base):
124129
def __init__(self, raw_identifier: dict):
125130
super().__init__(raw_identifier)
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
{
2+
"version": "14.0.4",
3+
"vulnerabilities": [
4+
{
5+
"id": "cccabffcfcf176e47f9138d7fbd763a83b310b071529b687cde3537a935cf251",
6+
"category": "sast",
7+
"name": "Spring CSRF unrestricted RequestMapping",
8+
"message": "Spring CSRF unrestricted RequestMapping",
9+
"description": "Unrestricted Spring's RequestMapping makes the method vulnerable to CSRF attacks",
10+
"cve": "86d80cd1d198812fc1ba6860a9e965e1:SPRING_CSRF_UNRESTRICTED_REQUEST_MAPPING:src/main/java/pl/com/softnet/example/springbootsoftnetexample/PingController.java:23",
11+
"severity": "Medium",
12+
"confidence": "High",
13+
"scanner": {
14+
"id": "find_sec_bugs",
15+
"name": "Find Security Bugs"
16+
},
17+
"location": {
18+
"file": "src/main/java/pl/com/softnet/example/springbootsoftnetexample/PingController.java",
19+
"start_line": 23,
20+
"class": "pl.com.softnet.example.springbootsoftnetexample.PingController",
21+
"method": "ping"
22+
},
23+
"identifiers": [
24+
{
25+
"type": "find_sec_bugs_type",
26+
"name": "Find Security Bugs-SPRING_CSRF_UNRESTRICTED_REQUEST_MAPPING",
27+
"value": "SPRING_CSRF_UNRESTRICTED_REQUEST_MAPPING",
28+
"url": "https://find-sec-bugs.github.io/bugs.htm#SPRING_CSRF_UNRESTRICTED_REQUEST_MAPPING"
29+
},
30+
{
31+
"type": "cwe",
32+
"name": "CWE-352",
33+
"value": "352",
34+
"url": "https://cwe.mitre.org/data/definitions/352.html"
35+
}
36+
]
37+
},
38+
{
39+
"id": "db914ce5737b49650ae650fc3b0fe38a531eadd8ea780f48a013419c4adec7f0",
40+
"category": "sast",
41+
"name": "Found Spring endpoint",
42+
"message": "Found Spring endpoint",
43+
"description": "pl.com.softnet.example.springbootsoftnetexample.PingController is a Spring endpoint (Controller)",
44+
"cve": "21254b1dfdebd6b8bbd05e4ed8a960c3:SPRING_ENDPOINT:src/main/java/pl/com/softnet/example/springbootsoftnetexample/PingController.java:23",
45+
"severity": "Low",
46+
"confidence": "Low",
47+
"scanner": {
48+
"id": "find_sec_bugs",
49+
"name": "Find Security Bugs"
50+
},
51+
"location": {
52+
"file": "src/main/java/pl/com/softnet/example/springbootsoftnetexample/PingController.java",
53+
"start_line": 23,
54+
"class": "pl.com.softnet.example.springbootsoftnetexample.PingController",
55+
"method": "ping"
56+
},
57+
"identifiers": [
58+
{
59+
"type": "find_sec_bugs_type",
60+
"name": "Find Security Bugs-SPRING_ENDPOINT",
61+
"value": "SPRING_ENDPOINT",
62+
"url": "https://find-sec-bugs.github.io/bugs.htm#SPRING_ENDPOINT"
63+
}
64+
]
65+
},
66+
{
67+
"id": "f4e1ee2a65c5d8837cfe6e3b16fc368f23462596f41ca15b182625a259a58baf",
68+
"category": "sast",
69+
"name": "Found Spring endpoint",
70+
"message": "Found Spring endpoint",
71+
"description": "pl.com.softnet.example.springbootsoftnetexample.FakeErrorController is a Spring endpoint (Controller)",
72+
"cve": "62a35767e47f86da1958c888ab0ddb98:SPRING_ENDPOINT:src/main/java/pl/com/softnet/example/springbootsoftnetexample/FakeErrorController.java:16",
73+
"severity": "Low",
74+
"confidence": "Low",
75+
"scanner": {
76+
"id": "find_sec_bugs",
77+
"name": "Find Security Bugs"
78+
},
79+
"location": {
80+
"file": "src/main/java/pl/com/softnet/example/springbootsoftnetexample/FakeErrorController.java",
81+
"start_line": 16,
82+
"class": "pl.com.softnet.example.springbootsoftnetexample.FakeErrorController",
83+
"method": "getDomainError"
84+
},
85+
"identifiers": [
86+
{
87+
"type": "find_sec_bugs_type",
88+
"name": "Find Security Bugs-SPRING_ENDPOINT",
89+
"value": "SPRING_ENDPOINT",
90+
"url": "https://find-sec-bugs.github.io/bugs.htm#SPRING_ENDPOINT"
91+
}
92+
]
93+
},
94+
{
95+
"id": "e5104af1e9b781ffa19a0f9299e9c44bb62b3dd62c4483e9f2e087dc03e8cd95",
96+
"category": "sast",
97+
"name": "HTTP headers untrusted",
98+
"message": "HTTP headers untrusted",
99+
"description": "Request header can easily be altered by the client",
100+
"cve": "6b0c63f9593aecd2ad80afdc4a85656d:SERVLET_HEADER:src/main/java/pl/com/softnet/example/springbootsoftnetexample/PingController.java:50",
101+
"severity": "Low",
102+
"confidence": "Low",
103+
"scanner": {
104+
"id": "find_sec_bugs",
105+
"name": "Find Security Bugs"
106+
},
107+
"location": {
108+
"file": "src/main/java/pl/com/softnet/example/springbootsoftnetexample/PingController.java",
109+
"start_line": 50,
110+
"class": "pl.com.softnet.example.springbootsoftnetexample.PingController$IpAddressUtils",
111+
"method": "getIpAddressFromRequest"
112+
},
113+
"identifiers": [
114+
{
115+
"type": "find_sec_bugs_type",
116+
"name": "Find Security Bugs-SERVLET_HEADER",
117+
"value": "SERVLET_HEADER",
118+
"url": "https://find-sec-bugs.github.io/bugs.htm#SERVLET_HEADER"
119+
}
120+
]
121+
},
122+
{
123+
"id": "dd623e3dafc27991b80b00c2b38b8ec69ef4b2635a5838622b3efb921e2cbfac",
124+
"category": "sast",
125+
"name": "Found Spring endpoint",
126+
"message": "Found Spring endpoint",
127+
"description": "pl.com.softnet.example.springbootsoftnetexample.FakeErrorController is a Spring endpoint (Controller)",
128+
"cve": "8e968b3dea7c8b68b43c07ab9b37c120:SPRING_ENDPOINT:src/main/java/pl/com/softnet/example/springbootsoftnetexample/FakeErrorController.java:11",
129+
"severity": "Low",
130+
"confidence": "Low",
131+
"scanner": {
132+
"id": "find_sec_bugs",
133+
"name": "Find Security Bugs"
134+
},
135+
"location": {
136+
"file": "src/main/java/pl/com/softnet/example/springbootsoftnetexample/FakeErrorController.java",
137+
"start_line": 11,
138+
"class": "pl.com.softnet.example.springbootsoftnetexample.FakeErrorController",
139+
"method": "getSomeFakeError"
140+
},
141+
"identifiers": [
142+
{
143+
"type": "find_sec_bugs_type",
144+
"name": "Find Security Bugs-SPRING_ENDPOINT",
145+
"value": "SPRING_ENDPOINT",
146+
"url": "https://find-sec-bugs.github.io/bugs.htm#SPRING_ENDPOINT"
147+
}
148+
]
149+
}
150+
],
151+
"scan": {
152+
"analyzer": {
153+
"id": "spotbugs",
154+
"name": "Spotbugs",
155+
"vendor": {
156+
"name": "GitLab"
157+
},
158+
"version": "3.2.1"
159+
},
160+
"scanner": {
161+
"id": "find_sec_bugs",
162+
"name": "Find Security Bugs",
163+
"url": "https://spotbugs.github.io",
164+
"vendor": {
165+
"name": "GitLab"
166+
},
167+
"version": "4.7.0"
168+
},
169+
"type": "sast",
170+
"start_time": "2022-08-04T05:31:38",
171+
"end_time": "2022-08-04T05:32:16",
172+
"status": "success"
173+
}
174+
}

0 commit comments

Comments
 (0)