secator 0.0.1__py3-none-any.whl → 0.3.6__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 +7 -67
- secator/cli.py +631 -274
- secator/decorators.py +54 -11
- 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 +105 -34
- secator/runners/_helpers.py +18 -17
- secator/runners/command.py +91 -55
- secator/runners/scan.py +2 -1
- secator/runners/task.py +5 -4
- secator/runners/workflow.py +12 -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 +3 -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.6.dist-info/METADATA +411 -0
- secator-0.3.6.dist-info/RECORD +100 -0
- {secator-0.0.1.dist-info → secator-0.3.6.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.6.dist-info}/entry_points.txt +0 -0
- {secator-0.0.1.dist-info → secator-0.3.6.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)
|
|
@@ -24,7 +24,6 @@ RUNNER_OPTS = {
|
|
|
24
24
|
|
|
25
25
|
RUNNER_GLOBAL_OPTS = {
|
|
26
26
|
'sync': {'is_flag': True, 'help': 'Run tasks synchronously (automatic if no worker is alive)'},
|
|
27
|
-
'worker': {'is_flag': True, 'help': 'Run tasks in worker (automatic if worker is alive)'},
|
|
28
27
|
'proxy': {'type': str, 'help': 'HTTP proxy'},
|
|
29
28
|
'driver': {'type': str, 'help': 'Export real-time results. E.g: "mongodb"'}
|
|
30
29
|
# 'debug': {'type': int, 'default': 0, 'help': 'Debug mode'},
|
|
@@ -38,18 +37,47 @@ class OrderedGroup(RichGroup):
|
|
|
38
37
|
super(OrderedGroup, self).__init__(name, commands, **attrs)
|
|
39
38
|
self.commands = commands or OrderedDict()
|
|
40
39
|
|
|
40
|
+
def command(self, *args, **kwargs):
|
|
41
|
+
"""Behaves the same as `click.Group.command()` but supports aliases.
|
|
42
|
+
"""
|
|
43
|
+
def decorator(f):
|
|
44
|
+
aliases = kwargs.pop("aliases", None)
|
|
45
|
+
if aliases:
|
|
46
|
+
max_width = _get_rich_console().width
|
|
47
|
+
aliases_str = ', '.join(f'[bold cyan]{alias}[/]' for alias in aliases)
|
|
48
|
+
padding = max_width // 4
|
|
49
|
+
|
|
50
|
+
name = kwargs.pop("name", None)
|
|
51
|
+
if not name:
|
|
52
|
+
raise click.UsageError("`name` command argument is required when using aliases.")
|
|
53
|
+
|
|
54
|
+
f.__doc__ = f.__doc__ or '\0'.ljust(padding+1)
|
|
55
|
+
f.__doc__ = f'{f.__doc__:<{padding}}[dim](aliases)[/] {aliases_str}'
|
|
56
|
+
base_command = super(OrderedGroup, self).command(
|
|
57
|
+
name, *args, **kwargs
|
|
58
|
+
)(f)
|
|
59
|
+
for alias in aliases:
|
|
60
|
+
cmd = super(OrderedGroup, self).command(alias, *args, hidden=True, **kwargs)(f)
|
|
61
|
+
cmd.help = f"Alias for '{name}'.\n\n{cmd.help}"
|
|
62
|
+
cmd.params = base_command.params
|
|
63
|
+
|
|
64
|
+
else:
|
|
65
|
+
cmd = super(OrderedGroup, self).command(*args, **kwargs)(f)
|
|
66
|
+
|
|
67
|
+
return cmd
|
|
68
|
+
return decorator
|
|
69
|
+
|
|
41
70
|
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.
|
|
71
|
+
"""Behaves the same as `click.Group.group()` but supports aliases.
|
|
44
72
|
"""
|
|
45
73
|
def decorator(f):
|
|
46
74
|
aliases = kwargs.pop('aliases', [])
|
|
47
75
|
aliased_group = []
|
|
48
76
|
if aliases:
|
|
49
77
|
max_width = _get_rich_console().width
|
|
50
|
-
# we have a list so create group aliases
|
|
51
78
|
aliases_str = ', '.join(f'[bold cyan]{alias}[/]' for alias in aliases)
|
|
52
79
|
padding = max_width // 4
|
|
80
|
+
f.__doc__ = f.__doc__ or '\0'.ljust(padding+1)
|
|
53
81
|
f.__doc__ = f'{f.__doc__:<{padding}}[dim](aliases)[/] {aliases_str}'
|
|
54
82
|
for alias in aliases:
|
|
55
83
|
grp = super(OrderedGroup, self).group(
|
|
@@ -143,6 +171,7 @@ def decorate_command_options(opts):
|
|
|
143
171
|
for opt_name, opt_conf in reversed_opts.items():
|
|
144
172
|
conf = opt_conf.copy()
|
|
145
173
|
short = conf.pop('short', None)
|
|
174
|
+
conf.pop('internal', False)
|
|
146
175
|
conf.pop('prefix', None)
|
|
147
176
|
long = f'--{opt_name}'
|
|
148
177
|
short = f'-{short}' if short else f'-{opt_name}'
|
|
@@ -185,6 +214,7 @@ def register_runner(cli_endpoint, config):
|
|
|
185
214
|
short_help += f' [dim]alias: {config.alias}'
|
|
186
215
|
fmt_opts['print_start'] = True
|
|
187
216
|
fmt_opts['print_run_summary'] = True
|
|
217
|
+
fmt_opts['print_progress'] = False
|
|
188
218
|
runner_cls = Scan
|
|
189
219
|
|
|
190
220
|
elif cli_endpoint.name == 'workflow':
|
|
@@ -199,6 +229,7 @@ def register_runner(cli_endpoint, config):
|
|
|
199
229
|
short_help = f'{short_help:<55} [dim](alias)[/][bold cyan] {config.alias}'
|
|
200
230
|
fmt_opts['print_start'] = True
|
|
201
231
|
fmt_opts['print_run_summary'] = True
|
|
232
|
+
fmt_opts['print_progress'] = False
|
|
202
233
|
runner_cls = Workflow
|
|
203
234
|
|
|
204
235
|
elif cli_endpoint.name == 'task':
|
|
@@ -232,7 +263,6 @@ def register_runner(cli_endpoint, config):
|
|
|
232
263
|
def func(ctx, **opts):
|
|
233
264
|
opts.update(fmt_opts)
|
|
234
265
|
sync = opts['sync']
|
|
235
|
-
worker = opts['worker']
|
|
236
266
|
# debug = opts['debug']
|
|
237
267
|
ws = opts.pop('workspace')
|
|
238
268
|
driver = opts.pop('driver', '')
|
|
@@ -245,10 +275,20 @@ def register_runner(cli_endpoint, config):
|
|
|
245
275
|
targets = expand_input(targets)
|
|
246
276
|
if sync or show:
|
|
247
277
|
sync = True
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
278
|
+
else:
|
|
279
|
+
from secator.celery import is_celery_worker_alive
|
|
280
|
+
worker_alive = is_celery_worker_alive()
|
|
281
|
+
if not worker_alive:
|
|
282
|
+
sync = True
|
|
283
|
+
else:
|
|
284
|
+
sync = False
|
|
285
|
+
from secator.definitions import CELERY_BROKER_URL, CELERY_RESULT_BACKEND
|
|
286
|
+
broker_protocol = CELERY_BROKER_URL.split('://')[0]
|
|
287
|
+
backend_protocol = CELERY_RESULT_BACKEND.split('://')[0]
|
|
288
|
+
if CELERY_BROKER_URL:
|
|
289
|
+
if (broker_protocol == 'redis' or backend_protocol == 'redis') and not ADDONS_ENABLED['redis']:
|
|
290
|
+
_get_rich_console().print('[bold red]Missing `redis` addon: please run `secator install addons redis`[/].')
|
|
291
|
+
sys.exit(1)
|
|
252
292
|
opts['sync'] = sync
|
|
253
293
|
opts.update({
|
|
254
294
|
'print_item': not sync,
|
|
@@ -260,6 +300,9 @@ def register_runner(cli_endpoint, config):
|
|
|
260
300
|
# Build hooks from driver name
|
|
261
301
|
hooks = {}
|
|
262
302
|
if driver == 'mongodb':
|
|
303
|
+
if not ADDONS_ENABLED['mongodb']:
|
|
304
|
+
_get_rich_console().print('[bold red]Missing `mongodb` addon: please run `secator install addons mongodb`[/].')
|
|
305
|
+
sys.exit(1)
|
|
263
306
|
from secator.hooks.mongodb import MONGODB_HOOKS
|
|
264
307
|
hooks = MONGODB_HOOKS
|
|
265
308
|
|
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):
|