secator 0.4.0__py3-none-any.whl → 0.5.0__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/cli.py +83 -27
- secator/config.py +6 -3
- secator/definitions.py +22 -50
- secator/output_types/exploit.py +3 -0
- secator/output_types/vulnerability.py +0 -6
- secator/report.py +1 -4
- 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 +29 -13
- secator/utils_test.py +13 -0
- {secator-0.4.0.dist-info → secator-0.5.0.dist-info}/METADATA +1 -1
- {secator-0.4.0.dist-info → secator-0.5.0.dist-info}/RECORD +17 -17
- {secator-0.4.0.dist-info → secator-0.5.0.dist-info}/WHEEL +0 -0
- {secator-0.4.0.dist-info → secator-0.5.0.dist-info}/entry_points.txt +0 -0
- {secator-0.4.0.dist-info → secator-0.5.0.dist-info}/licenses/LICENSE +0 -0
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
|
#--------#
|
|
@@ -592,6 +648,24 @@ def health(json, debug):
|
|
|
592
648
|
table.add_row(*row)
|
|
593
649
|
status['secator'] = info
|
|
594
650
|
|
|
651
|
+
# Check addons
|
|
652
|
+
console.print('\n:wrench: [bold gold3]Checking installed addons ...[/]')
|
|
653
|
+
table = get_health_table()
|
|
654
|
+
with Live(table, console=console):
|
|
655
|
+
for addon in ['worker', 'google', 'mongodb', 'redis', 'dev', 'trace', 'build']:
|
|
656
|
+
addon_var = ADDONS_ENABLED[addon]
|
|
657
|
+
info = {
|
|
658
|
+
'name': addon,
|
|
659
|
+
'version': None,
|
|
660
|
+
'status': 'ok' if addon_var else 'missing',
|
|
661
|
+
'latest_version': None,
|
|
662
|
+
'installed': addon_var,
|
|
663
|
+
'location': None
|
|
664
|
+
}
|
|
665
|
+
row = fmt_health_table_row(info, 'addons')
|
|
666
|
+
table.add_row(*row)
|
|
667
|
+
status['addons'][addon] = info
|
|
668
|
+
|
|
595
669
|
# Check languages
|
|
596
670
|
console.print('\n:wrench: [bold gold3]Checking installed languages ...[/]')
|
|
597
671
|
version_cmds = {'go': 'version', 'python3': '--version', 'ruby': '--version'}
|
|
@@ -616,24 +690,6 @@ def health(json, debug):
|
|
|
616
690
|
table.add_row(*row)
|
|
617
691
|
status['tools'][tool.__name__] = info
|
|
618
692
|
|
|
619
|
-
# # Check addons
|
|
620
|
-
console.print('\n:wrench: [bold gold3]Checking installed addons ...[/]')
|
|
621
|
-
table = get_health_table()
|
|
622
|
-
with Live(table, console=console):
|
|
623
|
-
for addon in ['worker', 'google', 'mongodb', 'redis', 'dev', 'trace', 'build']:
|
|
624
|
-
addon_var = ADDONS_ENABLED[addon]
|
|
625
|
-
info = {
|
|
626
|
-
'name': addon,
|
|
627
|
-
'version': None,
|
|
628
|
-
'status': 'ok' if addon_var else 'missing',
|
|
629
|
-
'latest_version': None,
|
|
630
|
-
'installed': addon_var,
|
|
631
|
-
'location': None
|
|
632
|
-
}
|
|
633
|
-
row = fmt_health_table_row(info, 'addons')
|
|
634
|
-
table.add_row(*row)
|
|
635
|
-
status['addons'][addon] = info
|
|
636
|
-
|
|
637
693
|
# Print JSON health
|
|
638
694
|
if json:
|
|
639
695
|
import json as _json
|
|
@@ -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
|
@@ -6,11 +6,14 @@ from typing_extensions import Annotated, Self
|
|
|
6
6
|
|
|
7
7
|
import requests
|
|
8
8
|
import yaml
|
|
9
|
+
from dotenv import find_dotenv, load_dotenv
|
|
9
10
|
from dotmap import DotMap
|
|
10
11
|
from pydantic import AfterValidator, BaseModel, model_validator, ValidationError
|
|
11
12
|
|
|
12
13
|
from secator.rich import console, console_stdout
|
|
13
14
|
|
|
15
|
+
load_dotenv(find_dotenv(usecwd=True), override=False)
|
|
16
|
+
|
|
14
17
|
Directory = Annotated[Path, AfterValidator(lambda v: v.expanduser())]
|
|
15
18
|
StrExpandHome = Annotated[str, AfterValidator(lambda v: v.replace('~', str(Path.home())))]
|
|
16
19
|
|
|
@@ -245,9 +248,9 @@ class Config(DotMap):
|
|
|
245
248
|
value = float(value)
|
|
246
249
|
elif isinstance(existing_value, Path):
|
|
247
250
|
value = Path(value)
|
|
248
|
-
except ValueError
|
|
249
|
-
from secator.utils import debug
|
|
250
|
-
debug(f'Could not cast value {value} to expected type {type(existing_value).__name__}: {str(e)}', sub='config')
|
|
251
|
+
except ValueError:
|
|
252
|
+
# from secator.utils import debug
|
|
253
|
+
# debug(f'Could not cast value {value} to expected type {type(existing_value).__name__}: {str(e)}', sub='config')
|
|
251
254
|
pass
|
|
252
255
|
finally:
|
|
253
256
|
target[final_key] = value
|
secator/definitions.py
CHANGED
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
|
|
5
|
-
from dotenv import find_dotenv, load_dotenv
|
|
6
5
|
from importlib.metadata import version
|
|
7
6
|
|
|
8
7
|
from secator.config import CONFIG, ROOT_FOLDER
|
|
9
8
|
|
|
10
|
-
load_dotenv(find_dotenv(usecwd=True), override=False)
|
|
11
9
|
|
|
12
10
|
# Globals
|
|
13
11
|
VERSION = version('secator')
|
|
@@ -105,56 +103,30 @@ WORDLIST = 'wordlist'
|
|
|
105
103
|
WORDS = 'words'
|
|
106
104
|
|
|
107
105
|
|
|
106
|
+
def is_importable(module_to_import):
|
|
107
|
+
import importlib
|
|
108
|
+
try:
|
|
109
|
+
importlib.import_module(module_to_import)
|
|
110
|
+
return True
|
|
111
|
+
except ModuleNotFoundError:
|
|
112
|
+
return False
|
|
113
|
+
except Exception as e:
|
|
114
|
+
print(f'Failed trying to import {module_to_import}: {str(e)}')
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
|
|
108
118
|
ADDONS_ENABLED = {}
|
|
109
119
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
ADDONS_ENABLED['google'] = True
|
|
121
|
-
except ModuleNotFoundError:
|
|
122
|
-
ADDONS_ENABLED['google'] = False
|
|
123
|
-
|
|
124
|
-
# Check mongodb addon
|
|
125
|
-
try:
|
|
126
|
-
import pymongo # noqa: F401
|
|
127
|
-
ADDONS_ENABLED['mongodb'] = True
|
|
128
|
-
except ModuleNotFoundError:
|
|
129
|
-
ADDONS_ENABLED['mongodb'] = False
|
|
130
|
-
|
|
131
|
-
# Check redis addon
|
|
132
|
-
try:
|
|
133
|
-
import redis # noqa: F401
|
|
134
|
-
ADDONS_ENABLED['redis'] = True
|
|
135
|
-
except ModuleNotFoundError:
|
|
136
|
-
ADDONS_ENABLED['redis'] = False
|
|
137
|
-
|
|
138
|
-
# Check dev addon
|
|
139
|
-
try:
|
|
140
|
-
import flake8 # noqa: F401
|
|
141
|
-
ADDONS_ENABLED['dev'] = True
|
|
142
|
-
except ModuleNotFoundError:
|
|
143
|
-
ADDONS_ENABLED['dev'] = False
|
|
144
|
-
|
|
145
|
-
# Check build addon
|
|
146
|
-
try:
|
|
147
|
-
import hatch # noqa: F401
|
|
148
|
-
ADDONS_ENABLED['build'] = True
|
|
149
|
-
except ModuleNotFoundError:
|
|
150
|
-
ADDONS_ENABLED['build'] = False
|
|
151
|
-
|
|
152
|
-
# Check trace addon
|
|
153
|
-
try:
|
|
154
|
-
import memray # noqa: F401
|
|
155
|
-
ADDONS_ENABLED['trace'] = True
|
|
156
|
-
except ModuleNotFoundError:
|
|
157
|
-
ADDONS_ENABLED['trace'] = False
|
|
120
|
+
for addon, module in [
|
|
121
|
+
('worker', 'eventlet'),
|
|
122
|
+
('google', 'gspread'),
|
|
123
|
+
('mongodb', 'pymongo'),
|
|
124
|
+
('redis', 'redis'),
|
|
125
|
+
('dev', 'flake8'),
|
|
126
|
+
('trace', 'memray'),
|
|
127
|
+
('build', 'hatch')
|
|
128
|
+
]:
|
|
129
|
+
ADDONS_ENABLED[addon] = is_importable(module)
|
|
158
130
|
|
|
159
131
|
# Check dev package
|
|
160
132
|
if os.path.exists(f'{ROOT_FOLDER}/pyproject.toml'):
|
secator/output_types/exploit.py
CHANGED
|
@@ -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,7 @@
|
|
|
1
1
|
import operator
|
|
2
2
|
|
|
3
3
|
from secator.output_types import OUTPUT_TYPES, OutputType
|
|
4
|
-
from secator.utils import merge_opts, get_file_timestamp
|
|
4
|
+
from secator.utils import merge_opts, get_file_timestamp
|
|
5
5
|
from secator.rich import console
|
|
6
6
|
|
|
7
7
|
|
|
@@ -22,9 +22,6 @@ class Report:
|
|
|
22
22
|
self.workspace_name = runner.workspace_name
|
|
23
23
|
self.output_folder = runner.reports_folder
|
|
24
24
|
|
|
25
|
-
def as_table(self):
|
|
26
|
-
print_results_table(self.results, self.title)
|
|
27
|
-
|
|
28
25
|
def send(self):
|
|
29
26
|
for report_cls in self.exporters:
|
|
30
27
|
try:
|
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
|
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
3
|
secator/celery.py,sha256=5Raua1rDFJACdmP4b1HLS15kx3ObcUrVConQ0UcopTc,12135
|
|
4
|
-
secator/cli.py,sha256
|
|
5
|
-
secator/config.py,sha256
|
|
4
|
+
secator/cli.py,sha256=f959ENOt4C107hcnG_z4bEVgnNS4y6VqkyybtNk356c,35996
|
|
5
|
+
secator/config.py,sha256=Mzubha9pSzmpJnwA6f87OUScKrYo-szf8i9PJvKkz8w,17501
|
|
6
6
|
secator/decorators.py,sha256=SIS_32SbeN9iTx82mvy9F9vLpjofRYspOOLCXytIO2g,10764
|
|
7
|
-
secator/definitions.py,sha256=
|
|
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=73FlUZ6lDyJjQ_lBwFcPRARWta_s6rlBJRx1dZ_MJ4g,2505
|
|
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=CeZ0GnjFSKEMnKwItoh92qVu37HwJYGEMri4qNQF_8g,11607
|
|
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
|
|
@@ -56,9 +56,9 @@ secator/output_types/tag.py,sha256=8AlT0VigsYP04GN8sPCTM07IlL5uMUmFgsNa9IDCoyY,1
|
|
|
56
56
|
secator/output_types/target.py,sha256=gJWzzqhal34Cnl9oAKf0m1MSaGxRtUGdA2XbkhD_yd0,848
|
|
57
57
|
secator/output_types/url.py,sha256=yDozBXCuPfuybH1iX_xGmbCJPXO6Ei14C8Hp5CnzNbE,2535
|
|
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.0.dist-info/METADATA,sha256=zxLyE2AfVBFlf752cRu5f9WrEfKK9owH1uWwZHFvcJ4,14095
|
|
98
|
+
secator-0.5.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
|
|
99
|
+
secator-0.5.0.dist-info/entry_points.txt,sha256=lPgsqqUXWgiuGSfKy-se5gHdQlAXIwS_A46NYq7Acic,44
|
|
100
|
+
secator-0.5.0.dist-info/licenses/LICENSE,sha256=19W5Jsy4WTctNkqmZIqLRV1gTDOp01S3LDj9iSgWaJ0,2867
|
|
101
|
+
secator-0.5.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|