secator 0.5.2__py3-none-any.whl → 0.7.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 +160 -185
- secator/celery_utils.py +268 -0
- secator/cli.py +327 -106
- secator/config.py +27 -11
- secator/configs/workflows/host_recon.yaml +5 -3
- secator/configs/workflows/port_scan.yaml +7 -3
- secator/configs/workflows/url_bypass.yaml +10 -0
- secator/configs/workflows/url_vuln.yaml +1 -1
- secator/decorators.py +169 -92
- secator/definitions.py +10 -3
- secator/exporters/__init__.py +7 -5
- secator/exporters/console.py +10 -0
- secator/exporters/csv.py +27 -19
- secator/exporters/gdrive.py +16 -11
- secator/exporters/json.py +3 -1
- secator/exporters/table.py +30 -2
- secator/exporters/txt.py +20 -16
- secator/hooks/gcs.py +53 -0
- secator/hooks/mongodb.py +54 -28
- secator/output_types/__init__.py +29 -11
- secator/output_types/_base.py +11 -1
- secator/output_types/error.py +36 -0
- secator/output_types/exploit.py +1 -1
- secator/output_types/info.py +24 -0
- secator/output_types/ip.py +7 -0
- secator/output_types/port.py +8 -1
- secator/output_types/progress.py +6 -1
- secator/output_types/record.py +3 -1
- secator/output_types/stat.py +33 -0
- secator/output_types/tag.py +6 -4
- secator/output_types/url.py +6 -3
- secator/output_types/vulnerability.py +3 -2
- secator/output_types/warning.py +24 -0
- secator/report.py +55 -23
- secator/rich.py +44 -39
- secator/runners/_base.py +622 -635
- secator/runners/_helpers.py +5 -91
- secator/runners/celery.py +18 -0
- secator/runners/command.py +364 -211
- secator/runners/scan.py +8 -24
- secator/runners/task.py +21 -55
- secator/runners/workflow.py +41 -40
- secator/scans/__init__.py +28 -0
- secator/serializers/dataclass.py +6 -0
- secator/serializers/json.py +10 -5
- secator/serializers/regex.py +12 -4
- secator/tasks/_categories.py +6 -3
- secator/tasks/bbot.py +293 -0
- secator/tasks/bup.py +98 -0
- secator/tasks/cariddi.py +38 -49
- secator/tasks/dalfox.py +3 -0
- secator/tasks/dirsearch.py +12 -23
- secator/tasks/dnsx.py +49 -30
- secator/tasks/dnsxbrute.py +2 -0
- secator/tasks/feroxbuster.py +8 -17
- secator/tasks/ffuf.py +3 -2
- secator/tasks/fping.py +3 -3
- secator/tasks/gau.py +5 -0
- secator/tasks/gf.py +2 -2
- secator/tasks/gospider.py +4 -0
- secator/tasks/grype.py +9 -9
- secator/tasks/h8mail.py +31 -41
- secator/tasks/httpx.py +58 -21
- secator/tasks/katana.py +18 -22
- secator/tasks/maigret.py +26 -24
- secator/tasks/mapcidr.py +2 -3
- secator/tasks/msfconsole.py +4 -16
- secator/tasks/naabu.py +3 -1
- secator/tasks/nmap.py +50 -35
- secator/tasks/nuclei.py +9 -2
- secator/tasks/searchsploit.py +17 -9
- secator/tasks/subfinder.py +5 -1
- secator/tasks/wpscan.py +79 -93
- secator/template.py +61 -45
- secator/thread.py +24 -0
- secator/utils.py +330 -80
- secator/utils_test.py +48 -23
- secator/workflows/__init__.py +28 -0
- {secator-0.5.2.dist-info → secator-0.7.0.dist-info}/METADATA +12 -6
- secator-0.7.0.dist-info/RECORD +115 -0
- {secator-0.5.2.dist-info → secator-0.7.0.dist-info}/WHEEL +1 -1
- secator-0.5.2.dist-info/RECORD +0 -101
- {secator-0.5.2.dist-info → secator-0.7.0.dist-info}/entry_points.txt +0 -0
- {secator-0.5.2.dist-info → secator-0.7.0.dist-info}/licenses/LICENSE +0 -0
secator/tasks/msfconsole.py
CHANGED
|
@@ -21,7 +21,6 @@ class msfconsole(VulnMulti):
|
|
|
21
21
|
input_type = HOST
|
|
22
22
|
input_chunk_size = 1
|
|
23
23
|
output_types = []
|
|
24
|
-
output_return_type = str
|
|
25
24
|
opt_prefix = '--'
|
|
26
25
|
opts = {
|
|
27
26
|
'resource': {'type': str, 'help': 'Metasploit resource script.', 'short': 'r'},
|
|
@@ -40,19 +39,11 @@ class msfconsole(VulnMulti):
|
|
|
40
39
|
THREADS: OPT_NOT_SUPPORTED,
|
|
41
40
|
TIMEOUT: OPT_NOT_SUPPORTED,
|
|
42
41
|
USER_AGENT: OPT_NOT_SUPPORTED,
|
|
43
|
-
THREADS: OPT_NOT_SUPPORTED,
|
|
44
42
|
}
|
|
45
43
|
encoding = 'ansi'
|
|
46
44
|
ignore_return_code = True
|
|
47
45
|
# install_cmd = 'wget -O - https://raw.githubusercontent.com/freelabz/secator/main/scripts/msfinstall.sh | sh'
|
|
48
46
|
|
|
49
|
-
@staticmethod
|
|
50
|
-
def validate_input(self, input):
|
|
51
|
-
"""No list input supported for this command. Pass a single input instead."""
|
|
52
|
-
if isinstance(input, list):
|
|
53
|
-
return False
|
|
54
|
-
return True
|
|
55
|
-
|
|
56
47
|
@staticmethod
|
|
57
48
|
def on_init(self):
|
|
58
49
|
command = self.get_opt_value('execute_command')
|
|
@@ -61,14 +52,14 @@ class msfconsole(VulnMulti):
|
|
|
61
52
|
env_vars = {}
|
|
62
53
|
if environment:
|
|
63
54
|
env_vars = dict(map(lambda x: x.split('='), environment.strip().split(',')))
|
|
64
|
-
env_vars['RHOST'] = self.
|
|
65
|
-
env_vars['RHOSTS'] = self.
|
|
55
|
+
env_vars['RHOST'] = self.inputs[0]
|
|
56
|
+
env_vars['RHOSTS'] = self.inputs[0]
|
|
66
57
|
|
|
67
58
|
# Passing msfconsole command directly, simply add RHOST / RHOSTS from host input and run then exit
|
|
68
59
|
if command:
|
|
69
60
|
self.run_opts['msfconsole.execute_command'] = (
|
|
70
|
-
f'setg RHOST {self.
|
|
71
|
-
f'setg RHOSTS {self.
|
|
61
|
+
f'setg RHOST {self.inputs[0]}; '
|
|
62
|
+
f'setg RHOSTS {self.inputs[0]}; '
|
|
72
63
|
f'{command.format(**env_vars)}; '
|
|
73
64
|
f'exit;'
|
|
74
65
|
)
|
|
@@ -101,9 +92,6 @@ class msfconsole(VulnMulti):
|
|
|
101
92
|
else:
|
|
102
93
|
raise ValueError('At least one of "inline_script" or "resource_script" must be passed.')
|
|
103
94
|
|
|
104
|
-
# Clear host input
|
|
105
|
-
self.input = ''
|
|
106
|
-
|
|
107
95
|
|
|
108
96
|
# TODO: This is better as it goes through an RPC API to communicate with
|
|
109
97
|
# metasploit rpc server, but it does not give any output.
|
secator/tasks/naabu.py
CHANGED
|
@@ -3,6 +3,7 @@ from secator.definitions import (DELAY, HOST, OPT_NOT_SUPPORTED, PORT, PORTS,
|
|
|
3
3
|
PROXY, RATE_LIMIT, RETRIES, STATE, THREADS,
|
|
4
4
|
TIMEOUT, TOP_PORTS)
|
|
5
5
|
from secator.output_types import Port
|
|
6
|
+
from secator.serializers import JSONSerializer
|
|
6
7
|
from secator.tasks._categories import ReconPort
|
|
7
8
|
|
|
8
9
|
|
|
@@ -16,7 +17,7 @@ class naabu(ReconPort):
|
|
|
16
17
|
opts = {
|
|
17
18
|
PORTS: {'type': str, 'short': 'p', 'help': 'Ports'},
|
|
18
19
|
TOP_PORTS: {'type': str, 'short': 'tp', 'help': 'Top ports'},
|
|
19
|
-
'scan_type': {'type': str, 'help': 'Scan type (SYN (s)/CONNECT(c))'},
|
|
20
|
+
'scan_type': {'type': str, 'short': 'st', 'help': 'Scan type (SYN (s)/CONNECT(c))'},
|
|
20
21
|
# 'health_check': {'is_flag': True, 'short': 'hc', 'help': 'Health check'}
|
|
21
22
|
}
|
|
22
23
|
opt_key_map = {
|
|
@@ -37,6 +38,7 @@ class naabu(ReconPort):
|
|
|
37
38
|
RETRIES: lambda x: 1 if x == 0 else x,
|
|
38
39
|
PROXY: lambda x: x.replace('socks5://', '')
|
|
39
40
|
}
|
|
41
|
+
item_loaders = [JSONSerializer()]
|
|
40
42
|
output_map = {
|
|
41
43
|
Port: {
|
|
42
44
|
PORT: lambda x: x['port'],
|
secator/tasks/nmap.py
CHANGED
|
@@ -8,15 +8,14 @@ from secator.config import CONFIG
|
|
|
8
8
|
from secator.decorators import task
|
|
9
9
|
from secator.definitions import (CONFIDENCE, CVSS_SCORE, DELAY,
|
|
10
10
|
DESCRIPTION, EXTRA_DATA, FOLLOW_REDIRECT,
|
|
11
|
-
HEADER, HOST, ID, IP, MATCHED_AT, NAME,
|
|
11
|
+
HEADER, HOST, ID, IP, PROTOCOL, MATCHED_AT, NAME,
|
|
12
12
|
OPT_NOT_SUPPORTED, OUTPUT_PATH, PORT, PORTS, PROVIDER,
|
|
13
13
|
PROXY, RATE_LIMIT, REFERENCE, REFERENCES,
|
|
14
14
|
RETRIES, SCRIPT, SERVICE_NAME, SEVERITY, STATE, TAGS,
|
|
15
15
|
THREADS, TIMEOUT, TOP_PORTS, USER_AGENT)
|
|
16
|
-
from secator.output_types import Exploit, Port, Vulnerability
|
|
17
|
-
from secator.rich import console
|
|
16
|
+
from secator.output_types import Exploit, Port, Vulnerability, Info, Error
|
|
18
17
|
from secator.tasks._categories import VulnMulti
|
|
19
|
-
from secator.utils import debug
|
|
18
|
+
from secator.utils import debug, traceback_as_string
|
|
20
19
|
|
|
21
20
|
logger = logging.getLogger(__name__)
|
|
22
21
|
|
|
@@ -24,7 +23,7 @@ logger = logging.getLogger(__name__)
|
|
|
24
23
|
@task()
|
|
25
24
|
class nmap(VulnMulti):
|
|
26
25
|
"""Network Mapper is a free and open source utility for network discovery and security auditing."""
|
|
27
|
-
cmd = 'nmap
|
|
26
|
+
cmd = 'nmap'
|
|
28
27
|
input_flag = None
|
|
29
28
|
input_chunk_size = 1
|
|
30
29
|
file_flag = '-iL'
|
|
@@ -34,8 +33,11 @@ class nmap(VulnMulti):
|
|
|
34
33
|
PORTS: {'type': str, 'short': 'p', 'help': 'Ports to scan'},
|
|
35
34
|
TOP_PORTS: {'type': int, 'short': 'tp', 'help': 'Top ports to scan [full, 100, 1000]'},
|
|
36
35
|
SCRIPT: {'type': str, 'default': 'vulners', 'help': 'NSE scripts'},
|
|
37
|
-
|
|
36
|
+
'skip_host_discovery': {'is_flag': True, 'short': 'Pn', 'default': False, 'help': 'Skip host discovery (no ping)'},
|
|
37
|
+
'version_detection': {'is_flag': True, 'short': 'sV', 'default': False, 'help': 'Version detection'},
|
|
38
38
|
'tcp_syn_stealth': {'is_flag': True, 'short': 'sS', 'default': False, 'help': 'TCP SYN Stealth'},
|
|
39
|
+
'tcp_connect': {'is_flag': True, 'short': 'sT', 'default': False, 'help': 'TCP Connect scan'},
|
|
40
|
+
'udp_scan': {'is_flag': True, 'short': 'sU', 'default': False, 'help': 'UDP scan'},
|
|
39
41
|
'output_path': {'type': str, 'short': 'oX', 'default': None, 'help': 'Output XML file path'},
|
|
40
42
|
}
|
|
41
43
|
opt_key_map = {
|
|
@@ -51,8 +53,12 @@ class nmap(VulnMulti):
|
|
|
51
53
|
|
|
52
54
|
# Nmap opts
|
|
53
55
|
PORTS: '-p',
|
|
56
|
+
'skip_host_discovery': '-Pn',
|
|
57
|
+
'version_detection': '-sV',
|
|
58
|
+
'tcp_connect': '-sT',
|
|
59
|
+
'tcp_syn_stealth': '-sS',
|
|
60
|
+
'udp_scan': '-sU',
|
|
54
61
|
'output_path': '-oX',
|
|
55
|
-
'tcp_syn_stealth': '-sS'
|
|
56
62
|
}
|
|
57
63
|
opt_value_map = {
|
|
58
64
|
PORTS: lambda x: ','.join([str(p) for p in x]) if isinstance(x, list) else x
|
|
@@ -75,21 +81,23 @@ class nmap(VulnMulti):
|
|
|
75
81
|
self.output_path = output_path
|
|
76
82
|
self.cmd += f' -oX {self.output_path}'
|
|
77
83
|
tcp_syn_stealth = self.get_opt_value('tcp_syn_stealth')
|
|
78
|
-
|
|
84
|
+
tcp_connect = self.get_opt_value('tcp_connect')
|
|
85
|
+
udp_scan = self.get_opt_value('udp_scan')
|
|
86
|
+
if tcp_syn_stealth or udp_scan:
|
|
79
87
|
self.cmd = f'sudo {self.cmd}'
|
|
80
|
-
|
|
88
|
+
if tcp_connect and tcp_syn_stealth:
|
|
89
|
+
self._print(
|
|
90
|
+
'Options -sT (SYN stealth scan) and -sS (CONNECT scan) are conflicting. Keeping only -sT.',
|
|
91
|
+
'bold gold3')
|
|
92
|
+
self.cmd = self.cmd.replace('-sT ', '')
|
|
81
93
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if self.
|
|
94
|
+
@staticmethod
|
|
95
|
+
def on_cmd_done(self):
|
|
96
|
+
if not os.path.exists(self.output_path):
|
|
97
|
+
yield Error(message=f'Could not find XML results in {self.output_path}')
|
|
85
98
|
return
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if self.print_line:
|
|
89
|
-
self._print(note)
|
|
90
|
-
if os.path.exists(self.output_path):
|
|
91
|
-
nmap_data = self.xml_to_json()
|
|
92
|
-
yield from nmap_data
|
|
99
|
+
yield Info(message=f'XML results saved to {self.output_path}')
|
|
100
|
+
yield from self.xml_to_json()
|
|
93
101
|
|
|
94
102
|
def xml_to_json(self):
|
|
95
103
|
results = []
|
|
@@ -97,12 +105,12 @@ class nmap(VulnMulti):
|
|
|
97
105
|
content = f.read()
|
|
98
106
|
try:
|
|
99
107
|
results = xmltodict.parse(content) # parse XML to dict
|
|
100
|
-
except Exception as
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
108
|
+
except Exception as exc:
|
|
109
|
+
yield Error(
|
|
110
|
+
message=f'Cannot parse XML output {self.output_path} to valid JSON.',
|
|
111
|
+
traceback=traceback_as_string(exc)
|
|
112
|
+
)
|
|
113
|
+
yield from nmapData(results)
|
|
106
114
|
|
|
107
115
|
|
|
108
116
|
class nmapData(dict):
|
|
@@ -123,14 +131,9 @@ class nmapData(dict):
|
|
|
123
131
|
|
|
124
132
|
# Get extra data
|
|
125
133
|
extra_data = self._get_extra_data(port)
|
|
126
|
-
service_name = extra_data
|
|
134
|
+
service_name = extra_data.get('service_name', '')
|
|
127
135
|
version_exact = extra_data.get('version_exact', False)
|
|
128
136
|
conf = extra_data.get('confidence')
|
|
129
|
-
if not version_exact:
|
|
130
|
-
console.print(
|
|
131
|
-
f'[bold orange1]nmap could not identify an exact version for {service_name} '
|
|
132
|
-
f'(detection confidence is {conf}): do not blindy trust the results ![/]'
|
|
133
|
-
)
|
|
134
137
|
|
|
135
138
|
# Grab CPEs
|
|
136
139
|
cpes = extra_data.get('cpe', [])
|
|
@@ -138,6 +141,9 @@ class nmapData(dict):
|
|
|
138
141
|
# Get script output
|
|
139
142
|
scripts = self._get_scripts(port)
|
|
140
143
|
|
|
144
|
+
# Get port protocol
|
|
145
|
+
protocol = port['@protocol'].lower()
|
|
146
|
+
|
|
141
147
|
# Yield port data
|
|
142
148
|
port = {
|
|
143
149
|
PORT: port_number,
|
|
@@ -145,7 +151,9 @@ class nmapData(dict):
|
|
|
145
151
|
STATE: state,
|
|
146
152
|
SERVICE_NAME: service_name,
|
|
147
153
|
IP: ip,
|
|
148
|
-
|
|
154
|
+
PROTOCOL: protocol,
|
|
155
|
+
EXTRA_DATA: extra_data,
|
|
156
|
+
CONFIDENCE: conf
|
|
149
157
|
}
|
|
150
158
|
yield port
|
|
151
159
|
|
|
@@ -197,7 +205,6 @@ class nmapData(dict):
|
|
|
197
205
|
|
|
198
206
|
def _get_hostname(self, host_cfg):
|
|
199
207
|
hostnames = host_cfg.get('hostnames', {})
|
|
200
|
-
hostname = self['_host']
|
|
201
208
|
if hostnames:
|
|
202
209
|
hostnames = hostnames.get('hostname', [])
|
|
203
210
|
if isinstance(hostnames, dict):
|
|
@@ -205,11 +212,19 @@ class nmapData(dict):
|
|
|
205
212
|
if hostnames:
|
|
206
213
|
hostname = hostnames[0]['@name']
|
|
207
214
|
else:
|
|
208
|
-
hostname =
|
|
215
|
+
hostname = self._get_address(host_cfg).get('@addr', None)
|
|
209
216
|
return hostname
|
|
210
217
|
|
|
218
|
+
def _get_address(self, host_cfg):
|
|
219
|
+
if isinstance(host_cfg.get('address', {}), list):
|
|
220
|
+
addresses = host_cfg.get('address', {})
|
|
221
|
+
for address in addresses:
|
|
222
|
+
if address.get('@addrtype') == "ipv4":
|
|
223
|
+
return address
|
|
224
|
+
return host_cfg.get('address', {})
|
|
225
|
+
|
|
211
226
|
def _get_ip(self, host_cfg):
|
|
212
|
-
return
|
|
227
|
+
return self._get_address(host_cfg).get('@addr', None)
|
|
213
228
|
|
|
214
229
|
def _get_extra_data(self, port_cfg):
|
|
215
230
|
extra_data = {
|
secator/tasks/nuclei.py
CHANGED
|
@@ -4,15 +4,16 @@ from secator.definitions import (CONFIDENCE, CVSS_SCORE, DELAY, DESCRIPTION,
|
|
|
4
4
|
MATCHED_AT, NAME, OPT_NOT_SUPPORTED, PERCENT,
|
|
5
5
|
PROVIDER, PROXY, RATE_LIMIT, REFERENCES,
|
|
6
6
|
RETRIES, SEVERITY, TAGS, THREADS, TIMEOUT,
|
|
7
|
-
USER_AGENT
|
|
7
|
+
USER_AGENT)
|
|
8
8
|
from secator.output_types import Progress, Vulnerability
|
|
9
|
+
from secator.serializers import JSONSerializer
|
|
9
10
|
from secator.tasks._categories import VulnMulti
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
@task()
|
|
13
14
|
class nuclei(VulnMulti):
|
|
14
15
|
"""Fast and customisable vulnerability scanner based on simple YAML based DSL."""
|
|
15
|
-
cmd =
|
|
16
|
+
cmd = 'nuclei'
|
|
16
17
|
file_flag = '-l'
|
|
17
18
|
input_flag = '-u'
|
|
18
19
|
json_flag = '-jsonl'
|
|
@@ -23,6 +24,11 @@ class nuclei(VulnMulti):
|
|
|
23
24
|
'exclude_severity': {'type': str, 'short': 'es', 'help': 'Exclude severity'},
|
|
24
25
|
'template_id': {'type': str, 'short': 'tid', 'help': 'Template id'},
|
|
25
26
|
'debug': {'type': str, 'help': 'Debug mode'},
|
|
27
|
+
'stats': {'is_flag': True, 'short': 'stats', 'default': True, 'help': 'Display statistics about the running scan'},
|
|
28
|
+
'stats_json': {'is_flag': True, 'short': 'sj', 'default': True, 'help': 'Display statistics in JSONL(ines) format'},
|
|
29
|
+
'stats_interval': {'type': str, 'short': 'si', 'default': 20, 'help': 'Number of seconds to wait between showing a statistics update'}, # noqa: E501
|
|
30
|
+
'hang_monitor': {'is_flag': True, 'short': 'hm', 'default': True, 'help': 'Enable nuclei hang monitoring'},
|
|
31
|
+
'omit_raw': {'is_flag': True, 'short': 'or', 'default': True, 'help': 'Omit requests/response pairs in the JSON, JSONL, and Markdown outputs (for findings only)'} # noqa: E501
|
|
26
32
|
}
|
|
27
33
|
opt_key_map = {
|
|
28
34
|
HEADER: 'header',
|
|
@@ -45,6 +51,7 @@ class nuclei(VulnMulti):
|
|
|
45
51
|
'templates': lambda x: ','.join(x) if isinstance(x, list) else x,
|
|
46
52
|
'exclude_tags': lambda x: ','.join(x) if isinstance(x, list) else x,
|
|
47
53
|
}
|
|
54
|
+
item_loaders = [JSONSerializer()]
|
|
48
55
|
output_types = [Vulnerability, Progress]
|
|
49
56
|
output_map = {
|
|
50
57
|
Vulnerability: {
|
secator/tasks/searchsploit.py
CHANGED
|
@@ -5,6 +5,7 @@ from secator.definitions import (CVES, EXTRA_DATA, ID, MATCHED_AT, NAME,
|
|
|
5
5
|
PROVIDER, REFERENCE, TAGS, OPT_NOT_SUPPORTED)
|
|
6
6
|
from secator.output_types import Exploit
|
|
7
7
|
from secator.runners import Command
|
|
8
|
+
from secator.serializers import JSONSerializer
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
SEARCHSPLOIT_TITLE_REGEX = re.compile(r'^((?:[a-zA-Z\-_!\.()]+\d?\s?)+)\.?\s*(.*)$')
|
|
@@ -21,6 +22,7 @@ class searchsploit(Command):
|
|
|
21
22
|
'strict': {'short': 's', 'is_flag': True, 'default': False, 'help': 'Strict match'}
|
|
22
23
|
}
|
|
23
24
|
opt_key_map = {}
|
|
25
|
+
item_loaders = [JSONSerializer()]
|
|
24
26
|
output_types = [Exploit]
|
|
25
27
|
output_map = {
|
|
26
28
|
Exploit: {
|
|
@@ -56,14 +58,15 @@ class searchsploit(Command):
|
|
|
56
58
|
|
|
57
59
|
@staticmethod
|
|
58
60
|
def before_init(self):
|
|
59
|
-
|
|
61
|
+
if len(self.inputs) == 0:
|
|
62
|
+
return
|
|
63
|
+
_in = self.inputs[0]
|
|
60
64
|
self.matched_at = None
|
|
61
65
|
if '~' in _in:
|
|
62
66
|
split = _in.split('~')
|
|
63
67
|
self.matched_at = split[0]
|
|
64
|
-
self.
|
|
65
|
-
|
|
66
|
-
self.input = self.input.replace('httpd', '').replace('/', ' ')
|
|
68
|
+
self.inputs[0] = split[1]
|
|
69
|
+
self.inputs[0] = self.inputs[0].replace('httpd', '').replace('/', ' ')
|
|
67
70
|
|
|
68
71
|
@staticmethod
|
|
69
72
|
def on_item_pre_convert(self, item):
|
|
@@ -80,12 +83,17 @@ class searchsploit(Command):
|
|
|
80
83
|
group = match.groups()
|
|
81
84
|
product = '-'.join(group[0].strip().split(' '))
|
|
82
85
|
if len(group[1]) > 1:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
try:
|
|
87
|
+
versions, title = tuple(group[1].split(' - '))
|
|
88
|
+
item.name = title
|
|
89
|
+
product_info = [f'{product.lower()} {v.strip()}' for v in versions.split('/')]
|
|
90
|
+
item.tags = product_info + item.tags
|
|
91
|
+
except ValueError:
|
|
92
|
+
item.name = item.name.split(' - ')[-1]
|
|
93
|
+
item.tags = [product.lower()]
|
|
94
|
+
pass
|
|
87
95
|
# else:
|
|
88
96
|
# self._print(f'[bold red]{item.name} ({item.reference}) did not quite match SEARCHSPLOIT_TITLE_REGEX. Please report this issue.[/]') # noqa: E501
|
|
89
|
-
input_tag = '-'.join(self.
|
|
97
|
+
input_tag = '-'.join(self.inputs[0].replace('\'', '').split(' '))
|
|
90
98
|
item.tags = [input_tag] + item.tags
|
|
91
99
|
return item
|
secator/tasks/subfinder.py
CHANGED
|
@@ -2,6 +2,7 @@ from secator.decorators import task
|
|
|
2
2
|
from secator.definitions import (DELAY, DOMAIN, OPT_NOT_SUPPORTED, PROXY,
|
|
3
3
|
RATE_LIMIT, RETRIES, THREADS, TIMEOUT)
|
|
4
4
|
from secator.output_types import Subdomain
|
|
5
|
+
from secator.serializers import JSONSerializer
|
|
5
6
|
from secator.tasks._categories import ReconDns
|
|
6
7
|
|
|
7
8
|
|
|
@@ -23,6 +24,7 @@ class subfinder(ReconDns):
|
|
|
23
24
|
opt_value_map = {
|
|
24
25
|
PROXY: lambda x: x.replace('http://', '').replace('https://', '') if x else None
|
|
25
26
|
}
|
|
27
|
+
item_loaders = [JSONSerializer()]
|
|
26
28
|
output_map = {
|
|
27
29
|
Subdomain: {
|
|
28
30
|
DOMAIN: 'input',
|
|
@@ -38,4 +40,6 @@ class subfinder(ReconDns):
|
|
|
38
40
|
|
|
39
41
|
@staticmethod
|
|
40
42
|
def validate_item(self, item):
|
|
41
|
-
|
|
43
|
+
if isinstance(item, dict):
|
|
44
|
+
return item['input'] != 'localhost'
|
|
45
|
+
return True
|
secator/tasks/wpscan.py
CHANGED
|
@@ -8,7 +8,7 @@ from secator.definitions import (CONFIDENCE, CVSS_SCORE, DELAY, DESCRIPTION,
|
|
|
8
8
|
PROXY, RATE_LIMIT, REFERENCES, RETRIES,
|
|
9
9
|
SEVERITY, TAGS, THREADS, TIMEOUT,
|
|
10
10
|
URL, USER_AGENT)
|
|
11
|
-
from secator.output_types import Tag, Vulnerability
|
|
11
|
+
from secator.output_types import Tag, Vulnerability, Info, Error
|
|
12
12
|
from secator.tasks._categories import VulnHttp
|
|
13
13
|
|
|
14
14
|
|
|
@@ -73,105 +73,91 @@ class wpscan(VulnHttp):
|
|
|
73
73
|
ignore_return_code = True
|
|
74
74
|
profile = 'io'
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
self.
|
|
83
|
-
if not self.output_json:
|
|
84
|
-
return
|
|
76
|
+
@staticmethod
|
|
77
|
+
def on_init(self):
|
|
78
|
+
output_path = self.get_opt_value(OUTPUT_PATH)
|
|
79
|
+
if not output_path:
|
|
80
|
+
output_path = f'{self.reports_folder}/.outputs/{self.unique_name}.json'
|
|
81
|
+
self.output_path = output_path
|
|
82
|
+
self.cmd += f' -o {self.output_path}'
|
|
85
83
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
@staticmethod
|
|
85
|
+
def on_cmd_done(self):
|
|
86
|
+
if not os.path.exists(self.output_path):
|
|
87
|
+
yield Error(message=f'Could not find JSON results in {self.output_path}')
|
|
88
|
+
return
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
yield Info(message=f'JSON results saved to {self.output_path}')
|
|
91
|
+
with open(self.output_path, 'r') as f:
|
|
92
|
+
data = json.load(f)
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return
|
|
94
|
+
# Get URL
|
|
95
|
+
target = data.get('target_url', self.inputs[0])
|
|
97
96
|
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
# Wordpress version
|
|
98
|
+
version = data.get('version', {})
|
|
99
|
+
if version:
|
|
100
|
+
wp_version = version['number']
|
|
101
|
+
wp_version_status = version['status']
|
|
102
|
+
if wp_version_status == 'outdated':
|
|
103
|
+
vuln = version
|
|
104
|
+
vuln.update({
|
|
105
|
+
'url': target,
|
|
106
|
+
'to_s': 'Wordpress outdated version',
|
|
107
|
+
'type': wp_version,
|
|
108
|
+
'references': {},
|
|
109
|
+
})
|
|
110
|
+
yield vuln
|
|
100
111
|
|
|
101
|
-
|
|
102
|
-
|
|
112
|
+
# Main theme
|
|
113
|
+
main_theme = data.get('main_theme', {})
|
|
114
|
+
if main_theme:
|
|
115
|
+
version = main_theme.get('version', {})
|
|
116
|
+
slug = main_theme['slug']
|
|
117
|
+
location = main_theme['location']
|
|
103
118
|
if version:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
'
|
|
111
|
-
'
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
version = main_theme.get('version', {})
|
|
120
|
-
slug = main_theme['slug']
|
|
121
|
-
location = main_theme['location']
|
|
122
|
-
if version:
|
|
123
|
-
number = version['number']
|
|
124
|
-
latest_version = main_theme.get('latest_version')
|
|
125
|
-
yield Tag(
|
|
126
|
-
name=f'Wordpress theme - {slug} {number}',
|
|
127
|
-
match=target,
|
|
128
|
-
extra_data={
|
|
129
|
-
'url': location,
|
|
130
|
-
'latest_version': latest_version
|
|
131
|
-
}
|
|
119
|
+
number = version['number']
|
|
120
|
+
latest_version = main_theme.get('latest_version')
|
|
121
|
+
yield Tag(
|
|
122
|
+
name=f'Wordpress theme - {slug} {number}',
|
|
123
|
+
match=target,
|
|
124
|
+
extra_data={
|
|
125
|
+
'url': location,
|
|
126
|
+
'latest_version': latest_version
|
|
127
|
+
}
|
|
128
|
+
)
|
|
129
|
+
if (latest_version and number < latest_version):
|
|
130
|
+
yield Vulnerability(
|
|
131
|
+
matched_at=target,
|
|
132
|
+
name=f'Wordpress theme - {slug} {number} outdated',
|
|
133
|
+
severity='info'
|
|
132
134
|
)
|
|
133
|
-
if (latest_version and number < latest_version):
|
|
134
|
-
yield Vulnerability(
|
|
135
|
-
matched_at=target,
|
|
136
|
-
name=f'Wordpress theme - {slug} {number} outdated',
|
|
137
|
-
severity='info'
|
|
138
|
-
)
|
|
139
135
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
136
|
+
# Interesting findings
|
|
137
|
+
interesting_findings = data.get('interesting_findings', [])
|
|
138
|
+
for item in interesting_findings:
|
|
139
|
+
yield item
|
|
144
140
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
141
|
+
# Plugins
|
|
142
|
+
plugins = data.get('plugins', {})
|
|
143
|
+
for _, data in plugins.items():
|
|
144
|
+
version = data.get('version', {})
|
|
145
|
+
slug = data['slug']
|
|
146
|
+
location = data['location']
|
|
147
|
+
if version:
|
|
148
|
+
number = version['number']
|
|
149
|
+
latest_version = data.get('latest_version')
|
|
150
|
+
yield Tag(
|
|
151
|
+
name=f'Wordpress plugin - {slug} {number}',
|
|
152
|
+
match=target,
|
|
153
|
+
extra_data={
|
|
154
|
+
'url': location,
|
|
155
|
+
'latest_version': latest_version
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
if (latest_version and number < latest_version):
|
|
159
|
+
yield Vulnerability(
|
|
160
|
+
matched_at=target,
|
|
161
|
+
name=f'Wordpress plugin - {slug} {number} outdated',
|
|
162
|
+
severity='info'
|
|
161
163
|
)
|
|
162
|
-
if (latest_version and number < latest_version):
|
|
163
|
-
yield Vulnerability(
|
|
164
|
-
matched_at=target,
|
|
165
|
-
name=f'Wordpress plugin - {slug} {number} outdated',
|
|
166
|
-
severity='info'
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
self.print_item_count = prev
|
|
170
|
-
|
|
171
|
-
@staticmethod
|
|
172
|
-
def on_init(self):
|
|
173
|
-
output_path = self.get_opt_value(OUTPUT_PATH)
|
|
174
|
-
if not output_path:
|
|
175
|
-
output_path = f'{self.reports_folder}/.outputs/{self.unique_name}.json'
|
|
176
|
-
self.output_path = output_path
|
|
177
|
-
self.cmd += f' -o {self.output_path}'
|