secator 0.3.0__py2.py3-none-any.whl → 0.3.1__py2.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 +1 -1
- secator/cli.py +64 -124
- secator/decorators.py +3 -4
- secator/definitions.py +51 -46
- secator/installer.py +159 -16
- secator/rich.py +2 -8
- secator/runners/_base.py +47 -15
- secator/runners/task.py +4 -3
- secator/runners/workflow.py +1 -1
- secator/tasks/_categories.py +6 -11
- secator/tasks/searchsploit.py +1 -0
- secator/utils.py +23 -1
- {secator-0.3.0.dist-info → secator-0.3.1.dist-info}/METADATA +1 -1
- {secator-0.3.0.dist-info → secator-0.3.1.dist-info}/RECORD +17 -17
- {secator-0.3.0.dist-info → secator-0.3.1.dist-info}/WHEEL +0 -0
- {secator-0.3.0.dist-info → secator-0.3.1.dist-info}/entry_points.txt +0 -0
- {secator-0.3.0.dist-info → secator-0.3.1.dist-info}/licenses/LICENSE +0 -0
secator/celery.py
CHANGED
|
@@ -106,7 +106,7 @@ def void(*args, **kwargs):
|
|
|
106
106
|
|
|
107
107
|
def revoke_task(task_id):
|
|
108
108
|
console.print(f'Revoking task {task_id}')
|
|
109
|
-
return app.control.revoke(task_id, terminate=True, signal='
|
|
109
|
+
return app.control.revoke(task_id, terminate=True, signal='SIGINT')
|
|
110
110
|
|
|
111
111
|
|
|
112
112
|
#--------------#
|
secator/cli.py
CHANGED
|
@@ -7,20 +7,20 @@ import rich_click as click
|
|
|
7
7
|
from dotmap import DotMap
|
|
8
8
|
from fp.fp import FreeProxy
|
|
9
9
|
from jinja2 import Template
|
|
10
|
+
from rich.live import Live
|
|
10
11
|
from rich.markdown import Markdown
|
|
11
12
|
from rich.rule import Rule
|
|
12
13
|
|
|
13
14
|
from secator.config import ConfigLoader
|
|
14
15
|
from secator.decorators import OrderedGroup, register_runner
|
|
15
|
-
from secator.definitions import (
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
TRACE_ADDON_ENABLED, VERSION, VERSION_STR, WORKER_ADDON_ENABLED)
|
|
19
|
-
from secator.installer import ToolInstaller
|
|
16
|
+
from secator.definitions import (ADDONS_ENABLED, ASCII, CVES_FOLDER, DATA_FOLDER, DEV_PACKAGE, OPT_NOT_SUPPORTED,
|
|
17
|
+
PAYLOADS_FOLDER, REVSHELLS_FOLDER, ROOT_FOLDER, VERSION)
|
|
18
|
+
from secator.installer import ToolInstaller, get_version_info, get_health_table, fmt_health_table_row
|
|
20
19
|
from secator.rich import console
|
|
21
20
|
from secator.runners import Command
|
|
22
21
|
from secator.serializers.dataclass import loads_dataclass
|
|
23
|
-
from secator.utils import debug, detect_host, discover_tasks, find_list_item, flatten,
|
|
22
|
+
from secator.utils import (debug, detect_host, discover_tasks, find_list_item, flatten,
|
|
23
|
+
print_results_table, print_version)
|
|
24
24
|
|
|
25
25
|
click.rich_click.USE_RICH_MARKUP = True
|
|
26
26
|
|
|
@@ -30,15 +30,6 @@ ALL_WORKFLOWS = ALL_CONFIGS.workflow
|
|
|
30
30
|
ALL_SCANS = ALL_CONFIGS.scan
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
def print_version():
|
|
34
|
-
console.print(f'[bold gold3]Current version[/]: {VERSION}', highlight=False)
|
|
35
|
-
console.print(f'[bold gold3]Latest version[/]: {VERSION_LATEST}', highlight=False)
|
|
36
|
-
console.print(f'[bold gold3]Python binary[/]: {sys.executable}')
|
|
37
|
-
if DEV_PACKAGE:
|
|
38
|
-
console.print(f'[bold gold3]Root folder[/]: {ROOT_FOLDER}')
|
|
39
|
-
console.print(f'[bold gold3]Lib folder[/]: {LIB_FOLDER}')
|
|
40
|
-
|
|
41
|
-
|
|
42
33
|
#-----#
|
|
43
34
|
# CLI #
|
|
44
35
|
#-----#
|
|
@@ -49,10 +40,6 @@ def print_version():
|
|
|
49
40
|
def cli(ctx, version):
|
|
50
41
|
"""Secator CLI."""
|
|
51
42
|
console.print(ASCII, highlight=False)
|
|
52
|
-
if VERSION_OBSOLETE:
|
|
53
|
-
console.print(
|
|
54
|
-
'[bold red]:warning: secator version is outdated: '
|
|
55
|
-
f'run "secator update" to install the newest version ({VERSION_LATEST}).\n')
|
|
56
43
|
if ctx.invoked_subcommand is None:
|
|
57
44
|
if version:
|
|
58
45
|
print_version()
|
|
@@ -119,7 +106,7 @@ for config in sorted(ALL_SCANS, key=lambda x: x['name']):
|
|
|
119
106
|
@click.option('--show', is_flag=True, help='Show command (celery multi).')
|
|
120
107
|
def worker(hostname, concurrency, reload, queue, pool, check, dev, stop, show):
|
|
121
108
|
"""Run a worker."""
|
|
122
|
-
if not
|
|
109
|
+
if not ADDONS_ENABLED['worker']:
|
|
123
110
|
console.print('[bold red]Missing worker addon: please run `secator install addons worker`[/].')
|
|
124
111
|
sys.exit(1)
|
|
125
112
|
from secator.celery import app, is_celery_worker_alive
|
|
@@ -408,7 +395,7 @@ def build():
|
|
|
408
395
|
@build.command('pypi')
|
|
409
396
|
def build_pypi():
|
|
410
397
|
"""Build secator PyPI package."""
|
|
411
|
-
if not
|
|
398
|
+
if not ADDONS_ENABLED['build']:
|
|
412
399
|
console.print('[bold red]Missing build addon: please run `secator install addons build`')
|
|
413
400
|
sys.exit(1)
|
|
414
401
|
with console.status('[bold gold3]Building PyPI package...[/]'):
|
|
@@ -444,7 +431,7 @@ def publish():
|
|
|
444
431
|
@publish.command('pypi')
|
|
445
432
|
def publish_pypi():
|
|
446
433
|
"""Publish secator PyPI package."""
|
|
447
|
-
if not
|
|
434
|
+
if not ADDONS_ENABLED['build']:
|
|
448
435
|
console.print('[bold red]Missing build addon: please run `secator install addons build`')
|
|
449
436
|
sys.exit(1)
|
|
450
437
|
os.environ['HATCH_INDEX_USER'] = '__token__'
|
|
@@ -526,114 +513,64 @@ def report_show(json_path, exclude_fields):
|
|
|
526
513
|
# HEALTH #
|
|
527
514
|
#--------#
|
|
528
515
|
|
|
529
|
-
|
|
530
|
-
def which(command):
|
|
531
|
-
"""Run which on a command.
|
|
532
|
-
|
|
533
|
-
Args:
|
|
534
|
-
command (str): Command to check.
|
|
535
|
-
|
|
536
|
-
Returns:
|
|
537
|
-
secator.Command: Command instance.
|
|
538
|
-
"""
|
|
539
|
-
return Command.execute(f'which {command}', quiet=True, print_errors=False)
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
def get_version_cls(cls):
|
|
543
|
-
"""Get version for a Command.
|
|
544
|
-
|
|
545
|
-
Args:
|
|
546
|
-
cls: Command class.
|
|
547
|
-
|
|
548
|
-
Returns:
|
|
549
|
-
string: Version string or 'n/a' if not found.
|
|
550
|
-
"""
|
|
551
|
-
base_cmd = cls.cmd.split(' ')[0]
|
|
552
|
-
if cls.version_flag == OPT_NOT_SUPPORTED:
|
|
553
|
-
return 'N/A'
|
|
554
|
-
version_flag = cls.version_flag or f'{cls.opt_prefix}version'
|
|
555
|
-
version_cmd = f'{base_cmd} {version_flag}'
|
|
556
|
-
return get_version(version_cmd)
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
def get_version(version_cmd):
|
|
560
|
-
"""Run version command and match first version number found.
|
|
561
|
-
|
|
562
|
-
Args:
|
|
563
|
-
version_cmd (str): Command to get the version.
|
|
564
|
-
|
|
565
|
-
Returns:
|
|
566
|
-
str: Version string.
|
|
567
|
-
"""
|
|
568
|
-
regex = r'[0-9]+\.[0-9]+\.?[0-9]*\.?[a-zA-Z]*'
|
|
569
|
-
ret = Command.execute(version_cmd, quiet=True, print_errors=False)
|
|
570
|
-
match = re.findall(regex, ret.output)
|
|
571
|
-
if not match:
|
|
572
|
-
return 'n/a'
|
|
573
|
-
return match[0]
|
|
574
|
-
|
|
575
|
-
|
|
576
516
|
@cli.command(name='health')
|
|
577
517
|
@click.option('--json', '-json', is_flag=True, default=False, help='JSON lines output')
|
|
578
518
|
@click.option('--debug', '-debug', is_flag=True, default=False, help='Debug health output')
|
|
579
519
|
def health(json, debug):
|
|
580
520
|
"""[dim]Get health status.[/]"""
|
|
581
|
-
tools =
|
|
582
|
-
status = {'
|
|
583
|
-
|
|
584
|
-
def print_status(cmd, return_code, version=None, bin=None, category=None):
|
|
585
|
-
s = '[bold green]ok [/]' if return_code == 0 else '[bold red]missing [/]'
|
|
586
|
-
s = f'[bold magenta]{cmd:<15}[/] {s} '
|
|
587
|
-
if return_code == 0 and version:
|
|
588
|
-
if version == 'N/A':
|
|
589
|
-
s += f'[dim blue]{version:<12}[/]'
|
|
590
|
-
else:
|
|
591
|
-
s += f'[bold blue]{version:<12}[/]'
|
|
592
|
-
elif category:
|
|
593
|
-
s += ' '*12 + f'[dim]# secator install {category} {cmd}'
|
|
594
|
-
if bin:
|
|
595
|
-
s += f'[dim gold3]{bin}[/]'
|
|
596
|
-
console.print(s, highlight=False)
|
|
521
|
+
tools = ALL_TASKS
|
|
522
|
+
status = {'secator': {}, 'languages': {}, 'tools': {}, 'addons': {}}
|
|
597
523
|
|
|
598
524
|
# Check secator
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
525
|
+
console.print(':wrench: [bold gold3]Checking secator ...[/]')
|
|
526
|
+
info = get_version_info('secator', '-version', 'freelabz/secator')
|
|
527
|
+
table = get_health_table()
|
|
528
|
+
with Live(table, console=console):
|
|
529
|
+
row = fmt_health_table_row(info)
|
|
530
|
+
table.add_row(*row)
|
|
531
|
+
status['secator'] = info
|
|
605
532
|
|
|
606
533
|
# Check languages
|
|
607
|
-
|
|
608
|
-
console.print('\n:wrench: [bold gold3]Checking installed languages ...[/]')
|
|
534
|
+
console.print('\n:wrench: [bold gold3]Checking installed languages ...[/]')
|
|
609
535
|
version_cmds = {'go': 'version', 'python3': '--version', 'ruby': '--version'}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
536
|
+
table = get_health_table()
|
|
537
|
+
with Live(table, console=console):
|
|
538
|
+
for lang, version_flag in version_cmds.items():
|
|
539
|
+
info = get_version_info(lang, version_flag)
|
|
540
|
+
row = fmt_health_table_row(info, 'langs')
|
|
541
|
+
table.add_row(*row)
|
|
542
|
+
status['languages'][lang] = info
|
|
616
543
|
|
|
617
544
|
# Check tools
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
545
|
+
console.print('\n:wrench: [bold gold3]Checking installed tools ...[/]')
|
|
546
|
+
table = get_health_table()
|
|
547
|
+
with Live(table, console=console):
|
|
548
|
+
for tool in tools:
|
|
549
|
+
cmd = tool.cmd.split(' ')[0]
|
|
550
|
+
version_flag = tool.version_flag or f'{tool.opt_prefix}version'
|
|
551
|
+
version_flag = None if tool.version_flag == OPT_NOT_SUPPORTED else version_flag
|
|
552
|
+
info = get_version_info(cmd, version_flag, tool.install_github_handle)
|
|
553
|
+
row = fmt_health_table_row(info, 'tools')
|
|
554
|
+
table.add_row(*row)
|
|
555
|
+
status['tools'][tool.__name__] = info
|
|
556
|
+
|
|
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 ['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
|
|
637
574
|
|
|
638
575
|
# Print JSON health
|
|
639
576
|
if json:
|
|
@@ -842,13 +779,16 @@ def install_cves(force):
|
|
|
842
779
|
@cli.command('update')
|
|
843
780
|
def update():
|
|
844
781
|
"""[dim]Update to latest version.[/]"""
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
782
|
+
info = get_version_info('secator', github_handle='freelabz/secator', version=VERSION)
|
|
783
|
+
latest_version = info['latest_version']
|
|
784
|
+
if info['status'] == 'latest':
|
|
785
|
+
console.print(f'[bold green]secator is already at the newest version {latest_version}[/] !')
|
|
786
|
+
sys.exit(0)
|
|
787
|
+
console.print(f'[bold gold3]:wrench: Updating secator from {VERSION} to {latest_version} ...[/]')
|
|
848
788
|
if 'pipx' in sys.executable:
|
|
849
|
-
Command.execute(f'pipx install secator=={
|
|
789
|
+
Command.execute(f'pipx install secator=={latest_version} --force')
|
|
850
790
|
else:
|
|
851
|
-
Command.execute(f'pip install secator=={
|
|
791
|
+
Command.execute(f'pip install secator=={latest_version}')
|
|
852
792
|
|
|
853
793
|
|
|
854
794
|
#-------#
|
|
@@ -953,7 +893,7 @@ def test():
|
|
|
953
893
|
if not DEV_PACKAGE:
|
|
954
894
|
console.print('[bold red]You MUST use a development version of secator to run tests.[/]')
|
|
955
895
|
sys.exit(1)
|
|
956
|
-
if not
|
|
896
|
+
if not ADDONS_ENABLED['dev']:
|
|
957
897
|
console.print('[bold red]Missing dev addon: please run `secator install addons dev`')
|
|
958
898
|
sys.exit(1)
|
|
959
899
|
pass
|
secator/decorators.py
CHANGED
|
@@ -5,8 +5,7 @@ import rich_click as click
|
|
|
5
5
|
from rich_click.rich_click import _get_rich_console
|
|
6
6
|
from rich_click.rich_group import RichGroup
|
|
7
7
|
|
|
8
|
-
from secator.definitions import
|
|
9
|
-
WORKER_ADDON_ENABLED)
|
|
8
|
+
from secator.definitions import ADDONS_ENABLED, OPT_NOT_SUPPORTED
|
|
10
9
|
from secator.runners import Scan, Task, Workflow
|
|
11
10
|
from secator.utils import (deduplicate, expand_input, get_command_category,
|
|
12
11
|
get_command_cls)
|
|
@@ -276,7 +275,7 @@ def register_runner(cli_endpoint, config):
|
|
|
276
275
|
# opts.update(unknown_opts)
|
|
277
276
|
targets = opts.pop(input_type)
|
|
278
277
|
targets = expand_input(targets)
|
|
279
|
-
if sync or show or not
|
|
278
|
+
if sync or show or not ADDONS_ENABLED['worker']:
|
|
280
279
|
sync = True
|
|
281
280
|
elif worker:
|
|
282
281
|
sync = False
|
|
@@ -294,7 +293,7 @@ def register_runner(cli_endpoint, config):
|
|
|
294
293
|
# Build hooks from driver name
|
|
295
294
|
hooks = {}
|
|
296
295
|
if driver == 'mongodb':
|
|
297
|
-
if not
|
|
296
|
+
if not ADDONS_ENABLED['mongo']:
|
|
298
297
|
_get_rich_console().print('[bold red]Missing MongoDB dependencies: please run `secator install addons mongodb`[/].')
|
|
299
298
|
sys.exit(1)
|
|
300
299
|
from secator.hooks.mongodb import MONGODB_HOOKS
|
secator/definitions.py
CHANGED
|
@@ -4,34 +4,20 @@ import os
|
|
|
4
4
|
import requests
|
|
5
5
|
|
|
6
6
|
from dotenv import find_dotenv, load_dotenv
|
|
7
|
-
from pkg_resources import get_distribution
|
|
8
|
-
|
|
9
|
-
load_dotenv(find_dotenv(usecwd=True), override=False)
|
|
7
|
+
from pkg_resources import get_distribution
|
|
10
8
|
|
|
9
|
+
from secator.rich import console
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
"""Get latest secator version from GitHub API."""
|
|
14
|
-
try:
|
|
15
|
-
resp = requests.get('https://api.github.com/repos/freelabz/secator/releases/latest', timeout=2)
|
|
16
|
-
resp.raise_for_status()
|
|
17
|
-
latest_version = resp.json()['name'].lstrip('v')
|
|
18
|
-
return latest_version
|
|
19
|
-
except (requests.exceptions.RequestException):
|
|
20
|
-
return None
|
|
21
|
-
|
|
11
|
+
load_dotenv(find_dotenv(usecwd=True), override=False)
|
|
22
12
|
|
|
23
13
|
# Globals
|
|
24
14
|
VERSION = get_distribution('secator').version
|
|
25
|
-
VERSION_LATEST = get_latest_version()
|
|
26
|
-
VERSION_OBSOLETE = parse_version(VERSION_LATEST) > parse_version(VERSION) if VERSION_LATEST else False
|
|
27
|
-
VERSION_STR = f'{VERSION} [bold red](outdated)[/]' if VERSION_OBSOLETE else VERSION
|
|
28
|
-
|
|
29
15
|
ASCII = f"""
|
|
30
16
|
__
|
|
31
17
|
________ _________ _/ /_____ _____
|
|
32
18
|
/ ___/ _ \/ ___/ __ `/ __/ __ \/ ___/
|
|
33
19
|
(__ / __/ /__/ /_/ / /_/ /_/ / /
|
|
34
|
-
/____/\___/\___/\__,_/\__/\____/_/ v{
|
|
20
|
+
/____/\___/\___/\__,_/\__/\____/_/ v{VERSION}
|
|
35
21
|
|
|
36
22
|
freelabz.com
|
|
37
23
|
""" # noqa: W605,W291
|
|
@@ -50,20 +36,10 @@ CVES_FOLDER = f'{DATA_FOLDER}/cves'
|
|
|
50
36
|
PAYLOADS_FOLDER = f'{DATA_FOLDER}/payloads'
|
|
51
37
|
REVSHELLS_FOLDER = f'{DATA_FOLDER}/revshells'
|
|
52
38
|
TESTS_FOLDER = f'{ROOT_FOLDER}/tests'
|
|
53
|
-
os.makedirs(BIN_FOLDER, exist_ok=True)
|
|
54
|
-
os.makedirs(DATA_FOLDER, exist_ok=True)
|
|
55
|
-
os.makedirs(REPORTS_FOLDER, exist_ok=True)
|
|
56
|
-
os.makedirs(WORDLISTS_FOLDER, exist_ok=True)
|
|
57
|
-
os.makedirs(SCRIPTS_FOLDER, exist_ok=True)
|
|
58
|
-
os.makedirs(CVES_FOLDER, exist_ok=True)
|
|
59
|
-
os.makedirs(PAYLOADS_FOLDER, exist_ok=True)
|
|
60
|
-
os.makedirs(REVSHELLS_FOLDER, exist_ok=True)
|
|
61
39
|
|
|
62
40
|
# Celery local fs folders
|
|
63
41
|
CELERY_DATA_FOLDER = f'{DATA_FOLDER}/celery/data'
|
|
64
42
|
CELERY_RESULTS_FOLDER = f'{DATA_FOLDER}/celery/results'
|
|
65
|
-
os.makedirs(CELERY_DATA_FOLDER, exist_ok=True)
|
|
66
|
-
os.makedirs(CELERY_RESULTS_FOLDER, exist_ok=True)
|
|
67
43
|
|
|
68
44
|
# Environment variables
|
|
69
45
|
DEBUG = int(os.environ.get('DEBUG', '0'))
|
|
@@ -99,8 +75,10 @@ DEFAULT_PROGRESS_UPDATE_FREQUENCY = int(os.environ.get('DEFAULT_PROGRESS_UPDATE_
|
|
|
99
75
|
DEFAULT_SKIP_CVE_SEARCH = bool(int(os.environ.get('DEFAULT_SKIP_CVE_SEARCH', 0)))
|
|
100
76
|
|
|
101
77
|
# Default wordlists
|
|
102
|
-
DEFAULT_HTTP_WORDLIST = os.environ.get('DEFAULT_HTTP_WORDLIST', f'{WORDLISTS_FOLDER}/
|
|
103
|
-
|
|
78
|
+
DEFAULT_HTTP_WORDLIST = os.environ.get('DEFAULT_HTTP_WORDLIST', f'{WORDLISTS_FOLDER}/fuzz-Bo0oM.txt')
|
|
79
|
+
DEFAULT_HTTP_WORDLIST_URL = 'https://raw.githubusercontent.com/Bo0oM/fuzz.txt/master/fuzz.txt'
|
|
80
|
+
DEFAULT_DNS_WORDLIST = os.environ.get('DEFAULT_DNS_WORDLIST', f'{WORDLISTS_FOLDER}/combined_subdomains.txt')
|
|
81
|
+
DEFAULT_DNS_WORDLIST_URL = 'https://raw.githubusercontent.com/danielmiessler/SecLists/master/Discovery/DNS/combined_subdomains.txt' # noqa: E501
|
|
104
82
|
|
|
105
83
|
# Constants
|
|
106
84
|
OPT_NOT_SUPPORTED = -1
|
|
@@ -175,57 +153,84 @@ WEBSERVER = 'webserver'
|
|
|
175
153
|
WORDLIST = 'wordlist'
|
|
176
154
|
WORDS = 'words'
|
|
177
155
|
|
|
156
|
+
|
|
157
|
+
# Create all folders
|
|
158
|
+
for folder in [BIN_FOLDER, DATA_FOLDER, REPORTS_FOLDER, WORDLISTS_FOLDER, SCRIPTS_FOLDER, CVES_FOLDER, PAYLOADS_FOLDER,
|
|
159
|
+
REVSHELLS_FOLDER, CELERY_DATA_FOLDER, CELERY_RESULTS_FOLDER]:
|
|
160
|
+
if not os.path.exists(folder):
|
|
161
|
+
console.print(f'[bold turquoise4]Creating folder {folder} ...[/] ', end='')
|
|
162
|
+
os.makedirs(folder)
|
|
163
|
+
console.print('[bold green]ok.[/]')
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# Download default wordlists
|
|
167
|
+
for wordlist in ['HTTP', 'DNS']:
|
|
168
|
+
wordlist_path = globals()[f'DEFAULT_{wordlist}_WORDLIST']
|
|
169
|
+
wordlist_url = globals()[f'DEFAULT_{wordlist}_WORDLIST_URL']
|
|
170
|
+
if not os.path.exists(wordlist_path):
|
|
171
|
+
try:
|
|
172
|
+
console.print(f'[bold turquoise4]Downloading default {wordlist} wordlist {wordlist_path} ...[/] ', end='')
|
|
173
|
+
resp = requests.get(wordlist_url)
|
|
174
|
+
with open(wordlist_path, 'w') as f:
|
|
175
|
+
f.write(resp.text)
|
|
176
|
+
console.print('[bold green]ok.[/]')
|
|
177
|
+
except requests.exceptions.RequestException as e:
|
|
178
|
+
console.print(f'[bold green]failed ({type(e).__name__}).[/]')
|
|
179
|
+
pass
|
|
180
|
+
|
|
181
|
+
ADDONS_ENABLED = {}
|
|
182
|
+
|
|
178
183
|
# Check worker addon
|
|
179
184
|
try:
|
|
180
185
|
import eventlet # noqa: F401
|
|
181
|
-
|
|
186
|
+
ADDONS_ENABLED['worker'] = True
|
|
182
187
|
except ModuleNotFoundError:
|
|
183
|
-
|
|
188
|
+
ADDONS_ENABLED['worker'] = False
|
|
184
189
|
|
|
185
190
|
# Check google addon
|
|
186
191
|
try:
|
|
187
192
|
import gspread # noqa: F401
|
|
188
|
-
|
|
193
|
+
ADDONS_ENABLED['google'] = True
|
|
189
194
|
except ModuleNotFoundError:
|
|
190
|
-
|
|
195
|
+
ADDONS_ENABLED['google'] = False
|
|
191
196
|
|
|
192
197
|
# Check mongodb addon
|
|
193
198
|
try:
|
|
194
199
|
import pymongo # noqa: F401
|
|
195
|
-
|
|
200
|
+
ADDONS_ENABLED['mongodb'] = True
|
|
196
201
|
except ModuleNotFoundError:
|
|
197
|
-
|
|
202
|
+
ADDONS_ENABLED['mongodb'] = False
|
|
198
203
|
|
|
199
204
|
# Check redis addon
|
|
200
205
|
try:
|
|
201
206
|
import redis # noqa: F401
|
|
202
|
-
|
|
207
|
+
ADDONS_ENABLED['redis'] = True
|
|
203
208
|
except ModuleNotFoundError:
|
|
204
|
-
|
|
209
|
+
ADDONS_ENABLED['redis'] = False
|
|
205
210
|
|
|
206
211
|
# Check dev addon
|
|
207
212
|
try:
|
|
208
213
|
import flake8 # noqa: F401
|
|
209
|
-
|
|
214
|
+
ADDONS_ENABLED['dev'] = True
|
|
210
215
|
except ModuleNotFoundError:
|
|
211
|
-
|
|
216
|
+
ADDONS_ENABLED['dev'] = False
|
|
212
217
|
|
|
213
218
|
# Check build addon
|
|
214
219
|
try:
|
|
215
220
|
import hatch # noqa: F401
|
|
216
|
-
|
|
221
|
+
ADDONS_ENABLED['build'] = True
|
|
217
222
|
except ModuleNotFoundError:
|
|
218
|
-
|
|
223
|
+
ADDONS_ENABLED['build'] = False
|
|
219
224
|
|
|
220
225
|
# Check trace addon
|
|
221
226
|
try:
|
|
222
227
|
import memray # noqa: F401
|
|
223
|
-
|
|
228
|
+
ADDONS_ENABLED['trace'] = True
|
|
224
229
|
except ModuleNotFoundError:
|
|
225
|
-
|
|
230
|
+
ADDONS_ENABLED['trace'] = False
|
|
226
231
|
|
|
227
232
|
# Check dev package
|
|
228
233
|
if os.path.exists(f'{ROOT_FOLDER}/pyproject.toml'):
|
|
229
|
-
DEV_PACKAGE =
|
|
234
|
+
DEV_PACKAGE = True
|
|
230
235
|
else:
|
|
231
|
-
DEV_PACKAGE =
|
|
236
|
+
DEV_PACKAGE = False
|
secator/installer.py
CHANGED
|
@@ -7,6 +7,8 @@ import tarfile
|
|
|
7
7
|
import zipfile
|
|
8
8
|
import io
|
|
9
9
|
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
|
|
10
12
|
from secator.rich import console
|
|
11
13
|
from secator.runners import Command
|
|
12
14
|
from secator.definitions import BIN_FOLDER, GITHUB_TOKEN
|
|
@@ -77,25 +79,14 @@ class GithubInstaller:
|
|
|
77
79
|
github_handle (str): A GitHub handle {user}/{repo}
|
|
78
80
|
|
|
79
81
|
Returns:
|
|
80
|
-
bool: True if install is successful
|
|
82
|
+
bool: True if install is successful, False otherwise.
|
|
81
83
|
"""
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
# Query latest release endpoint
|
|
86
|
-
headers = {}
|
|
87
|
-
if GITHUB_TOKEN:
|
|
88
|
-
headers['Authorization'] = f'Bearer {GITHUB_TOKEN}'
|
|
89
|
-
response = requests.get(releases_url, headers=headers)
|
|
90
|
-
if response.status_code == 403:
|
|
91
|
-
console.print('[bold red]Rate-limited by GitHub API. Retry later or set a GITHUB_TOKEN.')
|
|
92
|
-
return False
|
|
93
|
-
elif response.status_code == 404:
|
|
94
|
-
console.print('[dim red]No GitHub releases found.')
|
|
84
|
+
_, repo = tuple(github_handle.split('/'))
|
|
85
|
+
latest_release = cls.get_latest_release(github_handle)
|
|
86
|
+
if not latest_release:
|
|
95
87
|
return False
|
|
96
88
|
|
|
97
89
|
# Find the right asset to download
|
|
98
|
-
latest_release = response.json()
|
|
99
90
|
os_identifiers, arch_identifiers = cls._get_platform_identifier()
|
|
100
91
|
download_url = cls._find_matching_asset(latest_release['assets'], os_identifiers, arch_identifiers)
|
|
101
92
|
if not download_url:
|
|
@@ -107,6 +98,39 @@ class GithubInstaller:
|
|
|
107
98
|
cls._download_and_unpack(download_url, BIN_FOLDER, repo)
|
|
108
99
|
return True
|
|
109
100
|
|
|
101
|
+
@classmethod
|
|
102
|
+
def get_latest_release(cls, github_handle):
|
|
103
|
+
"""Get latest release from GitHub.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
github_handle (str): A GitHub handle {user}/{repo}.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
dict: Latest release JSON from GitHub releases.
|
|
110
|
+
"""
|
|
111
|
+
if not github_handle:
|
|
112
|
+
return False
|
|
113
|
+
owner, repo = tuple(github_handle.split('/'))
|
|
114
|
+
url = f"https://api.github.com/repos/{owner}/{repo}/releases/latest"
|
|
115
|
+
headers = {}
|
|
116
|
+
if GITHUB_TOKEN:
|
|
117
|
+
headers['Authorization'] = f'Bearer {GITHUB_TOKEN}'
|
|
118
|
+
try:
|
|
119
|
+
response = requests.get(url, headers=headers, timeout=5)
|
|
120
|
+
response.raise_for_status()
|
|
121
|
+
latest_release = response.json()
|
|
122
|
+
return latest_release
|
|
123
|
+
except requests.RequestException as e:
|
|
124
|
+
console.print(f'Failed to fetch latest release for {github_handle}: {str(e)}')
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
@classmethod
|
|
128
|
+
def get_latest_version(cls, github_handle):
|
|
129
|
+
latest_release = cls.get_latest_release(github_handle)
|
|
130
|
+
if not latest_release:
|
|
131
|
+
return None
|
|
132
|
+
return latest_release['tag_name'].lstrip('v')
|
|
133
|
+
|
|
110
134
|
@classmethod
|
|
111
135
|
def _get_platform_identifier(cls):
|
|
112
136
|
"""Generate lists of possible identifiers for the current platform."""
|
|
@@ -159,7 +183,7 @@ class GithubInstaller:
|
|
|
159
183
|
def _download_and_unpack(cls, url, destination, repo_name):
|
|
160
184
|
"""Download and unpack a release asset."""
|
|
161
185
|
console.print(f'Downloading and unpacking to {destination}...')
|
|
162
|
-
response = requests.get(url)
|
|
186
|
+
response = requests.get(url, timeout=5)
|
|
163
187
|
response.raise_for_status()
|
|
164
188
|
|
|
165
189
|
# Create a temporary directory to extract the archive
|
|
@@ -190,3 +214,122 @@ class GithubInstaller:
|
|
|
190
214
|
if file == binary_name:
|
|
191
215
|
return os.path.join(root, file)
|
|
192
216
|
return None
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def which(command):
|
|
220
|
+
"""Run which on a command.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
command (str): Command to check.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
secator.Command: Command instance.
|
|
227
|
+
"""
|
|
228
|
+
return Command.execute(f'which {command}', quiet=True, print_errors=False)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def get_version(version_cmd):
|
|
232
|
+
"""Run version command and match first version number found.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
version_cmd (str): Command to get the version.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
str: Version string.
|
|
239
|
+
"""
|
|
240
|
+
from secator.runners import Command
|
|
241
|
+
import re
|
|
242
|
+
regex = r'[0-9]+\.[0-9]+\.?[0-9]*\.?[a-zA-Z]*'
|
|
243
|
+
ret = Command.execute(version_cmd, quiet=True, print_errors=False)
|
|
244
|
+
match = re.findall(regex, ret.output)
|
|
245
|
+
if not match:
|
|
246
|
+
return ''
|
|
247
|
+
return match[0]
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def get_version_info(name, version_flag=None, github_handle=None, version=None):
|
|
251
|
+
"""Get version info for a command.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
name (str): Command name.
|
|
255
|
+
version_flag (str): Version flag.
|
|
256
|
+
github_handle (str): Github handle.
|
|
257
|
+
version (str): Existing version.
|
|
258
|
+
|
|
259
|
+
Return:
|
|
260
|
+
dict: Version info.
|
|
261
|
+
"""
|
|
262
|
+
from pkg_resources import parse_version
|
|
263
|
+
from secator.installer import GithubInstaller
|
|
264
|
+
info = {
|
|
265
|
+
'name': name,
|
|
266
|
+
'installed': False,
|
|
267
|
+
'version': version,
|
|
268
|
+
'latest_version': None,
|
|
269
|
+
'location': None,
|
|
270
|
+
'status': ''
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
# Get binary path
|
|
274
|
+
location = which(name).output
|
|
275
|
+
info['location'] = location
|
|
276
|
+
|
|
277
|
+
# Get current version
|
|
278
|
+
if version_flag:
|
|
279
|
+
version_cmd = f'{name} {version_flag}'
|
|
280
|
+
version = get_version(version_cmd)
|
|
281
|
+
info['version'] = version
|
|
282
|
+
|
|
283
|
+
# Get latest version
|
|
284
|
+
latest_version = GithubInstaller.get_latest_version(github_handle)
|
|
285
|
+
info['latest_version'] = latest_version
|
|
286
|
+
|
|
287
|
+
if location:
|
|
288
|
+
info['installed'] = True
|
|
289
|
+
if version and latest_version:
|
|
290
|
+
if parse_version(version) < parse_version(latest_version):
|
|
291
|
+
info['status'] = 'outdated'
|
|
292
|
+
else:
|
|
293
|
+
info['status'] = 'latest'
|
|
294
|
+
elif not version:
|
|
295
|
+
info['status'] = 'current unknown'
|
|
296
|
+
elif not latest_version:
|
|
297
|
+
info['status'] = 'latest unknown'
|
|
298
|
+
else:
|
|
299
|
+
info['status'] = 'missing'
|
|
300
|
+
|
|
301
|
+
return info
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def fmt_health_table_row(version_info, category=None):
|
|
305
|
+
name = version_info['name']
|
|
306
|
+
version = version_info['version']
|
|
307
|
+
status = version_info['status']
|
|
308
|
+
installed = version_info['installed']
|
|
309
|
+
name_str = f'[magenta]{name}[/]'
|
|
310
|
+
|
|
311
|
+
# Format version row
|
|
312
|
+
_version = version or ''
|
|
313
|
+
_version = f'[bold green]{_version:<10}[/]'
|
|
314
|
+
if status == 'latest':
|
|
315
|
+
_version += ' [bold green](latest)[/]'
|
|
316
|
+
elif status == 'outdated':
|
|
317
|
+
_version += ' [bold red](outdated)[/]'
|
|
318
|
+
elif status == 'missing':
|
|
319
|
+
_version = '[bold red]missing[/]'
|
|
320
|
+
elif status == 'ok':
|
|
321
|
+
_version = '[bold green]ok [/]'
|
|
322
|
+
elif status:
|
|
323
|
+
if not version and installed:
|
|
324
|
+
_version = '[bold green]ok [/]'
|
|
325
|
+
_version += f' [dim]({status}[/])'
|
|
326
|
+
|
|
327
|
+
row = (name_str, _version)
|
|
328
|
+
return row
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def get_health_table():
|
|
332
|
+
table = Table(box=None, show_header=False)
|
|
333
|
+
for col in ['name', 'version']:
|
|
334
|
+
table.add_column(col)
|
|
335
|
+
return table
|
secator/rich.py
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
import operator
|
|
2
2
|
|
|
3
|
-
import click
|
|
4
|
-
import rich_click
|
|
5
3
|
import yaml
|
|
6
4
|
from rich import box
|
|
7
5
|
from rich.console import Console
|
|
8
6
|
from rich.table import Table
|
|
9
|
-
from rich.traceback import install
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
console = Console(stderr=True, record=RECORD, color_system='truecolor')
|
|
8
|
+
console = Console(stderr=True, color_system='truecolor')
|
|
14
9
|
console_stdout = Console(record=True)
|
|
15
10
|
# handler = RichHandler(rich_tracebacks=True) # TODO: add logging handler
|
|
16
|
-
install(show_locals=DEBUG > 2, suppress=[click, rich_click])
|
|
17
11
|
|
|
18
12
|
|
|
19
13
|
def criticity_to_color(value):
|
|
@@ -73,7 +67,7 @@ def build_table(items, output_fields=[], exclude_fields=[], sort_by=None):
|
|
|
73
67
|
items = sorted(items, key=operator.attrgetter(*sort_by))
|
|
74
68
|
|
|
75
69
|
# Create rich table
|
|
76
|
-
box_style = box.
|
|
70
|
+
box_style = box.ROUNDED
|
|
77
71
|
table = Table(show_lines=True, box=box_style)
|
|
78
72
|
|
|
79
73
|
# Get table schema if any, default to first item keys
|
secator/runners/_base.py
CHANGED
|
@@ -106,7 +106,7 @@ class Runner:
|
|
|
106
106
|
self.context = context
|
|
107
107
|
self.delay = run_opts.get('delay', False)
|
|
108
108
|
self.uuids = []
|
|
109
|
-
self.
|
|
109
|
+
self.celery_result = None
|
|
110
110
|
|
|
111
111
|
# Determine report folder
|
|
112
112
|
default_reports_folder_base = f'{REPORTS_FOLDER}/{self.workspace_name}/{self.config.type}s'
|
|
@@ -159,19 +159,19 @@ class Runner:
|
|
|
159
159
|
for key in self.hooks:
|
|
160
160
|
|
|
161
161
|
# Register class specific hooks
|
|
162
|
-
|
|
163
|
-
if
|
|
162
|
+
class_hook = getattr(self, key, None)
|
|
163
|
+
if class_hook:
|
|
164
164
|
name = f'{self.__class__.__name__}.{key}'
|
|
165
|
-
fun =
|
|
165
|
+
fun = self.get_func_path(class_hook)
|
|
166
166
|
debug('', obj={name + ' [dim yellow]->[/] ' + fun: 'registered'}, sub='hooks', level=3)
|
|
167
|
-
self.hooks[key].append(
|
|
167
|
+
self.hooks[key].append(class_hook)
|
|
168
168
|
|
|
169
169
|
# Register user hooks
|
|
170
170
|
user_hooks = hooks.get(self.__class__, {}).get(key, [])
|
|
171
171
|
user_hooks.extend(hooks.get(key, []))
|
|
172
172
|
for hook in user_hooks:
|
|
173
173
|
name = f'{self.__class__.__name__}.{key}'
|
|
174
|
-
fun =
|
|
174
|
+
fun = self.get_func_path(hook)
|
|
175
175
|
debug('', obj={name + ' [dim yellow]->[/] ' + fun: 'registered (user)'}, sub='hooks', level=3)
|
|
176
176
|
self.hooks[key].extend(user_hooks)
|
|
177
177
|
|
|
@@ -280,9 +280,9 @@ class Runner:
|
|
|
280
280
|
|
|
281
281
|
except KeyboardInterrupt:
|
|
282
282
|
self._print('Process was killed manually (CTRL+C / CTRL+X).', color='bold red', rich=True)
|
|
283
|
-
if self.
|
|
283
|
+
if self.celery_result:
|
|
284
284
|
self._print('Revoking remote Celery tasks ...', color='bold red', rich=True)
|
|
285
|
-
self.stop_live_tasks(self.
|
|
285
|
+
self.stop_live_tasks(self.celery_result)
|
|
286
286
|
|
|
287
287
|
# Filter results and log info
|
|
288
288
|
self.mark_duplicates()
|
|
@@ -291,9 +291,10 @@ class Runner:
|
|
|
291
291
|
self.run_hooks('on_end')
|
|
292
292
|
|
|
293
293
|
def mark_duplicates(self):
|
|
294
|
-
debug('duplicate check', id=self.config.name, sub='runner.mark_duplicates')
|
|
294
|
+
debug('running duplicate check', id=self.config.name, sub='runner.mark_duplicates')
|
|
295
|
+
dupe_count = 0
|
|
295
296
|
for item in self.results:
|
|
296
|
-
debug('duplicate check', obj=item.toDict(), obj_breaklines=True, sub='runner.mark_duplicates', level=
|
|
297
|
+
debug('running duplicate check', obj=item.toDict(), obj_breaklines=True, sub='runner.mark_duplicates', level=5)
|
|
297
298
|
others = [f for f in self.results if f == item and f._uuid != item._uuid]
|
|
298
299
|
if others:
|
|
299
300
|
main = max(item, *others)
|
|
@@ -313,13 +314,16 @@ class Runner:
|
|
|
313
314
|
if not dupe._duplicate:
|
|
314
315
|
debug(
|
|
315
316
|
'found new duplicate', obj=dupe.toDict(), obj_breaklines=True,
|
|
316
|
-
sub='runner.mark_duplicates', level=
|
|
317
|
+
sub='runner.mark_duplicates', level=5)
|
|
318
|
+
dupe_count += 1
|
|
317
319
|
dupe._duplicate = True
|
|
318
320
|
dupe = self.run_hooks('on_duplicate', dupe)
|
|
319
321
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
322
|
+
duplicates = [repr(i) for i in self.results if i._duplicate]
|
|
323
|
+
if duplicates:
|
|
324
|
+
duplicates_str = '\n\t'.join(duplicates)
|
|
325
|
+
debug(f'Duplicates ({dupe_count}):\n\t{duplicates_str}', sub='runner.mark_duplicates', level=5)
|
|
326
|
+
debug(f'duplicate check completed: {dupe_count} found', id=self.config.name, sub='runner.mark_duplicates')
|
|
323
327
|
|
|
324
328
|
def yielder(self):
|
|
325
329
|
raise NotImplementedError()
|
|
@@ -356,7 +360,7 @@ class Runner:
|
|
|
356
360
|
return result
|
|
357
361
|
for hook in self.hooks[hook_type]:
|
|
358
362
|
name = f'{self.__class__.__name__}.{hook_type}'
|
|
359
|
-
fun =
|
|
363
|
+
fun = self.get_func_path(hook)
|
|
360
364
|
try:
|
|
361
365
|
_id = self.context.get('task_id', '') or self.context.get('workflow_id', '') or self.context.get('scan_id', '')
|
|
362
366
|
debug('', obj={name + ' [dim yellow]->[/] ' + fun: 'started'}, id=_id, sub='hooks', level=3)
|
|
@@ -871,3 +875,31 @@ class Runner:
|
|
|
871
875
|
elif isinstance(item, OutputType):
|
|
872
876
|
item = repr(item)
|
|
873
877
|
return item
|
|
878
|
+
|
|
879
|
+
@classmethod
|
|
880
|
+
def get_func_path(cls, func):
|
|
881
|
+
"""
|
|
882
|
+
Get the full symbolic path of a function or method, including staticmethods,
|
|
883
|
+
using function and method attributes.
|
|
884
|
+
|
|
885
|
+
Args:
|
|
886
|
+
func (function, method, or staticmethod): A function or method object.
|
|
887
|
+
"""
|
|
888
|
+
if hasattr(func, '__self__'):
|
|
889
|
+
if func.__self__ is not None:
|
|
890
|
+
# It's a method bound to an instance
|
|
891
|
+
class_name = func.__self__.__class__.__name__
|
|
892
|
+
return f"{func.__module__}.{class_name}.{func.__name__}"
|
|
893
|
+
else:
|
|
894
|
+
# It's a method bound to a class (class method)
|
|
895
|
+
class_name = func.__qualname__.rsplit('.', 1)[0]
|
|
896
|
+
return f"{func.__module__}.{class_name}.{func.__name__}"
|
|
897
|
+
else:
|
|
898
|
+
# Handle static and regular functions
|
|
899
|
+
if '.' in func.__qualname__:
|
|
900
|
+
# Static method or a function defined inside a class
|
|
901
|
+
class_name, func_name = func.__qualname__.rsplit('.', 1)
|
|
902
|
+
return f"{func.__module__}.{class_name}.{func_name}"
|
|
903
|
+
else:
|
|
904
|
+
# Regular function not attached to a class
|
|
905
|
+
return f"{func.__module__}.{func.__name__}"
|
secator/runners/task.py
CHANGED
|
@@ -39,7 +39,8 @@ class Task(Runner):
|
|
|
39
39
|
'print_input_file': DEBUG > 0,
|
|
40
40
|
'print_item': True,
|
|
41
41
|
'print_item_count': not self.sync and not dry_run,
|
|
42
|
-
'print_line':
|
|
42
|
+
'print_line': True
|
|
43
|
+
# 'print_line': self.sync and not self.output_quiet,
|
|
43
44
|
}
|
|
44
45
|
# self.print_item = not self.sync # enable print_item for base Task only if running remote
|
|
45
46
|
run_opts.update(fmt_opts)
|
|
@@ -59,9 +60,9 @@ class Task(Runner):
|
|
|
59
60
|
if dry_run: # don't run
|
|
60
61
|
return
|
|
61
62
|
else:
|
|
62
|
-
|
|
63
|
+
self.celery_result = task_cls.delay(self.targets, **run_opts)
|
|
63
64
|
task = self.process_live_tasks(
|
|
64
|
-
|
|
65
|
+
self.celery_result,
|
|
65
66
|
description=False,
|
|
66
67
|
results_only=True,
|
|
67
68
|
print_remote_status=self.print_remote_status)
|
secator/runners/workflow.py
CHANGED
|
@@ -58,7 +58,7 @@ class Workflow(Runner):
|
|
|
58
58
|
results = workflow.apply().get()
|
|
59
59
|
else:
|
|
60
60
|
result = workflow()
|
|
61
|
-
self.
|
|
61
|
+
self.celery_result = result
|
|
62
62
|
results = self.process_live_tasks(result, results_only=True, print_remote_status=self.print_remote_status)
|
|
63
63
|
|
|
64
64
|
# Get workflow results
|
secator/tasks/_categories.py
CHANGED
|
@@ -6,17 +6,12 @@ import requests
|
|
|
6
6
|
from bs4 import BeautifulSoup
|
|
7
7
|
from cpe import CPE
|
|
8
8
|
|
|
9
|
-
from secator.definitions import (CIDR_RANGE, CONFIDENCE, CVSS_SCORE,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
PROXY, RATE_LIMIT, REFERENCES, RETRIES,
|
|
16
|
-
SEVERITY, TAGS, DATA_FOLDER, THREADS, TIMEOUT,
|
|
17
|
-
URL, USER_AGENT, USERNAME, WORDLIST)
|
|
18
|
-
from secator.output_types import (Ip, Port, Subdomain, Tag, Url, UserAccount,
|
|
19
|
-
Vulnerability)
|
|
9
|
+
from secator.definitions import (CIDR_RANGE, CONFIDENCE, CVSS_SCORE, DATA_FOLDER, DEFAULT_HTTP_WORDLIST,
|
|
10
|
+
DEFAULT_SKIP_CVE_SEARCH, DELAY, DEPTH, DESCRIPTION, FILTER_CODES, FILTER_REGEX,
|
|
11
|
+
FILTER_SIZE, FILTER_WORDS, FOLLOW_REDIRECT, HEADER, HOST, ID, MATCH_CODES, MATCH_REGEX,
|
|
12
|
+
MATCH_SIZE, MATCH_WORDS, METHOD, NAME, PATH, PROVIDER, PROXY, RATE_LIMIT, REFERENCES,
|
|
13
|
+
RETRIES, SEVERITY, TAGS, THREADS, TIMEOUT, URL, USER_AGENT, USERNAME, WORDLIST)
|
|
14
|
+
from secator.output_types import Ip, Port, Subdomain, Tag, Url, UserAccount, Vulnerability
|
|
20
15
|
from secator.rich import console
|
|
21
16
|
from secator.runners import Command
|
|
22
17
|
|
secator/tasks/searchsploit.py
CHANGED
|
@@ -28,6 +28,7 @@ class searchsploit(Command):
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
install_cmd = 'sudo git clone https://gitlab.com/exploit-database/exploitdb.git /opt/exploitdb || true && sudo ln -sf /opt/exploitdb/searchsploit /usr/local/bin/searchsploit' # noqa: E501
|
|
31
|
+
install_github_handle = 'rad10/SearchSploit.py'
|
|
31
32
|
proxychains = False
|
|
32
33
|
proxy_socks5 = False
|
|
33
34
|
proxy_http = False
|
secator/utils.py
CHANGED
|
@@ -15,11 +15,13 @@ from pathlib import Path
|
|
|
15
15
|
from pkgutil import iter_modules
|
|
16
16
|
from urllib.parse import urlparse, quote
|
|
17
17
|
|
|
18
|
+
|
|
18
19
|
import ifaddr
|
|
19
20
|
import yaml
|
|
20
21
|
from rich.markdown import Markdown
|
|
21
22
|
|
|
22
|
-
from secator.definitions import DEBUG, DEBUG_COMPONENT, DEFAULT_STDIN_TIMEOUT
|
|
23
|
+
from secator.definitions import (DEBUG, DEBUG_COMPONENT, DEFAULT_STDIN_TIMEOUT, VERSION, DEV_PACKAGE, ROOT_FOLDER,
|
|
24
|
+
LIB_FOLDER)
|
|
23
25
|
from secator.rich import console
|
|
24
26
|
|
|
25
27
|
logger = logging.getLogger(__name__)
|
|
@@ -402,3 +404,23 @@ def escape_mongodb_url(url):
|
|
|
402
404
|
user, password = quote(user), quote(password)
|
|
403
405
|
return f'mongodb://{user}:{password}@{url}'
|
|
404
406
|
return url
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def print_version():
|
|
410
|
+
"""Print secator version information."""
|
|
411
|
+
from secator.installer import get_version_info
|
|
412
|
+
console.print(f'[bold gold3]Current version[/]: {VERSION}', highlight=False, end='')
|
|
413
|
+
info = get_version_info('secator', github_handle='freelabz/secator', version=VERSION)
|
|
414
|
+
latest_version = info['latest_version']
|
|
415
|
+
status = info['status']
|
|
416
|
+
location = info['location']
|
|
417
|
+
if status == 'outdated':
|
|
418
|
+
console.print('[bold red] (outdated)[/]')
|
|
419
|
+
console.print(f'[bold gold3]Latest version[/]: {latest_version}', highlight=False)
|
|
420
|
+
console.print(f'[bold gold3]Location[/]: {location}')
|
|
421
|
+
console.print(f'[bold gold3]Python binary[/]: {sys.executable}')
|
|
422
|
+
if DEV_PACKAGE:
|
|
423
|
+
console.print(f'[bold gold3]Root folder[/]: {ROOT_FOLDER}')
|
|
424
|
+
console.print(f'[bold gold3]Lib folder[/]: {LIB_FOLDER}')
|
|
425
|
+
if status == 'outdated':
|
|
426
|
+
console.print('[bold red]secator is outdated, run "secator update" to install the latest version.')
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
secator/.gitignore,sha256=da8MUc3hdb6Mo0WjZu2upn5uZMbXcBGvhdhTQ1L89HI,3093
|
|
2
2
|
secator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
secator/celery.py,sha256=
|
|
4
|
-
secator/cli.py,sha256=
|
|
3
|
+
secator/celery.py,sha256=QQlDblcCMfs7r2l0DhB2X8miLCtHE5MdC-XiGMK8IcA,12226
|
|
4
|
+
secator/cli.py,sha256=ipkIGc5NBY-SLWENhdeueYmpOnDP2eGURX4xBIcSNic,31531
|
|
5
5
|
secator/config.py,sha256=iOeRzq7u1rvR1-Oq5v9wGxQYB613X0xKGLIcrfhEGc4,3693
|
|
6
|
-
secator/decorators.py,sha256=
|
|
7
|
-
secator/definitions.py,sha256=
|
|
8
|
-
secator/installer.py,sha256=
|
|
6
|
+
secator/decorators.py,sha256=ZlrdUQ5kpisaNRI4-csQWwbrB4oXs6SXijramNMVqfE,10490
|
|
7
|
+
secator/definitions.py,sha256=JxoqbhkFH6jj5UwaMnrueimZOb6Zi3qksWuLC5O15oI,7636
|
|
8
|
+
secator/installer.py,sha256=pvyTVoG3prZm9V48CFCnIPjJQF7SdV9yU3sARnI12sI,9321
|
|
9
9
|
secator/report.py,sha256=g0stVCcx9klbUS01uKvWcxNE9MJfNFMexYA2SoDIWJU,2596
|
|
10
|
-
secator/rich.py,sha256=
|
|
11
|
-
secator/utils.py,sha256=
|
|
10
|
+
secator/rich.py,sha256=W4PipeZfIVnERfW3ySeWSvnZ90jhCFiABBoERYy_6kM,3177
|
|
11
|
+
secator/utils.py,sha256=i9nnkOuRQIZbWRfyOS1dsT4wfJ7vvaJeZ3IZPwX3vKM,10945
|
|
12
12
|
secator/utils_test.py,sha256=xVF9RH1-p3X0TdmODJi4k62H7Xth96Ib7qnUZ4vAJs8,5043
|
|
13
13
|
secator/configs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
14
|
secator/configs/profiles/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -57,18 +57,18 @@ secator/output_types/url.py,sha256=yDozBXCuPfuybH1iX_xGmbCJPXO6Ei14C8Hp5CnzNbE,2
|
|
|
57
57
|
secator/output_types/user_account.py,sha256=EiT2BFl2LTCdqHF1meoMEKVhjKGroyf8-JoWHPuBOTc,1378
|
|
58
58
|
secator/output_types/vulnerability.py,sha256=p0DTbr5w7Vv5D3dgbdnvsG5qXzqVVk4YPOPWYS1lxmM,2843
|
|
59
59
|
secator/runners/__init__.py,sha256=EBbOk37vkBy9p8Hhrbi-2VtM_rTwQ3b-0ggTyiD22cE,290
|
|
60
|
-
secator/runners/_base.py,sha256=
|
|
60
|
+
secator/runners/_base.py,sha256=wPCGBNEbTLSeFhYgQvTglo7gLA9ptM5Qa_vQSfe23Xk,28372
|
|
61
61
|
secator/runners/_helpers.py,sha256=7UUboSsr4b6srIOOHtSSYhJ9Jxq_qaMVbbF2gVEBnR4,3703
|
|
62
62
|
secator/runners/command.py,sha256=JzdwhbvsDujOyE-i_XgBGH-g6jaEoDNwL7CU2BIZ-Ng,18737
|
|
63
63
|
secator/runners/scan.py,sha256=FjmlL_zkraqhS3rBwy5jHnGsKt2n7Hb2gi4qhgeGenw,1727
|
|
64
|
-
secator/runners/task.py,sha256=
|
|
65
|
-
secator/runners/workflow.py,sha256=
|
|
64
|
+
secator/runners/task.py,sha256=PWFRFaI_GdKtgyNx9f7iiCUUUtl0XiinHD7rurspTvc,2823
|
|
65
|
+
secator/runners/workflow.py,sha256=90QvSJXNXTIS3_RecCnzMWYplG6gT-wc82b214XIivE,3773
|
|
66
66
|
secator/serializers/__init__.py,sha256=OP5cmFl77ovgSCW_IDcZ21St2mUt5UK4QHfrsK2KvH8,248
|
|
67
67
|
secator/serializers/dataclass.py,sha256=g5gMT4NwndjhGcGbFuYEs07AZW_Q_m9orov_edVEGlI,792
|
|
68
68
|
secator/serializers/json.py,sha256=XwuSQOBwrOAs16F5HtY-Q-rAGAxfNvlq3z-Nb2gwigE,304
|
|
69
69
|
secator/serializers/regex.py,sha256=hGJ_1JSOv9xPtfn_umHlsjnR_alnsDFv-UmjYCC3vwU,314
|
|
70
70
|
secator/tasks/__init__.py,sha256=Wp2QF5QS2e_BlVygsIEFbmYPTfTg7v_Vd3LQJeXTC7I,344
|
|
71
|
-
secator/tasks/_categories.py,sha256=
|
|
71
|
+
secator/tasks/_categories.py,sha256=w4vxKffTQFJEHNzi6BV5DslGpnSAlKEN0K7H6slG3Vg,9015
|
|
72
72
|
secator/tasks/cariddi.py,sha256=Np9QPMpuqGtsLGHANfcbNaYjoQaqjkFXX9Dbtbtcgu4,3109
|
|
73
73
|
secator/tasks/dalfox.py,sha256=nrLkIbTNz_J7LgUy_3kBgzhTUbQi3RmiSJhc9HWa05c,1744
|
|
74
74
|
secator/tasks/dirsearch.py,sha256=2hJeJZJwaAl3-UAjBwlmjW1w9bxjVWxxwfcaTTxqClc,2387
|
|
@@ -90,11 +90,11 @@ secator/tasks/msfconsole.py,sha256=VlhEzsdYMHb6eJy4HBRdXMtRKhdzf5KtQGh7qZqO9Rs,6
|
|
|
90
90
|
secator/tasks/naabu.py,sha256=RNs4NCZXgKhPqzR78l6l61tau0mGHuj6C3If7fimpgs,1594
|
|
91
91
|
secator/tasks/nmap.py,sha256=LS5FBo-vFxbHVK4DxF5x-O2cAvAK3zL1pROT1GddX9E,9459
|
|
92
92
|
secator/tasks/nuclei.py,sha256=7MlTygHd4EVz81ndrVwP5y6PZ-4j-Y8Oxuk3G3ayHPI,3343
|
|
93
|
-
secator/tasks/searchsploit.py,sha256=
|
|
93
|
+
secator/tasks/searchsploit.py,sha256=l0uNj5Jzax3lVMiMDxC8V3-bQ05y-FPaOhVdro1ibV4,1713
|
|
94
94
|
secator/tasks/subfinder.py,sha256=cpFyFCpVaDZ3QAjNId26ezOwntn3CA5Uk-AC2l0mo0E,1087
|
|
95
95
|
secator/tasks/wpscan.py,sha256=OgFCWEPOjOVdFreBXZDLBRc-PrFmTUv97UaXmaAS9yc,5413
|
|
96
|
-
secator-0.3.
|
|
97
|
-
secator-0.3.
|
|
98
|
-
secator-0.3.
|
|
99
|
-
secator-0.3.
|
|
100
|
-
secator-0.3.
|
|
96
|
+
secator-0.3.1.dist-info/METADATA,sha256=-y1zHhBhLVGECVIisVgjr7FEJQ_nIlrRGGJ0XxKZQ4s,13553
|
|
97
|
+
secator-0.3.1.dist-info/WHEEL,sha256=wpsUbWzR9la66_V7_eWTdyvs6WD26tazKT2BBEAC-EM,105
|
|
98
|
+
secator-0.3.1.dist-info/entry_points.txt,sha256=lPgsqqUXWgiuGSfKy-se5gHdQlAXIwS_A46NYq7Acic,44
|
|
99
|
+
secator-0.3.1.dist-info/licenses/LICENSE,sha256=19W5Jsy4WTctNkqmZIqLRV1gTDOp01S3LDj9iSgWaJ0,2867
|
|
100
|
+
secator-0.3.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|