secator 0.0.1__py3-none-any.whl → 0.3.5__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/.gitignore +162 -0
- secator/celery.py +8 -68
- secator/cli.py +631 -274
- secator/decorators.py +42 -6
- secator/definitions.py +104 -33
- secator/exporters/csv.py +1 -2
- secator/exporters/gdrive.py +1 -1
- secator/exporters/json.py +1 -2
- secator/exporters/txt.py +1 -2
- secator/hooks/mongodb.py +12 -12
- secator/installer.py +335 -0
- secator/report.py +2 -14
- secator/rich.py +3 -10
- secator/runners/_base.py +106 -34
- secator/runners/_helpers.py +18 -17
- secator/runners/command.py +91 -55
- secator/runners/scan.py +3 -1
- secator/runners/task.py +6 -4
- secator/runners/workflow.py +13 -11
- secator/tasks/_categories.py +14 -19
- secator/tasks/cariddi.py +2 -1
- secator/tasks/dalfox.py +2 -0
- secator/tasks/dirsearch.py +5 -7
- secator/tasks/dnsx.py +1 -0
- secator/tasks/dnsxbrute.py +1 -0
- secator/tasks/feroxbuster.py +6 -7
- secator/tasks/ffuf.py +4 -7
- secator/tasks/gau.py +1 -4
- secator/tasks/gf.py +2 -1
- secator/tasks/gospider.py +1 -0
- secator/tasks/grype.py +47 -47
- secator/tasks/h8mail.py +5 -6
- secator/tasks/httpx.py +24 -18
- secator/tasks/katana.py +11 -15
- secator/tasks/maigret.py +3 -3
- secator/tasks/mapcidr.py +1 -0
- secator/tasks/msfconsole.py +3 -1
- secator/tasks/naabu.py +2 -1
- secator/tasks/nmap.py +14 -17
- secator/tasks/nuclei.py +4 -3
- secator/tasks/searchsploit.py +4 -2
- secator/tasks/subfinder.py +1 -0
- secator/tasks/wpscan.py +11 -13
- secator/utils.py +64 -82
- secator/utils_test.py +3 -2
- secator-0.3.5.dist-info/METADATA +411 -0
- secator-0.3.5.dist-info/RECORD +100 -0
- {secator-0.0.1.dist-info → secator-0.3.5.dist-info}/WHEEL +1 -2
- secator-0.0.1.dist-info/METADATA +0 -199
- secator-0.0.1.dist-info/RECORD +0 -114
- secator-0.0.1.dist-info/top_level.txt +0 -2
- tests/__init__.py +0 -0
- tests/integration/__init__.py +0 -0
- tests/integration/inputs.py +0 -42
- tests/integration/outputs.py +0 -392
- tests/integration/test_scans.py +0 -82
- tests/integration/test_tasks.py +0 -103
- tests/integration/test_workflows.py +0 -163
- tests/performance/__init__.py +0 -0
- tests/performance/loadtester.py +0 -56
- tests/unit/__init__.py +0 -0
- tests/unit/test_celery.py +0 -39
- tests/unit/test_scans.py +0 -0
- tests/unit/test_serializers.py +0 -51
- tests/unit/test_tasks.py +0 -348
- tests/unit/test_workflows.py +0 -96
- {secator-0.0.1.dist-info → secator-0.3.5.dist-info}/entry_points.txt +0 -0
- {secator-0.0.1.dist-info → secator-0.3.5.dist-info/licenses}/LICENSE +0 -0
secator/decorators.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import sys
|
|
1
2
|
from collections import OrderedDict
|
|
2
3
|
|
|
3
4
|
import rich_click as click
|
|
4
5
|
from rich_click.rich_click import _get_rich_console
|
|
5
6
|
from rich_click.rich_group import RichGroup
|
|
6
7
|
|
|
7
|
-
from secator.
|
|
8
|
-
from secator.definitions import OPT_NOT_SUPPORTED
|
|
8
|
+
from secator.definitions import ADDONS_ENABLED, OPT_NOT_SUPPORTED
|
|
9
9
|
from secator.runners import Scan, Task, Workflow
|
|
10
10
|
from secator.utils import (deduplicate, expand_input, get_command_category,
|
|
11
11
|
get_command_cls)
|
|
@@ -38,18 +38,47 @@ class OrderedGroup(RichGroup):
|
|
|
38
38
|
super(OrderedGroup, self).__init__(name, commands, **attrs)
|
|
39
39
|
self.commands = commands or OrderedDict()
|
|
40
40
|
|
|
41
|
+
def command(self, *args, **kwargs):
|
|
42
|
+
"""Behaves the same as `click.Group.command()` but supports aliases.
|
|
43
|
+
"""
|
|
44
|
+
def decorator(f):
|
|
45
|
+
aliases = kwargs.pop("aliases", None)
|
|
46
|
+
if aliases:
|
|
47
|
+
max_width = _get_rich_console().width
|
|
48
|
+
aliases_str = ', '.join(f'[bold cyan]{alias}[/]' for alias in aliases)
|
|
49
|
+
padding = max_width // 4
|
|
50
|
+
|
|
51
|
+
name = kwargs.pop("name", None)
|
|
52
|
+
if not name:
|
|
53
|
+
raise click.UsageError("`name` command argument is required when using aliases.")
|
|
54
|
+
|
|
55
|
+
f.__doc__ = f.__doc__ or '\0'.ljust(padding+1)
|
|
56
|
+
f.__doc__ = f'{f.__doc__:<{padding}}[dim](aliases)[/] {aliases_str}'
|
|
57
|
+
base_command = super(OrderedGroup, self).command(
|
|
58
|
+
name, *args, **kwargs
|
|
59
|
+
)(f)
|
|
60
|
+
for alias in aliases:
|
|
61
|
+
cmd = super(OrderedGroup, self).command(alias, *args, hidden=True, **kwargs)(f)
|
|
62
|
+
cmd.help = f"Alias for '{name}'.\n\n{cmd.help}"
|
|
63
|
+
cmd.params = base_command.params
|
|
64
|
+
|
|
65
|
+
else:
|
|
66
|
+
cmd = super(OrderedGroup, self).command(*args, **kwargs)(f)
|
|
67
|
+
|
|
68
|
+
return cmd
|
|
69
|
+
return decorator
|
|
70
|
+
|
|
41
71
|
def group(self, *args, **kwargs):
|
|
42
|
-
"""Behaves the same as `click.Group.group()`
|
|
43
|
-
a list of names, all after the first will be aliases for the first.
|
|
72
|
+
"""Behaves the same as `click.Group.group()` but supports aliases.
|
|
44
73
|
"""
|
|
45
74
|
def decorator(f):
|
|
46
75
|
aliases = kwargs.pop('aliases', [])
|
|
47
76
|
aliased_group = []
|
|
48
77
|
if aliases:
|
|
49
78
|
max_width = _get_rich_console().width
|
|
50
|
-
# we have a list so create group aliases
|
|
51
79
|
aliases_str = ', '.join(f'[bold cyan]{alias}[/]' for alias in aliases)
|
|
52
80
|
padding = max_width // 4
|
|
81
|
+
f.__doc__ = f.__doc__ or '\0'.ljust(padding+1)
|
|
53
82
|
f.__doc__ = f'{f.__doc__:<{padding}}[dim](aliases)[/] {aliases_str}'
|
|
54
83
|
for alias in aliases:
|
|
55
84
|
grp = super(OrderedGroup, self).group(
|
|
@@ -143,6 +172,7 @@ def decorate_command_options(opts):
|
|
|
143
172
|
for opt_name, opt_conf in reversed_opts.items():
|
|
144
173
|
conf = opt_conf.copy()
|
|
145
174
|
short = conf.pop('short', None)
|
|
175
|
+
conf.pop('internal', False)
|
|
146
176
|
conf.pop('prefix', None)
|
|
147
177
|
long = f'--{opt_name}'
|
|
148
178
|
short = f'-{short}' if short else f'-{opt_name}'
|
|
@@ -185,6 +215,7 @@ def register_runner(cli_endpoint, config):
|
|
|
185
215
|
short_help += f' [dim]alias: {config.alias}'
|
|
186
216
|
fmt_opts['print_start'] = True
|
|
187
217
|
fmt_opts['print_run_summary'] = True
|
|
218
|
+
fmt_opts['print_progress'] = False
|
|
188
219
|
runner_cls = Scan
|
|
189
220
|
|
|
190
221
|
elif cli_endpoint.name == 'workflow':
|
|
@@ -199,6 +230,7 @@ def register_runner(cli_endpoint, config):
|
|
|
199
230
|
short_help = f'{short_help:<55} [dim](alias)[/][bold cyan] {config.alias}'
|
|
200
231
|
fmt_opts['print_start'] = True
|
|
201
232
|
fmt_opts['print_run_summary'] = True
|
|
233
|
+
fmt_opts['print_progress'] = False
|
|
202
234
|
runner_cls = Workflow
|
|
203
235
|
|
|
204
236
|
elif cli_endpoint.name == 'task':
|
|
@@ -243,11 +275,12 @@ def register_runner(cli_endpoint, config):
|
|
|
243
275
|
# opts.update(unknown_opts)
|
|
244
276
|
targets = opts.pop(input_type)
|
|
245
277
|
targets = expand_input(targets)
|
|
246
|
-
if sync or show:
|
|
278
|
+
if sync or show or not ADDONS_ENABLED['worker']:
|
|
247
279
|
sync = True
|
|
248
280
|
elif worker:
|
|
249
281
|
sync = False
|
|
250
282
|
else: # automatically run in worker if it's alive
|
|
283
|
+
from secator.celery import is_celery_worker_alive
|
|
251
284
|
sync = not is_celery_worker_alive()
|
|
252
285
|
opts['sync'] = sync
|
|
253
286
|
opts.update({
|
|
@@ -260,6 +293,9 @@ def register_runner(cli_endpoint, config):
|
|
|
260
293
|
# Build hooks from driver name
|
|
261
294
|
hooks = {}
|
|
262
295
|
if driver == 'mongodb':
|
|
296
|
+
if not ADDONS_ENABLED['mongo']:
|
|
297
|
+
_get_rich_console().print('[bold red]Missing MongoDB dependencies: please run `secator install addons mongodb`[/].')
|
|
298
|
+
sys.exit(1)
|
|
263
299
|
from secator.hooks.mongodb import MONGODB_HOOKS
|
|
264
300
|
hooks = MONGODB_HOOKS
|
|
265
301
|
|
secator/definitions.py
CHANGED
|
@@ -1,49 +1,45 @@
|
|
|
1
1
|
#!/usr/bin/python
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
from dotenv import find_dotenv, load_dotenv
|
|
7
|
+
from importlib.metadata import version
|
|
8
|
+
|
|
9
|
+
from secator.rich import console
|
|
4
10
|
|
|
5
|
-
from pkg_resources import get_distribution
|
|
6
|
-
from dotenv import load_dotenv, find_dotenv
|
|
7
11
|
load_dotenv(find_dotenv(usecwd=True), override=False)
|
|
8
12
|
|
|
9
13
|
# Globals
|
|
10
|
-
VERSION =
|
|
14
|
+
VERSION = version('secator')
|
|
11
15
|
ASCII = f"""
|
|
12
|
-
|
|
16
|
+
__
|
|
13
17
|
________ _________ _/ /_____ _____
|
|
14
18
|
/ ___/ _ \/ ___/ __ `/ __/ __ \/ ___/
|
|
15
19
|
(__ / __/ /__/ /_/ / /_/ /_/ / /
|
|
16
20
|
/____/\___/\___/\__,_/\__/\____/_/ v{VERSION}
|
|
17
21
|
|
|
18
|
-
|
|
22
|
+
freelabz.com
|
|
19
23
|
""" # noqa: W605,W291
|
|
20
24
|
|
|
21
25
|
# Secator folders
|
|
22
26
|
ROOT_FOLDER = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
|
23
|
-
|
|
27
|
+
LIB_FOLDER = ROOT_FOLDER + '/secator'
|
|
28
|
+
CONFIGS_FOLDER = LIB_FOLDER + '/configs'
|
|
24
29
|
EXTRA_CONFIGS_FOLDER = os.environ.get('SECATOR_EXTRA_CONFIGS_FOLDER')
|
|
30
|
+
BIN_FOLDER = os.environ.get('SECATOR_BIN_FOLDER', f'{os.path.expanduser("~")}/.local/bin')
|
|
25
31
|
DATA_FOLDER = os.environ.get('SECATOR_DATA_FOLDER', f'{os.path.expanduser("~")}/.secator')
|
|
26
|
-
TASKS_FOLDER = os.environ.get('SECATOR_TASKS_FOLDER', f'{DATA_FOLDER}/tasks')
|
|
27
32
|
REPORTS_FOLDER = os.environ.get('SECATOR_REPORTS_FOLDER', f'{DATA_FOLDER}/reports')
|
|
28
|
-
WORDLISTS_FOLDER = os.environ.get('SECATOR_WORDLISTS_FOLDER', '/
|
|
33
|
+
WORDLISTS_FOLDER = os.environ.get('SECATOR_WORDLISTS_FOLDER', f'{DATA_FOLDER}/wordlists')
|
|
29
34
|
SCRIPTS_FOLDER = f'{ROOT_FOLDER}/scripts'
|
|
30
35
|
CVES_FOLDER = f'{DATA_FOLDER}/cves'
|
|
31
36
|
PAYLOADS_FOLDER = f'{DATA_FOLDER}/payloads'
|
|
32
37
|
REVSHELLS_FOLDER = f'{DATA_FOLDER}/revshells'
|
|
33
|
-
|
|
34
|
-
os.makedirs(TASKS_FOLDER, exist_ok=True)
|
|
35
|
-
os.makedirs(REPORTS_FOLDER, exist_ok=True)
|
|
36
|
-
os.makedirs(WORDLISTS_FOLDER, exist_ok=True)
|
|
37
|
-
os.makedirs(SCRIPTS_FOLDER, exist_ok=True)
|
|
38
|
-
os.makedirs(CVES_FOLDER, exist_ok=True)
|
|
39
|
-
os.makedirs(PAYLOADS_FOLDER, exist_ok=True)
|
|
40
|
-
os.makedirs(REVSHELLS_FOLDER, exist_ok=True)
|
|
38
|
+
TESTS_FOLDER = f'{ROOT_FOLDER}/tests'
|
|
41
39
|
|
|
42
40
|
# Celery local fs folders
|
|
43
41
|
CELERY_DATA_FOLDER = f'{DATA_FOLDER}/celery/data'
|
|
44
42
|
CELERY_RESULTS_FOLDER = f'{DATA_FOLDER}/celery/results'
|
|
45
|
-
os.makedirs(CELERY_DATA_FOLDER, exist_ok=True)
|
|
46
|
-
os.makedirs(CELERY_RESULTS_FOLDER, exist_ok=True)
|
|
47
43
|
|
|
48
44
|
# Environment variables
|
|
49
45
|
DEBUG = int(os.environ.get('DEBUG', '0'))
|
|
@@ -57,11 +53,12 @@ CELERY_BROKER_VISIBILITY_TIMEOUT = int(os.environ.get('CELERY_BROKER_VISIBILITY_
|
|
|
57
53
|
CELERY_OVERRIDE_DEFAULT_LOGGING = bool(int(os.environ.get('CELERY_OVERRIDE_DEFAULT_LOGGING', 1)))
|
|
58
54
|
GOOGLE_DRIVE_PARENT_FOLDER_ID = os.environ.get('GOOGLE_DRIVE_PARENT_FOLDER_ID')
|
|
59
55
|
GOOGLE_CREDENTIALS_PATH = os.environ.get('GOOGLE_CREDENTIALS_PATH')
|
|
56
|
+
GITHUB_TOKEN = os.environ.get('GITHUB_TOKEN')
|
|
60
57
|
|
|
61
58
|
# Defaults HTTP and Proxy settings
|
|
62
59
|
DEFAULT_SOCKS5_PROXY = os.environ.get('SOCKS5_PROXY', "socks5://127.0.0.1:9050")
|
|
63
60
|
DEFAULT_HTTP_PROXY = os.environ.get('HTTP_PROXY', "https://127.0.0.1:9080")
|
|
64
|
-
DEFAULT_STORE_HTTP_RESPONSES = bool(int(os.environ.get('
|
|
61
|
+
DEFAULT_STORE_HTTP_RESPONSES = bool(int(os.environ.get('DEFAULT_STORE_HTTP_RESPONSES', 1)))
|
|
65
62
|
DEFAULT_PROXYCHAINS_COMMAND = "proxychains"
|
|
66
63
|
DEFAULT_FREEPROXY_TIMEOUT = 1 # seconds
|
|
67
64
|
|
|
@@ -74,11 +71,14 @@ DEFAULT_HTTPX_FLAGS = os.environ.get('DEFAULT_HTTPX_FLAGS', '-td')
|
|
|
74
71
|
DEFAULT_KATANA_FLAGS = os.environ.get('DEFAULT_KATANA_FLAGS', '-jc -js-crawl -known-files all -or -ob')
|
|
75
72
|
DEFAULT_NUCLEI_FLAGS = os.environ.get('DEFAULT_NUCLEI_FLAGS', '-stats -sj -si 20 -hm -or')
|
|
76
73
|
DEFAULT_FEROXBUSTER_FLAGS = os.environ.get('DEFAULT_FEROXBUSTER_FLAGS', '--auto-bail --no-state')
|
|
77
|
-
DEFAULT_PROGRESS_UPDATE_FREQUENCY =
|
|
74
|
+
DEFAULT_PROGRESS_UPDATE_FREQUENCY = int(os.environ.get('DEFAULT_PROGRESS_UPDATE_FREQUENCY', 60))
|
|
75
|
+
DEFAULT_SKIP_CVE_SEARCH = bool(int(os.environ.get('DEFAULT_SKIP_CVE_SEARCH', 0)))
|
|
78
76
|
|
|
79
77
|
# Default wordlists
|
|
80
|
-
DEFAULT_HTTP_WORDLIST = os.environ.get('DEFAULT_HTTP_WORDLIST', f'{WORDLISTS_FOLDER}/
|
|
81
|
-
|
|
78
|
+
DEFAULT_HTTP_WORDLIST = os.environ.get('DEFAULT_HTTP_WORDLIST', f'{WORDLISTS_FOLDER}/fuzz-Bo0oM.txt')
|
|
79
|
+
DEFAULT_HTTP_WORDLIST_URL = 'https://raw.githubusercontent.com/Bo0oM/fuzz.txt/master/fuzz.txt'
|
|
80
|
+
DEFAULT_DNS_WORDLIST = os.environ.get('DEFAULT_DNS_WORDLIST', f'{WORDLISTS_FOLDER}/combined_subdomains.txt')
|
|
81
|
+
DEFAULT_DNS_WORDLIST_URL = 'https://raw.githubusercontent.com/danielmiessler/SecLists/master/Discovery/DNS/combined_subdomains.txt' # noqa: E501
|
|
82
82
|
|
|
83
83
|
# Constants
|
|
84
84
|
OPT_NOT_SUPPORTED = -1
|
|
@@ -87,7 +87,6 @@ OPT_PIPE_INPUT = -1
|
|
|
87
87
|
# Vocab
|
|
88
88
|
ALIVE = 'alive'
|
|
89
89
|
AUTO_CALIBRATION = 'auto_calibration'
|
|
90
|
-
COOKIES = 'cookies'
|
|
91
90
|
CONTENT_TYPE = 'content_type'
|
|
92
91
|
CONTENT_LENGTH = 'content_length'
|
|
93
92
|
CIDR_RANGE = 'cidr_range'
|
|
@@ -98,7 +97,6 @@ DOMAIN = 'domain'
|
|
|
98
97
|
DEPTH = 'depth'
|
|
99
98
|
EXTRA_DATA = 'extra_data'
|
|
100
99
|
EMAIL = 'email'
|
|
101
|
-
FAILED_HTTP_STATUS = -1
|
|
102
100
|
FILTER_CODES = 'filter_codes'
|
|
103
101
|
FILTER_WORDS = 'filter_words'
|
|
104
102
|
FOLLOW_REDIRECT = 'follow_redirect'
|
|
@@ -106,9 +104,7 @@ FILTER_REGEX = 'filter_regex'
|
|
|
106
104
|
FILTER_SIZE = 'filter_size'
|
|
107
105
|
HEADER = 'header'
|
|
108
106
|
HOST = 'host'
|
|
109
|
-
INPUT = 'input'
|
|
110
107
|
IP = 'ip'
|
|
111
|
-
JSON = 'json'
|
|
112
108
|
LINES = 'lines'
|
|
113
109
|
METHOD = 'method'
|
|
114
110
|
MATCH_CODES = 'match_codes'
|
|
@@ -117,13 +113,10 @@ MATCH_SIZE = 'match_size'
|
|
|
117
113
|
MATCH_WORDS = 'match_words'
|
|
118
114
|
OUTPUT_PATH = 'output_path'
|
|
119
115
|
PATH = 'path'
|
|
120
|
-
PAYLOAD = 'payload'
|
|
121
116
|
PERCENT = 'percent'
|
|
122
|
-
PROBE = 'probe'
|
|
123
117
|
PORTS = 'ports'
|
|
124
118
|
PORT = 'port'
|
|
125
119
|
PROXY = 'proxy'
|
|
126
|
-
QUIET = 'quiet'
|
|
127
120
|
RATE_LIMIT = 'rate_limit'
|
|
128
121
|
RETRIES = 'retries'
|
|
129
122
|
TAGS = 'tags'
|
|
@@ -135,19 +128,16 @@ TYPE = 'type'
|
|
|
135
128
|
URL = 'url'
|
|
136
129
|
USER_AGENT = 'user_agent'
|
|
137
130
|
USERNAME = 'username'
|
|
138
|
-
SCREENSHOT_PATH = 'screenshot_path'
|
|
139
131
|
STORED_RESPONSE_PATH = 'stored_response_path'
|
|
140
132
|
SCRIPT = 'script'
|
|
141
133
|
SERVICE_NAME = 'service_name'
|
|
142
134
|
SOURCES = 'sources'
|
|
143
135
|
STATE = 'state'
|
|
144
136
|
STATUS_CODE = 'status_code'
|
|
145
|
-
SUBDOMAIN = 'subdomain'
|
|
146
137
|
TECH = 'tech'
|
|
147
138
|
TITLE = 'title'
|
|
148
139
|
SITE_NAME = 'site_name'
|
|
149
140
|
SERVICE_NAME = 'service_name'
|
|
150
|
-
VULN = 'vulnerability'
|
|
151
141
|
CONFIDENCE = 'confidence'
|
|
152
142
|
CVSS_SCORE = 'cvss_score'
|
|
153
143
|
DESCRIPTION = 'description'
|
|
@@ -159,7 +149,88 @@ REFERENCE = 'reference'
|
|
|
159
149
|
REFERENCES = 'references'
|
|
160
150
|
SEVERITY = 'severity'
|
|
161
151
|
TAGS = 'tags'
|
|
162
|
-
VULN_TYPE = 'type'
|
|
163
152
|
WEBSERVER = 'webserver'
|
|
164
153
|
WORDLIST = 'wordlist'
|
|
165
154
|
WORDS = 'words'
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# Create all folders
|
|
158
|
+
for folder in [BIN_FOLDER, DATA_FOLDER, REPORTS_FOLDER, WORDLISTS_FOLDER, CVES_FOLDER, PAYLOADS_FOLDER,
|
|
159
|
+
REVSHELLS_FOLDER, CELERY_DATA_FOLDER, CELERY_RESULTS_FOLDER]:
|
|
160
|
+
if not os.path.exists(folder):
|
|
161
|
+
console.print(f'[bold turquoise4]Creating folder {folder} ...[/] ', end='')
|
|
162
|
+
os.makedirs(folder)
|
|
163
|
+
console.print('[bold green]ok.[/]')
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# Download default wordlists
|
|
167
|
+
for wordlist in ['HTTP', 'DNS']:
|
|
168
|
+
wordlist_path = globals()[f'DEFAULT_{wordlist}_WORDLIST']
|
|
169
|
+
wordlist_url = globals()[f'DEFAULT_{wordlist}_WORDLIST_URL']
|
|
170
|
+
if not os.path.exists(wordlist_path):
|
|
171
|
+
try:
|
|
172
|
+
console.print(f'[bold turquoise4]Downloading default {wordlist} wordlist {wordlist_path} ...[/] ', end='')
|
|
173
|
+
resp = requests.get(wordlist_url)
|
|
174
|
+
with open(wordlist_path, 'w') as f:
|
|
175
|
+
f.write(resp.text)
|
|
176
|
+
console.print('[bold green]ok.[/]')
|
|
177
|
+
except requests.exceptions.RequestException as e:
|
|
178
|
+
console.print(f'[bold green]failed ({type(e).__name__}).[/]')
|
|
179
|
+
pass
|
|
180
|
+
|
|
181
|
+
ADDONS_ENABLED = {}
|
|
182
|
+
|
|
183
|
+
# Check worker addon
|
|
184
|
+
try:
|
|
185
|
+
import eventlet # noqa: F401
|
|
186
|
+
ADDONS_ENABLED['worker'] = True
|
|
187
|
+
except ModuleNotFoundError:
|
|
188
|
+
ADDONS_ENABLED['worker'] = False
|
|
189
|
+
|
|
190
|
+
# Check google addon
|
|
191
|
+
try:
|
|
192
|
+
import gspread # noqa: F401
|
|
193
|
+
ADDONS_ENABLED['google'] = True
|
|
194
|
+
except ModuleNotFoundError:
|
|
195
|
+
ADDONS_ENABLED['google'] = False
|
|
196
|
+
|
|
197
|
+
# Check mongodb addon
|
|
198
|
+
try:
|
|
199
|
+
import pymongo # noqa: F401
|
|
200
|
+
ADDONS_ENABLED['mongodb'] = True
|
|
201
|
+
except ModuleNotFoundError:
|
|
202
|
+
ADDONS_ENABLED['mongodb'] = False
|
|
203
|
+
|
|
204
|
+
# Check redis addon
|
|
205
|
+
try:
|
|
206
|
+
import redis # noqa: F401
|
|
207
|
+
ADDONS_ENABLED['redis'] = True
|
|
208
|
+
except ModuleNotFoundError:
|
|
209
|
+
ADDONS_ENABLED['redis'] = False
|
|
210
|
+
|
|
211
|
+
# Check dev addon
|
|
212
|
+
try:
|
|
213
|
+
import flake8 # noqa: F401
|
|
214
|
+
ADDONS_ENABLED['dev'] = True
|
|
215
|
+
except ModuleNotFoundError:
|
|
216
|
+
ADDONS_ENABLED['dev'] = False
|
|
217
|
+
|
|
218
|
+
# Check build addon
|
|
219
|
+
try:
|
|
220
|
+
import hatch # noqa: F401
|
|
221
|
+
ADDONS_ENABLED['build'] = True
|
|
222
|
+
except ModuleNotFoundError:
|
|
223
|
+
ADDONS_ENABLED['build'] = False
|
|
224
|
+
|
|
225
|
+
# Check trace addon
|
|
226
|
+
try:
|
|
227
|
+
import memray # noqa: F401
|
|
228
|
+
ADDONS_ENABLED['trace'] = True
|
|
229
|
+
except ModuleNotFoundError:
|
|
230
|
+
ADDONS_ENABLED['trace'] = False
|
|
231
|
+
|
|
232
|
+
# Check dev package
|
|
233
|
+
if os.path.exists(f'{ROOT_FOLDER}/pyproject.toml'):
|
|
234
|
+
DEV_PACKAGE = True
|
|
235
|
+
else:
|
|
236
|
+
DEV_PACKAGE = False
|
secator/exporters/csv.py
CHANGED
|
@@ -6,7 +6,6 @@ from secator.rich import console
|
|
|
6
6
|
|
|
7
7
|
class CsvExporter(Exporter):
|
|
8
8
|
def send(self):
|
|
9
|
-
title = self.report.data['info']['title']
|
|
10
9
|
results = self.report.data['results']
|
|
11
10
|
csv_paths = []
|
|
12
11
|
|
|
@@ -15,7 +14,7 @@ class CsvExporter(Exporter):
|
|
|
15
14
|
if not items:
|
|
16
15
|
continue
|
|
17
16
|
keys = list(items[0].keys())
|
|
18
|
-
csv_path = f'{self.report.output_folder}/{
|
|
17
|
+
csv_path = f'{self.report.output_folder}/report_{output_type}.csv'
|
|
19
18
|
csv_paths.append(csv_path)
|
|
20
19
|
with open(csv_path, 'w', newline='') as output_file:
|
|
21
20
|
dict_writer = _csv.DictWriter(output_file, keys)
|
secator/exporters/gdrive.py
CHANGED
|
@@ -55,7 +55,7 @@ class GdriveExporter(Exporter):
|
|
|
55
55
|
k.replace('_', ' ').upper()
|
|
56
56
|
for k in list(items[0].keys())
|
|
57
57
|
]
|
|
58
|
-
csv_path = f'{self.report.output_folder}/{
|
|
58
|
+
csv_path = f'{self.report.output_folder}/report_{output_type}.csv'
|
|
59
59
|
if not os.path.exists(csv_path):
|
|
60
60
|
console.print(
|
|
61
61
|
f'Unable to find CSV at {csv_path}. For Google sheets reports, please enable CSV reports as well.')
|
secator/exporters/json.py
CHANGED
|
@@ -5,8 +5,7 @@ from secator.serializers.dataclass import dumps_dataclass
|
|
|
5
5
|
|
|
6
6
|
class JsonExporter(Exporter):
|
|
7
7
|
def send(self):
|
|
8
|
-
|
|
9
|
-
json_path = f'{self.report.output_folder}/{title}_{self.report.timestamp}.json'
|
|
8
|
+
json_path = f'{self.report.output_folder}/report.json'
|
|
10
9
|
|
|
11
10
|
# Save JSON report to file
|
|
12
11
|
with open(json_path, 'w') as f:
|
secator/exporters/txt.py
CHANGED
|
@@ -4,7 +4,6 @@ from secator.rich import console
|
|
|
4
4
|
|
|
5
5
|
class TxtExporter(Exporter):
|
|
6
6
|
def send(self):
|
|
7
|
-
title = self.report.data['info']['title']
|
|
8
7
|
results = self.report.data['results']
|
|
9
8
|
txt_paths = []
|
|
10
9
|
|
|
@@ -12,7 +11,7 @@ class TxtExporter(Exporter):
|
|
|
12
11
|
items = [str(i) for i in items]
|
|
13
12
|
if not items:
|
|
14
13
|
continue
|
|
15
|
-
txt_path = f'{self.report.output_folder}/{
|
|
14
|
+
txt_path = f'{self.report.output_folder}/report_{output_type}.txt'
|
|
16
15
|
with open(txt_path, 'w') as f:
|
|
17
16
|
f.write('\n'.join(items))
|
|
18
17
|
txt_paths.append(txt_path)
|
secator/hooks/mongodb.py
CHANGED
|
@@ -1,40 +1,43 @@
|
|
|
1
|
-
from bson.objectid import ObjectId
|
|
2
|
-
import os
|
|
3
1
|
import logging
|
|
2
|
+
import os
|
|
4
3
|
import time
|
|
5
4
|
|
|
5
|
+
import pymongo
|
|
6
|
+
from bson.objectid import ObjectId
|
|
6
7
|
from celery import shared_task
|
|
7
8
|
|
|
8
9
|
from secator.definitions import DEFAULT_PROGRESS_UPDATE_FREQUENCY
|
|
9
|
-
from secator.runners import Task, Workflow, Scan
|
|
10
10
|
from secator.output_types import OUTPUT_TYPES
|
|
11
|
-
from secator.
|
|
11
|
+
from secator.runners import Scan, Task, Workflow
|
|
12
|
+
from secator.utils import debug, escape_mongodb_url
|
|
12
13
|
|
|
13
|
-
import pymongo
|
|
14
14
|
# import gevent.monkey
|
|
15
15
|
# gevent.monkey.patch_all()
|
|
16
16
|
|
|
17
17
|
MONGODB_URL = os.environ.get('MONGODB_URL', 'mongodb://localhost')
|
|
18
18
|
MONGODB_UPDATE_FREQUENCY = int(os.environ.get('MONGODB_UPDATE_FREQUENCY', DEFAULT_PROGRESS_UPDATE_FREQUENCY))
|
|
19
19
|
MAX_POOL_SIZE = 100
|
|
20
|
-
client = pymongo.MongoClient(MONGODB_URL, maxPoolSize=MAX_POOL_SIZE)
|
|
21
20
|
|
|
22
21
|
logger = logging.getLogger(__name__)
|
|
23
22
|
|
|
23
|
+
client = pymongo.MongoClient(escape_mongodb_url(MONGODB_URL), maxPoolSize=MAX_POOL_SIZE)
|
|
24
|
+
|
|
24
25
|
|
|
25
26
|
def update_runner(self):
|
|
26
27
|
db = client.main
|
|
27
28
|
type = self.config.type
|
|
28
29
|
collection = f'{type}s'
|
|
29
30
|
update = self.toDict()
|
|
31
|
+
debug_obj = {'type': 'runner', 'name': self.name, 'status': self.status}
|
|
30
32
|
chunk = update.get('chunk')
|
|
31
33
|
_id = self.context.get(f'{type}_chunk_id') if chunk else self.context.get(f'{type}_id')
|
|
34
|
+
debug('update', sub='hooks.mongodb', id=_id, obj=update, obj_after=True, level=4)
|
|
32
35
|
start_time = time.time()
|
|
33
36
|
if _id:
|
|
34
37
|
delta = start_time - self.last_updated if self.last_updated else MONGODB_UPDATE_FREQUENCY
|
|
35
38
|
if self.last_updated and delta < MONGODB_UPDATE_FREQUENCY and self.status == 'RUNNING':
|
|
36
39
|
debug(f'skipped ({delta:>.2f}s < {MONGODB_UPDATE_FREQUENCY}s)',
|
|
37
|
-
|
|
40
|
+
sub='hooks.mongodb', id=_id, obj=debug_obj, obj_after=False, level=3)
|
|
38
41
|
return
|
|
39
42
|
db = client.main
|
|
40
43
|
start_time = time.time()
|
|
@@ -42,8 +45,7 @@ def update_runner(self):
|
|
|
42
45
|
end_time = time.time()
|
|
43
46
|
elapsed = end_time - start_time
|
|
44
47
|
debug(
|
|
45
|
-
f'[dim gold4]updated in {elapsed:.4f}s[/]',
|
|
46
|
-
sub='hooks.mongodb', id=_id, obj={self.name: self.status}, obj_after=False, level=2)
|
|
48
|
+
f'[dim gold4]updated in {elapsed:.4f}s[/]', sub='hooks.mongodb', id=_id, obj=debug_obj, obj_after=False, level=2)
|
|
47
49
|
self.last_updated = start_time
|
|
48
50
|
else: # sync update and save result to runner object
|
|
49
51
|
runner = db[collection].insert_one(update)
|
|
@@ -54,9 +56,7 @@ def update_runner(self):
|
|
|
54
56
|
self.context[f'{type}_id'] = _id
|
|
55
57
|
end_time = time.time()
|
|
56
58
|
elapsed = end_time - start_time
|
|
57
|
-
debug(
|
|
58
|
-
f'created in {elapsed:.4f}s',
|
|
59
|
-
sub='hooks.mongodb', id=_id, obj={self.name: self.status}, obj_after=False, level=2)
|
|
59
|
+
debug(f'created in {elapsed:.4f}s', sub='hooks.mongodb', id=_id, obj=debug_obj, obj_after=False, level=2)
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
def update_finding(self, item):
|