secator 0.3.6__py3-none-any.whl → 0.4.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 +14 -19
- secator/cli.py +181 -99
- secator/config.py +558 -122
- secator/decorators.py +5 -5
- secator/definitions.py +25 -126
- secator/exporters/gdrive.py +10 -10
- secator/hooks/mongodb.py +3 -4
- secator/installer.py +10 -6
- secator/output_types/vulnerability.py +3 -1
- secator/runners/_base.py +11 -9
- secator/runners/_helpers.py +52 -34
- secator/runners/command.py +26 -30
- secator/runners/scan.py +4 -7
- secator/runners/task.py +2 -1
- secator/runners/workflow.py +3 -6
- secator/tasks/_categories.py +95 -44
- secator/tasks/dnsxbrute.py +3 -2
- secator/tasks/ffuf.py +2 -2
- secator/tasks/httpx.py +4 -4
- secator/tasks/katana.py +5 -4
- secator/tasks/msfconsole.py +3 -4
- secator/tasks/nmap.py +95 -48
- secator/tasks/nuclei.py +4 -0
- secator/template.py +137 -0
- secator/utils.py +3 -7
- {secator-0.3.6.dist-info → secator-0.4.1.dist-info}/METADATA +12 -6
- {secator-0.3.6.dist-info → secator-0.4.1.dist-info}/RECORD +30 -29
- {secator-0.3.6.dist-info → secator-0.4.1.dist-info}/WHEEL +1 -1
- {secator-0.3.6.dist-info → secator-0.4.1.dist-info}/entry_points.txt +0 -0
- {secator-0.3.6.dist-info → secator-0.4.1.dist-info}/licenses/LICENSE +0 -0
secator/celery.py
CHANGED
|
@@ -9,12 +9,7 @@ from celery.result import AsyncResult, allow_join_result
|
|
|
9
9
|
# from pyinstrument import Profiler # TODO: make pyinstrument optional
|
|
10
10
|
from rich.logging import RichHandler
|
|
11
11
|
|
|
12
|
-
from secator.
|
|
13
|
-
CELERY_BROKER_POOL_LIMIT, CELERY_BROKER_URL,
|
|
14
|
-
CELERY_BROKER_VISIBILITY_TIMEOUT,
|
|
15
|
-
CELERY_DATA_FOLDER,
|
|
16
|
-
CELERY_OVERRIDE_DEFAULT_LOGGING,
|
|
17
|
-
CELERY_RESULT_BACKEND, DEBUG)
|
|
12
|
+
from secator.config import CONFIG
|
|
18
13
|
from secator.rich import console
|
|
19
14
|
from secator.runners import Scan, Task, Workflow
|
|
20
15
|
from secator.runners._helpers import run_extractors
|
|
@@ -33,7 +28,7 @@ logging.basicConfig(
|
|
|
33
28
|
handlers=[rich_handler],
|
|
34
29
|
force=True)
|
|
35
30
|
logging.getLogger('kombu').setLevel(logging.ERROR)
|
|
36
|
-
logging.getLogger('celery').setLevel(logging.INFO if
|
|
31
|
+
logging.getLogger('celery').setLevel(logging.INFO if CONFIG.debug.level > 6 else logging.WARNING)
|
|
37
32
|
|
|
38
33
|
logger = logging.getLogger(__name__)
|
|
39
34
|
|
|
@@ -49,19 +44,19 @@ app.conf.update({
|
|
|
49
44
|
'worker_max_tasks_per_child': 10,
|
|
50
45
|
|
|
51
46
|
# Broker config
|
|
52
|
-
'broker_url':
|
|
47
|
+
'broker_url': CONFIG.celery.broker_url,
|
|
53
48
|
'broker_transport_options': {
|
|
54
|
-
'data_folder_in':
|
|
55
|
-
'data_folder_out':
|
|
56
|
-
'control_folder':
|
|
57
|
-
'visibility_timeout':
|
|
49
|
+
'data_folder_in': CONFIG.dirs.celery_data,
|
|
50
|
+
'data_folder_out': CONFIG.dirs.celery_data,
|
|
51
|
+
'control_folder': CONFIG.dirs.celery_data,
|
|
52
|
+
'visibility_timeout': CONFIG.celery.broker_visibility_timeout,
|
|
58
53
|
},
|
|
59
54
|
'broker_connection_retry_on_startup': True,
|
|
60
|
-
'broker_pool_limit':
|
|
61
|
-
'broker_connection_timeout':
|
|
55
|
+
'broker_pool_limit': CONFIG.celery.broker_pool_limit,
|
|
56
|
+
'broker_connection_timeout': CONFIG.celery.broker_connection_timeout,
|
|
62
57
|
|
|
63
58
|
# Backend config
|
|
64
|
-
'result_backend':
|
|
59
|
+
'result_backend': CONFIG.celery.result_backend,
|
|
65
60
|
'result_extended': True,
|
|
66
61
|
'result_backend_thread_safe': True,
|
|
67
62
|
# 'result_backend_transport_options': {'master_name': 'mymaster'}, # for Redis HA backend
|
|
@@ -90,7 +85,7 @@ app.autodiscover_tasks(['secator.hooks.mongodb'], related_name=None)
|
|
|
90
85
|
|
|
91
86
|
def maybe_override_logging():
|
|
92
87
|
def decorator(func):
|
|
93
|
-
if
|
|
88
|
+
if CONFIG.celery.override_default_logging:
|
|
94
89
|
return signals.setup_logging.connect(func)
|
|
95
90
|
else:
|
|
96
91
|
return func
|
|
@@ -151,7 +146,7 @@ def break_task(task_cls, task_opts, targets, results=[], chunk_size=1):
|
|
|
151
146
|
|
|
152
147
|
@app.task(bind=True)
|
|
153
148
|
def run_task(self, args=[], kwargs={}):
|
|
154
|
-
if
|
|
149
|
+
if CONFIG.debug.level > 1:
|
|
155
150
|
logger.info(f'Received task with args {args} and kwargs {kwargs}')
|
|
156
151
|
if 'context' not in kwargs:
|
|
157
152
|
kwargs['context'] = {}
|
|
@@ -162,7 +157,7 @@ def run_task(self, args=[], kwargs={}):
|
|
|
162
157
|
|
|
163
158
|
@app.task(bind=True)
|
|
164
159
|
def run_workflow(self, args=[], kwargs={}):
|
|
165
|
-
if
|
|
160
|
+
if CONFIG.debug.level > 1:
|
|
166
161
|
logger.info(f'Received workflow with args {args} and kwargs {kwargs}')
|
|
167
162
|
if 'context' not in kwargs:
|
|
168
163
|
kwargs['context'] = {}
|
|
@@ -173,7 +168,7 @@ def run_workflow(self, args=[], kwargs={}):
|
|
|
173
168
|
|
|
174
169
|
@app.task(bind=True)
|
|
175
170
|
def run_scan(self, args=[], kwargs={}):
|
|
176
|
-
if
|
|
171
|
+
if CONFIG.debug.level > 1:
|
|
177
172
|
logger.info(f'Received scan with args {args} and kwargs {kwargs}')
|
|
178
173
|
if 'context' not in kwargs:
|
|
179
174
|
kwargs['context'] = {}
|
secator/cli.py
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import os
|
|
3
3
|
import re
|
|
4
|
+
import shutil
|
|
4
5
|
import sys
|
|
5
6
|
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
6
9
|
import rich_click as click
|
|
7
10
|
from dotmap import DotMap
|
|
8
11
|
from fp.fp import FreeProxy
|
|
@@ -11,21 +14,20 @@ from rich.live import Live
|
|
|
11
14
|
from rich.markdown import Markdown
|
|
12
15
|
from rich.rule import Rule
|
|
13
16
|
|
|
14
|
-
from secator.config import
|
|
17
|
+
from secator.config import CONFIG, ROOT_FOLDER, Config, default_config, config_path
|
|
18
|
+
from secator.template import TemplateLoader
|
|
15
19
|
from secator.decorators import OrderedGroup, register_runner
|
|
16
|
-
from secator.definitions import
|
|
17
|
-
|
|
18
|
-
from secator.installer import ToolInstaller, get_version_info, get_health_table, fmt_health_table_row
|
|
20
|
+
from secator.definitions import ADDONS_ENABLED, ASCII, DEV_PACKAGE, OPT_NOT_SUPPORTED, VERSION
|
|
21
|
+
from secator.installer import ToolInstaller, fmt_health_table_row, get_health_table, get_version_info
|
|
19
22
|
from secator.rich import console
|
|
20
23
|
from secator.runners import Command
|
|
21
24
|
from secator.serializers.dataclass import loads_dataclass
|
|
22
|
-
from secator.utils import
|
|
23
|
-
print_results_table, print_version)
|
|
25
|
+
from secator.utils import debug, detect_host, discover_tasks, flatten, print_results_table, print_version
|
|
24
26
|
|
|
25
27
|
click.rich_click.USE_RICH_MARKUP = True
|
|
26
28
|
|
|
27
29
|
ALL_TASKS = discover_tasks()
|
|
28
|
-
ALL_CONFIGS =
|
|
30
|
+
ALL_CONFIGS = TemplateLoader.load_all()
|
|
29
31
|
ALL_WORKFLOWS = ALL_CONFIGS.workflow
|
|
30
32
|
ALL_SCANS = ALL_CONFIGS.scan
|
|
31
33
|
|
|
@@ -107,8 +109,14 @@ for config in sorted(ALL_SCANS, key=lambda x: x['name']):
|
|
|
107
109
|
def worker(hostname, concurrency, reload, queue, pool, check, dev, stop, show):
|
|
108
110
|
"""Run a worker."""
|
|
109
111
|
if not ADDONS_ENABLED['worker']:
|
|
110
|
-
console.print('[bold red]Missing worker addon: please run
|
|
112
|
+
console.print('[bold red]Missing worker addon: please run [bold green4]secator install addons worker[/][/].')
|
|
111
113
|
sys.exit(1)
|
|
114
|
+
broker_protocol = CONFIG.celery.broker_url.split('://')[0]
|
|
115
|
+
backend_protocol = CONFIG.celery.result_backend.split('://')[0]
|
|
116
|
+
if CONFIG.celery.broker_url:
|
|
117
|
+
if (broker_protocol == 'redis' or backend_protocol == 'redis') and not ADDONS_ENABLED['redis']:
|
|
118
|
+
console.print('[bold red]Missing `redis` addon: please run [bold green4]secator install addons redis[/][/].')
|
|
119
|
+
sys.exit(1)
|
|
112
120
|
from secator.celery import app, is_celery_worker_alive
|
|
113
121
|
debug('conf', obj=dict(app.conf), obj_breaklines=True, sub='celery.app.conf', level=4)
|
|
114
122
|
debug('registered tasks', obj=list(app.tasks.keys()), obj_breaklines=True, sub='celery.tasks', level=4)
|
|
@@ -155,6 +163,9 @@ def util():
|
|
|
155
163
|
@click.option('--number', '-n', type=int, default=1, help='Number of proxies')
|
|
156
164
|
def proxy(timeout, number):
|
|
157
165
|
"""Get random proxies from FreeProxy."""
|
|
166
|
+
if CONFIG.offline_mode:
|
|
167
|
+
console.print('[bold red]Cannot run this command in offline mode.[/]')
|
|
168
|
+
return
|
|
158
169
|
proxy = FreeProxy(timeout=timeout, rand=True, anonym=True)
|
|
159
170
|
for _ in range(number):
|
|
160
171
|
url = proxy.get()
|
|
@@ -177,12 +188,17 @@ def revshell(name, host, port, interface, listen, force):
|
|
|
177
188
|
f'Interface "{interface}" could not be found. Run "ifconfig" to see the list of available interfaces.',
|
|
178
189
|
style='bold red')
|
|
179
190
|
return
|
|
191
|
+
else:
|
|
192
|
+
console.print(f'[bold green]Detected host IP: [bold orange1]{host}[/].[/]')
|
|
180
193
|
|
|
181
194
|
# Download reverse shells JSON from repo
|
|
182
|
-
revshells_json = f'{
|
|
195
|
+
revshells_json = f'{CONFIG.dirs.revshells}/revshells.json'
|
|
183
196
|
if not os.path.exists(revshells_json) or force:
|
|
197
|
+
if CONFIG.offline_mode:
|
|
198
|
+
console.print('[bold red]Cannot run this command in offline mode.[/]')
|
|
199
|
+
return
|
|
184
200
|
ret = Command.execute(
|
|
185
|
-
f'wget https://raw.githubusercontent.com/freelabz/secator/main/scripts/revshells.json && mv revshells.json {
|
|
201
|
+
f'wget https://raw.githubusercontent.com/freelabz/secator/main/scripts/revshells.json && mv revshells.json {CONFIG.dirs.revshells}', # noqa: E501
|
|
186
202
|
cls_attributes={'shell': True}
|
|
187
203
|
)
|
|
188
204
|
if not ret.return_code == 0:
|
|
@@ -241,42 +257,12 @@ def revshell(name, host, port, interface, listen, force):
|
|
|
241
257
|
|
|
242
258
|
|
|
243
259
|
@util.command()
|
|
244
|
-
@click.option('--directory', '-d', type=str, default=
|
|
260
|
+
@click.option('--directory', '-d', type=str, default=CONFIG.dirs.payloads, help='HTTP server directory')
|
|
245
261
|
@click.option('--host', '-h', type=str, default=None, help='HTTP host')
|
|
246
262
|
@click.option('--port', '-p', type=int, default=9001, help='HTTP server port')
|
|
247
263
|
@click.option('--interface', '-i', type=str, default=None, help='Interface to use to auto-detect host IP')
|
|
248
264
|
def serve(directory, host, port, interface):
|
|
249
265
|
"""Run HTTP server to serve payloads."""
|
|
250
|
-
LSE_URL = 'https://github.com/diego-treitos/linux-smart-enumeration/releases/latest/download/lse.sh'
|
|
251
|
-
LINPEAS_URL = 'https://github.com/carlospolop/PEASS-ng/releases/latest/download/linpeas.sh'
|
|
252
|
-
SUDOKILLER_URL = 'https://raw.githubusercontent.com/TH3xACE/SUDO_KILLER/V3/SUDO_KILLERv3.sh'
|
|
253
|
-
PAYLOADS = [
|
|
254
|
-
{
|
|
255
|
-
'fname': 'lse.sh',
|
|
256
|
-
'description': 'Linux Smart Enumeration',
|
|
257
|
-
'command': f'wget {LSE_URL} -O lse.sh && chmod 700 lse.sh'
|
|
258
|
-
},
|
|
259
|
-
{
|
|
260
|
-
'fname': 'linpeas.sh',
|
|
261
|
-
'description': 'Linux Privilege Escalation Awesome Script',
|
|
262
|
-
'command': f'wget {LINPEAS_URL} -O linpeas.sh && chmod 700 linpeas.sh'
|
|
263
|
-
},
|
|
264
|
-
{
|
|
265
|
-
'fname': 'sudo_killer.sh',
|
|
266
|
-
'description': 'SUDO_KILLER',
|
|
267
|
-
'command': f'wget {SUDOKILLER_URL} -O sudo_killer.sh && chmod 700 sudo_killer.sh'
|
|
268
|
-
}
|
|
269
|
-
]
|
|
270
|
-
for ix, payload in enumerate(PAYLOADS):
|
|
271
|
-
descr = payload.get('description', '')
|
|
272
|
-
fname = payload['fname']
|
|
273
|
-
if not os.path.exists(f'{directory}/{fname}'):
|
|
274
|
-
with console.status(f'[bold yellow][{ix}/{len(PAYLOADS)}] Downloading {fname} [dim]({descr})[/] ...[/]'):
|
|
275
|
-
cmd = payload['command']
|
|
276
|
-
console.print(f'[bold magenta]{fname} [dim]({descr})[/] ...[/]', )
|
|
277
|
-
Command.execute(cmd, cls_attributes={'shell': True}, cwd=directory)
|
|
278
|
-
console.print()
|
|
279
|
-
|
|
280
266
|
console.print(Rule())
|
|
281
267
|
console.print(f'Available payloads in {directory}: ', style='bold yellow')
|
|
282
268
|
for fname in os.listdir(directory):
|
|
@@ -287,9 +273,7 @@ def serve(directory, host, port, interface):
|
|
|
287
273
|
f'Interface "{interface}" could not be found. Run "ifconfig" to see the list of interfaces.',
|
|
288
274
|
style='bold red')
|
|
289
275
|
return
|
|
290
|
-
|
|
291
|
-
fdescr = payload.get('description', 'No description')
|
|
292
|
-
console.print(f'{fname} [dim]({fdescr})[/]', style='bold magenta')
|
|
276
|
+
console.print(f'{fname} [dim][/]', style='bold magenta')
|
|
293
277
|
console.print(f'wget http://{host}:{port}/{fname}', style='dim italic')
|
|
294
278
|
console.print('')
|
|
295
279
|
console.print(Rule())
|
|
@@ -396,7 +380,7 @@ def build():
|
|
|
396
380
|
def build_pypi():
|
|
397
381
|
"""Build secator PyPI package."""
|
|
398
382
|
if not ADDONS_ENABLED['build']:
|
|
399
|
-
console.print('[bold red]Missing build addon: please run
|
|
383
|
+
console.print('[bold red]Missing build addon: please run [bold green4]secator install addons build[/][/]')
|
|
400
384
|
sys.exit(1)
|
|
401
385
|
with console.status('[bold gold3]Building PyPI package...[/]'):
|
|
402
386
|
ret = Command.execute(f'{sys.executable} -m hatch build', name='hatch build', cwd=ROOT_FOLDER)
|
|
@@ -432,7 +416,7 @@ def publish():
|
|
|
432
416
|
def publish_pypi():
|
|
433
417
|
"""Publish secator PyPI package."""
|
|
434
418
|
if not ADDONS_ENABLED['build']:
|
|
435
|
-
console.print('[bold red]Missing build addon: please run
|
|
419
|
+
console.print('[bold red]Missing build addon: please run [bold green4]secator install addons build[/][/]')
|
|
436
420
|
sys.exit(1)
|
|
437
421
|
os.environ['HATCH_INDEX_USER'] = '__token__'
|
|
438
422
|
hatch_token = os.environ.get('HATCH_INDEX_AUTH')
|
|
@@ -461,6 +445,84 @@ def publish_docker(tag, latest):
|
|
|
461
445
|
sys.exit(ret.return_code)
|
|
462
446
|
|
|
463
447
|
|
|
448
|
+
#--------#
|
|
449
|
+
# CONFIG #
|
|
450
|
+
#--------#
|
|
451
|
+
|
|
452
|
+
@cli.group(aliases=['c'])
|
|
453
|
+
def config():
|
|
454
|
+
"""View or edit config."""
|
|
455
|
+
pass
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
@config.command('get')
|
|
459
|
+
@click.option('--full', is_flag=True, help='Show full config (with defaults)')
|
|
460
|
+
@click.argument('key', required=False)
|
|
461
|
+
def config_get(full, key=None):
|
|
462
|
+
"""Get config value."""
|
|
463
|
+
if key is None:
|
|
464
|
+
partial = not full and CONFIG != default_config
|
|
465
|
+
CONFIG.print(partial=partial)
|
|
466
|
+
return
|
|
467
|
+
CONFIG.get(key)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
@config.command('set')
|
|
471
|
+
@click.argument('key')
|
|
472
|
+
@click.argument('value')
|
|
473
|
+
def config_set(key, value):
|
|
474
|
+
"""Set config value."""
|
|
475
|
+
CONFIG.set(key, value)
|
|
476
|
+
config = CONFIG.validate()
|
|
477
|
+
if config:
|
|
478
|
+
CONFIG.get(key)
|
|
479
|
+
saved = CONFIG.save()
|
|
480
|
+
if not saved:
|
|
481
|
+
return
|
|
482
|
+
console.print(f'[bold green]:tada: Saved config to [/]{CONFIG._path}')
|
|
483
|
+
else:
|
|
484
|
+
console.print('[bold red]:x: Invalid config, not saving it.')
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
@config.command('edit')
|
|
488
|
+
@click.option('--resume', is_flag=True)
|
|
489
|
+
def config_edit(resume):
|
|
490
|
+
"""Edit config."""
|
|
491
|
+
tmp_config = CONFIG.dirs.data / 'config.yml.patch'
|
|
492
|
+
if not tmp_config.exists() or not resume:
|
|
493
|
+
shutil.copyfile(config_path, tmp_config)
|
|
494
|
+
click.edit(filename=tmp_config)
|
|
495
|
+
config = Config.parse(path=tmp_config)
|
|
496
|
+
if config:
|
|
497
|
+
config.save(config_path)
|
|
498
|
+
console.print(f'\n[bold green]:tada: Saved config to [/]{config_path}.')
|
|
499
|
+
tmp_config.unlink()
|
|
500
|
+
else:
|
|
501
|
+
console.print('\n[bold green]Hint:[/] Run "secator config edit --resume" to edit your patch and fix issues.')
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
@config.command('default')
|
|
505
|
+
@click.option('--save', type=str, help='Save default config to file.')
|
|
506
|
+
def config_default(save):
|
|
507
|
+
"""Get default config."""
|
|
508
|
+
default_config.print(partial=False)
|
|
509
|
+
if save:
|
|
510
|
+
default_config.save(target_path=Path(save), partial=False)
|
|
511
|
+
console.print(f'\n[bold green]:tada: Saved default config to [/]{save}.')
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
# TODO: implement reset method
|
|
515
|
+
# @_config.command('reset')
|
|
516
|
+
# @click.argument('key')
|
|
517
|
+
# def config_reset(key):
|
|
518
|
+
# """Reset config value to default."""
|
|
519
|
+
# success = CONFIG.set(key, None)
|
|
520
|
+
# if success:
|
|
521
|
+
# CONFIG.print()
|
|
522
|
+
# CONFIG.save()
|
|
523
|
+
# console.print(f'\n[bold green]:tada: Saved config to [/]{CONFIG._path}')
|
|
524
|
+
|
|
525
|
+
|
|
464
526
|
#--------#
|
|
465
527
|
# REPORT #
|
|
466
528
|
#--------#
|
|
@@ -530,6 +592,24 @@ def health(json, debug):
|
|
|
530
592
|
table.add_row(*row)
|
|
531
593
|
status['secator'] = info
|
|
532
594
|
|
|
595
|
+
# Check addons
|
|
596
|
+
console.print('\n:wrench: [bold gold3]Checking installed addons ...[/]')
|
|
597
|
+
table = get_health_table()
|
|
598
|
+
with Live(table, console=console):
|
|
599
|
+
for addon in ['worker', 'google', 'mongodb', 'redis', 'dev', 'trace', 'build']:
|
|
600
|
+
addon_var = ADDONS_ENABLED[addon]
|
|
601
|
+
info = {
|
|
602
|
+
'name': addon,
|
|
603
|
+
'version': None,
|
|
604
|
+
'status': 'ok' if addon_var else 'missing',
|
|
605
|
+
'latest_version': None,
|
|
606
|
+
'installed': addon_var,
|
|
607
|
+
'location': None
|
|
608
|
+
}
|
|
609
|
+
row = fmt_health_table_row(info, 'addons')
|
|
610
|
+
table.add_row(*row)
|
|
611
|
+
status['addons'][addon] = info
|
|
612
|
+
|
|
533
613
|
# Check languages
|
|
534
614
|
console.print('\n:wrench: [bold gold3]Checking installed languages ...[/]')
|
|
535
615
|
version_cmds = {'go': 'version', 'python3': '--version', 'ruby': '--version'}
|
|
@@ -554,24 +634,6 @@ def health(json, debug):
|
|
|
554
634
|
table.add_row(*row)
|
|
555
635
|
status['tools'][tool.__name__] = info
|
|
556
636
|
|
|
557
|
-
# # Check addons
|
|
558
|
-
console.print('\n:wrench: [bold gold3]Checking installed addons ...[/]')
|
|
559
|
-
table = get_health_table()
|
|
560
|
-
with Live(table, console=console):
|
|
561
|
-
for addon in ['worker', 'google', 'mongodb', 'redis', 'dev', 'trace', 'build']:
|
|
562
|
-
addon_var = ADDONS_ENABLED[addon]
|
|
563
|
-
info = {
|
|
564
|
-
'name': addon,
|
|
565
|
-
'version': None,
|
|
566
|
-
'status': 'ok' if addon_var else 'missing',
|
|
567
|
-
'latest_version': None,
|
|
568
|
-
'installed': addon_var,
|
|
569
|
-
'location': None
|
|
570
|
-
}
|
|
571
|
-
row = fmt_health_table_row(info, 'addons')
|
|
572
|
-
table.add_row(*row)
|
|
573
|
-
status['addons'][addon] = info
|
|
574
|
-
|
|
575
637
|
# Print JSON health
|
|
576
638
|
if json:
|
|
577
639
|
import json as _json
|
|
@@ -583,6 +645,9 @@ def health(json, debug):
|
|
|
583
645
|
|
|
584
646
|
|
|
585
647
|
def run_install(cmd, title, next_steps=None):
|
|
648
|
+
if CONFIG.offline_mode:
|
|
649
|
+
console.print('[bold red]Cannot run this command in offline mode.[/]')
|
|
650
|
+
return
|
|
586
651
|
with console.status(f'[bold yellow] Installing {title}...'):
|
|
587
652
|
ret = Command.execute(cmd, cls_attributes={'shell': True}, print_cmd=True, print_line=True)
|
|
588
653
|
if ret.return_code != 0:
|
|
@@ -615,9 +680,9 @@ def install_worker():
|
|
|
615
680
|
cmd=f'{sys.executable} -m pip install secator[worker]',
|
|
616
681
|
title='worker addon',
|
|
617
682
|
next_steps=[
|
|
618
|
-
'Run
|
|
619
|
-
'Run
|
|
620
|
-
'[dim]\[optional][/dim] Run
|
|
683
|
+
'Run [bold green4]secator worker[/] to run a Celery worker using the file system as a backend and broker.',
|
|
684
|
+
'Run [bold green4]secator x httpx testphp.vulnweb.com[/] to admire your task running in a worker.',
|
|
685
|
+
'[dim]\[optional][/dim] Run [bold green4]secator install addons redis[/] to setup Redis backend / broker.'
|
|
621
686
|
]
|
|
622
687
|
)
|
|
623
688
|
|
|
@@ -629,8 +694,9 @@ def install_google():
|
|
|
629
694
|
cmd=f'{sys.executable} -m pip install secator[google]',
|
|
630
695
|
title='google addon',
|
|
631
696
|
next_steps=[
|
|
632
|
-
'
|
|
633
|
-
'Run
|
|
697
|
+
'Run [bold green4]secator config set addons.google.credentials_path <VALUE>[/].',
|
|
698
|
+
'Run [bold green4]secator config set addons.google.drive_parent_folder_id <VALUE>[/].',
|
|
699
|
+
'Run [bold green4]secator x httpx testphp.vulnweb.com -o gdrive[/] to send reports to Google Drive.'
|
|
634
700
|
]
|
|
635
701
|
)
|
|
636
702
|
|
|
@@ -642,9 +708,9 @@ def install_mongodb():
|
|
|
642
708
|
cmd=f'{sys.executable} -m pip install secator[mongodb]',
|
|
643
709
|
title='mongodb addon',
|
|
644
710
|
next_steps=[
|
|
645
|
-
'[dim]\[optional][/] Run
|
|
646
|
-
'
|
|
647
|
-
'Run
|
|
711
|
+
'[dim]\[optional][/] Run [bold green4]docker run --name mongo -p 27017:27017 -d mongo:latest[/] to run a local MongoDB instance.', # noqa: E501
|
|
712
|
+
'Run [bold green4]secator config set addons.mongodb.url mongodb://<URL>[/].',
|
|
713
|
+
'Run [bold green4]secator x httpx testphp.vulnweb.com -driver mongodb[/] to save results to MongoDB.'
|
|
648
714
|
]
|
|
649
715
|
)
|
|
650
716
|
|
|
@@ -656,11 +722,11 @@ def install_redis():
|
|
|
656
722
|
cmd=f'{sys.executable} -m pip install secator[redis]',
|
|
657
723
|
title='redis addon',
|
|
658
724
|
next_steps=[
|
|
659
|
-
'[dim]\[optional][/] Run
|
|
660
|
-
'
|
|
661
|
-
'
|
|
662
|
-
'Run
|
|
663
|
-
'Run
|
|
725
|
+
'[dim]\[optional][/] Run [bold green4]docker run --name redis -p 6379:6379 -d redis[/] to run a local Redis instance.', # noqa: E501
|
|
726
|
+
'Run [bold green4]secator config set celery.broker_url redis://<URL>[/]',
|
|
727
|
+
'Run [bold green4]secator config set celery.result_backend redis://<URL>[/]',
|
|
728
|
+
'Run [bold green4]secator worker[/] to run a worker.',
|
|
729
|
+
'Run [bold green4]secator x httpx testphp.vulnweb.com[/] to run a test task.'
|
|
664
730
|
]
|
|
665
731
|
)
|
|
666
732
|
|
|
@@ -672,9 +738,9 @@ def install_dev():
|
|
|
672
738
|
cmd=f'{sys.executable} -m pip install secator[dev]',
|
|
673
739
|
title='dev addon',
|
|
674
740
|
next_steps=[
|
|
675
|
-
'Run
|
|
676
|
-
'Run
|
|
677
|
-
'Run
|
|
741
|
+
'Run [bold green4]secator test lint[/] to run lint tests.',
|
|
742
|
+
'Run [bold green4]secator test unit[/] to run unit tests.',
|
|
743
|
+
'Run [bold green4]secator test integration[/] to run integration tests.',
|
|
678
744
|
]
|
|
679
745
|
)
|
|
680
746
|
|
|
@@ -686,9 +752,6 @@ def install_trace():
|
|
|
686
752
|
cmd=f'{sys.executable} -m pip install secator[trace]',
|
|
687
753
|
title='dev addon',
|
|
688
754
|
next_steps=[
|
|
689
|
-
'Run "secator test lint" to run lint tests.',
|
|
690
|
-
'Run "secator test unit" to run unit tests.',
|
|
691
|
-
'Run "secator test integration" to run integration tests.',
|
|
692
755
|
]
|
|
693
756
|
)
|
|
694
757
|
|
|
@@ -700,9 +763,10 @@ def install_build():
|
|
|
700
763
|
cmd=f'{sys.executable} -m pip install secator[build]',
|
|
701
764
|
title='build addon',
|
|
702
765
|
next_steps=[
|
|
703
|
-
'Run
|
|
704
|
-
'Run
|
|
705
|
-
'Run
|
|
766
|
+
'Run [bold green4]secator u build pypi[/] to build the PyPI package.',
|
|
767
|
+
'Run [bold green4]secator u publish pypi[/] to publish the PyPI package.',
|
|
768
|
+
'Run [bold green4]secator u build docker[/] to build the Docker image.',
|
|
769
|
+
'Run [bold green4]secator u publish docker[/] to publish the Docker image.',
|
|
706
770
|
]
|
|
707
771
|
)
|
|
708
772
|
|
|
@@ -738,6 +802,9 @@ def install_ruby():
|
|
|
738
802
|
@click.argument('cmds', required=False)
|
|
739
803
|
def install_tools(cmds):
|
|
740
804
|
"""Install supported tools."""
|
|
805
|
+
if CONFIG.offline_mode:
|
|
806
|
+
console.print('[bold red]Cannot run this command in offline mode.[/]')
|
|
807
|
+
return
|
|
741
808
|
if cmds is not None:
|
|
742
809
|
cmds = cmds.split(',')
|
|
743
810
|
tools = [cls for cls in ALL_TASKS if cls.__name__ in cmds]
|
|
@@ -754,18 +821,21 @@ def install_tools(cmds):
|
|
|
754
821
|
@click.option('--force', is_flag=True)
|
|
755
822
|
def install_cves(force):
|
|
756
823
|
"""Install CVEs (enables passive vulnerability search)."""
|
|
757
|
-
|
|
824
|
+
if CONFIG.offline_mode:
|
|
825
|
+
console.print('[bold red]Cannot run this command in offline mode.[/]')
|
|
826
|
+
return
|
|
827
|
+
cve_json_path = f'{CONFIG.dirs.cves}/circl-cve-search-expanded.json'
|
|
758
828
|
if not os.path.exists(cve_json_path) or force:
|
|
759
829
|
with console.status('[bold yellow]Downloading zipped CVEs from cve.circl.lu ...[/]'):
|
|
760
|
-
Command.execute('wget https://cve.circl.lu/static/circl-cve-search-expanded.json.gz', cwd=
|
|
830
|
+
Command.execute('wget https://cve.circl.lu/static/circl-cve-search-expanded.json.gz', cwd=CONFIG.dirs.cves)
|
|
761
831
|
with console.status('[bold yellow]Unzipping CVEs ...[/]'):
|
|
762
|
-
Command.execute(f'gunzip {
|
|
763
|
-
with console.status(f'[bold yellow]Installing CVEs to {
|
|
832
|
+
Command.execute(f'gunzip {CONFIG.dirs.cves}/circl-cve-search-expanded.json.gz', cwd=CONFIG.dirs.cves)
|
|
833
|
+
with console.status(f'[bold yellow]Installing CVEs to {CONFIG.dirs.cves} ...[/]'):
|
|
764
834
|
with open(cve_json_path, 'r') as f:
|
|
765
835
|
for line in f:
|
|
766
836
|
data = json.loads(line)
|
|
767
837
|
cve_id = data['id']
|
|
768
|
-
cve_path = f'{
|
|
838
|
+
cve_path = f'{CONFIG.dirs.cves}/{cve_id}.json'
|
|
769
839
|
with open(cve_path, 'w') as f:
|
|
770
840
|
f.write(line)
|
|
771
841
|
console.print(f'CVE saved to {cve_path}')
|
|
@@ -779,6 +849,9 @@ def install_cves(force):
|
|
|
779
849
|
@cli.command('update')
|
|
780
850
|
def update():
|
|
781
851
|
"""[dim]Update to latest version.[/]"""
|
|
852
|
+
if CONFIG.offline_mode:
|
|
853
|
+
console.print('[bold red]Cannot run this command in offline mode.[/]')
|
|
854
|
+
return
|
|
782
855
|
info = get_version_info('secator', github_handle='freelabz/secator', version=VERSION)
|
|
783
856
|
latest_version = info['latest_version']
|
|
784
857
|
if info['status'] == 'latest':
|
|
@@ -806,7 +879,7 @@ def alias():
|
|
|
806
879
|
@click.pass_context
|
|
807
880
|
def enable_aliases(ctx):
|
|
808
881
|
"""Enable aliases."""
|
|
809
|
-
fpath = f'{
|
|
882
|
+
fpath = f'{CONFIG.dirs.data}/.aliases'
|
|
810
883
|
aliases = ctx.invoke(list_aliases, silent=True)
|
|
811
884
|
aliases_str = '\n'.join(aliases)
|
|
812
885
|
with open(fpath, 'w') as f:
|
|
@@ -828,7 +901,7 @@ echo "source {fpath} >> ~/.bashrc" # or add this line to your ~/.bashrc to load
|
|
|
828
901
|
@click.pass_context
|
|
829
902
|
def disable_aliases(ctx):
|
|
830
903
|
"""Disable aliases."""
|
|
831
|
-
fpath = f'{
|
|
904
|
+
fpath = f'{CONFIG.dirs.data}/.unalias'
|
|
832
905
|
aliases = ctx.invoke(list_aliases, silent=True)
|
|
833
906
|
aliases_str = ''
|
|
834
907
|
for alias in aliases:
|
|
@@ -894,7 +967,7 @@ def test():
|
|
|
894
967
|
console.print('[bold red]You MUST use a development version of secator to run tests.[/]')
|
|
895
968
|
sys.exit(1)
|
|
896
969
|
if not ADDONS_ENABLED['dev']:
|
|
897
|
-
console.print('[bold red]Missing dev addon: please run
|
|
970
|
+
console.print('[bold red]Missing dev addon: please run [bold green4]secator install addons dev[/][/]')
|
|
898
971
|
sys.exit(1)
|
|
899
972
|
pass
|
|
900
973
|
|
|
@@ -930,9 +1003,9 @@ def unit(tasks, workflows, scans, test, debug=False):
|
|
|
930
1003
|
os.environ['TEST_TASKS'] = tasks or ''
|
|
931
1004
|
os.environ['TEST_WORKFLOWS'] = workflows or ''
|
|
932
1005
|
os.environ['TEST_SCANS'] = scans or ''
|
|
933
|
-
os.environ['
|
|
934
|
-
os.environ['
|
|
935
|
-
os.environ['
|
|
1006
|
+
os.environ['SECATOR_DEBUG_LEVEL'] = str(debug)
|
|
1007
|
+
os.environ['SECATOR_HTTP_STORE_RESPONSES'] = '0'
|
|
1008
|
+
os.environ['SECATOR_RUNNERS_SKIP_CVE_SEARCH'] = '1'
|
|
936
1009
|
|
|
937
1010
|
cmd = f'{sys.executable} -m coverage run --omit="*test*" -m unittest'
|
|
938
1011
|
if test:
|
|
@@ -955,8 +1028,17 @@ def integration(tasks, workflows, scans, test, debug):
|
|
|
955
1028
|
os.environ['TEST_TASKS'] = tasks or ''
|
|
956
1029
|
os.environ['TEST_WORKFLOWS'] = workflows or ''
|
|
957
1030
|
os.environ['TEST_SCANS'] = scans or ''
|
|
958
|
-
os.environ['
|
|
959
|
-
os.environ['
|
|
1031
|
+
os.environ['SECATOR_DEBUG_LEVEL'] = str(debug)
|
|
1032
|
+
os.environ['SECATOR_RUNNERS_SKIP_CVE_SEARCH'] = '1'
|
|
1033
|
+
os.environ['SECATOR_DIRS_DATA'] = '/tmp/data'
|
|
1034
|
+
os.environ['SECATOR_DIRS_REPORTS'] = '/tmp/data/reports'
|
|
1035
|
+
os.environ['SECATOR_DIRS_CELERY'] = '/tmp/celery'
|
|
1036
|
+
os.environ['SECATOR_DIRS_CELERY_DATA'] = '/tmp/celery/data'
|
|
1037
|
+
os.environ['SECATOR_DIRS_CELERY_RESULTS'] = '/tmp/celery/results'
|
|
1038
|
+
import shutil
|
|
1039
|
+
for path in ['/tmp/data', '/tmp/celery', '/tmp/celery/data', '/tmp/celery/results']:
|
|
1040
|
+
shutil.rmtree(path, ignore_errors=True)
|
|
1041
|
+
|
|
960
1042
|
cmd = f'{sys.executable} -m unittest'
|
|
961
1043
|
if test:
|
|
962
1044
|
if not test.startswith('tests.integration'):
|