secator 0.10.1a12__py3-none-any.whl → 0.15.1__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 +10 -5
- secator/celery_signals.py +2 -11
- secator/cli.py +309 -69
- secator/config.py +3 -2
- secator/configs/profiles/aggressive.yaml +6 -5
- secator/configs/profiles/default.yaml +6 -7
- secator/configs/profiles/insane.yaml +8 -0
- secator/configs/profiles/paranoid.yaml +8 -0
- secator/configs/profiles/polite.yaml +8 -0
- secator/configs/profiles/sneaky.yaml +8 -0
- secator/configs/profiles/tor.yaml +5 -0
- secator/configs/workflows/host_recon.yaml +11 -2
- secator/configs/workflows/url_dirsearch.yaml +5 -0
- secator/configs/workflows/url_params_fuzz.yaml +25 -0
- secator/configs/workflows/wordpress.yaml +4 -1
- secator/decorators.py +64 -34
- secator/definitions.py +8 -4
- secator/installer.py +84 -49
- secator/output_types/__init__.py +2 -1
- secator/output_types/certificate.py +78 -0
- secator/output_types/stat.py +3 -0
- secator/output_types/user_account.py +1 -1
- secator/report.py +2 -2
- secator/rich.py +1 -1
- secator/runners/_base.py +50 -11
- secator/runners/_helpers.py +15 -3
- secator/runners/command.py +85 -21
- secator/runners/scan.py +6 -3
- secator/runners/task.py +1 -0
- secator/runners/workflow.py +22 -4
- secator/tasks/_categories.py +25 -17
- secator/tasks/arjun.py +92 -0
- secator/tasks/bbot.py +33 -4
- secator/tasks/bup.py +4 -2
- secator/tasks/cariddi.py +17 -4
- secator/tasks/dalfox.py +4 -2
- secator/tasks/dirsearch.py +4 -2
- secator/tasks/dnsx.py +5 -2
- secator/tasks/dnsxbrute.py +4 -1
- secator/tasks/feroxbuster.py +5 -2
- secator/tasks/ffuf.py +7 -3
- secator/tasks/fping.py +4 -1
- secator/tasks/gau.py +5 -2
- secator/tasks/gf.py +4 -2
- secator/tasks/gitleaks.py +79 -0
- secator/tasks/gospider.py +5 -2
- secator/tasks/grype.py +5 -2
- secator/tasks/h8mail.py +4 -2
- secator/tasks/httpx.py +6 -3
- secator/tasks/katana.py +6 -3
- secator/tasks/maigret.py +4 -2
- secator/tasks/mapcidr.py +5 -3
- secator/tasks/msfconsole.py +8 -6
- secator/tasks/naabu.py +16 -5
- secator/tasks/nmap.py +31 -29
- secator/tasks/nuclei.py +18 -10
- secator/tasks/searchsploit.py +8 -3
- secator/tasks/subfinder.py +6 -3
- secator/tasks/testssl.py +276 -0
- secator/tasks/trivy.py +98 -0
- secator/tasks/wafw00f.py +85 -0
- secator/tasks/wpprobe.py +96 -0
- secator/tasks/wpscan.py +8 -4
- secator/template.py +61 -67
- secator/utils.py +31 -18
- secator/utils_test.py +34 -10
- {secator-0.10.1a12.dist-info → secator-0.15.1.dist-info}/METADATA +11 -3
- secator-0.15.1.dist-info/RECORD +128 -0
- secator/configs/profiles/stealth.yaml +0 -7
- secator-0.10.1a12.dist-info/RECORD +0 -116
- {secator-0.10.1a12.dist-info → secator-0.15.1.dist-info}/WHEEL +0 -0
- {secator-0.10.1a12.dist-info → secator-0.15.1.dist-info}/entry_points.txt +0 -0
- {secator-0.10.1a12.dist-info → secator-0.15.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -7,10 +7,19 @@ input_types:
|
|
|
7
7
|
- host
|
|
8
8
|
- cidr_range
|
|
9
9
|
tasks:
|
|
10
|
+
naabu:
|
|
11
|
+
description: Find open ports
|
|
12
|
+
ports: "-" # scan all ports
|
|
10
13
|
nmap:
|
|
11
14
|
description: Search for vulnerabilities on open ports
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
version_detection: True
|
|
16
|
+
script: vulners
|
|
17
|
+
targets_:
|
|
18
|
+
- port.host
|
|
19
|
+
- target.name
|
|
20
|
+
ports_:
|
|
21
|
+
- port.port
|
|
22
|
+
ports: "-" # default if no port found by naabu
|
|
14
23
|
_group/1:
|
|
15
24
|
httpx:
|
|
16
25
|
description: Probe HTTP services on open ports
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
type: workflow
|
|
2
|
+
name: url_params_fuzz
|
|
3
|
+
alias: url_params_fuzz
|
|
4
|
+
description: Extract parameters from an URL and fuzz them
|
|
5
|
+
tags: [http, fuzz]
|
|
6
|
+
input_types:
|
|
7
|
+
- url
|
|
8
|
+
tasks:
|
|
9
|
+
arjun:
|
|
10
|
+
description: Extract parameters from URLs
|
|
11
|
+
ffuf:
|
|
12
|
+
description: Fuzz URL params
|
|
13
|
+
wordlist: https://raw.githubusercontent.com/danielmiessler/SecLists/refs/heads/master/Discovery/Web-Content/burp-parameter-names.txt
|
|
14
|
+
auto_calibration: true
|
|
15
|
+
follow_redirect: true
|
|
16
|
+
targets_:
|
|
17
|
+
- type: url
|
|
18
|
+
field: url
|
|
19
|
+
condition: item._source.startswith('arjun')
|
|
20
|
+
httpx:
|
|
21
|
+
description: Probe fuzzed URLs
|
|
22
|
+
targets_:
|
|
23
|
+
- type: url
|
|
24
|
+
field: url
|
|
25
|
+
condition: item._source.startswith('ffuf')
|
secator/decorators.py
CHANGED
|
@@ -13,22 +13,24 @@ from secator.utils import (deduplicate, expand_input, get_command_category)
|
|
|
13
13
|
|
|
14
14
|
RUNNER_OPTS = {
|
|
15
15
|
'output': {'type': str, 'default': None, 'help': 'Output options (-o table,json,csv,gdrive)', 'short': 'o'},
|
|
16
|
+
'profiles': {'type': str, 'default': 'default', 'help': 'Profiles', 'short': 'pf'},
|
|
16
17
|
'workspace': {'type': str, 'default': 'default', 'help': 'Workspace', 'short': 'ws'},
|
|
17
18
|
'print_json': {'is_flag': True, 'short': 'json', 'default': False, 'help': 'Print items as JSON lines'},
|
|
18
19
|
'print_raw': {'is_flag': True, 'short': 'raw', 'default': False, 'help': 'Print items in raw format'},
|
|
19
20
|
'print_stat': {'is_flag': True, 'short': 'stat', 'default': False, 'help': 'Print runtime statistics'},
|
|
20
21
|
'print_format': {'default': '', 'short': 'fmt', 'help': 'Output formatting string'},
|
|
21
22
|
'enable_profiler': {'is_flag': True, 'short': 'prof', 'default': False, 'help': 'Enable runner profiling'},
|
|
22
|
-
'
|
|
23
|
-
'no_process': {'is_flag': True, 'default': False, 'help': 'Disable secator processing'},
|
|
23
|
+
'no_process': {'is_flag': True, 'short': 'nps', 'default': False, 'help': 'Disable secator processing'},
|
|
24
24
|
# 'filter': {'default': '', 'short': 'f', 'help': 'Results filter', 'short': 'of'}, # TODO add this
|
|
25
|
-
'quiet': {'is_flag': True, 'default':
|
|
25
|
+
'quiet': {'is_flag': True, 'short': 'q', 'default': not CONFIG.runners.show_command_output, 'opposite': 'verbose', 'help': 'Enable quiet mode'}, # noqa: E501
|
|
26
|
+
'dry_run': {'is_flag': True, 'short': 'dr', 'default': False, 'help': 'Enable dry run'},
|
|
27
|
+
'show': {'is_flag': True, 'short': 'yml', 'default': False, 'help': 'Show runner yaml'},
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
RUNNER_GLOBAL_OPTS = {
|
|
29
31
|
'sync': {'is_flag': True, 'help': 'Run tasks synchronously (automatic if no worker is alive)'},
|
|
30
32
|
'worker': {'is_flag': True, 'default': False, 'help': 'Run tasks in worker'},
|
|
31
|
-
'no_poll': {'is_flag': True, 'default': False, 'help': 'Do not live poll for tasks results when running in worker'},
|
|
33
|
+
'no_poll': {'is_flag': True, 'short': 'np', 'default': False, 'help': 'Do not live poll for tasks results when running in worker'}, # noqa: E501
|
|
32
34
|
'proxy': {'type': str, 'help': 'HTTP proxy'},
|
|
33
35
|
'driver': {'type': str, 'help': 'Export real-time results. E.g: "mongodb"'}
|
|
34
36
|
# 'debug': {'type': int, 'default': 0, 'help': 'Debug mode'},
|
|
@@ -162,35 +164,39 @@ def get_command_options(config):
|
|
|
162
164
|
elif opt in RUNNER_GLOBAL_OPTS:
|
|
163
165
|
prefix = 'Execution'
|
|
164
166
|
|
|
167
|
+
# Get opt value from YAML config
|
|
168
|
+
opt_conf_value = task_config_opts.get(opt)
|
|
169
|
+
|
|
165
170
|
# Get opt conf
|
|
166
171
|
conf = opt_conf.copy()
|
|
172
|
+
opt_is_flag = conf.get('is_flag', False)
|
|
173
|
+
opt_default = conf.get('default', False if opt_is_flag else None)
|
|
174
|
+
opt_is_required = conf.get('required', False)
|
|
167
175
|
conf['show_default'] = True
|
|
168
176
|
conf['prefix'] = prefix
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
opt_value_in_config = task_config_opts.get(opt)
|
|
177
|
+
conf['default'] = opt_default
|
|
178
|
+
conf['reverse'] = False
|
|
172
179
|
|
|
173
|
-
#
|
|
174
|
-
if
|
|
175
|
-
if
|
|
180
|
+
# Change CLI opt defaults if opt was overriden in YAML config
|
|
181
|
+
if opt_conf_value:
|
|
182
|
+
if opt_is_required:
|
|
176
183
|
debug('OPT (skipped: opt is required and defined in config)', obj={'opt': opt}, sub=f'cli.{config.name}', verbose=True) # noqa: E501
|
|
177
184
|
continue
|
|
178
185
|
mapped_value = cls.opt_value_map.get(opt)
|
|
179
186
|
if callable(mapped_value):
|
|
180
|
-
|
|
187
|
+
opt_conf_value = mapped_value(opt_conf_value)
|
|
181
188
|
elif mapped_value:
|
|
182
|
-
|
|
183
|
-
|
|
189
|
+
opt_conf_value = mapped_value
|
|
190
|
+
|
|
191
|
+
# Handle option defaults
|
|
192
|
+
if opt_conf_value != opt_default:
|
|
184
193
|
if opt in opt_cache:
|
|
185
194
|
continue
|
|
186
195
|
if opt_is_flag:
|
|
187
|
-
conf['
|
|
188
|
-
conf['default'] = not conf['default']
|
|
189
|
-
# print(f'{opt}: change default to {opt_value_in_config}')
|
|
190
|
-
conf['default'] = opt_value_in_config
|
|
196
|
+
conf['default'] = opt_default = opt_conf_value
|
|
191
197
|
|
|
192
|
-
#
|
|
193
|
-
if
|
|
198
|
+
# Add reverse flag
|
|
199
|
+
if opt_default is True:
|
|
194
200
|
conf['reverse'] = True
|
|
195
201
|
|
|
196
202
|
# Check if opt already processed before
|
|
@@ -204,7 +210,7 @@ def get_command_options(config):
|
|
|
204
210
|
all_opts[opt] = conf
|
|
205
211
|
|
|
206
212
|
# Debug
|
|
207
|
-
debug_conf = OrderedDict({'opt': opt, 'config_val':
|
|
213
|
+
debug_conf = OrderedDict({'opt': opt, 'config_val': opt_conf_value or 'N/A', **conf.copy()})
|
|
208
214
|
debug('OPT', obj=debug_conf, sub=f'cli.{config.name}', verbose=True)
|
|
209
215
|
|
|
210
216
|
return all_opts
|
|
@@ -224,18 +230,28 @@ def decorate_command_options(opts):
|
|
|
224
230
|
for opt_name, opt_conf in reversed_opts.items():
|
|
225
231
|
conf = opt_conf.copy()
|
|
226
232
|
short_opt = conf.pop('short', None)
|
|
227
|
-
conf.pop('internal',
|
|
233
|
+
internal = conf.pop('internal', False)
|
|
234
|
+
display = conf.pop('display', True)
|
|
235
|
+
if internal and not display:
|
|
236
|
+
continue
|
|
228
237
|
conf.pop('prefix', None)
|
|
229
238
|
conf.pop('shlex', None)
|
|
230
239
|
conf.pop('meta', None)
|
|
231
240
|
conf.pop('supported', None)
|
|
232
241
|
conf.pop('process', None)
|
|
242
|
+
conf.pop('requires_sudo', None)
|
|
233
243
|
reverse = conf.pop('reverse', False)
|
|
244
|
+
opposite = conf.pop('opposite', None)
|
|
234
245
|
long = f'--{opt_name}'
|
|
235
246
|
short = f'-{short_opt}' if short_opt else f'-{opt_name}'
|
|
236
247
|
if reverse:
|
|
237
|
-
|
|
238
|
-
|
|
248
|
+
if opposite:
|
|
249
|
+
long += f'/--{opposite}'
|
|
250
|
+
short += f'/-{opposite[0]}'
|
|
251
|
+
conf['help'] = conf['help'].replace(opt_name, f'{opt_name} / {opposite}')
|
|
252
|
+
else:
|
|
253
|
+
long += f'/--no-{opt_name}'
|
|
254
|
+
short += f'/-n{short_opt}' if short else f'/-n{opt_name}'
|
|
239
255
|
f = click.option(long, short, **conf)(f)
|
|
240
256
|
return f
|
|
241
257
|
return decorator
|
|
@@ -255,7 +271,6 @@ def generate_cli_subcommand(cli_endpoint, func, **opts):
|
|
|
255
271
|
def register_runner(cli_endpoint, config):
|
|
256
272
|
name = config.name
|
|
257
273
|
input_required = True
|
|
258
|
-
input_type = 'targets'
|
|
259
274
|
command_opts = {
|
|
260
275
|
'no_args_is_help': True,
|
|
261
276
|
'context_settings': {
|
|
@@ -266,37 +281,44 @@ def register_runner(cli_endpoint, config):
|
|
|
266
281
|
|
|
267
282
|
if cli_endpoint.name == 'scan':
|
|
268
283
|
runner_cls = Scan
|
|
284
|
+
input_required = False # allow targets from stdin
|
|
269
285
|
short_help = config.description or ''
|
|
270
286
|
short_help += f' [dim]alias: {config.alias}' if config.alias else ''
|
|
271
287
|
command_opts.update({
|
|
272
288
|
'name': name,
|
|
273
|
-
'short_help': short_help
|
|
289
|
+
'short_help': short_help,
|
|
290
|
+
'no_args_is_help': False
|
|
274
291
|
})
|
|
292
|
+
input_types = config.input_types
|
|
275
293
|
|
|
276
294
|
elif cli_endpoint.name == 'workflow':
|
|
277
295
|
runner_cls = Workflow
|
|
296
|
+
input_required = False # allow targets from stdin
|
|
278
297
|
short_help = config.description or ''
|
|
279
298
|
short_help = f'{short_help:<55} [dim](alias)[/][bold cyan] {config.alias}' if config.alias else ''
|
|
280
299
|
command_opts.update({
|
|
281
300
|
'name': name,
|
|
282
|
-
'short_help': short_help
|
|
301
|
+
'short_help': short_help,
|
|
302
|
+
'no_args_is_help': False
|
|
283
303
|
})
|
|
304
|
+
input_types = config.input_types
|
|
284
305
|
|
|
285
306
|
elif cli_endpoint.name == 'task':
|
|
286
307
|
runner_cls = Task
|
|
287
308
|
input_required = False # allow targets from stdin
|
|
288
309
|
task_cls = Task.get_task_class(config.name)
|
|
289
310
|
task_category = get_command_category(task_cls)
|
|
290
|
-
|
|
291
|
-
short_help = f'[magenta]{task_category:<15}[/]{task_cls.__doc__}'
|
|
311
|
+
short_help = f'[magenta]{task_category:<25}[/] {task_cls.__doc__}'
|
|
292
312
|
command_opts.update({
|
|
293
313
|
'name': name,
|
|
294
314
|
'short_help': short_help,
|
|
295
315
|
'no_args_is_help': False
|
|
296
316
|
})
|
|
317
|
+
input_types = task_cls.input_types
|
|
297
318
|
|
|
298
319
|
else:
|
|
299
320
|
raise ValueError(f"Unrecognized runner endpoint name {cli_endpoint.name}")
|
|
321
|
+
input_types_str = '|'.join(input_types) if input_types else 'targets'
|
|
300
322
|
options = get_command_options(config)
|
|
301
323
|
|
|
302
324
|
# TODO: maybe allow this in the future
|
|
@@ -308,7 +330,7 @@ def register_runner(cli_endpoint, config):
|
|
|
308
330
|
# for i in range(0, len(ctx.args), 2)
|
|
309
331
|
# }
|
|
310
332
|
|
|
311
|
-
@click.argument(
|
|
333
|
+
@click.argument(input_types_str, required=input_required)
|
|
312
334
|
@decorate_command_options(options)
|
|
313
335
|
@click.pass_context
|
|
314
336
|
def func(ctx, **opts):
|
|
@@ -316,9 +338,16 @@ def register_runner(cli_endpoint, config):
|
|
|
316
338
|
worker = opts.pop('worker')
|
|
317
339
|
ws = opts.pop('workspace')
|
|
318
340
|
driver = opts.pop('driver', '')
|
|
341
|
+
quiet = opts['quiet']
|
|
342
|
+
dry_run = opts['dry_run']
|
|
319
343
|
show = opts['show']
|
|
320
344
|
context = {'workspace_name': ws}
|
|
321
345
|
|
|
346
|
+
# Show runner yaml
|
|
347
|
+
if show:
|
|
348
|
+
config.print()
|
|
349
|
+
sys.exit(0)
|
|
350
|
+
|
|
322
351
|
# Remove options whose values are default values
|
|
323
352
|
for k, v in options.items():
|
|
324
353
|
opt_name = k.replace('-', '_')
|
|
@@ -330,7 +359,7 @@ def register_runner(cli_endpoint, config):
|
|
|
330
359
|
# opts.update(unknown_opts)
|
|
331
360
|
|
|
332
361
|
# Expand input
|
|
333
|
-
inputs = opts.pop(
|
|
362
|
+
inputs = opts.pop(input_types_str)
|
|
334
363
|
inputs = expand_input(inputs, ctx)
|
|
335
364
|
|
|
336
365
|
# Build hooks from driver name
|
|
@@ -359,7 +388,7 @@ def register_runner(cli_endpoint, config):
|
|
|
359
388
|
hooks = deep_merge_dicts(*hooks)
|
|
360
389
|
|
|
361
390
|
# Enable sync or not
|
|
362
|
-
if sync or
|
|
391
|
+
if sync or dry_run:
|
|
363
392
|
sync = True
|
|
364
393
|
else:
|
|
365
394
|
from secator.celery import is_celery_worker_alive
|
|
@@ -389,6 +418,7 @@ def register_runner(cli_endpoint, config):
|
|
|
389
418
|
'piped_output': ctx.obj['piped_output'],
|
|
390
419
|
'caller': 'cli',
|
|
391
420
|
'sync': sync,
|
|
421
|
+
'quiet': quiet
|
|
392
422
|
})
|
|
393
423
|
|
|
394
424
|
# Start runner
|
|
@@ -396,10 +426,10 @@ def register_runner(cli_endpoint, config):
|
|
|
396
426
|
runner.run()
|
|
397
427
|
|
|
398
428
|
generate_cli_subcommand(cli_endpoint, func, **command_opts)
|
|
399
|
-
generate_rich_click_opt_groups(cli_endpoint, name,
|
|
429
|
+
generate_rich_click_opt_groups(cli_endpoint, name, input_types, options)
|
|
400
430
|
|
|
401
431
|
|
|
402
|
-
def generate_rich_click_opt_groups(cli_endpoint, name,
|
|
432
|
+
def generate_rich_click_opt_groups(cli_endpoint, name, input_types, options):
|
|
403
433
|
sortorder = {
|
|
404
434
|
'Execution': 0,
|
|
405
435
|
'Output': 1,
|
|
@@ -410,7 +440,7 @@ def generate_rich_click_opt_groups(cli_endpoint, name, input_type, options):
|
|
|
410
440
|
opt_group = [
|
|
411
441
|
{
|
|
412
442
|
'name': 'Targets',
|
|
413
|
-
'options':
|
|
443
|
+
'options': input_types,
|
|
414
444
|
},
|
|
415
445
|
]
|
|
416
446
|
for prefix in prefixes:
|
secator/definitions.py
CHANGED
|
@@ -23,10 +23,6 @@ ASCII = rf"""
|
|
|
23
23
|
DEBUG = CONFIG.debug.level
|
|
24
24
|
DEBUG_COMPONENT = CONFIG.debug.component.split(',')
|
|
25
25
|
|
|
26
|
-
# Default tasks settings
|
|
27
|
-
DEFAULT_NUCLEI_FLAGS = os.environ.get('DEFAULT_NUCLEI_FLAGS', '-stats -sj -si 20 -hm -or')
|
|
28
|
-
DEFAULT_FEROXBUSTER_FLAGS = os.environ.get('DEFAULT_FEROXBUSTER_FLAGS', '--auto-bail --no-state')
|
|
29
|
-
|
|
30
26
|
# Constants
|
|
31
27
|
OPT_NOT_SUPPORTED = -1
|
|
32
28
|
OPT_PIPE_INPUT = -1
|
|
@@ -44,6 +40,9 @@ AUTO_CALIBRATION = 'auto_calibration'
|
|
|
44
40
|
CONTENT_TYPE = 'content_type'
|
|
45
41
|
CONTENT_LENGTH = 'content_length'
|
|
46
42
|
CIDR_RANGE = 'cidr_range'
|
|
43
|
+
DOCKER_IMAGE = 'docker_image'
|
|
44
|
+
FILENAME = 'filename'
|
|
45
|
+
GIT_REPOSITORY = 'git_repository'
|
|
47
46
|
CPES = 'cpes'
|
|
48
47
|
CVES = 'cves'
|
|
49
48
|
DELAY = 'delay'
|
|
@@ -66,6 +65,7 @@ MATCH_CODES = 'match_codes'
|
|
|
66
65
|
MATCH_REGEX = 'match_regex'
|
|
67
66
|
MATCH_SIZE = 'match_size'
|
|
68
67
|
MATCH_WORDS = 'match_words'
|
|
68
|
+
ORG_NAME = 'org_name'
|
|
69
69
|
OUTPUT_PATH = 'output_path'
|
|
70
70
|
PATH = 'path'
|
|
71
71
|
PERCENT = 'percent'
|
|
@@ -104,9 +104,13 @@ REFERENCE = 'reference'
|
|
|
104
104
|
REFERENCES = 'references'
|
|
105
105
|
SEVERITY = 'severity'
|
|
106
106
|
TAGS = 'tags'
|
|
107
|
+
TECHNOLOGY = 'technology'
|
|
107
108
|
WEBSERVER = 'webserver'
|
|
108
109
|
WORDLIST = 'wordlist'
|
|
109
110
|
WORDS = 'words'
|
|
111
|
+
CERTIFICATE_STATUS_UNKNOWN = 'Unknown'
|
|
112
|
+
CERTIFICATE_STATUS_TRUSTED = 'Trusted'
|
|
113
|
+
CERTIFICATE_STATUS_REVOKED = 'Revoked'
|
|
110
114
|
|
|
111
115
|
|
|
112
116
|
def is_importable(module_to_import):
|