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
secator/celery.py
CHANGED
|
@@ -223,10 +223,11 @@ def forward_results(results):
|
|
|
223
223
|
|
|
224
224
|
|
|
225
225
|
@app.task
|
|
226
|
-
def mark_runner_started(runner, enable_hooks=True):
|
|
226
|
+
def mark_runner_started(results, runner, enable_hooks=True):
|
|
227
227
|
"""Mark a runner as started and run on_start hooks.
|
|
228
228
|
|
|
229
229
|
Args:
|
|
230
|
+
results (List): Previous results.
|
|
230
231
|
runner (Runner): Secator runner instance.
|
|
231
232
|
enable_hooks (bool): Enable hooks.
|
|
232
233
|
|
|
@@ -234,9 +235,12 @@ def mark_runner_started(runner, enable_hooks=True):
|
|
|
234
235
|
list: Runner results
|
|
235
236
|
"""
|
|
236
237
|
debug(f'Runner {runner.unique_name} has started, running mark_started', sub='celery')
|
|
238
|
+
if results:
|
|
239
|
+
runner.results = forward_results(results)
|
|
237
240
|
runner.enable_hooks = enable_hooks
|
|
238
|
-
runner.
|
|
239
|
-
|
|
241
|
+
if not runner.dry_run:
|
|
242
|
+
runner.mark_started()
|
|
243
|
+
return runner.results
|
|
240
244
|
|
|
241
245
|
|
|
242
246
|
@app.task
|
|
@@ -254,8 +258,9 @@ def mark_runner_completed(results, runner, enable_hooks=True):
|
|
|
254
258
|
debug(f'Runner {runner.unique_name} has finished, running mark_completed', sub='celery')
|
|
255
259
|
results = forward_results(results)
|
|
256
260
|
runner.enable_hooks = enable_hooks
|
|
257
|
-
|
|
258
|
-
|
|
261
|
+
if not runner.dry_run:
|
|
262
|
+
[runner.add_result(item) for item in results]
|
|
263
|
+
runner.mark_completed()
|
|
259
264
|
return runner.results
|
|
260
265
|
|
|
261
266
|
|
secator/celery_signals.py
CHANGED
|
@@ -65,16 +65,6 @@ def setup_idle_timer(timeout):
|
|
|
65
65
|
timer.start()
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
def maybe_override_logging():
|
|
69
|
-
def decorator(func):
|
|
70
|
-
if CONFIG.celery.override_default_logging:
|
|
71
|
-
return signals.setup_logging.connect(func)
|
|
72
|
-
else:
|
|
73
|
-
return func
|
|
74
|
-
return decorator
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
@maybe_override_logging()
|
|
78
68
|
def setup_logging(*args, **kwargs):
|
|
79
69
|
"""Override celery's logging setup to prevent it from altering our settings.
|
|
80
70
|
github.com/celery/celery/issues/1867
|
|
@@ -134,8 +124,9 @@ def worker_shutdown_handler(**kwargs):
|
|
|
134
124
|
|
|
135
125
|
|
|
136
126
|
def setup_handlers():
|
|
127
|
+
if CONFIG.celery.override_default_logging:
|
|
128
|
+
signals.setup_logging.connect(setup_logging)
|
|
137
129
|
signals.celeryd_after_setup.connect(capture_worker_name)
|
|
138
|
-
signals.setup_logging.connect(setup_logging)
|
|
139
130
|
signals.task_prerun.connect(task_prerun_handler)
|
|
140
131
|
signals.task_postrun.connect(task_postrun_handler)
|
|
141
132
|
signals.task_revoked.connect(task_revoked_handler)
|
secator/cli.py
CHANGED
|
@@ -18,14 +18,14 @@ from rich.table import Table
|
|
|
18
18
|
|
|
19
19
|
from secator.config import CONFIG, ROOT_FOLDER, Config, default_config, config_path
|
|
20
20
|
from secator.decorators import OrderedGroup, register_runner
|
|
21
|
-
from secator.definitions import ADDONS_ENABLED, ASCII, DEV_PACKAGE,
|
|
21
|
+
from secator.definitions import ADDONS_ENABLED, ASCII, DEV_PACKAGE, VERSION, STATE_COLORS
|
|
22
22
|
from secator.installer import ToolInstaller, fmt_health_table_row, get_health_table, get_version_info, get_distro_config
|
|
23
23
|
from secator.output_types import FINDING_TYPES, Info, Warning, Error
|
|
24
24
|
from secator.report import Report
|
|
25
25
|
from secator.rich import console
|
|
26
26
|
from secator.runners import Command, Runner
|
|
27
27
|
from secator.serializers.dataclass import loads_dataclass
|
|
28
|
-
from secator.template import TemplateLoader
|
|
28
|
+
from secator.template import TEMPLATES, TemplateLoader
|
|
29
29
|
from secator.utils import (
|
|
30
30
|
debug, detect_host, discover_tasks, flatten, print_version, get_file_date,
|
|
31
31
|
sort_files_by_date, get_file_timestamp, list_reports, get_info_from_report_path, human_to_timedelta
|
|
@@ -34,26 +34,29 @@ from secator.utils import (
|
|
|
34
34
|
click.rich_click.USE_RICH_MARKUP = True
|
|
35
35
|
|
|
36
36
|
ALL_TASKS = discover_tasks()
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
ALL_WORKFLOWS = [t for t in TEMPLATES if t.type == 'workflow']
|
|
38
|
+
ALL_SCANS = [t for t in TEMPLATES if t.type == 'scan']
|
|
39
|
+
ALL_PROFILES = [t for t in TEMPLATES if t.type == 'profile']
|
|
40
40
|
FINDING_TYPES_LOWER = [c.__name__.lower() for c in FINDING_TYPES]
|
|
41
|
+
CONTEXT_SETTINGS = dict(help_option_names=['-h', '-help', '--help'])
|
|
41
42
|
|
|
42
43
|
|
|
43
44
|
#-----#
|
|
44
45
|
# CLI #
|
|
45
46
|
#-----#
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
@click.
|
|
48
|
+
|
|
49
|
+
@click.group(cls=OrderedGroup, invoke_without_command=True, context_settings=CONTEXT_SETTINGS)
|
|
50
|
+
@click.option('--version', '-version', '-v', is_flag=True, default=False)
|
|
51
|
+
@click.option('--quiet', '-quiet', '-q', is_flag=True, default=False)
|
|
49
52
|
@click.pass_context
|
|
50
|
-
def cli(ctx, version):
|
|
53
|
+
def cli(ctx, version, quiet):
|
|
51
54
|
"""Secator CLI."""
|
|
52
55
|
ctx.obj = {
|
|
53
56
|
'piped_input': S_ISFIFO(os.fstat(0).st_mode),
|
|
54
57
|
'piped_output': not sys.stdout.isatty()
|
|
55
58
|
}
|
|
56
|
-
if not ctx.obj['piped_output']:
|
|
59
|
+
if not ctx.obj['piped_output'] and not quiet:
|
|
57
60
|
console.print(ASCII, highlight=False)
|
|
58
61
|
if ctx.invoked_subcommand is None:
|
|
59
62
|
if version:
|
|
@@ -66,11 +69,16 @@ def cli(ctx, version):
|
|
|
66
69
|
# TASK #
|
|
67
70
|
#------#
|
|
68
71
|
|
|
69
|
-
@cli.group(aliases=['x', 't'])
|
|
72
|
+
@cli.group(aliases=['x', 't'], invoke_without_command=True)
|
|
73
|
+
@click.option('--list', '-list', is_flag=True, default=False)
|
|
70
74
|
@click.pass_context
|
|
71
|
-
def task(ctx):
|
|
75
|
+
def task(ctx, list=False):
|
|
72
76
|
"""Run a task."""
|
|
73
|
-
|
|
77
|
+
if list:
|
|
78
|
+
print("\n".join(sorted([t.__name__ for t in ALL_TASKS])))
|
|
79
|
+
return
|
|
80
|
+
if ctx.invoked_subcommand is None:
|
|
81
|
+
ctx.get_help()
|
|
74
82
|
|
|
75
83
|
|
|
76
84
|
for cls in ALL_TASKS:
|
|
@@ -82,11 +90,16 @@ for cls in ALL_TASKS:
|
|
|
82
90
|
#----------#
|
|
83
91
|
|
|
84
92
|
|
|
85
|
-
@cli.group(cls=OrderedGroup, aliases=['w'])
|
|
93
|
+
@cli.group(cls=OrderedGroup, aliases=['w'], invoke_without_command=True)
|
|
94
|
+
@click.option('--list', '-list', is_flag=True, default=False)
|
|
86
95
|
@click.pass_context
|
|
87
|
-
def workflow(ctx):
|
|
96
|
+
def workflow(ctx, list=False):
|
|
88
97
|
"""Run a workflow."""
|
|
89
|
-
|
|
98
|
+
if list:
|
|
99
|
+
print("\n".join(sorted([t.name for t in ALL_WORKFLOWS])))
|
|
100
|
+
return
|
|
101
|
+
if ctx.invoked_subcommand is None:
|
|
102
|
+
ctx.get_help()
|
|
90
103
|
|
|
91
104
|
|
|
92
105
|
for config in sorted(ALL_WORKFLOWS, key=lambda x: x['name']):
|
|
@@ -97,17 +110,41 @@ for config in sorted(ALL_WORKFLOWS, key=lambda x: x['name']):
|
|
|
97
110
|
# SCAN #
|
|
98
111
|
#------#
|
|
99
112
|
|
|
100
|
-
@cli.group(cls=OrderedGroup, aliases=['s'])
|
|
113
|
+
@cli.group(cls=OrderedGroup, aliases=['s'], invoke_without_command=True)
|
|
114
|
+
@click.option('--list', '-list', is_flag=True, default=False)
|
|
101
115
|
@click.pass_context
|
|
102
|
-
def scan(ctx):
|
|
116
|
+
def scan(ctx, list=False):
|
|
103
117
|
"""Run a scan."""
|
|
104
|
-
|
|
118
|
+
if list:
|
|
119
|
+
print("\n".join(sorted([t.name for t in ALL_SCANS])))
|
|
120
|
+
return
|
|
121
|
+
if ctx.invoked_subcommand is None:
|
|
122
|
+
ctx.get_help()
|
|
105
123
|
|
|
106
124
|
|
|
107
125
|
for config in sorted(ALL_SCANS, key=lambda x: x['name']):
|
|
108
126
|
register_runner(scan, config)
|
|
109
127
|
|
|
110
128
|
|
|
129
|
+
@cli.group(aliases=['p'])
|
|
130
|
+
@click.pass_context
|
|
131
|
+
def profile(ctx):
|
|
132
|
+
"""Show profiles"""
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@profile.command('list')
|
|
137
|
+
def profile_list():
|
|
138
|
+
table = Table()
|
|
139
|
+
table.add_column("Profile name", style="bold gold3")
|
|
140
|
+
table.add_column("Description", overflow='fold')
|
|
141
|
+
table.add_column("Options", overflow='fold')
|
|
142
|
+
for profile in ALL_PROFILES:
|
|
143
|
+
opts_str = ','.join(f'{k}={v}' for k, v in profile.opts.items())
|
|
144
|
+
table.add_row(profile.name, profile.description, opts_str)
|
|
145
|
+
console.print(table)
|
|
146
|
+
|
|
147
|
+
|
|
111
148
|
#--------#
|
|
112
149
|
# WORKER #
|
|
113
150
|
#--------#
|
|
@@ -118,12 +155,13 @@ for config in sorted(ALL_SCANS, key=lambda x: x['name']):
|
|
|
118
155
|
@click.option('-r', '--reload', is_flag=True, help='Autoreload Celery on code changes.')
|
|
119
156
|
@click.option('-Q', '--queue', type=str, default='', help='Listen to a specific queue.')
|
|
120
157
|
@click.option('-P', '--pool', type=str, default='eventlet', help='Pool implementation.')
|
|
121
|
-
@click.option('--quiet', is_flag=True, help='Quiet mode.')
|
|
158
|
+
@click.option('--quiet', is_flag=True, default=False, help='Quiet mode.')
|
|
159
|
+
@click.option('--loglevel', type=str, default='INFO', help='Log level.')
|
|
122
160
|
@click.option('--check', is_flag=True, help='Check if Celery worker is alive.')
|
|
123
161
|
@click.option('--dev', is_flag=True, help='Start a worker in dev mode (celery multi).')
|
|
124
162
|
@click.option('--stop', is_flag=True, help='Stop a worker in dev mode (celery multi).')
|
|
125
163
|
@click.option('--show', is_flag=True, help='Show command (celery multi).')
|
|
126
|
-
def worker(hostname, concurrency, reload, queue, pool, quiet, check, dev, stop, show):
|
|
164
|
+
def worker(hostname, concurrency, reload, queue, pool, quiet, loglevel, check, dev, stop, show):
|
|
127
165
|
"""Run a worker."""
|
|
128
166
|
|
|
129
167
|
# Check Celery addon is installed
|
|
@@ -169,6 +207,7 @@ def worker(hostname, concurrency, reload, queue, pool, quiet, check, dev, stop,
|
|
|
169
207
|
|
|
170
208
|
cmd += f' -P {pool}' if pool else ''
|
|
171
209
|
cmd += f' -c {concurrency}' if concurrency else ''
|
|
210
|
+
cmd += f' -l {loglevel}' if loglevel else ''
|
|
172
211
|
|
|
173
212
|
if reload:
|
|
174
213
|
patterns = "celery.py;tasks/*.py;runners/*.py;serializers/*.py;output_types/*.py;hooks/*.py;exporters/*.py"
|
|
@@ -666,7 +705,7 @@ def report_show(report_query, output, runner_type, time_delta, type, query, work
|
|
|
666
705
|
all_results.extend(runner.results)
|
|
667
706
|
continue
|
|
668
707
|
report = Report(runner, title=f"Consolidated report - {current}", exporters=exporters)
|
|
669
|
-
report.build(extractors=extractors if not unified else [])
|
|
708
|
+
report.build(extractors=extractors if not unified else [], dedupe=unified)
|
|
670
709
|
file_date = get_file_date(path)
|
|
671
710
|
runner_name = data['info']['name']
|
|
672
711
|
console.print(
|
|
@@ -744,7 +783,7 @@ def report_list(workspace, runner_type, time_delta):
|
|
|
744
783
|
@report.command('export')
|
|
745
784
|
@click.argument('json_path', type=str)
|
|
746
785
|
@click.option('--output-folder', '-of', type=str)
|
|
747
|
-
@click.option('-output', '-o', type=str)
|
|
786
|
+
@click.option('-output', '-o', type=str, required=True)
|
|
748
787
|
def report_export(json_path, output_folder, output):
|
|
749
788
|
with open(json_path, 'r') as f:
|
|
750
789
|
data = loads_dataclass(f.read())
|
|
@@ -857,13 +896,21 @@ def health(json, debug, strict):
|
|
|
857
896
|
import json as _json
|
|
858
897
|
print(_json.dumps(status))
|
|
859
898
|
|
|
899
|
+
# Print errors and warnings
|
|
900
|
+
error = False
|
|
901
|
+
for tool, info in status['tools'].items():
|
|
902
|
+
if not info['installed']:
|
|
903
|
+
console.print(Warning(message=f'{tool} is not installed.'))
|
|
904
|
+
error = True
|
|
905
|
+
elif info['outdated']:
|
|
906
|
+
message = (
|
|
907
|
+
f'{tool} is outdated (current:{info["version"]}, latest:{info["latest_version"]}).'
|
|
908
|
+
f' Run `secator install tools {tool}` to update it.'
|
|
909
|
+
)
|
|
910
|
+
console.print(Warning(message=message))
|
|
911
|
+
|
|
860
912
|
# Strict mode
|
|
861
913
|
if strict:
|
|
862
|
-
error = False
|
|
863
|
-
for tool, info in status['tools'].items():
|
|
864
|
-
if not info['installed']:
|
|
865
|
-
console.print(Error(message=f'{tool} not installed and strict mode is enabled.'))
|
|
866
|
-
error = True
|
|
867
914
|
if error:
|
|
868
915
|
sys.exit(1)
|
|
869
916
|
console.print(Info(message='Strict healthcheck passed !'))
|
|
@@ -878,21 +925,21 @@ def run_install(title=None, cmd=None, packages=None, next_steps=None):
|
|
|
878
925
|
if CONFIG.offline_mode:
|
|
879
926
|
console.print(Error(message='Cannot run this command in offline mode.'))
|
|
880
927
|
return
|
|
881
|
-
with console.status(f'[bold yellow] Installing {title}...'):
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
928
|
+
# with console.status(f'[bold yellow] Installing {title}...'):
|
|
929
|
+
if cmd:
|
|
930
|
+
from secator.installer import SourceInstaller
|
|
931
|
+
status = SourceInstaller.install(cmd)
|
|
932
|
+
elif packages:
|
|
933
|
+
from secator.installer import PackageInstaller
|
|
934
|
+
status = PackageInstaller.install(packages)
|
|
935
|
+
return_code = 1
|
|
936
|
+
if status.is_ok():
|
|
937
|
+
return_code = 0
|
|
938
|
+
if next_steps:
|
|
939
|
+
console.print('[bold gold3]:wrench: Next steps:[/]')
|
|
940
|
+
for ix, step in enumerate(next_steps):
|
|
941
|
+
console.print(f' :keycap_{ix}: {step}')
|
|
942
|
+
sys.exit(return_code)
|
|
896
943
|
|
|
897
944
|
|
|
898
945
|
@cli.group()
|
|
@@ -1051,28 +1098,42 @@ def install_ruby():
|
|
|
1051
1098
|
|
|
1052
1099
|
@install.command('tools')
|
|
1053
1100
|
@click.argument('cmds', required=False)
|
|
1054
|
-
@click.option('--cleanup', is_flag=True, default=False)
|
|
1055
|
-
|
|
1101
|
+
@click.option('--cleanup', is_flag=True, default=False, help='Clean up tools after installation.')
|
|
1102
|
+
@click.option('--fail-fast', is_flag=True, default=False, help='Fail fast if any tool fails to install.')
|
|
1103
|
+
def install_tools(cmds, cleanup, fail_fast):
|
|
1056
1104
|
"""Install supported tools."""
|
|
1057
1105
|
if CONFIG.offline_mode:
|
|
1058
1106
|
console.print(Error(message='Cannot run this command in offline mode.'))
|
|
1059
1107
|
return
|
|
1108
|
+
tools = []
|
|
1060
1109
|
if cmds is not None:
|
|
1061
1110
|
cmds = cmds.split(',')
|
|
1062
|
-
|
|
1111
|
+
for cmd in cmds:
|
|
1112
|
+
if '==' in cmd:
|
|
1113
|
+
cmd, version = tuple(cmd.split('=='))
|
|
1114
|
+
else:
|
|
1115
|
+
cmd, version = cmd, None
|
|
1116
|
+
cls = next((cls for cls in ALL_TASKS if cls.__name__ == cmd), None)
|
|
1117
|
+
if cls:
|
|
1118
|
+
if version:
|
|
1119
|
+
cls.install_version = version
|
|
1120
|
+
tools.append(cls)
|
|
1121
|
+
else:
|
|
1122
|
+
console.print(Warning(message=f'Tool {cmd} is not supported or inexistent.'))
|
|
1063
1123
|
else:
|
|
1064
1124
|
tools = ALL_TASKS
|
|
1065
1125
|
tools.sort(key=lambda x: x.__name__)
|
|
1066
1126
|
return_code = 0
|
|
1067
1127
|
if not tools:
|
|
1068
|
-
|
|
1069
|
-
console.print(Error(message=f'No tools found for {cmd_str}.'))
|
|
1128
|
+
console.print(Error(message='No tools found for installing.'))
|
|
1070
1129
|
return
|
|
1071
1130
|
for ix, cls in enumerate(tools):
|
|
1072
|
-
with console.status(f'[bold yellow][{ix + 1}/{len(tools)}] Installing {cls.__name__} ...'):
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1131
|
+
# with console.status(f'[bold yellow][{ix + 1}/{len(tools)}] Installing {cls.__name__} ...'):
|
|
1132
|
+
status = ToolInstaller.install(cls)
|
|
1133
|
+
if not status.is_ok():
|
|
1134
|
+
return_code = 1
|
|
1135
|
+
if fail_fast:
|
|
1136
|
+
sys.exit(return_code)
|
|
1076
1137
|
console.print()
|
|
1077
1138
|
if cleanup:
|
|
1078
1139
|
distro = get_distro_config()
|
|
@@ -1132,14 +1193,13 @@ def update(all):
|
|
|
1132
1193
|
return_code = 0
|
|
1133
1194
|
for cls in ALL_TASKS:
|
|
1134
1195
|
cmd = cls.cmd.split(' ')[0]
|
|
1135
|
-
version_flag = cls.
|
|
1136
|
-
version_flag = None if cls.version_flag == OPT_NOT_SUPPORTED else version_flag
|
|
1196
|
+
version_flag = cls.get_version_flag()
|
|
1137
1197
|
info = get_version_info(cmd, version_flag, cls.install_github_handle)
|
|
1138
|
-
if not info['installed'] or info['
|
|
1139
|
-
with console.status(f'[bold yellow]Installing {cls.__name__} ...'):
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1198
|
+
if not info['installed'] or info['outdated'] or not info['latest_version']:
|
|
1199
|
+
# with console.status(f'[bold yellow]Installing {cls.__name__} ...'):
|
|
1200
|
+
status = ToolInstaller.install(cls)
|
|
1201
|
+
if not status.is_ok():
|
|
1202
|
+
return_code = 1
|
|
1143
1203
|
sys.exit(return_code)
|
|
1144
1204
|
|
|
1145
1205
|
#-------#
|
|
@@ -1250,24 +1310,35 @@ def test():
|
|
|
1250
1310
|
pass
|
|
1251
1311
|
|
|
1252
1312
|
|
|
1253
|
-
def run_test(cmd, name):
|
|
1313
|
+
def run_test(cmd, name=None, exit=True, verbose=False):
|
|
1254
1314
|
"""Run a test and return the result.
|
|
1255
1315
|
|
|
1256
1316
|
Args:
|
|
1257
|
-
cmd: Command to run.
|
|
1258
|
-
name: Name of the test.
|
|
1317
|
+
cmd (str): Command to run.
|
|
1318
|
+
name (str, optional): Name of the test.
|
|
1319
|
+
exit (bool, optional): Exit after running the test with the return code.
|
|
1320
|
+
verbose (bool, optional): Print verbose output.
|
|
1321
|
+
|
|
1322
|
+
Returns:
|
|
1323
|
+
Return code of the test.
|
|
1259
1324
|
"""
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1325
|
+
cmd_name = name + ' tests' if name else 'tests'
|
|
1326
|
+
result = Command.execute(cmd, name=cmd_name, cwd=ROOT_FOLDER, quiet=not verbose)
|
|
1327
|
+
if name:
|
|
1328
|
+
if result.return_code == 0:
|
|
1329
|
+
console.print(f':tada: {name.capitalize()} tests passed !', style='bold green')
|
|
1330
|
+
else:
|
|
1331
|
+
console.print(f':x: {name.capitalize()} tests failed !', style='bold red')
|
|
1332
|
+
if exit:
|
|
1333
|
+
sys.exit(result.return_code)
|
|
1334
|
+
return result.return_code
|
|
1264
1335
|
|
|
1265
1336
|
|
|
1266
1337
|
@test.command()
|
|
1267
1338
|
def lint():
|
|
1268
1339
|
"""Run lint tests."""
|
|
1269
1340
|
cmd = f'{sys.executable} -m flake8 secator/'
|
|
1270
|
-
run_test(cmd, 'lint')
|
|
1341
|
+
run_test(cmd, 'lint', verbose=True)
|
|
1271
1342
|
|
|
1272
1343
|
|
|
1273
1344
|
@test.command()
|
|
@@ -1285,13 +1356,21 @@ def unit(tasks, workflows, scans, test):
|
|
|
1285
1356
|
os.environ['SECATOR_HTTP_STORE_RESPONSES'] = '0'
|
|
1286
1357
|
os.environ['SECATOR_RUNNERS_SKIP_CVE_SEARCH'] = '1'
|
|
1287
1358
|
|
|
1359
|
+
if not test:
|
|
1360
|
+
if tasks:
|
|
1361
|
+
test = 'test_tasks'
|
|
1362
|
+
elif workflows:
|
|
1363
|
+
test = 'test_workflows'
|
|
1364
|
+
elif scans:
|
|
1365
|
+
test = 'test_scans'
|
|
1366
|
+
|
|
1288
1367
|
import shutil
|
|
1289
1368
|
shutil.rmtree('/tmp/.secator', ignore_errors=True)
|
|
1290
1369
|
cmd = f'{sys.executable} -m coverage run --omit="*test*" --data-file=.coverage.unit -m pytest -s -v tests/unit'
|
|
1291
1370
|
if test:
|
|
1292
1371
|
test_str = ' or '.join(test.split(','))
|
|
1293
1372
|
cmd += f' -k "{test_str}"'
|
|
1294
|
-
run_test(cmd, 'unit')
|
|
1373
|
+
run_test(cmd, 'unit', verbose=True)
|
|
1295
1374
|
|
|
1296
1375
|
|
|
1297
1376
|
@test.command()
|
|
@@ -1307,6 +1386,14 @@ def integration(tasks, workflows, scans, test):
|
|
|
1307
1386
|
os.environ['SECATOR_DIRS_DATA'] = '/tmp/.secator'
|
|
1308
1387
|
os.environ['SECATOR_RUNNERS_SKIP_CVE_SEARCH'] = '1'
|
|
1309
1388
|
|
|
1389
|
+
if not test:
|
|
1390
|
+
if tasks:
|
|
1391
|
+
test = 'test_tasks'
|
|
1392
|
+
elif workflows:
|
|
1393
|
+
test = 'test_workflows'
|
|
1394
|
+
elif scans:
|
|
1395
|
+
test = 'test_scans'
|
|
1396
|
+
|
|
1310
1397
|
import shutil
|
|
1311
1398
|
shutil.rmtree('/tmp/.secator', ignore_errors=True)
|
|
1312
1399
|
|
|
@@ -1314,7 +1401,7 @@ def integration(tasks, workflows, scans, test):
|
|
|
1314
1401
|
if test:
|
|
1315
1402
|
test_str = ' or '.join(test.split(','))
|
|
1316
1403
|
cmd += f' -k "{test_str}"'
|
|
1317
|
-
run_test(cmd, 'integration')
|
|
1404
|
+
run_test(cmd, 'integration', verbose=True)
|
|
1318
1405
|
|
|
1319
1406
|
|
|
1320
1407
|
@test.command()
|
|
@@ -1337,7 +1424,160 @@ def performance(tasks, workflows, scans, test):
|
|
|
1337
1424
|
if test:
|
|
1338
1425
|
test_str = ' or '.join(test.split(','))
|
|
1339
1426
|
cmd += f' -k "{test_str}"'
|
|
1340
|
-
run_test(cmd, 'performance')
|
|
1427
|
+
run_test(cmd, 'performance', verbose=True)
|
|
1428
|
+
|
|
1429
|
+
|
|
1430
|
+
@test.command()
|
|
1431
|
+
@click.argument('name', type=str)
|
|
1432
|
+
@click.option('--verbose', '-v', is_flag=True, default=False, help='Print verbose output')
|
|
1433
|
+
@click.option('--check', '-c', is_flag=True, default=False, help='Check task semantics only (no unit + integration tests)') # noqa: E501
|
|
1434
|
+
def task(name, verbose, check):
|
|
1435
|
+
"""Test a single task for semantics errors, and run unit + integration tests."""
|
|
1436
|
+
console.print(f'[bold gold3]:wrench: Testing task {name} ...[/]')
|
|
1437
|
+
task = [task for task in ALL_TASKS if task.__name__ == name]
|
|
1438
|
+
warnings = []
|
|
1439
|
+
errors = []
|
|
1440
|
+
exit_code = 0
|
|
1441
|
+
|
|
1442
|
+
# Check if task is correctly registered
|
|
1443
|
+
task = task[0]
|
|
1444
|
+
task_name = task.__name__
|
|
1445
|
+
|
|
1446
|
+
# Check task command is set
|
|
1447
|
+
check_test(
|
|
1448
|
+
task.cmd,
|
|
1449
|
+
'Check task command is set (cls.cmd)',
|
|
1450
|
+
'Task has no cmd attribute.',
|
|
1451
|
+
errors
|
|
1452
|
+
)
|
|
1453
|
+
if errors:
|
|
1454
|
+
sys.exit(0)
|
|
1455
|
+
|
|
1456
|
+
# Run install
|
|
1457
|
+
cmd = f'secator install tools {task_name}'
|
|
1458
|
+
ret_code = Command.execute(cmd, name='install', quiet=not verbose, cwd=ROOT_FOLDER)
|
|
1459
|
+
version_info = task.get_version_info()
|
|
1460
|
+
if verbose:
|
|
1461
|
+
console.print(f'Version info:\n{version_info}')
|
|
1462
|
+
status = version_info['status']
|
|
1463
|
+
check_test(
|
|
1464
|
+
version_info['installed'],
|
|
1465
|
+
'Check task is installed',
|
|
1466
|
+
'Failed to install command. Fix your installation command.',
|
|
1467
|
+
errors
|
|
1468
|
+
)
|
|
1469
|
+
check_test(
|
|
1470
|
+
any(cmd for cmd in [task.install_pre, task.install_cmd, task.install_github_handle]),
|
|
1471
|
+
'Check task installation command is defined',
|
|
1472
|
+
'Task has no installation command. Please define one or more of the following class attributes: `install_pre`, `install_cmd`, `install_post`, `install_github_handle`.', # noqa: E501
|
|
1473
|
+
errors
|
|
1474
|
+
)
|
|
1475
|
+
check_test(
|
|
1476
|
+
version_info['version'],
|
|
1477
|
+
'Check task version can be fetched',
|
|
1478
|
+
'Failed to detect current version. Consider updating your `version_flag` class attribute.',
|
|
1479
|
+
warnings,
|
|
1480
|
+
warn=True
|
|
1481
|
+
)
|
|
1482
|
+
check_test(
|
|
1483
|
+
status != 'latest unknown',
|
|
1484
|
+
'Check latest version',
|
|
1485
|
+
'Failed to detect latest version.',
|
|
1486
|
+
warnings,
|
|
1487
|
+
warn=True
|
|
1488
|
+
)
|
|
1489
|
+
check_test(
|
|
1490
|
+
not version_info['outdated'],
|
|
1491
|
+
'Check task version is up to date',
|
|
1492
|
+
f'Task is not up to date (current version: {version_info["version"]}, latest: {version_info["latest_version"]}). Consider updating your `install_version` class attribute.', # noqa: E501
|
|
1493
|
+
warnings,
|
|
1494
|
+
warn=True
|
|
1495
|
+
)
|
|
1496
|
+
|
|
1497
|
+
# Run task-specific tests
|
|
1498
|
+
check_test(
|
|
1499
|
+
task.__doc__,
|
|
1500
|
+
'Check task description is set (cls.__doc__)',
|
|
1501
|
+
'Task has no description (class docstring).',
|
|
1502
|
+
errors
|
|
1503
|
+
)
|
|
1504
|
+
check_test(
|
|
1505
|
+
task.input_types,
|
|
1506
|
+
'Check task input type is set (cls.input_type)',
|
|
1507
|
+
'Task has no input_type attribute.',
|
|
1508
|
+
warnings,
|
|
1509
|
+
warn=True
|
|
1510
|
+
)
|
|
1511
|
+
check_test(
|
|
1512
|
+
task.output_types,
|
|
1513
|
+
'Check task output types is set (cls.output_types)',
|
|
1514
|
+
'Task has no output_types attribute. Consider setting some so that secator can load your task outputs.',
|
|
1515
|
+
warnings,
|
|
1516
|
+
warn=True
|
|
1517
|
+
)
|
|
1518
|
+
check_test(
|
|
1519
|
+
task.install_version,
|
|
1520
|
+
'Check task install_version is set (cls.install_version)',
|
|
1521
|
+
'Task has no install_version attribute. Consider setting it to pin the tool version and ensure it does not break in the future.', # noqa: E501
|
|
1522
|
+
warnings,
|
|
1523
|
+
warn=True
|
|
1524
|
+
)
|
|
1525
|
+
|
|
1526
|
+
if not check:
|
|
1527
|
+
|
|
1528
|
+
# Run unit tests
|
|
1529
|
+
cmd = f'secator test unit --tasks {name}'
|
|
1530
|
+
ret_code = run_test(cmd, exit=False, verbose=verbose)
|
|
1531
|
+
check_test(
|
|
1532
|
+
ret_code == 0,
|
|
1533
|
+
'Check unit tests pass',
|
|
1534
|
+
'Unit tests failed.',
|
|
1535
|
+
errors
|
|
1536
|
+
)
|
|
1537
|
+
|
|
1538
|
+
# Run integration tests
|
|
1539
|
+
cmd = f'secator test integration --tasks {name}'
|
|
1540
|
+
ret_code = run_test(cmd, exit=False, verbose=verbose)
|
|
1541
|
+
check_test(
|
|
1542
|
+
ret_code == 0,
|
|
1543
|
+
'Check integration tests pass',
|
|
1544
|
+
'Integration tests failed.',
|
|
1545
|
+
errors
|
|
1546
|
+
)
|
|
1547
|
+
|
|
1548
|
+
# Exit with exit code
|
|
1549
|
+
exit_code = 1 if len(errors) > 0 else 0
|
|
1550
|
+
if exit_code == 0:
|
|
1551
|
+
console.print(f':tada: Task {name} tests passed !', style='bold green')
|
|
1552
|
+
else:
|
|
1553
|
+
console.print('\n[bold gold3]Errors:[/]')
|
|
1554
|
+
for error in errors:
|
|
1555
|
+
console.print(error)
|
|
1556
|
+
console.print(Error(message=f'Task {name} tests failed. Please fix the issues above.'))
|
|
1557
|
+
|
|
1558
|
+
if warnings:
|
|
1559
|
+
console.print('\n[bold gold3]Warnings:[/]')
|
|
1560
|
+
for warning in warnings:
|
|
1561
|
+
console.print(warning)
|
|
1562
|
+
|
|
1563
|
+
console.print("\n")
|
|
1564
|
+
sys.exit(exit_code)
|
|
1565
|
+
|
|
1566
|
+
|
|
1567
|
+
def check_test(condition, message, fail_message, results=[], warn=False):
|
|
1568
|
+
console.print(f'[bold magenta]:zap: {message} ...[/]', end='')
|
|
1569
|
+
if not condition:
|
|
1570
|
+
if not warn:
|
|
1571
|
+
error = Error(message=fail_message)
|
|
1572
|
+
console.print(' [bold red]FAILED[/]', style='dim')
|
|
1573
|
+
results.append(error)
|
|
1574
|
+
else:
|
|
1575
|
+
warning = Warning(message=fail_message)
|
|
1576
|
+
console.print(' [bold yellow]WARNING[/]', style='dim')
|
|
1577
|
+
results.append(warning)
|
|
1578
|
+
else:
|
|
1579
|
+
console.print(' [bold green]OK[/]', style='dim')
|
|
1580
|
+
return True
|
|
1341
1581
|
|
|
1342
1582
|
|
|
1343
1583
|
@test.command()
|
secator/config.py
CHANGED
|
@@ -94,6 +94,7 @@ class Runners(StrictModel):
|
|
|
94
94
|
skip_cve_low_confidence: bool = False
|
|
95
95
|
remove_duplicates: bool = False
|
|
96
96
|
show_chunk_progress: bool = False
|
|
97
|
+
show_command_output: bool = False
|
|
97
98
|
|
|
98
99
|
|
|
99
100
|
class Security(StrictModel):
|
|
@@ -623,8 +624,8 @@ for name, dir in CONFIG.dirs.items():
|
|
|
623
624
|
console.print('[bold green]ok.[/]')
|
|
624
625
|
|
|
625
626
|
# Download wordlists and payloads
|
|
626
|
-
download_files(CONFIG.wordlists.templates, CONFIG.dirs.wordlists, CONFIG.offline_mode, 'wordlist')
|
|
627
|
-
download_files(CONFIG.payloads.templates, CONFIG.dirs.payloads, CONFIG.offline_mode, 'payload')
|
|
627
|
+
# download_files(CONFIG.wordlists.templates, CONFIG.dirs.wordlists, CONFIG.offline_mode, 'wordlist')
|
|
628
|
+
# download_files(CONFIG.payloads.templates, CONFIG.dirs.payloads, CONFIG.offline_mode, 'payload')
|
|
628
629
|
|
|
629
630
|
# Print config
|
|
630
631
|
if CONFIG.debug.component == 'config':
|