secator 0.15.1__py3-none-any.whl → 0.16.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of secator might be problematic. Click here for more details.
- secator/celery.py +40 -24
- secator/celery_signals.py +71 -68
- secator/celery_utils.py +43 -27
- secator/cli.py +520 -280
- secator/cli_helper.py +394 -0
- secator/click.py +87 -0
- secator/config.py +67 -39
- secator/configs/profiles/http_headless.yaml +6 -0
- secator/configs/profiles/http_record.yaml +6 -0
- secator/configs/profiles/tor.yaml +1 -1
- secator/configs/scans/domain.yaml +4 -2
- secator/configs/scans/host.yaml +1 -1
- secator/configs/scans/network.yaml +1 -4
- secator/configs/scans/subdomain.yaml +13 -1
- secator/configs/scans/url.yaml +1 -2
- secator/configs/workflows/cidr_recon.yaml +6 -4
- secator/configs/workflows/code_scan.yaml +1 -1
- secator/configs/workflows/host_recon.yaml +29 -3
- secator/configs/workflows/subdomain_recon.yaml +67 -16
- secator/configs/workflows/url_crawl.yaml +44 -15
- secator/configs/workflows/url_dirsearch.yaml +4 -4
- secator/configs/workflows/url_fuzz.yaml +25 -17
- secator/configs/workflows/url_params_fuzz.yaml +7 -0
- secator/configs/workflows/url_vuln.yaml +33 -8
- secator/configs/workflows/user_hunt.yaml +2 -1
- secator/configs/workflows/wordpress.yaml +5 -3
- secator/cve.py +718 -0
- secator/decorators.py +0 -454
- secator/definitions.py +49 -30
- secator/exporters/_base.py +2 -2
- secator/exporters/console.py +2 -2
- secator/exporters/table.py +4 -3
- secator/exporters/txt.py +1 -1
- secator/hooks/mongodb.py +2 -4
- secator/installer.py +77 -49
- secator/loader.py +116 -0
- secator/output_types/_base.py +3 -0
- secator/output_types/certificate.py +63 -63
- secator/output_types/error.py +4 -5
- secator/output_types/info.py +2 -2
- secator/output_types/ip.py +3 -1
- secator/output_types/progress.py +5 -9
- secator/output_types/state.py +17 -17
- secator/output_types/tag.py +3 -0
- secator/output_types/target.py +10 -2
- secator/output_types/url.py +19 -7
- secator/output_types/vulnerability.py +11 -7
- secator/output_types/warning.py +2 -2
- secator/report.py +27 -15
- secator/rich.py +18 -10
- secator/runners/_base.py +446 -233
- secator/runners/_helpers.py +133 -24
- secator/runners/command.py +182 -102
- secator/runners/scan.py +33 -5
- secator/runners/task.py +13 -7
- secator/runners/workflow.py +105 -72
- secator/scans/__init__.py +2 -2
- secator/serializers/dataclass.py +20 -20
- secator/tasks/__init__.py +4 -4
- secator/tasks/_categories.py +39 -27
- secator/tasks/arjun.py +9 -5
- secator/tasks/bbot.py +53 -21
- secator/tasks/bup.py +19 -5
- secator/tasks/cariddi.py +24 -3
- secator/tasks/dalfox.py +26 -7
- secator/tasks/dirsearch.py +10 -4
- secator/tasks/dnsx.py +70 -25
- secator/tasks/feroxbuster.py +11 -3
- secator/tasks/ffuf.py +42 -6
- secator/tasks/fping.py +20 -8
- secator/tasks/gau.py +3 -1
- secator/tasks/gf.py +3 -3
- secator/tasks/gitleaks.py +2 -2
- secator/tasks/gospider.py +7 -1
- secator/tasks/grype.py +5 -4
- secator/tasks/h8mail.py +2 -1
- secator/tasks/httpx.py +18 -5
- secator/tasks/katana.py +35 -15
- secator/tasks/maigret.py +4 -4
- secator/tasks/mapcidr.py +3 -3
- secator/tasks/msfconsole.py +4 -4
- secator/tasks/naabu.py +2 -2
- secator/tasks/nmap.py +12 -14
- secator/tasks/nuclei.py +3 -3
- secator/tasks/searchsploit.py +4 -5
- secator/tasks/subfinder.py +2 -2
- secator/tasks/testssl.py +264 -263
- secator/tasks/trivy.py +5 -5
- secator/tasks/wafw00f.py +21 -3
- secator/tasks/wpprobe.py +90 -83
- secator/tasks/wpscan.py +6 -5
- secator/template.py +218 -104
- secator/thread.py +15 -15
- secator/tree.py +196 -0
- secator/utils.py +131 -123
- secator/utils_test.py +60 -19
- secator/workflows/__init__.py +2 -2
- {secator-0.15.1.dist-info → secator-0.16.0.dist-info}/METADATA +36 -36
- secator-0.16.0.dist-info/RECORD +132 -0
- secator/configs/profiles/default.yaml +0 -8
- secator/configs/workflows/url_nuclei.yaml +0 -11
- secator/tasks/dnsxbrute.py +0 -42
- secator-0.15.1.dist-info/RECORD +0 -128
- {secator-0.15.1.dist-info → secator-0.16.0.dist-info}/WHEEL +0 -0
- {secator-0.15.1.dist-info → secator-0.16.0.dist-info}/entry_points.txt +0 -0
- {secator-0.15.1.dist-info → secator-0.16.0.dist-info}/licenses/LICENSE +0 -0
secator/tasks/nuclei.py
CHANGED
|
@@ -14,8 +14,9 @@ from secator.tasks._categories import VulnMulti
|
|
|
14
14
|
class nuclei(VulnMulti):
|
|
15
15
|
"""Fast and customisable vulnerability scanner based on simple YAML based DSL."""
|
|
16
16
|
cmd = 'nuclei'
|
|
17
|
-
tags = ['vuln', 'scan']
|
|
18
17
|
input_types = [HOST, IP, URL]
|
|
18
|
+
output_types = [Vulnerability, Progress]
|
|
19
|
+
tags = ['vuln', 'scan']
|
|
19
20
|
file_flag = '-l'
|
|
20
21
|
input_flag = '-u'
|
|
21
22
|
json_flag = '-jsonl'
|
|
@@ -59,7 +60,6 @@ class nuclei(VulnMulti):
|
|
|
59
60
|
'exclude_tags': lambda x: ','.join(x) if isinstance(x, list) else x,
|
|
60
61
|
}
|
|
61
62
|
item_loaders = [JSONSerializer()]
|
|
62
|
-
output_types = [Vulnerability, Progress]
|
|
63
63
|
output_map = {
|
|
64
64
|
Vulnerability: {
|
|
65
65
|
ID: lambda x: nuclei.id_extractor(x),
|
|
@@ -77,7 +77,7 @@ class nuclei(VulnMulti):
|
|
|
77
77
|
},
|
|
78
78
|
Progress: {
|
|
79
79
|
PERCENT: lambda x: int(x['percent']),
|
|
80
|
-
EXTRA_DATA: lambda x: {k: v for k, v in x.items() if k not in ['
|
|
80
|
+
EXTRA_DATA: lambda x: {k: v for k, v in x.items() if k not in ['percent']}
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
install_pre = {
|
secator/tasks/searchsploit.py
CHANGED
|
@@ -3,7 +3,7 @@ import re
|
|
|
3
3
|
from secator.config import CONFIG
|
|
4
4
|
from secator.decorators import task
|
|
5
5
|
from secator.definitions import (CVES, EXTRA_DATA, ID, MATCHED_AT, NAME,
|
|
6
|
-
PROVIDER, REFERENCE, TAGS,
|
|
6
|
+
PROVIDER, REFERENCE, TAGS, OPT_NOT_SUPPORTED, STRING)
|
|
7
7
|
from secator.output_types import Exploit
|
|
8
8
|
from secator.runners import Command
|
|
9
9
|
from secator.serializers import JSONSerializer
|
|
@@ -16,9 +16,10 @@ SEARCHSPLOIT_TITLE_REGEX = re.compile(r'^((?:[a-zA-Z\-_!\.()]+\d?\s?)+)\.?\s*(.*
|
|
|
16
16
|
class searchsploit(Command):
|
|
17
17
|
"""Exploit searcher based on ExploitDB."""
|
|
18
18
|
cmd = 'searchsploit'
|
|
19
|
+
input_types = [STRING]
|
|
20
|
+
output_types = [Exploit]
|
|
19
21
|
tags = ['exploit', 'recon']
|
|
20
|
-
|
|
21
|
-
input_types = [TECHNOLOGY]
|
|
22
|
+
input_chunk_size = 1
|
|
22
23
|
json_flag = '--json'
|
|
23
24
|
version_flag = OPT_NOT_SUPPORTED
|
|
24
25
|
opts = {
|
|
@@ -26,7 +27,6 @@ class searchsploit(Command):
|
|
|
26
27
|
}
|
|
27
28
|
opt_key_map = {}
|
|
28
29
|
item_loaders = [JSONSerializer()]
|
|
29
|
-
output_types = [Exploit]
|
|
30
30
|
output_map = {
|
|
31
31
|
Exploit: {
|
|
32
32
|
NAME: 'Title',
|
|
@@ -51,7 +51,6 @@ class searchsploit(Command):
|
|
|
51
51
|
proxychains = False
|
|
52
52
|
proxy_socks5 = False
|
|
53
53
|
proxy_http = False
|
|
54
|
-
input_chunk_size = 1
|
|
55
54
|
profile = 'io'
|
|
56
55
|
|
|
57
56
|
@staticmethod
|
secator/tasks/subfinder.py
CHANGED
|
@@ -10,8 +10,9 @@ from secator.tasks._categories import ReconDns
|
|
|
10
10
|
class subfinder(ReconDns):
|
|
11
11
|
"""Fast passive subdomain enumeration tool."""
|
|
12
12
|
cmd = 'subfinder -cs'
|
|
13
|
-
tags = ['dns', 'recon']
|
|
14
13
|
input_types = [HOST]
|
|
14
|
+
output_types = [Subdomain]
|
|
15
|
+
tags = ['dns', 'recon']
|
|
15
16
|
file_flag = '-dL'
|
|
16
17
|
input_flag = '-d'
|
|
17
18
|
json_flag = '-json'
|
|
@@ -32,7 +33,6 @@ class subfinder(ReconDns):
|
|
|
32
33
|
DOMAIN: 'input',
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
|
-
output_types = [Subdomain]
|
|
36
36
|
install_version = 'v2.7.0'
|
|
37
37
|
install_cmd = 'go install -v github.com/projectdiscovery/subfinder/v2/cmd/subfinder@[install_version]'
|
|
38
38
|
install_github_handle = 'projectdiscovery/subfinder'
|
secator/tasks/testssl.py
CHANGED
|
@@ -6,271 +6,272 @@ from secator.config import CONFIG
|
|
|
6
6
|
from secator.decorators import task
|
|
7
7
|
from secator.output_types import Vulnerability, Certificate, Error, Info, Ip, Tag
|
|
8
8
|
from secator.definitions import (PROXY, HOST, USER_AGENT, HEADER, OUTPUT_PATH,
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
CERTIFICATE_STATUS_UNKNOWN, CERTIFICATE_STATUS_TRUSTED, CERTIFICATE_STATUS_REVOKED,
|
|
10
|
+
TIMEOUT)
|
|
11
11
|
from secator.tasks._categories import Command, OPTS
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@task()
|
|
15
15
|
class testssl(Command):
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
16
|
+
"""SSL/TLS security scanner, including ciphers, protocols and cryptographic flaws."""
|
|
17
|
+
cmd = 'testssl.sh'
|
|
18
|
+
input_types = [HOST]
|
|
19
|
+
output_types = [Certificate, Vulnerability, Ip, Tag]
|
|
20
|
+
tags = ['dns', 'recon', 'tls']
|
|
21
|
+
input_flag = None
|
|
22
|
+
file_flag = '-iL'
|
|
23
|
+
file_eof_newline = True
|
|
24
|
+
version_flag = ''
|
|
25
|
+
opt_prefix = '--'
|
|
26
|
+
opts = {
|
|
27
|
+
'verbose': {'is_flag': True, 'default': False, 'internal': True, 'display': True, 'help': 'Record all SSL/TLS info, not only critical info'}, # noqa: E501
|
|
28
|
+
'parallel': {'is_flag': True, 'default': False, 'help': 'Test multiple hosts in parallel'},
|
|
29
|
+
'warnings': {'type': str, 'default': None, 'help': 'Set to "batch" to stop on errors, and "off" to skip errors and continue'}, # noqa: E501
|
|
30
|
+
'ids_friendly': {'is_flag': True, 'default': False, 'help': 'Avoid IDS blocking by skipping a few vulnerability checks'}, # noqa: E501
|
|
31
|
+
'hints': {'is_flag': True, 'default': False, 'help': 'Additional hints to findings'},
|
|
32
|
+
'server_defaults': {'is_flag': True, 'default': False, 'help': 'Displays the server default picks and certificate info'}, # noqa: E501
|
|
33
|
+
}
|
|
34
|
+
meta_opts = {
|
|
35
|
+
PROXY: OPTS[PROXY],
|
|
36
|
+
USER_AGENT: OPTS[USER_AGENT],
|
|
37
|
+
HEADER: OPTS[HEADER],
|
|
38
|
+
TIMEOUT: OPTS[TIMEOUT],
|
|
39
|
+
}
|
|
40
|
+
opt_key_map = {
|
|
41
|
+
PROXY: 'proxy',
|
|
42
|
+
USER_AGENT: 'user-agent',
|
|
43
|
+
HEADER: 'reqheader',
|
|
44
|
+
TIMEOUT: 'connect-timeout',
|
|
45
|
+
'ipv6': '-6',
|
|
46
|
+
}
|
|
47
|
+
proxy_http = True
|
|
48
|
+
proxychains = False
|
|
49
|
+
proxy_socks5 = False
|
|
50
|
+
profile = 'io'
|
|
51
|
+
install_pre = {
|
|
52
|
+
'apk': ['hexdump', 'coreutils', 'procps'],
|
|
53
|
+
'pacman': ['util-linux'],
|
|
54
|
+
'*': ['bsdmainutils']
|
|
55
|
+
}
|
|
56
|
+
install_github_handle = 'testssl/testssl.sh'
|
|
57
|
+
install_version = 'v3.2.0'
|
|
58
|
+
install_cmd = (
|
|
59
|
+
f'git clone --depth 1 --single-branch -b [install_version] https://github.com/drwetter/testssl.sh.git {CONFIG.dirs.share}/testssl.sh_[install_version] || true && ' # noqa: E501
|
|
60
|
+
f'ln -sf {CONFIG.dirs.share}/testssl.sh_[install_version]/testssl.sh {CONFIG.dirs.bin}'
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def on_cmd(self):
|
|
65
|
+
output_path = self.get_opt_value(OUTPUT_PATH)
|
|
66
|
+
if not output_path:
|
|
67
|
+
output_path = f'{self.reports_folder}/.outputs/{self.unique_name}.json'
|
|
68
|
+
self.output_path = output_path
|
|
69
|
+
self.cmd += f' --jsonfile {self.output_path}'
|
|
70
|
+
|
|
71
|
+
# Hack because target needs to be the last argument in testssl.sh
|
|
72
|
+
if len(self.inputs) == 1:
|
|
73
|
+
target = self.inputs[0]
|
|
74
|
+
self.cmd = self.cmd.replace(f' {target}', '')
|
|
75
|
+
self.cmd += f' {target}'
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def on_cmd_done(self):
|
|
79
|
+
if not os.path.exists(self.output_path):
|
|
80
|
+
yield Error(message=f'Could not find JSON results in {self.output_path}')
|
|
81
|
+
return
|
|
82
|
+
yield Info(message=f'JSON results saved to {self.output_path}')
|
|
83
|
+
|
|
84
|
+
verbose = self.get_opt_value('verbose')
|
|
85
|
+
with open(self.output_path, 'r') as f:
|
|
86
|
+
data = json.load(f)
|
|
87
|
+
bad_cyphers = {}
|
|
88
|
+
retrieved_certificates = {}
|
|
89
|
+
ignored_item_ids = ["scanTime", "overall_grade", "DNS_CAArecord"]
|
|
90
|
+
ip_addresses = []
|
|
91
|
+
host_to_ips = {}
|
|
92
|
+
|
|
93
|
+
for item in data:
|
|
94
|
+
host, ip = tuple(item['ip'].split('/'))
|
|
95
|
+
id = item['id']
|
|
96
|
+
# port = item['port']
|
|
97
|
+
finding = item['finding']
|
|
98
|
+
severity = item['severity'].lower()
|
|
99
|
+
cwe = item.get('cwe')
|
|
100
|
+
vuln_tags = ['ssl', 'tls']
|
|
101
|
+
if cwe:
|
|
102
|
+
vuln_tags.append(cwe)
|
|
103
|
+
|
|
104
|
+
# Skip ignored items
|
|
105
|
+
if id.startswith(tuple(ignored_item_ids)):
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
# Add IP to address pool
|
|
109
|
+
host_to_ips.setdefault(host, []).append(ip)
|
|
110
|
+
if ip not in ip_addresses:
|
|
111
|
+
ip_addresses.append(ip)
|
|
112
|
+
yield Ip(
|
|
113
|
+
host=host,
|
|
114
|
+
ip=ip,
|
|
115
|
+
alive=True
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Process errors
|
|
119
|
+
if id.startswith("scanProblem"):
|
|
120
|
+
yield Error(message=finding)
|
|
121
|
+
|
|
122
|
+
# Process bad ciphers
|
|
123
|
+
elif id.startswith('cipher-'):
|
|
124
|
+
splited_item = item["finding"].split(" ")
|
|
125
|
+
concerned_protocol = splited_item[0]
|
|
126
|
+
bad_cypher = splited_item[-1]
|
|
127
|
+
bad_cyphers.setdefault(ip, {}).setdefault(concerned_protocol, []).append(bad_cypher) # noqa: E501
|
|
128
|
+
|
|
129
|
+
# Process certificates
|
|
130
|
+
elif id.startswith('cert_') or id.startswith('cert '):
|
|
131
|
+
retrieved_certificates.setdefault(ip, []).append(item)
|
|
132
|
+
|
|
133
|
+
# Process intermediate certificates
|
|
134
|
+
elif id.startswith('intermediate_cert_'):
|
|
135
|
+
# TODO: implement this
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
# If info or ok, create a tag only if 'verbose' option is set
|
|
139
|
+
elif severity in ['info', 'ok']:
|
|
140
|
+
if not verbose:
|
|
141
|
+
continue
|
|
142
|
+
yield Tag(
|
|
143
|
+
name=f'SSL/TLS [{id}]',
|
|
144
|
+
match=host,
|
|
145
|
+
extra_data={
|
|
146
|
+
'type': id,
|
|
147
|
+
'finding': finding,
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Create vulnerability
|
|
152
|
+
else:
|
|
153
|
+
if id in ['TLS1', 'TLS1_1']:
|
|
154
|
+
human_name = f'SSL/TLS deprecated protocol offered: {id}'
|
|
155
|
+
else:
|
|
156
|
+
human_name = f'SSL/TLS {id}: {finding}'
|
|
157
|
+
yield Vulnerability(
|
|
158
|
+
name=human_name,
|
|
159
|
+
matched_at=host,
|
|
160
|
+
ip=ip,
|
|
161
|
+
tags=vuln_tags,
|
|
162
|
+
severity=severity,
|
|
163
|
+
confidence='high',
|
|
164
|
+
extra_data={
|
|
165
|
+
'id': id,
|
|
166
|
+
'finding': finding
|
|
167
|
+
}
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Creating vulnerability for the deprecated ciphers
|
|
171
|
+
for ip, protocols in bad_cyphers.items():
|
|
172
|
+
for protocol, cyphers in protocols.items():
|
|
173
|
+
yield Vulnerability(
|
|
174
|
+
name=f'SSL/TLS vulnerability ciphers for {protocol} deprecated',
|
|
175
|
+
matched_at=ip,
|
|
176
|
+
ip=ip,
|
|
177
|
+
confidence='high',
|
|
178
|
+
severity='low',
|
|
179
|
+
extra_data={
|
|
180
|
+
'cyphers': cyphers
|
|
181
|
+
}
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Creating certificates for each founded target
|
|
185
|
+
host_to_ips = {k: set(v) for k, v in host_to_ips.items()}
|
|
186
|
+
for ip, certs in retrieved_certificates.items():
|
|
187
|
+
host = [k for k, v in host_to_ips.items() if ip in v][0]
|
|
188
|
+
cert_data = {
|
|
189
|
+
'host': host,
|
|
190
|
+
'ip': ip,
|
|
191
|
+
'fingerprint_sha256': None,
|
|
192
|
+
'subject_cn': None,
|
|
193
|
+
'subject_an': None,
|
|
194
|
+
'not_before': None,
|
|
195
|
+
'not_after': None,
|
|
196
|
+
'issuer_cn': None,
|
|
197
|
+
'self_signed': None,
|
|
198
|
+
'trusted': None,
|
|
199
|
+
'status': None,
|
|
200
|
+
'keysize': None,
|
|
201
|
+
'serial_number': None,
|
|
202
|
+
}
|
|
203
|
+
for cert in certs:
|
|
204
|
+
host = [k for k, v in host_to_ips.items() if ip in v][0]
|
|
205
|
+
id = cert['id']
|
|
206
|
+
finding = cert['finding']
|
|
207
|
+
|
|
208
|
+
if id.startswith('cert_crlDistributionPoints') and finding != '--':
|
|
209
|
+
# TODO not implemented, need to find a certificate that is revoked by CRL
|
|
210
|
+
cert_data['status'] = CERTIFICATE_STATUS_UNKNOWN
|
|
211
|
+
|
|
212
|
+
if id.startswith('cert_ocspRevoked'):
|
|
213
|
+
if finding.startswith('not revoked'):
|
|
214
|
+
cert_data['status'] = CERTIFICATE_STATUS_TRUSTED
|
|
215
|
+
else:
|
|
216
|
+
cert_data['status'] = CERTIFICATE_STATUS_REVOKED
|
|
217
|
+
|
|
218
|
+
if id.startswith('cert_fingerprintSHA256'):
|
|
219
|
+
cert_data['fingerprint_sha256'] = finding
|
|
220
|
+
|
|
221
|
+
if id.startswith('cert_commonName'):
|
|
222
|
+
cert_data['subject_cn'] = finding
|
|
223
|
+
|
|
224
|
+
if id.startswith('cert_subjectAltName'):
|
|
225
|
+
cert_data['subject_an'] = finding.split(" ")
|
|
226
|
+
|
|
227
|
+
if id.startswith('cert_notBefore'):
|
|
228
|
+
cert_data['not_before'] = datetime.strptime(finding, "%Y-%m-%d %H:%M")
|
|
229
|
+
|
|
230
|
+
if id.startswith('cert_notAfter'):
|
|
231
|
+
cert_data['not_after'] = datetime.strptime(finding, "%Y-%m-%d %H:%M")
|
|
232
|
+
|
|
233
|
+
if id.startswith('cert_caIssuers'):
|
|
234
|
+
cert_data['issuer_cn'] = finding
|
|
235
|
+
|
|
236
|
+
if id.startswith('cert_chain_of_trust'):
|
|
237
|
+
cert_data['self_signed'] = 'self signed' in finding
|
|
238
|
+
|
|
239
|
+
if id.startswith('cert_chain_of_trust'):
|
|
240
|
+
cert_data['trusted'] = finding.startswith('passed')
|
|
241
|
+
|
|
242
|
+
if id.startswith('cert_keySize'):
|
|
243
|
+
cert_data['keysize'] = int(finding.split(" ")[1])
|
|
244
|
+
|
|
245
|
+
if id.startswith('cert_serialNumber'):
|
|
246
|
+
cert_data['serial_number'] = finding
|
|
247
|
+
|
|
248
|
+
if id.startswith('cert ') and finding.startswith('-----BEGIN CERTIFICATE-----'):
|
|
249
|
+
cert_data['raw_value'] = finding
|
|
250
|
+
|
|
251
|
+
# For the following attributes commented, it's because at the time of writting it
|
|
252
|
+
# I did not found the value inside the result of testssl
|
|
253
|
+
cert = Certificate(
|
|
254
|
+
**cert_data
|
|
255
|
+
# issuer_dn='',
|
|
256
|
+
# issuer='',
|
|
257
|
+
# TODO: delete the ciphers attribute from certificate outputType
|
|
258
|
+
# ciphers=None,
|
|
259
|
+
# TODO: need to find a way to retrieve the parent certificate,
|
|
260
|
+
# parent_certificate=None,
|
|
261
|
+
)
|
|
262
|
+
yield cert
|
|
263
|
+
if cert.is_expired():
|
|
264
|
+
yield Vulnerability(
|
|
265
|
+
name='SSL certificate expired',
|
|
266
|
+
provider='testssl',
|
|
267
|
+
description='The SSL certificate is expired. This can easily lead to domain takeovers',
|
|
268
|
+
matched_at=host,
|
|
269
|
+
ip=ip,
|
|
270
|
+
tags=['ssl', 'tls'],
|
|
271
|
+
severity='medium',
|
|
272
|
+
confidence='high',
|
|
273
|
+
extra_data={
|
|
274
|
+
'id': id,
|
|
275
|
+
'expiration_date': Certificate.format_date(cert.not_after)
|
|
276
|
+
}
|
|
277
|
+
)
|