secator 0.4.1__py3-none-any.whl → 0.5.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 +4 -52
- secator/cli.py +65 -9
- secator/config.py +1 -0
- secator/output_types/exploit.py +3 -0
- secator/output_types/url.py +3 -0
- secator/output_types/vulnerability.py +0 -6
- secator/report.py +5 -5
- secator/runners/_base.py +14 -10
- secator/tasks/__init__.py +4 -6
- secator/tasks/nmap.py +6 -1
- secator/tasks/searchsploit.py +42 -4
- secator/utils.py +32 -14
- secator/utils_test.py +13 -0
- {secator-0.4.1.dist-info → secator-0.5.1.dist-info}/METADATA +1 -1
- {secator-0.4.1.dist-info → secator-0.5.1.dist-info}/RECORD +18 -18
- {secator-0.4.1.dist-info → secator-0.5.1.dist-info}/WHEEL +0 -0
- {secator-0.4.1.dist-info → secator-0.5.1.dist-info}/entry_points.txt +0 -0
- {secator-0.4.1.dist-info → secator-0.5.1.dist-info}/licenses/LICENSE +0 -0
secator/celery.py
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import gc
|
|
2
2
|
import logging
|
|
3
3
|
import traceback
|
|
4
|
-
from time import sleep
|
|
5
4
|
|
|
6
5
|
from celery import Celery, chain, chord, signals
|
|
7
6
|
from celery.app import trace
|
|
8
|
-
from celery.result import
|
|
7
|
+
from celery.result import allow_join_result
|
|
9
8
|
# from pyinstrument import Profiler # TODO: make pyinstrument optional
|
|
10
9
|
from rich.logging import RichHandler
|
|
11
10
|
|
|
@@ -354,56 +353,9 @@ def forward_results(results):
|
|
|
354
353
|
results = deduplicate(results, attr='_uuid')
|
|
355
354
|
return results
|
|
356
355
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
#---------------------#
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
def poll_task(result, seen=[]):
|
|
364
|
-
"""Poll Celery result tree recursively to get results live.
|
|
365
|
-
|
|
366
|
-
TODO: function is incomplete, as it does not parse all results.
|
|
367
|
-
|
|
368
|
-
Args:
|
|
369
|
-
result (Union[AsyncResult, GroupResult]): Celery result object.
|
|
370
|
-
seen (list): List of seen results (do not yield again).
|
|
371
|
-
|
|
372
|
-
Yields:
|
|
373
|
-
dict: Result.
|
|
374
|
-
"""
|
|
375
|
-
if result is None:
|
|
376
|
-
return
|
|
377
|
-
|
|
378
|
-
if result.children:
|
|
379
|
-
for child in result.children:
|
|
380
|
-
yield from poll_task(child, seen=seen)
|
|
381
|
-
else:
|
|
382
|
-
res = AsyncResult(result.id)
|
|
383
|
-
if not res.info:
|
|
384
|
-
sleep(0.1)
|
|
385
|
-
yield from poll_task(result, seen=seen)
|
|
386
|
-
|
|
387
|
-
# Task done running
|
|
388
|
-
if isinstance(res.info, list):
|
|
389
|
-
for item in res.info:
|
|
390
|
-
if item._uuid not in seen:
|
|
391
|
-
yield res.id, None, item
|
|
392
|
-
seen.append(item._uuid)
|
|
393
|
-
return
|
|
394
|
-
|
|
395
|
-
# Get task partial results, remove duplicates
|
|
396
|
-
results = res.info['results']
|
|
397
|
-
name = res.info['name']
|
|
398
|
-
for item in results:
|
|
399
|
-
if item._uuid not in seen:
|
|
400
|
-
yield res.id, name, item
|
|
401
|
-
seen.append(item._uuid)
|
|
402
|
-
|
|
403
|
-
# Task still running, keep polling
|
|
404
|
-
if not res.ready():
|
|
405
|
-
sleep(0.1)
|
|
406
|
-
yield from poll_task(result, seen=seen)
|
|
356
|
+
#--------------#
|
|
357
|
+
# Celery utils #
|
|
358
|
+
#--------------#
|
|
407
359
|
|
|
408
360
|
|
|
409
361
|
def is_celery_worker_alive():
|
secator/cli.py
CHANGED
|
@@ -20,7 +20,8 @@ from secator.decorators import OrderedGroup, register_runner
|
|
|
20
20
|
from secator.definitions import ADDONS_ENABLED, ASCII, DEV_PACKAGE, OPT_NOT_SUPPORTED, VERSION
|
|
21
21
|
from secator.installer import ToolInstaller, fmt_health_table_row, get_health_table, get_version_info
|
|
22
22
|
from secator.rich import console
|
|
23
|
-
from secator.runners import Command
|
|
23
|
+
from secator.runners import Command, Runner
|
|
24
|
+
from secator.report import Report
|
|
24
25
|
from secator.serializers.dataclass import loads_dataclass
|
|
25
26
|
from secator.utils import debug, detect_host, discover_tasks, flatten, print_results_table, print_version
|
|
26
27
|
|
|
@@ -536,17 +537,72 @@ def report():
|
|
|
536
537
|
|
|
537
538
|
@report.command('show')
|
|
538
539
|
@click.argument('json_path')
|
|
540
|
+
@click.option('-o', '--output', type=str, default='console', help='Format')
|
|
539
541
|
@click.option('-e', '--exclude-fields', type=str, default='', help='List of fields to exclude (comma-separated)')
|
|
540
|
-
def report_show(json_path, exclude_fields):
|
|
541
|
-
"""Show a JSON report
|
|
542
|
+
def report_show(json_path, output, exclude_fields):
|
|
543
|
+
"""Show a JSON report."""
|
|
542
544
|
with open(json_path, 'r') as f:
|
|
543
545
|
report = loads_dataclass(f.read())
|
|
544
546
|
results = flatten(list(report['results'].values()))
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
exclude_fields=exclude_fields)
|
|
547
|
+
if output == 'console':
|
|
548
|
+
for result in results:
|
|
549
|
+
console.print(result)
|
|
550
|
+
elif output == 'table':
|
|
551
|
+
exclude_fields = exclude_fields.split(',')
|
|
552
|
+
print_results_table(
|
|
553
|
+
results,
|
|
554
|
+
title=report['info']['title'],
|
|
555
|
+
exclude_fields=exclude_fields)
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
@report.command('list')
|
|
559
|
+
@click.option('-ws', '--workspace', type=str)
|
|
560
|
+
def report_list(workspace):
|
|
561
|
+
reports_dir = CONFIG.dirs.reports
|
|
562
|
+
json_reports = reports_dir.glob("**/**/report.json")
|
|
563
|
+
ws_reports = {}
|
|
564
|
+
for path in json_reports:
|
|
565
|
+
ws, runner, number = str(path).split('/')[-4:-1]
|
|
566
|
+
if ws not in ws_reports:
|
|
567
|
+
ws_reports[ws] = []
|
|
568
|
+
with open(path, 'r') as f:
|
|
569
|
+
try:
|
|
570
|
+
content = json.loads(f.read())
|
|
571
|
+
data = {'path': path, 'name': content['info']['name'], 'runner': runner}
|
|
572
|
+
ws_reports[ws].append(data)
|
|
573
|
+
except json.JSONDecodeError as e:
|
|
574
|
+
console.print(f'[bold red]Could not load {path}: {str(e)}')
|
|
575
|
+
|
|
576
|
+
for ws in ws_reports:
|
|
577
|
+
if workspace and not ws == workspace:
|
|
578
|
+
continue
|
|
579
|
+
console.print(f'[bold gold3]{ws}:')
|
|
580
|
+
for data in sorted(ws_reports[ws], key=lambda x: x['path']):
|
|
581
|
+
console.print(f' • {data["path"]} ([bold blue]{data["name"]}[/] [dim]{data["runner"][:-1]}[/])')
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
@report.command('export')
|
|
585
|
+
@click.argument('json_path', type=str)
|
|
586
|
+
@click.option('--output-folder', '-of', type=str)
|
|
587
|
+
@click.option('-output', '-o', type=str)
|
|
588
|
+
def report_export(json_path, output_folder, output):
|
|
589
|
+
with open(json_path, 'r') as f:
|
|
590
|
+
data = loads_dataclass(f.read())
|
|
591
|
+
flatten(list(data['results'].values()))
|
|
592
|
+
|
|
593
|
+
runner_instance = DotMap({
|
|
594
|
+
"config": {
|
|
595
|
+
"name": data['info']['name']
|
|
596
|
+
},
|
|
597
|
+
"workspace_name": json_path.split('/')[-4],
|
|
598
|
+
"reports_folder": output_folder or Path.cwd(),
|
|
599
|
+
"data": data,
|
|
600
|
+
"results": flatten(list(data['results'].values()))
|
|
601
|
+
})
|
|
602
|
+
exporters = Runner.resolve_exporters(output)
|
|
603
|
+
report = Report(runner_instance, title=data['info']['title'], exporters=exporters)
|
|
604
|
+
report.data = data
|
|
605
|
+
report.send()
|
|
550
606
|
|
|
551
607
|
|
|
552
608
|
#--------#
|
|
@@ -1052,5 +1108,5 @@ def integration(tasks, workflows, scans, test, debug):
|
|
|
1052
1108
|
@test.command()
|
|
1053
1109
|
def coverage():
|
|
1054
1110
|
"""Run coverage report."""
|
|
1055
|
-
cmd = f'{sys.executable} -m coverage report -m --omit=*/site-packages/*,*/tests/*'
|
|
1111
|
+
cmd = f'{sys.executable} -m coverage report -m --omit=*/site-packages/*,*/tests/*,*/templates/*'
|
|
1056
1112
|
run_test(cmd, 'coverage')
|
secator/config.py
CHANGED
secator/output_types/exploit.py
CHANGED
secator/output_types/url.py
CHANGED
|
@@ -23,6 +23,7 @@ class Url(OutputType):
|
|
|
23
23
|
lines: int = field(default=0, compare=False)
|
|
24
24
|
screenshot_path: str = field(default='', compare=False)
|
|
25
25
|
stored_response_path: str = field(default='', compare=False)
|
|
26
|
+
headers: dict = field(default_factory=dict, repr=True, compare=False)
|
|
26
27
|
_source: str = field(default='', repr=True, compare=False)
|
|
27
28
|
_type: str = field(default='url', repr=True)
|
|
28
29
|
_timestamp: int = field(default_factory=lambda: time.time(), compare=False)
|
|
@@ -55,6 +56,8 @@ class Url(OutputType):
|
|
|
55
56
|
|
|
56
57
|
def __repr__(self):
|
|
57
58
|
s = f'🔗 [white]{self.url}'
|
|
59
|
+
if self.method and self.method != 'GET':
|
|
60
|
+
s += f' \[[turquoise4]{self.method}[/]]'
|
|
58
61
|
if self.status_code and self.status_code != 0:
|
|
59
62
|
if self.status_code < 400:
|
|
60
63
|
s += f' \[[green]{self.status_code}[/]]'
|
|
@@ -89,11 +89,5 @@ class Vulnerability(OutputType):
|
|
|
89
89
|
s = f'[dim]{s}[/]'
|
|
90
90
|
return rich_to_ansi(s)
|
|
91
91
|
|
|
92
|
-
# def __gt__(self, other):
|
|
93
|
-
# # favor httpx over other url info tools
|
|
94
|
-
# if self._source == 'httpx' and other._source != 'httpx':
|
|
95
|
-
# return True
|
|
96
|
-
# return super().__gt__(other)
|
|
97
|
-
|
|
98
92
|
def __str__(self):
|
|
99
93
|
return self.matched_at + ' -> ' + self.name
|
secator/report.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import operator
|
|
2
2
|
|
|
3
|
+
from secator.config import CONFIG
|
|
3
4
|
from secator.output_types import OUTPUT_TYPES, OutputType
|
|
4
|
-
from secator.utils import merge_opts, get_file_timestamp
|
|
5
|
+
from secator.utils import merge_opts, get_file_timestamp
|
|
5
6
|
from secator.rich import console
|
|
6
7
|
|
|
7
8
|
|
|
@@ -22,9 +23,6 @@ class Report:
|
|
|
22
23
|
self.workspace_name = runner.workspace_name
|
|
23
24
|
self.output_folder = runner.reports_folder
|
|
24
25
|
|
|
25
|
-
def as_table(self):
|
|
26
|
-
print_results_table(self.results, self.title)
|
|
27
|
-
|
|
28
26
|
def send(self):
|
|
29
27
|
for report_cls in self.exporters:
|
|
30
28
|
try:
|
|
@@ -67,8 +65,10 @@ class Report:
|
|
|
67
65
|
sort_by, _ = get_table_fields(output_type)
|
|
68
66
|
items = [
|
|
69
67
|
item for item in self.runner.results
|
|
70
|
-
if isinstance(item, OutputType) and item._type == output_name
|
|
68
|
+
if isinstance(item, OutputType) and item._type == output_name
|
|
71
69
|
]
|
|
70
|
+
if CONFIG.runners.remove_duplicates:
|
|
71
|
+
items = [item for item in items if not item._duplicate]
|
|
72
72
|
if items:
|
|
73
73
|
if sort_by and all(sort_by):
|
|
74
74
|
items = sorted(items, key=operator.attrgetter(*sort_by))
|
secator/runners/_base.py
CHANGED
|
@@ -92,7 +92,6 @@ class Runner:
|
|
|
92
92
|
self.workspace_name = context.get('workspace_name', 'default')
|
|
93
93
|
self.run_opts = run_opts.copy()
|
|
94
94
|
self.sync = run_opts.get('sync', True)
|
|
95
|
-
self.exporters = self.resolve_exporters()
|
|
96
95
|
self.done = False
|
|
97
96
|
self.start_time = datetime.fromtimestamp(time())
|
|
98
97
|
self.last_updated = None
|
|
@@ -109,6 +108,10 @@ class Runner:
|
|
|
109
108
|
self.uuids = []
|
|
110
109
|
self.celery_result = None
|
|
111
110
|
|
|
111
|
+
# Determine exporters
|
|
112
|
+
exporters_str = self.run_opts.get('output') or self.default_exporters
|
|
113
|
+
self.exporters = Runner.resolve_exporters(exporters_str)
|
|
114
|
+
|
|
112
115
|
# Determine report folder
|
|
113
116
|
default_reports_folder_base = f'{CONFIG.dirs.reports}/{self.workspace_name}/{self.config.type}s'
|
|
114
117
|
_id = get_task_folder_id(default_reports_folder_base)
|
|
@@ -294,7 +297,7 @@ class Runner:
|
|
|
294
297
|
debug('running duplicate check', id=self.config.name, sub='runner.mark_duplicates')
|
|
295
298
|
dupe_count = 0
|
|
296
299
|
for item in self.results:
|
|
297
|
-
debug('running duplicate check', obj=item.toDict(), obj_breaklines=True, sub='runner.mark_duplicates', level=5)
|
|
300
|
+
# debug('running duplicate check', obj=item.toDict(), obj_breaklines=True, sub='runner.mark_duplicates', level=5)
|
|
298
301
|
others = [f for f in self.results if f == item and f._uuid != item._uuid]
|
|
299
302
|
if others:
|
|
300
303
|
main = max(item, *others)
|
|
@@ -303,6 +306,7 @@ class Runner:
|
|
|
303
306
|
main._related.extend([dupe._uuid for dupe in dupes])
|
|
304
307
|
main._related = list(dict.fromkeys(main._related))
|
|
305
308
|
if main._uuid != item._uuid:
|
|
309
|
+
debug(f'found {len(others)} duplicates for', obj=item.toDict(), obj_breaklines=True, sub='runner.mark_duplicates', level=5) # noqa: E501
|
|
306
310
|
item._duplicate = True
|
|
307
311
|
item = self.run_hooks('on_item', item)
|
|
308
312
|
if item._uuid not in main._related:
|
|
@@ -390,19 +394,19 @@ class Runner:
|
|
|
390
394
|
return False
|
|
391
395
|
return True
|
|
392
396
|
|
|
393
|
-
|
|
397
|
+
@staticmethod
|
|
398
|
+
def resolve_exporters(exporters):
|
|
394
399
|
"""Resolve exporters from output options."""
|
|
395
|
-
|
|
396
|
-
if not output or output in ['false', 'False']:
|
|
400
|
+
if not exporters or exporters in ['false', 'False']:
|
|
397
401
|
return []
|
|
398
|
-
if isinstance(
|
|
399
|
-
|
|
400
|
-
|
|
402
|
+
if isinstance(exporters, str):
|
|
403
|
+
exporters = exporters.split(',')
|
|
404
|
+
classes = [
|
|
401
405
|
import_dynamic(f'secator.exporters.{o.capitalize()}Exporter', 'Exporter')
|
|
402
|
-
for o in
|
|
406
|
+
for o in exporters
|
|
403
407
|
if o
|
|
404
408
|
]
|
|
405
|
-
return [
|
|
409
|
+
return [cls for cls in classes if cls]
|
|
406
410
|
|
|
407
411
|
def log_start(self):
|
|
408
412
|
"""Log runner start."""
|
secator/tasks/__init__.py
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
from secator.utils import
|
|
2
|
-
|
|
3
|
-
EXTERNAL_TASKS = discover_external_tasks()
|
|
4
|
-
ALL_TASKS = INTERNAL_TASKS + EXTERNAL_TASKS
|
|
1
|
+
from secator.utils import discover_tasks
|
|
2
|
+
TASKS = discover_tasks()
|
|
5
3
|
__all__ = [
|
|
6
4
|
cls.__name__
|
|
7
|
-
for cls in
|
|
5
|
+
for cls in TASKS
|
|
8
6
|
]
|
|
9
|
-
for cls in
|
|
7
|
+
for cls in TASKS:
|
|
10
8
|
exec(f'from .{cls.__name__} import {cls.__name__}')
|
secator/tasks/nmap.py
CHANGED
|
@@ -51,7 +51,8 @@ class nmap(VulnMulti):
|
|
|
51
51
|
|
|
52
52
|
# Nmap opts
|
|
53
53
|
PORTS: '-p',
|
|
54
|
-
'output_path': '-oX'
|
|
54
|
+
'output_path': '-oX',
|
|
55
|
+
'tcp_syn_stealth': '-sS'
|
|
55
56
|
}
|
|
56
57
|
opt_value_map = {
|
|
57
58
|
PORTS: lambda x: ','.join([str(p) for p in x]) if isinstance(x, list) else x
|
|
@@ -73,6 +74,10 @@ class nmap(VulnMulti):
|
|
|
73
74
|
output_path = f'{self.reports_folder}/.outputs/{self.unique_name}.xml'
|
|
74
75
|
self.output_path = output_path
|
|
75
76
|
self.cmd += f' -oX {self.output_path}'
|
|
77
|
+
tcp_syn_stealth = self.get_opt_value('tcp_syn_stealth')
|
|
78
|
+
if tcp_syn_stealth:
|
|
79
|
+
self.cmd = f'sudo {self.cmd}'
|
|
80
|
+
self.cmd = self.cmd.replace('-sT', '')
|
|
76
81
|
|
|
77
82
|
def yielder(self):
|
|
78
83
|
yield from super().yielder()
|
secator/tasks/searchsploit.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
1
3
|
from secator.decorators import task
|
|
2
4
|
from secator.definitions import (CVES, EXTRA_DATA, ID, MATCHED_AT, NAME,
|
|
3
5
|
PROVIDER, REFERENCE, TAGS, OPT_NOT_SUPPORTED)
|
|
@@ -5,6 +7,9 @@ from secator.output_types import Exploit
|
|
|
5
7
|
from secator.runners import Command
|
|
6
8
|
|
|
7
9
|
|
|
10
|
+
SEARCHSPLOIT_TITLE_REGEX = re.compile(r'^((?:[a-zA-Z\-_!\.()]+\d?\s?)+)\.?\s*(.*)$')
|
|
11
|
+
|
|
12
|
+
|
|
8
13
|
@task()
|
|
9
14
|
class searchsploit(Command):
|
|
10
15
|
"""Exploit-DB command line search tool."""
|
|
@@ -19,12 +24,15 @@ class searchsploit(Command):
|
|
|
19
24
|
output_types = [Exploit]
|
|
20
25
|
output_map = {
|
|
21
26
|
Exploit: {
|
|
22
|
-
NAME:
|
|
23
|
-
PROVIDER: lambda x: 'EDB',
|
|
27
|
+
NAME: 'Title',
|
|
24
28
|
ID: 'EDB-ID',
|
|
29
|
+
PROVIDER: lambda x: 'EDB',
|
|
25
30
|
CVES: lambda x: [c for c in x['Codes'].split(';') if c.startswith('CVE-')],
|
|
26
31
|
REFERENCE: lambda x: f'https://exploit-db.com/exploits/{x["EDB-ID"]}',
|
|
27
|
-
|
|
32
|
+
TAGS: lambda x: searchsploit.tags_extractor(x),
|
|
33
|
+
EXTRA_DATA: lambda x: {
|
|
34
|
+
k.lower().replace('date_', ''): v for k, v in x.items() if k not in ['Title', 'EDB-ID', 'Codes', 'Tags', 'Source'] and v != '' # noqa: E501
|
|
35
|
+
}
|
|
28
36
|
}
|
|
29
37
|
}
|
|
30
38
|
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
|
|
@@ -34,6 +42,18 @@ class searchsploit(Command):
|
|
|
34
42
|
input_chunk_size = 1
|
|
35
43
|
profile = 'io'
|
|
36
44
|
|
|
45
|
+
@staticmethod
|
|
46
|
+
def tags_extractor(item):
|
|
47
|
+
tags = []
|
|
48
|
+
for tag in item['Tags'].split(','):
|
|
49
|
+
_tag = '_'.join(
|
|
50
|
+
tag.lower().replace('-', '_',).replace('(', '').replace(')', '').split(' ')
|
|
51
|
+
)
|
|
52
|
+
if not _tag:
|
|
53
|
+
continue
|
|
54
|
+
tags.append(tag)
|
|
55
|
+
return tags
|
|
56
|
+
|
|
37
57
|
@staticmethod
|
|
38
58
|
def before_init(self):
|
|
39
59
|
_in = self.input
|
|
@@ -49,5 +69,23 @@ class searchsploit(Command):
|
|
|
49
69
|
def on_item_pre_convert(self, item):
|
|
50
70
|
if self.matched_at:
|
|
51
71
|
item[MATCHED_AT] = self.matched_at
|
|
52
|
-
item
|
|
72
|
+
return item
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def on_item(self, item):
|
|
76
|
+
match = SEARCHSPLOIT_TITLE_REGEX.match(item.name)
|
|
77
|
+
# if not match:
|
|
78
|
+
# self._print(f'[bold red]{item.name} ({item.reference}) did not match SEARCHSPLOIT_TITLE_REGEX. Please report this issue.[/]') # noqa: E501
|
|
79
|
+
if match:
|
|
80
|
+
group = match.groups()
|
|
81
|
+
product = '-'.join(group[0].strip().split(' '))
|
|
82
|
+
if len(group[1]) > 1:
|
|
83
|
+
versions, title = tuple(group[1].split(' - '))
|
|
84
|
+
item.name = title
|
|
85
|
+
product_info = [f'{product.lower()} {v.strip()}' for v in versions.split('/')]
|
|
86
|
+
item.tags = product_info + item.tags
|
|
87
|
+
# else:
|
|
88
|
+
# self._print(f'[bold red]{item.name} ({item.reference}) did not quite match SEARCHSPLOIT_TITLE_REGEX. Please report this issue.[/]') # noqa: E501
|
|
89
|
+
input_tag = '-'.join(self.input.replace('\'', '').split(' '))
|
|
90
|
+
item.tags = [input_tag] + item.tags
|
|
53
91
|
return item
|
secator/utils.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import inspect
|
|
2
|
+
import importlib
|
|
2
3
|
import itertools
|
|
3
4
|
import logging
|
|
4
5
|
import operator
|
|
@@ -8,7 +9,7 @@ import select
|
|
|
8
9
|
import sys
|
|
9
10
|
import warnings
|
|
10
11
|
from datetime import datetime
|
|
11
|
-
|
|
12
|
+
|
|
12
13
|
from inspect import isclass
|
|
13
14
|
from pathlib import Path
|
|
14
15
|
from pkgutil import iter_modules
|
|
@@ -138,7 +139,7 @@ def discover_internal_tasks():
|
|
|
138
139
|
if module_name.startswith('_'):
|
|
139
140
|
continue
|
|
140
141
|
try:
|
|
141
|
-
module = import_module(f'secator.tasks.{module_name}')
|
|
142
|
+
module = importlib.import_module(f'secator.tasks.{module_name}')
|
|
142
143
|
except ImportError as e:
|
|
143
144
|
console.print(f'[bold red]Could not import secator.tasks.{module_name}:[/]')
|
|
144
145
|
console.print(f'\t[bold red]{type(e).__name__}[/]: {str(e)}')
|
|
@@ -160,17 +161,32 @@ def discover_internal_tasks():
|
|
|
160
161
|
|
|
161
162
|
def discover_external_tasks():
|
|
162
163
|
"""Find external secator tasks."""
|
|
163
|
-
if not os.path.exists('config.secator'):
|
|
164
|
-
return []
|
|
165
|
-
with open('config.secator', 'r') as f:
|
|
166
|
-
classes = f.read().splitlines()
|
|
167
164
|
output = []
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
165
|
+
sys.dont_write_bytecode = True
|
|
166
|
+
for path in CONFIG.dirs.templates.glob('**/*.py'):
|
|
167
|
+
try:
|
|
168
|
+
task_name = path.stem
|
|
169
|
+
module_name = f'secator.tasks.{task_name}'
|
|
170
|
+
|
|
171
|
+
# console.print(f'Importing module {module_name} from {path}')
|
|
172
|
+
spec = importlib.util.spec_from_file_location(module_name, path)
|
|
173
|
+
module = importlib.util.module_from_spec(spec)
|
|
174
|
+
# console.print(f'Adding module "{module_name}" to sys path')
|
|
175
|
+
sys.modules[module_name] = module
|
|
176
|
+
|
|
177
|
+
# console.print(f'Executing module "{module}"')
|
|
178
|
+
spec.loader.exec_module(module)
|
|
179
|
+
|
|
180
|
+
# console.print(f'Checking that {module} contains task {task_name}')
|
|
181
|
+
if not hasattr(module, task_name):
|
|
182
|
+
console.print(f'[bold orange1]Could not load external task "{task_name}" from module {path.name}[/] ({path})')
|
|
183
|
+
continue
|
|
184
|
+
cls = getattr(module, task_name)
|
|
185
|
+
console.print(f'[bold green]Successfully loaded external task "{task_name}"[/] ({path})')
|
|
186
|
+
output.append(cls)
|
|
187
|
+
except Exception as e:
|
|
188
|
+
console.print(f'[bold red]Could not load external module {path.name}. Reason: {str(e)}.[/] ({path})')
|
|
189
|
+
sys.dont_write_bytecode = False
|
|
174
190
|
return output
|
|
175
191
|
|
|
176
192
|
|
|
@@ -194,7 +210,7 @@ def import_dynamic(cls_path, cls_root='Command'):
|
|
|
194
210
|
"""
|
|
195
211
|
try:
|
|
196
212
|
package, name = cls_path.rsplit(".", maxsplit=1)
|
|
197
|
-
cls = getattr(import_module(package), name)
|
|
213
|
+
cls = getattr(importlib.import_module(package), name)
|
|
198
214
|
root_cls = inspect.getmro(cls)[-2]
|
|
199
215
|
if root_cls.__name__ == cls_root:
|
|
200
216
|
return cls
|
|
@@ -327,8 +343,10 @@ def print_results_table(results, title=None, exclude_fields=[], log=False):
|
|
|
327
343
|
if output_type.__name__ == 'Progress':
|
|
328
344
|
continue
|
|
329
345
|
items = [
|
|
330
|
-
item for item in results if item._type == output_type.get_name()
|
|
346
|
+
item for item in results if item._type == output_type.get_name()
|
|
331
347
|
]
|
|
348
|
+
if CONFIG.runners.remove_duplicates:
|
|
349
|
+
items = [item for item in items if not item._duplicate]
|
|
332
350
|
if items:
|
|
333
351
|
_table = build_table(
|
|
334
352
|
items,
|
secator/utils_test.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import contextlib
|
|
2
2
|
import json
|
|
3
3
|
import os
|
|
4
|
+
import sys
|
|
4
5
|
import unittest.mock
|
|
5
6
|
|
|
6
7
|
from fp.fp import FreeProxy
|
|
@@ -182,3 +183,15 @@ class CommandOutputTester: # Mixin for unittest.TestCase
|
|
|
182
183
|
raise
|
|
183
184
|
|
|
184
185
|
console.print('[bold green] ok[/]')
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def clear_modules():
|
|
189
|
+
"""Clear all secator modules imports.
|
|
190
|
+
See https://stackoverflow.com/questions/7460363/re-import-module-under-test-to-lose-context for context.
|
|
191
|
+
"""
|
|
192
|
+
keys_to_delete = []
|
|
193
|
+
for k, _ in sys.modules.items():
|
|
194
|
+
if k.startswith('secator'):
|
|
195
|
+
keys_to_delete.append(k)
|
|
196
|
+
for k in keys_to_delete:
|
|
197
|
+
del sys.modules[k]
|
|
@@ -1,16 +1,16 @@
|
|
|
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=
|
|
5
|
-
secator/config.py,sha256=
|
|
3
|
+
secator/celery.py,sha256=8BPgxnxBWe018tZL6-0WaZQdzHV8rdKS_cxYGuPStiw,10999
|
|
4
|
+
secator/cli.py,sha256=f959ENOt4C107hcnG_z4bEVgnNS4y6VqkyybtNk356c,35996
|
|
5
|
+
secator/config.py,sha256=Yzb7429RweDCB9Qz88710FKnHiFvg9o0tU2Gu_JFzyE,17534
|
|
6
6
|
secator/decorators.py,sha256=SIS_32SbeN9iTx82mvy9F9vLpjofRYspOOLCXytIO2g,10764
|
|
7
7
|
secator/definitions.py,sha256=Spr62Nc0TweBAa84iRGSwNkIvXlKofPxtIi795gqTjc,3047
|
|
8
8
|
secator/installer.py,sha256=fpjf5fbA5M5zDQVP4Fdr51sLoMwtoGZTe3mXh7DvD6s,9466
|
|
9
|
-
secator/report.py,sha256=
|
|
9
|
+
secator/report.py,sha256=RCNowMcGDalB51hCoB0aO1adTmDrcnVbKZnpZPjfCCo,2615
|
|
10
10
|
secator/rich.py,sha256=W4PipeZfIVnERfW3ySeWSvnZ90jhCFiABBoERYy_6kM,3177
|
|
11
11
|
secator/template.py,sha256=MRCzvn8FJ7D4n8V4ceBwAjPsdLhTWjDRu5VLefmLb6M,3705
|
|
12
|
-
secator/utils.py,sha256=
|
|
13
|
-
secator/utils_test.py,sha256=
|
|
12
|
+
secator/utils.py,sha256=cN1xi1hB_Wmi30TUgx_HztsC5hosS327cf1szGAzkgU,11681
|
|
13
|
+
secator/utils_test.py,sha256=95KNjEePk39SrDPL9XXV_L1GAdkf_LwBKU02Vuj-tZA,5387
|
|
14
14
|
secator/configs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
15
|
secator/configs/profiles/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
secator/configs/profiles/aggressive.yaml,sha256=JilVySABlSCYEFMjH7V0Oc3dAVlkfHOh1odTGhtm7BQ,108
|
|
@@ -46,7 +46,7 @@ secator/hooks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
46
46
|
secator/hooks/mongodb.py,sha256=PGcM6hkTl4bt46-cPlFgcY-PfNWmHpz_eiv77XeV0-A,7036
|
|
47
47
|
secator/output_types/__init__.py,sha256=uj6AXDeorECPwhwekNVGjQbGv41jHG_8udkuoc4XzW0,854
|
|
48
48
|
secator/output_types/_base.py,sha256=bld1ED0pN1hOvwBV2canrlKrfBCgawzWKPDH6F3jVQE,2469
|
|
49
|
-
secator/output_types/exploit.py,sha256=
|
|
49
|
+
secator/output_types/exploit.py,sha256=tMQcEr4kAZ-na4F6sU2WpVH6Pa6r0oPumXOrxptTrbY,1621
|
|
50
50
|
secator/output_types/ip.py,sha256=ySEqH3Gs7U41I1kS8olZ_p3Mk7JryEbXHLyexqlBQNA,995
|
|
51
51
|
secator/output_types/port.py,sha256=1ZmV4FDvwk1dvFXySnz5yIp13hbaRhnunxnETm66Os0,1607
|
|
52
52
|
secator/output_types/progress.py,sha256=u_-4IiECTSCJf-X_RkFOoFyb8mrff2rMcm8GGqLZ8hs,1231
|
|
@@ -54,11 +54,11 @@ secator/output_types/record.py,sha256=WnI0yvwzrO2Wt7OWciHMOuIRRLbuSOAJczdNshV7tY
|
|
|
54
54
|
secator/output_types/subdomain.py,sha256=lmCoK7_8I4FXWgl9kToRvDn3gr3E3uBTaQzFAOHbswE,1343
|
|
55
55
|
secator/output_types/tag.py,sha256=8AlT0VigsYP04GN8sPCTM07IlL5uMUmFgsNa9IDCoyY,1431
|
|
56
56
|
secator/output_types/target.py,sha256=gJWzzqhal34Cnl9oAKf0m1MSaGxRtUGdA2XbkhD_yd0,848
|
|
57
|
-
secator/output_types/url.py,sha256=
|
|
57
|
+
secator/output_types/url.py,sha256=G_28pWwwLe52rWQGz58GH5vTYYN9sTpv-1AgqmCHECw,2693
|
|
58
58
|
secator/output_types/user_account.py,sha256=EiT2BFl2LTCdqHF1meoMEKVhjKGroyf8-JoWHPuBOTc,1378
|
|
59
|
-
secator/output_types/vulnerability.py,sha256=
|
|
59
|
+
secator/output_types/vulnerability.py,sha256=cWaz6zzClnrKQmdXr4gOcudcWqnvlp-6SxOLPpO29Z8,2712
|
|
60
60
|
secator/runners/__init__.py,sha256=EBbOk37vkBy9p8Hhrbi-2VtM_rTwQ3b-0ggTyiD22cE,290
|
|
61
|
-
secator/runners/_base.py,sha256=
|
|
61
|
+
secator/runners/_base.py,sha256=Xmcq-WsIsSgNv9zavXixXDTt9qmRO4eyU-Hh1EAn1-k,28602
|
|
62
62
|
secator/runners/_helpers.py,sha256=kxXfxP4LOCz49p5Y-OKuUqvVAmRPtAoK2O9PHoUxCX0,3947
|
|
63
63
|
secator/runners/command.py,sha256=F6Eg5hLisk6KLXKSBgNIURu2A67heNzmkVF_OYF_Xdo,18688
|
|
64
64
|
secator/runners/scan.py,sha256=ZN6bgb3yqu5wemq_VVqul5VTU64TtYUl_0wkcW1aXRU,1647
|
|
@@ -68,7 +68,7 @@ secator/serializers/__init__.py,sha256=OP5cmFl77ovgSCW_IDcZ21St2mUt5UK4QHfrsK2Kv
|
|
|
68
68
|
secator/serializers/dataclass.py,sha256=g5gMT4NwndjhGcGbFuYEs07AZW_Q_m9orov_edVEGlI,792
|
|
69
69
|
secator/serializers/json.py,sha256=XwuSQOBwrOAs16F5HtY-Q-rAGAxfNvlq3z-Nb2gwigE,304
|
|
70
70
|
secator/serializers/regex.py,sha256=hGJ_1JSOv9xPtfn_umHlsjnR_alnsDFv-UmjYCC3vwU,314
|
|
71
|
-
secator/tasks/__init__.py,sha256=
|
|
71
|
+
secator/tasks/__init__.py,sha256=yRIZf9E47aS7o6rpgAJLgJUpX2cug1ofZeq8QsxgyjU,192
|
|
72
72
|
secator/tasks/_categories.py,sha256=2cUsZOdYHA-YXJwryU2FTTT4Y4xXzmDJ92F8ud-MDJQ,10402
|
|
73
73
|
secator/tasks/cariddi.py,sha256=GKVJ8nWtJu9fB_FhAVYA2TX3fMdKYdbMpH2IhCkj_no,3155
|
|
74
74
|
secator/tasks/dalfox.py,sha256=nrLkIbTNz_J7LgUy_3kBgzhTUbQi3RmiSJhc9HWa05c,1744
|
|
@@ -89,13 +89,13 @@ secator/tasks/maigret.py,sha256=PZDTICJ4LZF3joKe-dXu2alffakD_1sxBuNEUBtJDm4,2098
|
|
|
89
89
|
secator/tasks/mapcidr.py,sha256=7aa2WXQATWgIQo5oA12URjAg80L6MFMGdxScxls8DuA,980
|
|
90
90
|
secator/tasks/msfconsole.py,sha256=Cm0vzOFff17C4M1YjkgU6T7Jc5-ClBK0Qi_529qVRb0,6065
|
|
91
91
|
secator/tasks/naabu.py,sha256=RNs4NCZXgKhPqzR78l6l61tau0mGHuj6C3If7fimpgs,1594
|
|
92
|
-
secator/tasks/nmap.py,sha256=
|
|
92
|
+
secator/tasks/nmap.py,sha256=bzxFSijOjBIky_6jIPgiD-Rham0bmdeBoI2nY0uHJks,11422
|
|
93
93
|
secator/tasks/nuclei.py,sha256=lKZYPVcnCYomd830-ZCOz4fyc8xAKjNDuKayyz0BPek,3507
|
|
94
|
-
secator/tasks/searchsploit.py,sha256=
|
|
94
|
+
secator/tasks/searchsploit.py,sha256=tIqCwYFIyHIgJbtcTL56PXqd-MCvoXOpvSDgoK_dxzc,2953
|
|
95
95
|
secator/tasks/subfinder.py,sha256=cpFyFCpVaDZ3QAjNId26ezOwntn3CA5Uk-AC2l0mo0E,1087
|
|
96
96
|
secator/tasks/wpscan.py,sha256=UVWnBPOQ1RDB2wzMswWR6vc6cucYgHtuJ8pLZoqCM40,5434
|
|
97
|
-
secator-0.
|
|
98
|
-
secator-0.
|
|
99
|
-
secator-0.
|
|
100
|
-
secator-0.
|
|
101
|
-
secator-0.
|
|
97
|
+
secator-0.5.1.dist-info/METADATA,sha256=wEJx34zkxon4GAdURXZBuEjQTWzeTuz-LvLPXReNgtM,14095
|
|
98
|
+
secator-0.5.1.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
|
|
99
|
+
secator-0.5.1.dist-info/entry_points.txt,sha256=lPgsqqUXWgiuGSfKy-se5gHdQlAXIwS_A46NYq7Acic,44
|
|
100
|
+
secator-0.5.1.dist-info/licenses/LICENSE,sha256=19W5Jsy4WTctNkqmZIqLRV1gTDOp01S3LDj9iSgWaJ0,2867
|
|
101
|
+
secator-0.5.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|