secator 0.10.1a12__py3-none-any.whl → 0.11.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of secator might be problematic. Click here for more details.
- secator/celery.py +10 -5
- secator/celery_signals.py +2 -11
- secator/cli.py +153 -46
- secator/config.py +2 -2
- secator/configs/workflows/url_params_fuzz.yaml +23 -0
- secator/configs/workflows/wordpress.yaml +4 -1
- secator/decorators.py +10 -5
- secator/definitions.py +3 -0
- secator/installer.py +46 -32
- secator/output_types/__init__.py +2 -1
- secator/output_types/certificate.py +78 -0
- secator/output_types/user_account.py +1 -1
- secator/rich.py +1 -1
- secator/runners/_base.py +14 -6
- secator/runners/_helpers.py +4 -3
- secator/runners/command.py +81 -21
- secator/runners/scan.py +5 -3
- secator/runners/workflow.py +22 -4
- secator/tasks/_categories.py +12 -4
- secator/tasks/arjun.py +82 -0
- secator/tasks/ffuf.py +2 -1
- secator/tasks/fping.py +1 -0
- secator/tasks/gitleaks.py +76 -0
- secator/tasks/grype.py +1 -1
- secator/tasks/mapcidr.py +1 -1
- secator/tasks/naabu.py +7 -1
- secator/tasks/nmap.py +29 -29
- secator/tasks/subfinder.py +1 -1
- secator/tasks/testssl.py +274 -0
- secator/tasks/trivy.py +95 -0
- secator/tasks/wafw00f.py +83 -0
- secator/tasks/wpprobe.py +94 -0
- secator/template.py +49 -67
- secator/utils.py +16 -7
- secator/utils_test.py +26 -8
- {secator-0.10.1a12.dist-info → secator-0.11.1.dist-info}/METADATA +1 -1
- {secator-0.10.1a12.dist-info → secator-0.11.1.dist-info}/RECORD +40 -32
- {secator-0.10.1a12.dist-info → secator-0.11.1.dist-info}/WHEEL +0 -0
- {secator-0.10.1a12.dist-info → secator-0.11.1.dist-info}/entry_points.txt +0 -0
- {secator-0.10.1a12.dist-info → secator-0.11.1.dist-info}/licenses/LICENSE +0 -0
secator/tasks/wafw00f.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import yaml
|
|
3
|
+
|
|
4
|
+
from secator.decorators import task
|
|
5
|
+
from secator.runners import Command
|
|
6
|
+
from secator.definitions import (OUTPUT_PATH, HEADER, PROXY, URL, TIMEOUT)
|
|
7
|
+
from secator.output_types import Tag, Info, Error
|
|
8
|
+
from secator.tasks._categories import OPTS
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@task()
|
|
12
|
+
class wafw00f(Command):
|
|
13
|
+
"""Web Application Firewall Fingerprinting tool."""
|
|
14
|
+
cmd = 'wafw00f'
|
|
15
|
+
input_type = URL
|
|
16
|
+
input_flag = None
|
|
17
|
+
file_flag = '-i'
|
|
18
|
+
json_flag = '-f json'
|
|
19
|
+
encoding = 'ansi'
|
|
20
|
+
opt_prefix = '--'
|
|
21
|
+
meta_opts = {
|
|
22
|
+
PROXY: OPTS[PROXY],
|
|
23
|
+
HEADER: OPTS[HEADER],
|
|
24
|
+
TIMEOUT: OPTS[TIMEOUT]
|
|
25
|
+
}
|
|
26
|
+
opts = {
|
|
27
|
+
'list': {'is_flag': True, 'default': False, 'help': 'List all WAFs that WAFW00F is able to detect'},
|
|
28
|
+
'waf_type': {'type': str, 'short': 'wt', 'help': 'Test for one specific WAF'},
|
|
29
|
+
'find_all': {'is_flag': True, 'short': 'ta', 'default': False, 'help': 'Find all WAFs which match the signatures, do not stop testing on the first one'}, # noqa: E501
|
|
30
|
+
'no_follow_redirects': {'is_flag': True, 'short': 'nfr', 'default': False, 'help': 'Do not follow redirections given by 3xx responses'}, # noqa: E501
|
|
31
|
+
}
|
|
32
|
+
opt_key_map = {
|
|
33
|
+
HEADER: 'headers',
|
|
34
|
+
PROXY: 'proxy',
|
|
35
|
+
'waf_type': 'test',
|
|
36
|
+
'find_all': 'findall',
|
|
37
|
+
'no_follow_redirects': 'noredirect',
|
|
38
|
+
}
|
|
39
|
+
output_types = [Tag]
|
|
40
|
+
install_cmd = 'pipx install git+https://github.com/EnableSecurity/wafw00f.git && pipx upgrade wafw00f'
|
|
41
|
+
install_github_handle = 'EnableSecurity/wafw00f'
|
|
42
|
+
proxy_http = True
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def on_cmd(self):
|
|
46
|
+
self.output_path = self.get_opt_value(OUTPUT_PATH)
|
|
47
|
+
if not self.output_path:
|
|
48
|
+
self.output_path = f'{self.reports_folder}/.outputs/{self.unique_name}.json'
|
|
49
|
+
self.cmd += f' -o {self.output_path}'
|
|
50
|
+
|
|
51
|
+
self.headers = self.get_opt_value(HEADER)
|
|
52
|
+
if self.headers:
|
|
53
|
+
header_file = f'{self.reports_folder}/.inputs/headers.txt'
|
|
54
|
+
with open(header_file, 'w') as f:
|
|
55
|
+
for header in self.headers.split(';;'):
|
|
56
|
+
f.write(f'{header}\n')
|
|
57
|
+
self.cmd = self.cmd.replace(self.headers, header_file)
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def on_cmd_done(self):
|
|
61
|
+
# Skip parsing if list mode
|
|
62
|
+
list_mode = self.get_opt_value('list')
|
|
63
|
+
if list_mode:
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
if not os.path.exists(self.output_path):
|
|
67
|
+
yield Error(message=f'Could not find JSON results in {self.output_path}')
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
yield Info(message=f'JSON results saved to {self.output_path}')
|
|
71
|
+
with open(self.output_path, 'r') as f:
|
|
72
|
+
results = yaml.safe_load(f.read())
|
|
73
|
+
|
|
74
|
+
if len(results) > 0 and results[0]['detected']:
|
|
75
|
+
waf_name = results[0]['firewall']
|
|
76
|
+
url = results[0]['url']
|
|
77
|
+
match = results[0]['trigger_url']
|
|
78
|
+
manufacter = results[0]['manufacturer']
|
|
79
|
+
yield Tag(
|
|
80
|
+
name=waf_name + ' WAF',
|
|
81
|
+
match=url,
|
|
82
|
+
extra_data={'waf_name': waf_name, 'manufacter': manufacter, 'trigger_url': match}
|
|
83
|
+
)
|
secator/tasks/wpprobe.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import yaml
|
|
3
|
+
|
|
4
|
+
from secator.decorators import task
|
|
5
|
+
from secator.runners import Command
|
|
6
|
+
from secator.definitions import OUTPUT_PATH, THREADS, URL
|
|
7
|
+
from secator.output_types import Vulnerability, Tag, Info, Warning
|
|
8
|
+
from secator.tasks._categories import OPTS
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@task()
|
|
12
|
+
class wpprobe(Command):
|
|
13
|
+
"""Fast wordpress plugin enumeration tool."""
|
|
14
|
+
cmd = 'wpprobe'
|
|
15
|
+
file_flag = '-f'
|
|
16
|
+
input_flag = '-u'
|
|
17
|
+
input_type = URL
|
|
18
|
+
opt_prefix = '-'
|
|
19
|
+
opts = {
|
|
20
|
+
'mode': {'type': click.Choice(['scan', 'update', 'update-db']), 'default': 'scan', 'help': 'WPProbe mode', 'required': True, 'internal': True}, # noqa: E501
|
|
21
|
+
'output_path': {'type': str, 'default': None, 'help': 'Output JSON file path', 'internal': True, 'display': False}, # noqa: E501
|
|
22
|
+
}
|
|
23
|
+
meta_opts = {
|
|
24
|
+
THREADS: OPTS[THREADS]
|
|
25
|
+
}
|
|
26
|
+
opt_key_map = {
|
|
27
|
+
THREADS: 't'
|
|
28
|
+
}
|
|
29
|
+
output_types = [Vulnerability, Tag]
|
|
30
|
+
install_cmd = 'go install github.com/Chocapikk/wpprobe@latest'
|
|
31
|
+
install_github_handle = 'Chocapikk/wpprobe'
|
|
32
|
+
install_post = {
|
|
33
|
+
'*': 'wpprobe update && wpprobe update-db'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def on_cmd(self):
|
|
38
|
+
mode = self.get_opt_value('mode')
|
|
39
|
+
if mode == 'update' or mode == 'update-db':
|
|
40
|
+
self.cmd = f'{wpprobe.cmd} {mode}'
|
|
41
|
+
return
|
|
42
|
+
self.cmd = self.cmd.replace(wpprobe.cmd, f'{wpprobe.cmd} {mode}')
|
|
43
|
+
output_path = self.get_opt_value(OUTPUT_PATH)
|
|
44
|
+
if not output_path:
|
|
45
|
+
output_path = f'{self.reports_folder}/.outputs/{self.unique_name}.json'
|
|
46
|
+
self.output_path = output_path
|
|
47
|
+
self.cmd += f' -o {self.output_path}'
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def on_cmd_done(self):
|
|
51
|
+
if not self.get_opt_value('mode') == 'scan':
|
|
52
|
+
return
|
|
53
|
+
yield Info(message=f'JSON results saved to {self.output_path}')
|
|
54
|
+
with open(self.output_path, 'r') as f:
|
|
55
|
+
results = yaml.safe_load(f.read())
|
|
56
|
+
if not results or 'url' not in results:
|
|
57
|
+
yield Warning(message='No results found !')
|
|
58
|
+
return
|
|
59
|
+
url = results['url']
|
|
60
|
+
for plugin_name, plugin_data in results['plugins'].items():
|
|
61
|
+
for plugin_data_version in plugin_data:
|
|
62
|
+
plugin_version = plugin_data_version['version']
|
|
63
|
+
yield Tag(
|
|
64
|
+
name=f'Wordpress plugin - {plugin_name} {plugin_version}',
|
|
65
|
+
match=url,
|
|
66
|
+
extra_data={
|
|
67
|
+
'name': plugin_name,
|
|
68
|
+
'version': plugin_version
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
severities = plugin_data_version.get('severities', {})
|
|
72
|
+
for severity, severity_data in severities.items():
|
|
73
|
+
if severity == 'None':
|
|
74
|
+
severity = 'unknown'
|
|
75
|
+
for item in severity_data:
|
|
76
|
+
for vuln in item['vulnerabilities']:
|
|
77
|
+
auth_type = item.get('auth_type')
|
|
78
|
+
extra_data = {
|
|
79
|
+
'plugin_name': plugin_name,
|
|
80
|
+
'plugin_version': plugin_version,
|
|
81
|
+
}
|
|
82
|
+
if auth_type:
|
|
83
|
+
extra_data['auth_type'] = auth_type
|
|
84
|
+
yield Vulnerability(
|
|
85
|
+
name=vuln['title'],
|
|
86
|
+
id=vuln['cve'],
|
|
87
|
+
severity=severity,
|
|
88
|
+
cvss_score=vuln['cvss_score'],
|
|
89
|
+
tags=[plugin_name],
|
|
90
|
+
reference=vuln['cve_link'],
|
|
91
|
+
extra_data=extra_data,
|
|
92
|
+
matched_at=url,
|
|
93
|
+
confidence='high'
|
|
94
|
+
)
|
secator/template.py
CHANGED
|
@@ -8,85 +8,44 @@ from dotmap import DotMap
|
|
|
8
8
|
|
|
9
9
|
from secator.config import CONFIG, CONFIGS_FOLDER
|
|
10
10
|
from secator.rich import console
|
|
11
|
-
from secator.utils import convert_functions_to_strings
|
|
11
|
+
from secator.utils import convert_functions_to_strings, debug
|
|
12
|
+
from secator.output_types import Error
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
TEMPLATES_DIR_KEYS = ['workflow', 'scan', 'profile']
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def load_template(name):
|
|
18
|
-
"""Load a config by name.
|
|
19
|
-
|
|
20
|
-
Args:
|
|
21
|
-
name: Name of the config, for instances profiles/aggressive or workflows/domain_scan.
|
|
22
|
-
|
|
23
|
-
Returns:
|
|
24
|
-
dict: Loaded config.
|
|
25
|
-
"""
|
|
26
|
-
path = CONFIGS_FOLDER / f'{name}.yaml'
|
|
27
|
-
if not path.exists():
|
|
28
|
-
console.log(f'Config "{name}" could not be loaded.')
|
|
29
|
-
return
|
|
30
|
-
with path.open('r') as f:
|
|
31
|
-
return yaml.load(f.read(), Loader=yaml.Loader)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def find_templates():
|
|
35
|
-
results = {'scan': [], 'workflow': [], 'profile': []}
|
|
36
|
-
dirs_type = [CONFIGS_FOLDER]
|
|
37
|
-
if CONFIG.dirs.templates:
|
|
38
|
-
dirs_type.append(CONFIG.dirs.templates)
|
|
39
|
-
paths = []
|
|
40
|
-
for dir in dirs_type:
|
|
41
|
-
dir_paths = [
|
|
42
|
-
Path(path)
|
|
43
|
-
for path in glob.glob(str(dir).rstrip('/') + '/**/*.y*ml', recursive=True)
|
|
44
|
-
]
|
|
45
|
-
paths.extend(dir_paths)
|
|
46
|
-
for path in paths:
|
|
47
|
-
with path.open('r') as f:
|
|
48
|
-
try:
|
|
49
|
-
config = yaml.load(f.read(), yaml.Loader)
|
|
50
|
-
type = config.get('type')
|
|
51
|
-
if type:
|
|
52
|
-
results[type].append(path)
|
|
53
|
-
except yaml.YAMLError as exc:
|
|
54
|
-
console.log(f'Unable to load config at {path}')
|
|
55
|
-
console.log(str(exc))
|
|
56
|
-
return results
|
|
14
|
+
TEMPLATES = []
|
|
57
15
|
|
|
58
16
|
|
|
59
17
|
class TemplateLoader(DotMap):
|
|
60
18
|
|
|
61
19
|
def __init__(self, input={}, name=None, **kwargs):
|
|
62
20
|
if name:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
21
|
+
if '/' not in name:
|
|
22
|
+
console.print(Error(message=f'Cannot load {name}: you should specify a type for the template when loading by name (e.g. workflow/<workflow_name>)')) # noqa: E501
|
|
23
|
+
return
|
|
24
|
+
_type, name = name.split('/')
|
|
25
|
+
config = next((p for p in TEMPLATES if p['type'] == _type and p['name'] == name in str(p)), None)
|
|
26
|
+
if not config:
|
|
27
|
+
console.print(Error(message=f'Template {name} not found in loaded templates'))
|
|
28
|
+
config = {}
|
|
29
|
+
elif isinstance(input, dict):
|
|
68
30
|
config = input
|
|
69
|
-
|
|
31
|
+
elif isinstance(input, Path) or Path(input).exists():
|
|
32
|
+
config = self._load_from_path(input)
|
|
33
|
+
elif isinstance(input, str):
|
|
34
|
+
config = self._load(input)
|
|
35
|
+
super().__init__(config, **kwargs)
|
|
36
|
+
|
|
37
|
+
def add_to_templates(self):
|
|
38
|
+
TEMPLATES.append(self)
|
|
70
39
|
|
|
71
|
-
def
|
|
72
|
-
if isinstance(path, str):
|
|
73
|
-
path = Path(path)
|
|
40
|
+
def _load_from_path(self, path):
|
|
74
41
|
if not path.exists():
|
|
75
|
-
console.
|
|
42
|
+
console.print(Error(message=f'Config path {path} does not exists'))
|
|
76
43
|
return
|
|
77
44
|
with path.open('r') as f:
|
|
78
|
-
return
|
|
45
|
+
return self._load(f.read())
|
|
79
46
|
|
|
80
|
-
def
|
|
81
|
-
return
|
|
82
|
-
|
|
83
|
-
@classmethod
|
|
84
|
-
def load_all(cls):
|
|
85
|
-
configs = find_templates()
|
|
86
|
-
return TemplateLoader({
|
|
87
|
-
key: [TemplateLoader(path) for path in configs[key]]
|
|
88
|
-
for key in TEMPLATES_DIR_KEYS
|
|
89
|
-
})
|
|
47
|
+
def _load(self, input):
|
|
48
|
+
return yaml.load(input, Loader=yaml.Loader)
|
|
90
49
|
|
|
91
50
|
@property
|
|
92
51
|
def supported_opts(self):
|
|
@@ -141,7 +100,7 @@ class TemplateLoader(DotMap):
|
|
|
141
100
|
# For each workflow in the scan, load it and incorporate it with a unique prefix
|
|
142
101
|
for wf_name, _ in self.workflows.items():
|
|
143
102
|
name = wf_name.split('/')[0]
|
|
144
|
-
config = TemplateLoader(name=f'
|
|
103
|
+
config = TemplateLoader(name=f'workflow/{name}')
|
|
145
104
|
wf_tasks = config.flat_tasks
|
|
146
105
|
# Prefix tasks from this workflow with its name to prevent collision
|
|
147
106
|
for task_key, task_val in wf_tasks.items():
|
|
@@ -153,3 +112,26 @@ class TemplateLoader(DotMap):
|
|
|
153
112
|
parse_config(self.tasks)
|
|
154
113
|
|
|
155
114
|
return dict(tasks)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def find_templates():
|
|
118
|
+
results = []
|
|
119
|
+
dirs = [CONFIGS_FOLDER]
|
|
120
|
+
if CONFIG.dirs.templates:
|
|
121
|
+
dirs.append(CONFIG.dirs.templates)
|
|
122
|
+
paths = []
|
|
123
|
+
for dir in dirs:
|
|
124
|
+
config_paths = [
|
|
125
|
+
Path(path)
|
|
126
|
+
for path in glob.glob(str(dir).rstrip('/') + '/**/*.y*ml', recursive=True)
|
|
127
|
+
]
|
|
128
|
+
debug(f'Found {len(config_paths)} templates in {dir}', sub='template')
|
|
129
|
+
paths.extend(config_paths)
|
|
130
|
+
for path in paths:
|
|
131
|
+
config = TemplateLoader(input=path)
|
|
132
|
+
debug(f'Loaded template from {path}', sub='template')
|
|
133
|
+
results.append(config)
|
|
134
|
+
return results
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
TEMPLATES = find_templates()
|
secator/utils.py
CHANGED
|
@@ -373,11 +373,15 @@ def rich_to_ansi(text):
|
|
|
373
373
|
Returns:
|
|
374
374
|
str: Converted text (ANSI).
|
|
375
375
|
"""
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
tmp_console.
|
|
380
|
-
|
|
376
|
+
try:
|
|
377
|
+
from rich.console import Console
|
|
378
|
+
tmp_console = Console(file=None, highlight=False)
|
|
379
|
+
with tmp_console.capture() as capture:
|
|
380
|
+
tmp_console.print(text, end='', soft_wrap=True)
|
|
381
|
+
return capture.get()
|
|
382
|
+
except Exception:
|
|
383
|
+
console.print(f'[bold red]Could not convert rich text to ansi: {text}[/]', highlight=False, markup=False)
|
|
384
|
+
return text
|
|
381
385
|
|
|
382
386
|
|
|
383
387
|
def rich_escape(obj):
|
|
@@ -459,6 +463,10 @@ def escape_mongodb_url(url):
|
|
|
459
463
|
return url
|
|
460
464
|
|
|
461
465
|
|
|
466
|
+
def caml_to_snake(s):
|
|
467
|
+
return re.sub(r'(?<!^)(?=[A-Z])', '_', s).lower()
|
|
468
|
+
|
|
469
|
+
|
|
462
470
|
def print_version():
|
|
463
471
|
"""Print secator version information."""
|
|
464
472
|
from secator.installer import get_version_info
|
|
@@ -773,8 +781,9 @@ def process_wordlist(val):
|
|
|
773
781
|
val = default_wordlist
|
|
774
782
|
template_wordlist = getattr(CONFIG.wordlists.templates, val)
|
|
775
783
|
if template_wordlist:
|
|
776
|
-
|
|
777
|
-
|
|
784
|
+
val = template_wordlist
|
|
785
|
+
|
|
786
|
+
if Path(val).exists():
|
|
778
787
|
return val
|
|
779
788
|
else:
|
|
780
789
|
return download_file(
|
secator/utils_test.py
CHANGED
|
@@ -14,7 +14,7 @@ from secator.cli import ALL_WORKFLOWS, ALL_TASKS, ALL_SCANS
|
|
|
14
14
|
from secator.output_types import EXECUTION_TYPES, STAT_TYPES
|
|
15
15
|
from secator.runners import Command
|
|
16
16
|
from secator.rich import console
|
|
17
|
-
from secator.utils import load_fixture
|
|
17
|
+
from secator.utils import load_fixture, debug
|
|
18
18
|
|
|
19
19
|
#---------#
|
|
20
20
|
# GLOBALS #
|
|
@@ -60,7 +60,8 @@ INPUTS_TASKS = {
|
|
|
60
60
|
USERNAME: 'test',
|
|
61
61
|
IP: '192.168.1.23',
|
|
62
62
|
CIDR_RANGE: '192.168.1.0/24',
|
|
63
|
-
EMAIL: 'fake@fake.com'
|
|
63
|
+
EMAIL: 'fake@fake.com',
|
|
64
|
+
'folder': '.'
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
#---------------------#
|
|
@@ -97,11 +98,18 @@ META_OPTS = {
|
|
|
97
98
|
'nmap.skip_host_discovery': True,
|
|
98
99
|
'msfconsole.resource': load_fixture('msfconsole_input', FIXTURES_DIR, only_path=True),
|
|
99
100
|
'dirsearch.output_path': load_fixture('dirsearch_output', FIXTURES_DIR, only_path=True),
|
|
101
|
+
'gitleaks_output_path': load_fixture('gitleaks_output', FIXTURES_DIR, only_path=True),
|
|
100
102
|
'maigret.output_path': load_fixture('maigret_output', FIXTURES_DIR, only_path=True),
|
|
101
103
|
'nuclei.template_id': 'prometheus-metrics',
|
|
102
104
|
'wpscan.output_path': load_fixture('wpscan_output', FIXTURES_DIR, only_path=True),
|
|
103
105
|
'h8mail.output_path': load_fixture('h8mail_output', FIXTURES_DIR, only_path=True),
|
|
104
|
-
'h8mail.local_breach': load_fixture('h8mail_breach', FIXTURES_DIR, only_path=True)
|
|
106
|
+
'h8mail.local_breach': load_fixture('h8mail_breach', FIXTURES_DIR, only_path=True),
|
|
107
|
+
'wpprobe.output_path': load_fixture('wpprobe_output', FIXTURES_DIR, only_path=True),
|
|
108
|
+
'arjun.output_path': load_fixture('arjun_output', FIXTURES_DIR, only_path=True),
|
|
109
|
+
'arjun.wordlist': False,
|
|
110
|
+
'trivy.output_path': load_fixture('trivy_output', FIXTURES_DIR, only_path=True),
|
|
111
|
+
'wafw00f.output_path': load_fixture('wafw00f_output', FIXTURES_DIR, only_path=True),
|
|
112
|
+
'testssl.output_path': load_fixture('testssl_output', FIXTURES_DIR, only_path=True),
|
|
105
113
|
}
|
|
106
114
|
|
|
107
115
|
|
|
@@ -158,17 +166,20 @@ class CommandOutputTester: # Mixin for unittest.TestCase
|
|
|
158
166
|
empty_results_allowed=False):
|
|
159
167
|
|
|
160
168
|
console.print(f'[dim]Testing {runner.config.type} {runner.name} ...[/]', end='')
|
|
169
|
+
debug('', sub='unittest')
|
|
161
170
|
|
|
162
171
|
if not runner.inputs:
|
|
163
172
|
console.print('[dim gold3] skipped (no inputs defined).[/]')
|
|
164
173
|
return
|
|
165
174
|
|
|
166
|
-
if not expected_results and not expected_output_keys:
|
|
175
|
+
if not expected_results and not expected_output_keys and not expected_output_types:
|
|
167
176
|
console.print('[dim gold3] (no outputs defined).[/]', end='')
|
|
168
177
|
|
|
169
178
|
try:
|
|
170
179
|
# Run runner
|
|
171
180
|
results = runner.run()
|
|
181
|
+
for result in results:
|
|
182
|
+
debug(result.toDict(), sub='unittest')
|
|
172
183
|
|
|
173
184
|
# Add execution types to allowed output types
|
|
174
185
|
expected_output_types.extend(EXECUTION_TYPES + STAT_TYPES)
|
|
@@ -176,32 +187,39 @@ class CommandOutputTester: # Mixin for unittest.TestCase
|
|
|
176
187
|
# Check return code
|
|
177
188
|
if isinstance(runner, Command):
|
|
178
189
|
if not runner.ignore_return_code:
|
|
179
|
-
|
|
190
|
+
debug(f'{runner.name} should have a 0 return code', sub='unittest')
|
|
191
|
+
self.assertEqual(runner.return_code, 0, f'{runner.name} should have a 0 return code. Runner return code: {runner.return_code}') # noqa: E501
|
|
180
192
|
|
|
181
193
|
# Check results not empty
|
|
182
194
|
if not empty_results_allowed:
|
|
195
|
+
debug(f'{runner.name} should return at least 1 result', sub='unittest')
|
|
183
196
|
self.assertGreater(len(results), 0, f'{runner.name} should return at least 1 result')
|
|
184
197
|
|
|
185
198
|
# Check status
|
|
186
|
-
|
|
199
|
+
debug(f'{runner.name} should have the status {expected_status}.', sub='unittest')
|
|
200
|
+
self.assertEqual(runner.status, expected_status, f'{runner.name} should have the status {expected_status}. Errors: {runner.errors}') # noqa: E501
|
|
187
201
|
|
|
188
202
|
# Check results
|
|
189
203
|
for item in results:
|
|
204
|
+
debug(f'{runner.name} yielded {repr(item)}', sub='unittest')
|
|
190
205
|
|
|
191
206
|
if expected_output_types:
|
|
207
|
+
debug(f'{runner.name} item should have an output type in {[_._type for _ in expected_output_types]}', sub='unittest') # noqa: E501
|
|
192
208
|
self.assertIn(type(item), expected_output_types, f'{runner.name}: item has an unexpected output type "{type(item)}"') # noqa: E501
|
|
193
209
|
|
|
194
210
|
if expected_output_keys:
|
|
195
211
|
keys = [k for k in list(item.keys()) if not k.startswith('_')]
|
|
212
|
+
debug(f'{runner.name} item should have output keys {keys}', sub='unittest')
|
|
196
213
|
self.assertEqual(
|
|
197
214
|
set(keys).difference(set(expected_output_keys)),
|
|
198
215
|
set(),
|
|
199
|
-
f'{runner.name}: item is missing expected keys {set(expected_output_keys)}')
|
|
216
|
+
f'{runner.name}: item is missing expected keys {set(expected_output_keys)}. Item keys: {keys}') # noqa: E501
|
|
200
217
|
|
|
201
218
|
# Check if runner results in expected results
|
|
202
219
|
if expected_results:
|
|
203
220
|
for result in expected_results:
|
|
204
|
-
|
|
221
|
+
debug(f'{runner.name} item should be in expected results {result}.', sub='unittest')
|
|
222
|
+
self.assertIn(result, results, f'{runner.name}: {result} should be in runner results.') # noqa: E501
|
|
205
223
|
|
|
206
224
|
except Exception:
|
|
207
225
|
console.print('[dim red] failed[/]')
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
secator/.gitignore,sha256=da8MUc3hdb6Mo0WjZu2upn5uZMbXcBGvhdhTQ1L89HI,3093
|
|
2
2
|
secator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
secator/celery.py,sha256=
|
|
4
|
-
secator/celery_signals.py,sha256=
|
|
3
|
+
secator/celery.py,sha256=Xg8e0zpQu4_-jlsZeC65NZtkopHGsGIyQ3SiW5fyH4E,9771
|
|
4
|
+
secator/celery_signals.py,sha256=hG62Gr34xKJYZTgZFn_wZcsAlMgKuTazQhx55FC5cDA,4259
|
|
5
5
|
secator/celery_utils.py,sha256=_wcUC42VPUotPhh9YYqbuq0dkARI8_RoCklDlhQL9Jg,8903
|
|
6
|
-
secator/cli.py,sha256=
|
|
7
|
-
secator/config.py,sha256=
|
|
8
|
-
secator/decorators.py,sha256=
|
|
9
|
-
secator/definitions.py,sha256=
|
|
10
|
-
secator/installer.py,sha256=
|
|
6
|
+
secator/cli.py,sha256=f_ySIOXyHmFwH3cSElxdXlnDGAYH72VXPFDhx0q9pnY,48866
|
|
7
|
+
secator/config.py,sha256=Nuwhkd9jh-5TTq6e7M9SmfZWyBjblbjMsNz1Mba32zg,19692
|
|
8
|
+
secator/decorators.py,sha256=Ac926Sxt7HvR5JTvpy5uqa4oxKWBXzx5VpeAOHJevJ4,14197
|
|
9
|
+
secator/definitions.py,sha256=f8Otl2MLo2XAVcl-CqbZ1PP4fAi-WXCTq5YfDhOzCR8,3201
|
|
10
|
+
secator/installer.py,sha256=mxtt-TK1rr6uuw7nECZgrw3031rac5P2wWtXBUeuD4c,18937
|
|
11
11
|
secator/report.py,sha256=55xKvYY0MKNPaEgTrx66mj-Siohx3drLbTX2LpEx6zs,3627
|
|
12
|
-
secator/rich.py,sha256=
|
|
13
|
-
secator/template.py,sha256=
|
|
12
|
+
secator/rich.py,sha256=0P6TECNePsfivc5h1JsJoAqKmpFnME5m8k29ZJjvbwM,3277
|
|
13
|
+
secator/template.py,sha256=8EVsRRDAzNeALqTMzO9cNtxiXSbpSi0Nw7uSBb2Hbs0,4167
|
|
14
14
|
secator/thread.py,sha256=rgRgEtcMgs2wyfLWVlCTUCLWeg6jsMo5iKpyyrON5rY,655
|
|
15
|
-
secator/utils.py,sha256=
|
|
16
|
-
secator/utils_test.py,sha256=
|
|
15
|
+
secator/utils.py,sha256=kaBqWgBmUZohlvn6nFY805tG26viIu_LdJysNfZWMY4,22055
|
|
16
|
+
secator/utils_test.py,sha256=jiCvgL4JMhIC1-ZGe6j9umNRZreSDp6nlkMgEJhf5ho,7996
|
|
17
17
|
secator/configs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
18
|
secator/configs/profiles/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
19
|
secator/configs/profiles/aggressive.yaml,sha256=JilVySABlSCYEFMjH7V0Oc3dAVlkfHOh1odTGhtm7BQ,108
|
|
@@ -35,9 +35,10 @@ secator/configs/workflows/url_crawl.yaml,sha256=h74dvDBNLuY1EHc9FMby3ydr34VH1qFJ
|
|
|
35
35
|
secator/configs/workflows/url_dirsearch.yaml,sha256=wiiVY3RK0s8DYxg7UWcxLjkxMdIIINWefG1mhps2E-0,627
|
|
36
36
|
secator/configs/workflows/url_fuzz.yaml,sha256=K1RkplXrgc7q2YJVv5A6B5MMkAzIIv31HInhRCKMpyI,774
|
|
37
37
|
secator/configs/workflows/url_nuclei.yaml,sha256=Qigz-hJzM7GeNA_UD46dThVIoqbWlBgiYb_i5fSyJiI,265
|
|
38
|
+
secator/configs/workflows/url_params_fuzz.yaml,sha256=ooOMxQDtuYq3jR2GOU6e-69H4bLvsq-CzemGFvmktjY,647
|
|
38
39
|
secator/configs/workflows/url_vuln.yaml,sha256=_nX_D96NbD8fHU4wXov7ZHD1cmiFgKc86Mh0enWGS7s,1354
|
|
39
40
|
secator/configs/workflows/user_hunt.yaml,sha256=e5b-CkkjhOPE8Yh5LUh0K60GKmxTgn4s-Joo7m9jKrk,180
|
|
40
|
-
secator/configs/workflows/wordpress.yaml,sha256=
|
|
41
|
+
secator/configs/workflows/wordpress.yaml,sha256=m80JdK2G9O5TGxk_BTiRjzhrXrLJQz0a1foIOQTKp3U,341
|
|
41
42
|
secator/exporters/__init__.py,sha256=PnT9Ra4ArHt9VQTK5Cpc4CPY89XRwLLUGtZ8nUcknm0,415
|
|
42
43
|
secator/exporters/_base.py,sha256=-RrrwO_qp0ETLLHSta4T-zKtMbWdiEmz1Cw5mNo6USU,77
|
|
43
44
|
secator/exporters/console.py,sha256=idVotf9B0LnYZ4JQJHrgMtVfE26XkRDuvbCyJvCY2hU,249
|
|
@@ -49,8 +50,9 @@ secator/exporters/txt.py,sha256=oMtr22di6cqyE_5yJoiWP-KElrI5QgvK1cOUrj7H7js,730
|
|
|
49
50
|
secator/hooks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
50
51
|
secator/hooks/gcs.py,sha256=MIhntyWYz9BZdTXhWl5JznaczSq1_7fl3TVqPufuTSo,1490
|
|
51
52
|
secator/hooks/mongodb.py,sha256=XKbm_SrcSbQ2koILWvhzSg4tqdvHXgX5aU5x46Edu1s,7716
|
|
52
|
-
secator/output_types/__init__.py,sha256=
|
|
53
|
+
secator/output_types/__init__.py,sha256=CJcYy2_Ek-opKiBz4wFlDHQBTm3t0JVwZ4w_2Jxoeuw,1291
|
|
53
54
|
secator/output_types/_base.py,sha256=OgS6ICt66TzPsqo1JZwRIIwbng2HRX1i_u5qbUECgNk,2820
|
|
55
|
+
secator/output_types/certificate.py,sha256=G2Z39UIT037oSujf8BwwgXXBIEVWSI-Oc-PorhyYT38,3346
|
|
54
56
|
secator/output_types/error.py,sha256=39gpEJfKM2EuyOhD9lSkjjna2QicMvnLdFav6kHmhlg,1529
|
|
55
57
|
secator/output_types/exploit.py,sha256=-BKTqPBg94rVgjw8YSmcYuBCI2x-73WwMd9ITP9qr3Y,1750
|
|
56
58
|
secator/output_types/info.py,sha256=R8xeiD3ocNOsvkJPhrQgsx6q-Ea1G0eTAqyuh5JrAR0,843
|
|
@@ -64,24 +66,25 @@ secator/output_types/subdomain.py,sha256=ivJ_2kmrJ8hdB8wmvRJYlKV1BcE3Cds_vAI_5wL
|
|
|
64
66
|
secator/output_types/tag.py,sha256=_XEqWAvAvmi7nd2ldfEE71zQx97jTSph2iDHkeqGTyk,1470
|
|
65
67
|
secator/output_types/target.py,sha256=lmPw2aFOGIOFG4XXo6vNVZBBAZlnApJjyDVepDY54TU,871
|
|
66
68
|
secator/output_types/url.py,sha256=gTGShwXIA2mA9cBLP7_I4qA5XUP-EYB1xnPRLtUDAi8,2912
|
|
67
|
-
secator/output_types/user_account.py,sha256=
|
|
69
|
+
secator/output_types/user_account.py,sha256=EvF3Ebg9eXS_-iDguU1dSHZ9wAsJimEJznDvpSt_RSY,1417
|
|
68
70
|
secator/output_types/vulnerability.py,sha256=nF7OT9zGez8sZvLrkhjBOORjVi8hCqfCYUFq3eZ_ywo,2870
|
|
69
71
|
secator/output_types/warning.py,sha256=47GtmG083GqGPb_R5JDFmARJ9Mqrme58UxwJhgdGPuI,853
|
|
70
72
|
secator/runners/__init__.py,sha256=EBbOk37vkBy9p8Hhrbi-2VtM_rTwQ3b-0ggTyiD22cE,290
|
|
71
|
-
secator/runners/_base.py,sha256=
|
|
72
|
-
secator/runners/_helpers.py,sha256=
|
|
73
|
+
secator/runners/_base.py,sha256=bd_k3xn1DMxt7RaWhiaSI9zN_qbN_sTUrmwh7j2_xmk,31657
|
|
74
|
+
secator/runners/_helpers.py,sha256=_2pyPgDK0obqlOEYJRDrqJgvDntz_zL483spFLtGSXs,2514
|
|
73
75
|
secator/runners/celery.py,sha256=bqvDTTdoHiGRCt0FRvlgFHQ_nsjKMP5P0PzGbwfCj_0,425
|
|
74
|
-
secator/runners/command.py,sha256=
|
|
75
|
-
secator/runners/scan.py,sha256=
|
|
76
|
+
secator/runners/command.py,sha256=rb1g7Sj-GfA5T4klz1ClgQWSY0Ut_wBqBFbLSwVq158,26847
|
|
77
|
+
secator/runners/scan.py,sha256=n8RL9vXPmNqukdB2a5wcPhSmxsZB4KFaqtZt0wfa8dU,1622
|
|
76
78
|
secator/runners/task.py,sha256=LIgcBqORVPG5Kfx6g6RnEni1kgWchMfa3Oo2JEZri1Y,2037
|
|
77
|
-
secator/runners/workflow.py,sha256=
|
|
79
|
+
secator/runners/workflow.py,sha256=sO3B9GcndQLgGPnDz-A6XKnBmYO_ABAU_UXpW-q1K1A,3890
|
|
78
80
|
secator/scans/__init__.py,sha256=s4Ojsk5CWwyWqHu_A4zaXUL5Hm5L5nCmCHZn7wdD3Io,623
|
|
79
81
|
secator/serializers/__init__.py,sha256=OP5cmFl77ovgSCW_IDcZ21St2mUt5UK4QHfrsK2KvH8,248
|
|
80
82
|
secator/serializers/dataclass.py,sha256=RqICpfsYWGjHAACAA2h2jZ_69CFHim4VZwcBqowGMcQ,1010
|
|
81
83
|
secator/serializers/json.py,sha256=UJwAymRzjF-yBKOgz1MTOyBhQcdQg7fOKRXgmHIu8fo,411
|
|
82
84
|
secator/serializers/regex.py,sha256=fh-fE0RGvKSGKByFtwmKsWriRpZR9PXZQsY9JybHBWI,489
|
|
83
85
|
secator/tasks/__init__.py,sha256=yRIZf9E47aS7o6rpgAJLgJUpX2cug1ofZeq8QsxgyjU,192
|
|
84
|
-
secator/tasks/_categories.py,sha256=
|
|
86
|
+
secator/tasks/_categories.py,sha256=kpFyTsFlcFlf4Veqa5CfLvODODGKP8VKsYgCfFg5iUI,14387
|
|
87
|
+
secator/tasks/arjun.py,sha256=viuFJtSk1-UBLQF_5Yl5dJEcx_-JDKs2eHAcn8NdBrg,2581
|
|
85
88
|
secator/tasks/bbot.py,sha256=pvA435toxYBxP-Nr6DB70fe38FGl9tKg2S9dDWUW4Vo,7527
|
|
86
89
|
secator/tasks/bup.py,sha256=4PM123Km3uOkMUwfdLY6J7pyCqIsbwMvOLYx7XYCAZc,3030
|
|
87
90
|
secator/tasks/cariddi.py,sha256=7S92pp7tvihoz9fAiMpmcfPzEvxEJKMlk-IqAvVDISA,2906
|
|
@@ -90,27 +93,32 @@ secator/tasks/dirsearch.py,sha256=DF-yXHANHb3ARgMOUbqpRuHc8-mE3bteHwTkFpXQtKc,22
|
|
|
90
93
|
secator/tasks/dnsx.py,sha256=nK14_DeyX0QTpAMdIP0LSSEOEu5_tQemyFW0XPjA2f8,2266
|
|
91
94
|
secator/tasks/dnsxbrute.py,sha256=5VnSpd5ken7vWxFX1bcsGcUN8LpaVhcjafnuETzkMGs,1422
|
|
92
95
|
secator/tasks/feroxbuster.py,sha256=3bKolPIdDBhdJ2fu4BP3w1cOlxDyI8WmtM-_2pDQ0AM,2773
|
|
93
|
-
secator/tasks/ffuf.py,sha256=
|
|
94
|
-
secator/tasks/fping.py,sha256=
|
|
96
|
+
secator/tasks/ffuf.py,sha256=YmMBttdInDLsOGNeDM17c0POQLNxILHdYkLiGeKO9wY,2551
|
|
97
|
+
secator/tasks/fping.py,sha256=OFF_uR3YzXhQJcb-ObdY178cHHjnJBPXtjMjdzVSlGI,1116
|
|
95
98
|
secator/tasks/gau.py,sha256=1Qt0I_FqTh-QyJ0YR8I7i_T80HehWSvem_SS-TQKVm0,1648
|
|
96
99
|
secator/tasks/gf.py,sha256=y8Fc0sRLGqNuwUjTBgLk3HEw3ZOnh09nB_GTufGErNA,962
|
|
100
|
+
secator/tasks/gitleaks.py,sha256=Y8q3rOTvuvfmZ4tG3awng2vG7NJLxkLZBzE0mLFat6o,2500
|
|
97
101
|
secator/tasks/gospider.py,sha256=mpoBq2VQXUqgwWPLz41fzdW85hJeo8mn9FUUJj_DrUw,2275
|
|
98
|
-
secator/tasks/grype.py,sha256=
|
|
102
|
+
secator/tasks/grype.py,sha256=ay85FlOyWeYGwQR-O0_qMTatHmfjrdzub1WeLFDnRng,2456
|
|
99
103
|
secator/tasks/h8mail.py,sha256=wNukV-aB-bXPZNq7WL8n1nFgH5b5tGh6vOF80Yna33I,1934
|
|
100
104
|
secator/tasks/httpx.py,sha256=ONfCdAOV7ARCM9tSnlucIAM3UQeWcMUm8QZX8F7u9Pg,5895
|
|
101
105
|
secator/tasks/katana.py,sha256=J0HKPT4QIrDj4uW2gZe7ByW6iEwPortSszqaHDvziwY,5355
|
|
102
106
|
secator/tasks/maigret.py,sha256=6anhBzB4lEM90Lk23cAD_ku7I_ghTpj0W0i3h6HARD8,2088
|
|
103
|
-
secator/tasks/mapcidr.py,sha256=
|
|
107
|
+
secator/tasks/mapcidr.py,sha256=bw5NMLfNj3k48MaoDGFu30fJh0pHASgPXEO2LqlzMA8,982
|
|
104
108
|
secator/tasks/msfconsole.py,sha256=TXVrvzSWw9Ncv2h9QJtaEinTMbps_z0zX1PFirERVho,6430
|
|
105
|
-
secator/tasks/naabu.py,sha256=
|
|
106
|
-
secator/tasks/nmap.py,sha256=
|
|
109
|
+
secator/tasks/naabu.py,sha256=anIGoaPlfmiaZ_HqVs9GTVzm3yYgYMKEcIQ5k5HYCPI,2214
|
|
110
|
+
secator/tasks/nmap.py,sha256=ZQJnlratjEqASHXb05QYknX2J1gWaZAN3-O4Y1Dw3UA,16970
|
|
107
111
|
secator/tasks/nuclei.py,sha256=bMXCRU5VWyrwI7Cv6BCj84NTpfjuALFumPqUSZ4Y6Ug,4243
|
|
108
112
|
secator/tasks/searchsploit.py,sha256=gvtLZbL2hzAZ07Cf0cSj2Qs0GvWK94XyHvoPFsetXu8,3321
|
|
109
|
-
secator/tasks/subfinder.py,sha256=
|
|
113
|
+
secator/tasks/subfinder.py,sha256=dfn_Cd2rqDqeh8aJde0gFwLMNc8-UcEK276iTU7wM4Q,1205
|
|
114
|
+
secator/tasks/testssl.py,sha256=xjv0cUW_K9KT3Yeon71v8OrdJUJSE4JWmbumViPgNes,11460
|
|
115
|
+
secator/tasks/trivy.py,sha256=vf68wXnhM_7N-RAcyyA1MnKl3cb75zc-i0E9ibXudv8,3204
|
|
116
|
+
secator/tasks/wafw00f.py,sha256=sjjaDSch5gfo6vRFJ0j4ccdGHGda9YodHwuIHBEIWY8,2717
|
|
117
|
+
secator/tasks/wpprobe.py,sha256=wDwpbA4OvLB-APDyRMSsLZbfgOZ60c0BiYj5f7zHQg8,3983
|
|
110
118
|
secator/tasks/wpscan.py,sha256=cSRwRfJEsZlqt1pVP3jWQ-7XhFfwR3i7FuL1PDRyWBQ,5594
|
|
111
119
|
secator/workflows/__init__.py,sha256=R_TTyjg9f2Ph2_LYiF0lL07IjTrfRE_zqJzy-N7_WCk,675
|
|
112
|
-
secator-0.
|
|
113
|
-
secator-0.
|
|
114
|
-
secator-0.
|
|
115
|
-
secator-0.
|
|
116
|
-
secator-0.
|
|
120
|
+
secator-0.11.1.dist-info/METADATA,sha256=eAUM8aG26_2gLv0BqVvSq7lk823S6QGdWxmctEin_DM,14724
|
|
121
|
+
secator-0.11.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
122
|
+
secator-0.11.1.dist-info/entry_points.txt,sha256=lPgsqqUXWgiuGSfKy-se5gHdQlAXIwS_A46NYq7Acic,44
|
|
123
|
+
secator-0.11.1.dist-info/licenses/LICENSE,sha256=19W5Jsy4WTctNkqmZIqLRV1gTDOp01S3LDj9iSgWaJ0,2867
|
|
124
|
+
secator-0.11.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|