secator 0.1.1__py2.py3-none-any.whl → 0.3.0__py2.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 +441 -334
- secator/decorators.py +2 -2
- secator/definitions.py +64 -16
- secator/exporters/txt.py +1 -1
- secator/installer.py +192 -0
- secator/runners/command.py +27 -27
- secator/tasks/dalfox.py +1 -0
- secator/tasks/dnsx.py +1 -0
- secator/tasks/dnsxbrute.py +1 -0
- secator/tasks/feroxbuster.py +1 -0
- secator/tasks/ffuf.py +2 -2
- secator/tasks/gau.py +1 -0
- secator/tasks/gospider.py +1 -0
- secator/tasks/grype.py +1 -0
- secator/tasks/httpx.py +1 -0
- secator/tasks/katana.py +2 -1
- secator/tasks/mapcidr.py +1 -0
- secator/tasks/msfconsole.py +1 -1
- secator/tasks/naabu.py +2 -1
- secator/tasks/nuclei.py +1 -0
- secator/tasks/subfinder.py +1 -0
- secator/tasks/wpscan.py +1 -1
- secator/utils_test.py +1 -0
- {secator-0.1.1.dist-info → secator-0.3.0.dist-info}/METADATA +23 -1
- {secator-0.1.1.dist-info → secator-0.3.0.dist-info}/RECORD +28 -27
- {secator-0.1.1.dist-info → secator-0.3.0.dist-info}/WHEEL +0 -0
- {secator-0.1.1.dist-info → secator-0.3.0.dist-info}/entry_points.txt +0 -0
- {secator-0.1.1.dist-info → secator-0.3.0.dist-info}/licenses/LICENSE +0 -0
secator/decorators.py
CHANGED
|
@@ -53,7 +53,7 @@ class OrderedGroup(RichGroup):
|
|
|
53
53
|
if not name:
|
|
54
54
|
raise click.UsageError("`name` command argument is required when using aliases.")
|
|
55
55
|
|
|
56
|
-
f.__doc__ = f.__doc__ or '
|
|
56
|
+
f.__doc__ = f.__doc__ or '\0'.ljust(padding+1)
|
|
57
57
|
f.__doc__ = f'{f.__doc__:<{padding}}[dim](aliases)[/] {aliases_str}'
|
|
58
58
|
base_command = super(OrderedGroup, self).command(
|
|
59
59
|
name, *args, **kwargs
|
|
@@ -79,7 +79,7 @@ class OrderedGroup(RichGroup):
|
|
|
79
79
|
max_width = _get_rich_console().width
|
|
80
80
|
aliases_str = ', '.join(f'[bold cyan]{alias}[/]' for alias in aliases)
|
|
81
81
|
padding = max_width // 4
|
|
82
|
-
f.__doc__ = f.__doc__ or '
|
|
82
|
+
f.__doc__ = f.__doc__ or '\0'.ljust(padding+1)
|
|
83
83
|
f.__doc__ = f'{f.__doc__:<{padding}}[dim](aliases)[/] {aliases_str}'
|
|
84
84
|
for alias in aliases:
|
|
85
85
|
grp = super(OrderedGroup, self).group(
|
secator/definitions.py
CHANGED
|
@@ -1,22 +1,39 @@
|
|
|
1
1
|
#!/usr/bin/python
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
+
import requests
|
|
4
5
|
|
|
5
6
|
from dotenv import find_dotenv, load_dotenv
|
|
6
|
-
from pkg_resources import get_distribution
|
|
7
|
+
from pkg_resources import get_distribution, parse_version
|
|
7
8
|
|
|
8
9
|
load_dotenv(find_dotenv(usecwd=True), override=False)
|
|
9
10
|
|
|
11
|
+
|
|
12
|
+
def get_latest_version():
|
|
13
|
+
"""Get latest secator version from GitHub API."""
|
|
14
|
+
try:
|
|
15
|
+
resp = requests.get('https://api.github.com/repos/freelabz/secator/releases/latest', timeout=2)
|
|
16
|
+
resp.raise_for_status()
|
|
17
|
+
latest_version = resp.json()['name'].lstrip('v')
|
|
18
|
+
return latest_version
|
|
19
|
+
except (requests.exceptions.RequestException):
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
|
|
10
23
|
# Globals
|
|
11
24
|
VERSION = get_distribution('secator').version
|
|
25
|
+
VERSION_LATEST = get_latest_version()
|
|
26
|
+
VERSION_OBSOLETE = parse_version(VERSION_LATEST) > parse_version(VERSION) if VERSION_LATEST else False
|
|
27
|
+
VERSION_STR = f'{VERSION} [bold red](outdated)[/]' if VERSION_OBSOLETE else VERSION
|
|
28
|
+
|
|
12
29
|
ASCII = f"""
|
|
13
|
-
|
|
30
|
+
__
|
|
14
31
|
________ _________ _/ /_____ _____
|
|
15
32
|
/ ___/ _ \/ ___/ __ `/ __/ __ \/ ___/
|
|
16
33
|
(__ / __/ /__/ /_/ / /_/ /_/ / /
|
|
17
|
-
/____/\___/\___/\__,_/\__/\____/_/ v{
|
|
34
|
+
/____/\___/\___/\__,_/\__/\____/_/ v{VERSION_STR}
|
|
18
35
|
|
|
19
|
-
|
|
36
|
+
freelabz.com
|
|
20
37
|
""" # noqa: W605,W291
|
|
21
38
|
|
|
22
39
|
# Secator folders
|
|
@@ -24,6 +41,7 @@ ROOT_FOLDER = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
|
|
24
41
|
LIB_FOLDER = ROOT_FOLDER + '/secator'
|
|
25
42
|
CONFIGS_FOLDER = LIB_FOLDER + '/configs'
|
|
26
43
|
EXTRA_CONFIGS_FOLDER = os.environ.get('SECATOR_EXTRA_CONFIGS_FOLDER')
|
|
44
|
+
BIN_FOLDER = os.environ.get('SECATOR_BIN_FOLDER', f'{os.path.expanduser("~")}/.local/bin')
|
|
27
45
|
DATA_FOLDER = os.environ.get('SECATOR_DATA_FOLDER', f'{os.path.expanduser("~")}/.secator')
|
|
28
46
|
REPORTS_FOLDER = os.environ.get('SECATOR_REPORTS_FOLDER', f'{DATA_FOLDER}/reports')
|
|
29
47
|
WORDLISTS_FOLDER = os.environ.get('SECATOR_WORDLISTS_FOLDER', f'{DATA_FOLDER}/wordlists')
|
|
@@ -32,6 +50,7 @@ CVES_FOLDER = f'{DATA_FOLDER}/cves'
|
|
|
32
50
|
PAYLOADS_FOLDER = f'{DATA_FOLDER}/payloads'
|
|
33
51
|
REVSHELLS_FOLDER = f'{DATA_FOLDER}/revshells'
|
|
34
52
|
TESTS_FOLDER = f'{ROOT_FOLDER}/tests'
|
|
53
|
+
os.makedirs(BIN_FOLDER, exist_ok=True)
|
|
35
54
|
os.makedirs(DATA_FOLDER, exist_ok=True)
|
|
36
55
|
os.makedirs(REPORTS_FOLDER, exist_ok=True)
|
|
37
56
|
os.makedirs(WORDLISTS_FOLDER, exist_ok=True)
|
|
@@ -58,6 +77,7 @@ CELERY_BROKER_VISIBILITY_TIMEOUT = int(os.environ.get('CELERY_BROKER_VISIBILITY_
|
|
|
58
77
|
CELERY_OVERRIDE_DEFAULT_LOGGING = bool(int(os.environ.get('CELERY_OVERRIDE_DEFAULT_LOGGING', 1)))
|
|
59
78
|
GOOGLE_DRIVE_PARENT_FOLDER_ID = os.environ.get('GOOGLE_DRIVE_PARENT_FOLDER_ID')
|
|
60
79
|
GOOGLE_CREDENTIALS_PATH = os.environ.get('GOOGLE_CREDENTIALS_PATH')
|
|
80
|
+
GITHUB_TOKEN = os.environ.get('GITHUB_TOKEN')
|
|
61
81
|
|
|
62
82
|
# Defaults HTTP and Proxy settings
|
|
63
83
|
DEFAULT_SOCKS5_PROXY = os.environ.get('SOCKS5_PROXY', "socks5://127.0.0.1:9050")
|
|
@@ -157,27 +177,55 @@ WORDS = 'words'
|
|
|
157
177
|
|
|
158
178
|
# Check worker addon
|
|
159
179
|
try:
|
|
160
|
-
|
|
161
|
-
|
|
180
|
+
import eventlet # noqa: F401
|
|
181
|
+
WORKER_ADDON_ENABLED = 1
|
|
162
182
|
except ModuleNotFoundError:
|
|
163
|
-
|
|
183
|
+
WORKER_ADDON_ENABLED = 0
|
|
184
|
+
|
|
185
|
+
# Check google addon
|
|
186
|
+
try:
|
|
187
|
+
import gspread # noqa: F401
|
|
188
|
+
GOOGLE_ADDON_ENABLED = 1
|
|
189
|
+
except ModuleNotFoundError:
|
|
190
|
+
GOOGLE_ADDON_ENABLED = 0
|
|
164
191
|
|
|
165
192
|
# Check mongodb addon
|
|
166
193
|
try:
|
|
167
|
-
|
|
168
|
-
|
|
194
|
+
import pymongo # noqa: F401
|
|
195
|
+
MONGODB_ADDON_ENABLED = 1
|
|
196
|
+
except ModuleNotFoundError:
|
|
197
|
+
MONGODB_ADDON_ENABLED = 0
|
|
198
|
+
|
|
199
|
+
# Check redis addon
|
|
200
|
+
try:
|
|
201
|
+
import redis # noqa: F401
|
|
202
|
+
REDIS_ADDON_ENABLED = 1
|
|
169
203
|
except ModuleNotFoundError:
|
|
170
|
-
|
|
204
|
+
REDIS_ADDON_ENABLED = 0
|
|
171
205
|
|
|
172
206
|
# Check dev addon
|
|
173
207
|
try:
|
|
174
|
-
|
|
175
|
-
|
|
208
|
+
import flake8 # noqa: F401
|
|
209
|
+
DEV_ADDON_ENABLED = 1
|
|
210
|
+
except ModuleNotFoundError:
|
|
211
|
+
DEV_ADDON_ENABLED = 0
|
|
212
|
+
|
|
213
|
+
# Check build addon
|
|
214
|
+
try:
|
|
215
|
+
import hatch # noqa: F401
|
|
216
|
+
BUILD_ADDON_ENABLED = 1
|
|
217
|
+
except ModuleNotFoundError:
|
|
218
|
+
BUILD_ADDON_ENABLED = 0
|
|
219
|
+
|
|
220
|
+
# Check trace addon
|
|
221
|
+
try:
|
|
222
|
+
import memray # noqa: F401
|
|
223
|
+
TRACE_ADDON_ENABLED = 1
|
|
176
224
|
except ModuleNotFoundError:
|
|
177
|
-
|
|
225
|
+
TRACE_ADDON_ENABLED = 0
|
|
178
226
|
|
|
179
227
|
# Check dev package
|
|
180
|
-
if
|
|
181
|
-
|
|
228
|
+
if os.path.exists(f'{ROOT_FOLDER}/pyproject.toml'):
|
|
229
|
+
DEV_PACKAGE = 1
|
|
182
230
|
else:
|
|
183
|
-
|
|
231
|
+
DEV_PACKAGE = 0
|
secator/exporters/txt.py
CHANGED
|
@@ -11,7 +11,7 @@ class TxtExporter(Exporter):
|
|
|
11
11
|
items = [str(i) for i in items]
|
|
12
12
|
if not items:
|
|
13
13
|
continue
|
|
14
|
-
txt_path = f'{self.report.output_folder}/
|
|
14
|
+
txt_path = f'{self.report.output_folder}/report_{output_type}.txt'
|
|
15
15
|
with open(txt_path, 'w') as f:
|
|
16
16
|
f.write('\n'.join(items))
|
|
17
17
|
txt_paths.append(txt_path)
|
secator/installer.py
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
|
|
2
|
+
import requests
|
|
3
|
+
import os
|
|
4
|
+
import platform
|
|
5
|
+
import shutil
|
|
6
|
+
import tarfile
|
|
7
|
+
import zipfile
|
|
8
|
+
import io
|
|
9
|
+
|
|
10
|
+
from secator.rich import console
|
|
11
|
+
from secator.runners import Command
|
|
12
|
+
from secator.definitions import BIN_FOLDER, GITHUB_TOKEN
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ToolInstaller:
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def install(cls, tool_cls):
|
|
19
|
+
"""Install a tool.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
cls: ToolInstaller class.
|
|
23
|
+
tool_cls: Tool class (derived from secator.runners.Command).
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
bool: True if install is successful, False otherwise.
|
|
27
|
+
"""
|
|
28
|
+
console.print(f'[bold gold3]:wrench: Installing {tool_cls.__name__}')
|
|
29
|
+
success = False
|
|
30
|
+
|
|
31
|
+
if not tool_cls.install_github_handle and not tool_cls.install_cmd:
|
|
32
|
+
console.print(
|
|
33
|
+
f'[bold red]{tool_cls.__name__} install is not supported yet. Please install it manually.[/]')
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
if tool_cls.install_github_handle:
|
|
37
|
+
success = GithubInstaller.install(tool_cls.install_github_handle)
|
|
38
|
+
|
|
39
|
+
if tool_cls.install_cmd and not success:
|
|
40
|
+
success = SourceInstaller.install(tool_cls.install_cmd)
|
|
41
|
+
|
|
42
|
+
if success:
|
|
43
|
+
console.print(
|
|
44
|
+
f'[bold green]:tada: {tool_cls.__name__} installed successfully[/] !')
|
|
45
|
+
else:
|
|
46
|
+
console.print(
|
|
47
|
+
f'[bold red]:exclamation_mark: Failed to install {tool_cls.__name__}.[/]')
|
|
48
|
+
return success
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class SourceInstaller:
|
|
52
|
+
"""Install a tool from source."""
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def install(cls, install_cmd):
|
|
56
|
+
"""Install from source.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
cls: ToolInstaller class.
|
|
60
|
+
install_cmd (str): Install command.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
bool: True if install is successful, False otherwise.
|
|
64
|
+
"""
|
|
65
|
+
ret = Command.execute(install_cmd, cls_attributes={'shell': True})
|
|
66
|
+
return ret.return_code == 0
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class GithubInstaller:
|
|
70
|
+
"""Install a tool from GitHub releases."""
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def install(cls, github_handle):
|
|
74
|
+
"""Find and install a release from a GitHub handle {user}/{repo}.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
github_handle (str): A GitHub handle {user}/{repo}
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
bool: True if install is successful,, False otherwise.
|
|
81
|
+
"""
|
|
82
|
+
owner, repo = tuple(github_handle.split('/'))
|
|
83
|
+
releases_url = f"https://api.github.com/repos/{owner}/{repo}/releases/latest"
|
|
84
|
+
|
|
85
|
+
# Query latest release endpoint
|
|
86
|
+
headers = {}
|
|
87
|
+
if GITHUB_TOKEN:
|
|
88
|
+
headers['Authorization'] = f'Bearer {GITHUB_TOKEN}'
|
|
89
|
+
response = requests.get(releases_url, headers=headers)
|
|
90
|
+
if response.status_code == 403:
|
|
91
|
+
console.print('[bold red]Rate-limited by GitHub API. Retry later or set a GITHUB_TOKEN.')
|
|
92
|
+
return False
|
|
93
|
+
elif response.status_code == 404:
|
|
94
|
+
console.print('[dim red]No GitHub releases found.')
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
# Find the right asset to download
|
|
98
|
+
latest_release = response.json()
|
|
99
|
+
os_identifiers, arch_identifiers = cls._get_platform_identifier()
|
|
100
|
+
download_url = cls._find_matching_asset(latest_release['assets'], os_identifiers, arch_identifiers)
|
|
101
|
+
if not download_url:
|
|
102
|
+
console.print('[dim red]Could not find a GitHub release matching distribution.[/]')
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
# Download and unpack asset
|
|
106
|
+
console.print(f'Found release URL: {download_url}')
|
|
107
|
+
cls._download_and_unpack(download_url, BIN_FOLDER, repo)
|
|
108
|
+
return True
|
|
109
|
+
|
|
110
|
+
@classmethod
|
|
111
|
+
def _get_platform_identifier(cls):
|
|
112
|
+
"""Generate lists of possible identifiers for the current platform."""
|
|
113
|
+
system = platform.system().lower()
|
|
114
|
+
arch = platform.machine().lower()
|
|
115
|
+
|
|
116
|
+
# Mapping common platform.system() values to those found in release names
|
|
117
|
+
os_mapping = {
|
|
118
|
+
'linux': ['linux'],
|
|
119
|
+
'windows': ['windows', 'win'],
|
|
120
|
+
'darwin': ['darwin', 'macos', 'osx', 'mac']
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
# Enhanced architecture mapping to avoid conflicts
|
|
124
|
+
arch_mapping = {
|
|
125
|
+
'x86_64': ['amd64', 'x86_64'],
|
|
126
|
+
'amd64': ['amd64', 'x86_64'],
|
|
127
|
+
'aarch64': ['arm64', 'aarch64'],
|
|
128
|
+
'armv7l': ['armv7', 'arm'],
|
|
129
|
+
'386': ['386', 'x86', 'i386'],
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
os_identifiers = os_mapping.get(system, [])
|
|
133
|
+
arch_identifiers = arch_mapping.get(arch, [])
|
|
134
|
+
return os_identifiers, arch_identifiers
|
|
135
|
+
|
|
136
|
+
@classmethod
|
|
137
|
+
def _find_matching_asset(cls, assets, os_identifiers, arch_identifiers):
|
|
138
|
+
"""Find a release asset matching the current platform more precisely."""
|
|
139
|
+
potential_matches = []
|
|
140
|
+
|
|
141
|
+
for asset in assets:
|
|
142
|
+
asset_name = asset['name'].lower()
|
|
143
|
+
if any(os_id in asset_name for os_id in os_identifiers) and \
|
|
144
|
+
any(arch_id in asset_name for arch_id in arch_identifiers):
|
|
145
|
+
potential_matches.append(asset['browser_download_url'])
|
|
146
|
+
|
|
147
|
+
# Preference ordering for file formats, if needed
|
|
148
|
+
preferred_formats = ['.tar.gz', '.zip']
|
|
149
|
+
|
|
150
|
+
for format in preferred_formats:
|
|
151
|
+
for match in potential_matches:
|
|
152
|
+
if match.endswith(format):
|
|
153
|
+
return match
|
|
154
|
+
|
|
155
|
+
if potential_matches:
|
|
156
|
+
return potential_matches[0]
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def _download_and_unpack(cls, url, destination, repo_name):
|
|
160
|
+
"""Download and unpack a release asset."""
|
|
161
|
+
console.print(f'Downloading and unpacking to {destination}...')
|
|
162
|
+
response = requests.get(url)
|
|
163
|
+
response.raise_for_status()
|
|
164
|
+
|
|
165
|
+
# Create a temporary directory to extract the archive
|
|
166
|
+
temp_dir = os.path.join("/tmp", repo_name)
|
|
167
|
+
os.makedirs(temp_dir, exist_ok=True)
|
|
168
|
+
|
|
169
|
+
if url.endswith('.zip'):
|
|
170
|
+
with zipfile.ZipFile(io.BytesIO(response.content)) as zip_ref:
|
|
171
|
+
zip_ref.extractall(temp_dir)
|
|
172
|
+
elif url.endswith('.tar.gz'):
|
|
173
|
+
with tarfile.open(fileobj=io.BytesIO(response.content), mode='r:gz') as tar:
|
|
174
|
+
tar.extractall(path=temp_dir)
|
|
175
|
+
|
|
176
|
+
# For archives, find and move the binary that matches the repo name
|
|
177
|
+
binary_path = cls._find_binary_in_directory(temp_dir, repo_name)
|
|
178
|
+
if binary_path:
|
|
179
|
+
os.chmod(binary_path, 0o755) # Make it executable
|
|
180
|
+
shutil.move(binary_path, os.path.join(destination, repo_name)) # Move the binary
|
|
181
|
+
else:
|
|
182
|
+
console.print('[bold red]Binary matching the repository name was not found in the archive.[/]')
|
|
183
|
+
|
|
184
|
+
@classmethod
|
|
185
|
+
def _find_binary_in_directory(cls, directory, binary_name):
|
|
186
|
+
"""Search for the binary in the given directory that matches the repository name."""
|
|
187
|
+
for root, _, files in os.walk(directory):
|
|
188
|
+
for file in files:
|
|
189
|
+
# Match the file name exactly with the repository name
|
|
190
|
+
if file == binary_name:
|
|
191
|
+
return os.path.join(root, file)
|
|
192
|
+
return None
|
secator/runners/command.py
CHANGED
|
@@ -16,7 +16,6 @@ from secator.definitions import (DEFAULT_HTTP_PROXY,
|
|
|
16
16
|
DEFAULT_PROXYCHAINS_COMMAND,
|
|
17
17
|
DEFAULT_SOCKS5_PROXY, OPT_NOT_SUPPORTED,
|
|
18
18
|
OPT_PIPE_INPUT, DEFAULT_INPUT_CHUNK_SIZE)
|
|
19
|
-
from secator.rich import console
|
|
20
19
|
from secator.runners import Runner
|
|
21
20
|
from secator.serializers import JSONSerializer
|
|
22
21
|
from secator.utils import debug
|
|
@@ -81,8 +80,9 @@ class Command(Runner):
|
|
|
81
80
|
# Flag to show version
|
|
82
81
|
version_flag = None
|
|
83
82
|
|
|
84
|
-
# Install
|
|
83
|
+
# Install
|
|
85
84
|
install_cmd = None
|
|
85
|
+
install_github_handle = None
|
|
86
86
|
|
|
87
87
|
# Serializer
|
|
88
88
|
item_loader = None
|
|
@@ -134,6 +134,9 @@ class Command(Runner):
|
|
|
134
134
|
# No capturing of stdout / stderr.
|
|
135
135
|
self.no_capture = self.run_opts.get('no_capture', False)
|
|
136
136
|
|
|
137
|
+
# No processing of output lines.
|
|
138
|
+
self.no_process = self.run_opts.get('no_process', False)
|
|
139
|
+
|
|
137
140
|
# Proxy config (global)
|
|
138
141
|
self.proxy = self.run_opts.pop('proxy', False)
|
|
139
142
|
self.configure_proxy()
|
|
@@ -155,7 +158,7 @@ class Command(Runner):
|
|
|
155
158
|
if self.print_cmd and not self.has_children:
|
|
156
159
|
if self.sync and self.description:
|
|
157
160
|
self._print(f'\n:wrench: {self.description} ...', color='bold gold3', rich=True)
|
|
158
|
-
self._print(self.cmd, color='bold cyan', rich=True)
|
|
161
|
+
self._print(self.cmd.replace('[', '\\['), color='bold cyan', rich=True)
|
|
159
162
|
|
|
160
163
|
# Print built input
|
|
161
164
|
if self.print_input_file and self.input_path:
|
|
@@ -250,36 +253,30 @@ class Command(Runner):
|
|
|
250
253
|
#---------------#
|
|
251
254
|
|
|
252
255
|
@classmethod
|
|
253
|
-
def
|
|
254
|
-
"""
|
|
255
|
-
console.print(f':heavy_check_mark: Installing {cls.__name__}...', style='bold yellow')
|
|
256
|
-
if not cls.install_cmd:
|
|
257
|
-
console.print(f'{cls.__name__} install is not supported yet. Please install it manually.', style='bold red')
|
|
258
|
-
return
|
|
259
|
-
ret = cls.run_command(
|
|
260
|
-
cls.install_cmd,
|
|
261
|
-
name=cls.__name__,
|
|
262
|
-
print_cmd=True,
|
|
263
|
-
print_line=True,
|
|
264
|
-
cls_attributes={'shell': True}
|
|
265
|
-
)
|
|
266
|
-
if ret.return_code != 0:
|
|
267
|
-
console.print(f':exclamation_mark: Failed to install {cls.__name__}.', style='bold red')
|
|
268
|
-
else:
|
|
269
|
-
console.print(f':tada: {cls.__name__} installed successfully !', style='bold green')
|
|
270
|
-
return ret
|
|
256
|
+
def execute(cls, cmd, name=None, cls_attributes={}, **kwargs):
|
|
257
|
+
"""Execute an ad-hoc command.
|
|
271
258
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
259
|
+
Can be used without defining an inherited class to run a command, while still enjoying all the good stuff in
|
|
260
|
+
this class.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
cls (object): Class.
|
|
264
|
+
cmd (str): Command.
|
|
265
|
+
name (str): Printed name.
|
|
266
|
+
cls_attributes (dict): Class attributes.
|
|
267
|
+
kwargs (dict): Options.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
secator.runners.Command: instance of the Command.
|
|
276
271
|
"""
|
|
277
272
|
name = name or cmd.split(' ')[0]
|
|
273
|
+
kwargs['no_process'] = True
|
|
274
|
+
kwargs['print_cmd'] = not kwargs.get('quiet', False)
|
|
275
|
+
kwargs['print_item'] = not kwargs.get('quiet', False)
|
|
276
|
+
kwargs['print_line'] = not kwargs.get('quiet', False)
|
|
278
277
|
cmd_instance = type(name, (Command,), {'cmd': cmd})(**kwargs)
|
|
279
278
|
for k, v in cls_attributes.items():
|
|
280
279
|
setattr(cmd_instance, k, v)
|
|
281
|
-
cmd_instance.print_line = not kwargs.get('quiet', False)
|
|
282
|
-
cmd_instance.print_item = not kwargs.get('quiet', False)
|
|
283
280
|
cmd_instance.run()
|
|
284
281
|
return cmd_instance
|
|
285
282
|
|
|
@@ -400,6 +397,9 @@ class Command(Runner):
|
|
|
400
397
|
|
|
401
398
|
# Strip line endings
|
|
402
399
|
line = line.rstrip()
|
|
400
|
+
if self.no_process:
|
|
401
|
+
yield line
|
|
402
|
+
continue
|
|
403
403
|
|
|
404
404
|
# Some commands output ANSI text, so we need to remove those ANSI chars
|
|
405
405
|
if self.encoding == 'ansi':
|
secator/tasks/dalfox.py
CHANGED
secator/tasks/dnsx.py
CHANGED
secator/tasks/dnsxbrute.py
CHANGED
secator/tasks/feroxbuster.py
CHANGED
|
@@ -64,6 +64,7 @@ class feroxbuster(HttpFuzzer):
|
|
|
64
64
|
'curl -sL https://raw.githubusercontent.com/epi052/feroxbuster/master/install-nix.sh | '
|
|
65
65
|
'bash && sudo mv feroxbuster /usr/local/bin'
|
|
66
66
|
)
|
|
67
|
+
install_github_handle = 'epi052/feroxbuster'
|
|
67
68
|
proxychains = False
|
|
68
69
|
proxy_socks5 = True
|
|
69
70
|
proxy_http = True
|
secator/tasks/ffuf.py
CHANGED
|
@@ -70,8 +70,8 @@ class ffuf(HttpFuzzer):
|
|
|
70
70
|
},
|
|
71
71
|
}
|
|
72
72
|
encoding = 'ansi'
|
|
73
|
-
install_cmd = 'go install -v github.com/ffuf/ffuf@latest && '
|
|
74
|
-
|
|
73
|
+
install_cmd = f'go install -v github.com/ffuf/ffuf@latest && sudo git clone https://github.com/danielmiessler/SecLists {WORDLISTS_FOLDER}/seclists || true' # noqa: E501
|
|
74
|
+
install_github_handle = 'ffuf/ffuf'
|
|
75
75
|
proxychains = False
|
|
76
76
|
proxy_socks5 = True
|
|
77
77
|
proxy_http = True
|
secator/tasks/gau.py
CHANGED
secator/tasks/gospider.py
CHANGED
|
@@ -52,6 +52,7 @@ class gospider(HttpCrawler):
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
install_cmd = 'go install -v github.com/jaeles-project/gospider@latest'
|
|
55
|
+
install_github_handle = 'jaeles-project/gospider'
|
|
55
56
|
ignore_return_code = True
|
|
56
57
|
proxychains = False
|
|
57
58
|
proxy_socks5 = True # with leaks... https://github.com/jaeles-project/gospider/issues/61
|
secator/tasks/grype.py
CHANGED
secator/tasks/httpx.py
CHANGED
|
@@ -60,6 +60,7 @@ class httpx(Http):
|
|
|
60
60
|
DELAY: lambda x: str(x) + 's' if x else None,
|
|
61
61
|
}
|
|
62
62
|
install_cmd = 'go install -v github.com/projectdiscovery/httpx/cmd/httpx@latest'
|
|
63
|
+
install_github_handle = 'projectdiscovery/httpx'
|
|
63
64
|
proxychains = False
|
|
64
65
|
proxy_socks5 = True
|
|
65
66
|
proxy_http = True
|
secator/tasks/katana.py
CHANGED
|
@@ -70,7 +70,8 @@ class katana(HttpCrawler):
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
item_loaders = []
|
|
73
|
-
install_cmd = 'go install -v github.com/projectdiscovery/katana/cmd/katana@latest'
|
|
73
|
+
install_cmd = 'sudo apt install build-essential && go install -v github.com/projectdiscovery/katana/cmd/katana@latest'
|
|
74
|
+
install_github_handle = 'projectdiscovery/katana'
|
|
74
75
|
proxychains = False
|
|
75
76
|
proxy_socks5 = True
|
|
76
77
|
proxy_http = True
|
secator/tasks/mapcidr.py
CHANGED
|
@@ -14,6 +14,7 @@ class mapcidr(ReconIp):
|
|
|
14
14
|
input_flag = '-cidr'
|
|
15
15
|
file_flag = '-cl'
|
|
16
16
|
install_cmd = 'go install -v github.com/projectdiscovery/mapcidr/cmd/mapcidr@latest'
|
|
17
|
+
install_github_handle = 'projectdiscovery/mapcidr'
|
|
17
18
|
input_type = CIDR_RANGE
|
|
18
19
|
output_types = [Ip]
|
|
19
20
|
opt_key_map = {
|
secator/tasks/msfconsole.py
CHANGED
|
@@ -135,7 +135,7 @@ class msfconsole(VulnMulti):
|
|
|
135
135
|
# self.client = MsfRpcClient(pw, ssl=True, **run_opts)
|
|
136
136
|
#
|
|
137
137
|
# # def start_msgrpc(self):
|
|
138
|
-
# # code, out =
|
|
138
|
+
# # code, out = Command.execute(f'msfrpcd -P {self.password}')
|
|
139
139
|
# # logger.info(out)
|
|
140
140
|
#
|
|
141
141
|
# def get_lhost(self):
|
secator/tasks/naabu.py
CHANGED
|
@@ -45,7 +45,8 @@ class naabu(ReconPort):
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
output_types = [Port]
|
|
48
|
-
install_cmd = 'sudo apt install -y libpcap-dev && go install -v github.com/projectdiscovery/naabu/v2/cmd/naabu@latest'
|
|
48
|
+
install_cmd = 'sudo apt install -y build-essential libpcap-dev && go install -v github.com/projectdiscovery/naabu/v2/cmd/naabu@latest' # noqa: E501
|
|
49
|
+
install_github_handle = 'projectdiscovery/naabu'
|
|
49
50
|
proxychains = False
|
|
50
51
|
proxy_socks5 = True
|
|
51
52
|
proxy_http = False
|
secator/tasks/nuclei.py
CHANGED
|
@@ -68,6 +68,7 @@ class nuclei(VulnMulti):
|
|
|
68
68
|
}
|
|
69
69
|
ignore_return_code = True
|
|
70
70
|
install_cmd = 'go install -v github.com/projectdiscovery/nuclei/v2/cmd/nuclei@latest && nuclei update-templates'
|
|
71
|
+
install_github_handle = 'projectdiscovery/nuclei'
|
|
71
72
|
proxychains = False
|
|
72
73
|
proxy_socks5 = True # kind of, leaks data when running network / dns templates
|
|
73
74
|
proxy_http = True # same
|
secator/tasks/subfinder.py
CHANGED
|
@@ -30,6 +30,7 @@ class subfinder(ReconDns):
|
|
|
30
30
|
}
|
|
31
31
|
output_types = [Subdomain]
|
|
32
32
|
install_cmd = 'go install -v github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest'
|
|
33
|
+
install_github_handle = 'projectdiscovery/subfinder'
|
|
33
34
|
proxychains = False
|
|
34
35
|
proxy_http = True
|
|
35
36
|
proxy_socks5 = False
|
secator/tasks/wpscan.py
CHANGED
|
@@ -66,7 +66,7 @@ class wpscan(VulnHttp):
|
|
|
66
66
|
},
|
|
67
67
|
}
|
|
68
68
|
output_types = [Vulnerability, Tag]
|
|
69
|
-
install_cmd = 'sudo gem install wpscan'
|
|
69
|
+
install_cmd = 'sudo apt install build-essential && sudo gem install wpscan'
|
|
70
70
|
proxychains = False
|
|
71
71
|
proxy_http = True
|
|
72
72
|
proxy_socks5 = False
|
secator/utils_test.py
CHANGED
|
@@ -92,6 +92,7 @@ META_OPTS = {
|
|
|
92
92
|
'msfconsole.resource': load_fixture('msfconsole_input', FIXTURES_DIR, only_path=True),
|
|
93
93
|
'dirsearch.output_path': load_fixture('dirsearch_output', FIXTURES_DIR, only_path=True),
|
|
94
94
|
'maigret.output_path': load_fixture('maigret_output', FIXTURES_DIR, only_path=True),
|
|
95
|
+
'nuclei.template_id': 'prometheus-metrics',
|
|
95
96
|
'wpscan.output_path': load_fixture('wpscan_output', FIXTURES_DIR, only_path=True),
|
|
96
97
|
'h8mail.output_path': load_fixture('h8mail_output', FIXTURES_DIR, only_path=True),
|
|
97
98
|
'h8mail.local_breach': load_fixture('h8mail_breach', FIXTURES_DIR, only_path=True)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: secator
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: The pentester's swiss knife.
|
|
5
5
|
Project-URL: Homepage, https://github.com/freelabz/secator
|
|
6
6
|
Project-URL: Issues, https://github.com/freelabz/secator/issues
|
|
@@ -34,6 +34,8 @@ Requires-Dist: rich-click<1.7
|
|
|
34
34
|
Requires-Dist: rich<14
|
|
35
35
|
Requires-Dist: validators<1
|
|
36
36
|
Requires-Dist: xmltodict<1
|
|
37
|
+
Provides-Extra: build
|
|
38
|
+
Requires-Dist: hatch<2; extra == 'build'
|
|
37
39
|
Provides-Extra: dev
|
|
38
40
|
Requires-Dist: asciinema-automation<1; extra == 'dev'
|
|
39
41
|
Requires-Dist: coverage<8; extra == 'dev'
|
|
@@ -319,6 +321,26 @@ secator install addons trace
|
|
|
319
321
|
|
|
320
322
|
</details>
|
|
321
323
|
|
|
324
|
+
<details>
|
|
325
|
+
<summary>build</summary>
|
|
326
|
+
|
|
327
|
+
Add `hatch` for building and publishing the PyPI package.
|
|
328
|
+
|
|
329
|
+
```sh
|
|
330
|
+
secator install addons build
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
</details>
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
### Install CVEs
|
|
337
|
+
|
|
338
|
+
`secator` makes remote API calls to https://cve.circl.lu/ to get in-depth information about the CVEs it encounters.
|
|
339
|
+
We provide a subcommand to download all known CVEs locally so that future lookups are made from disk instead:
|
|
340
|
+
```sh
|
|
341
|
+
secator install cves
|
|
342
|
+
```
|
|
343
|
+
|
|
322
344
|
### Checking installation health
|
|
323
345
|
|
|
324
346
|
To figure out which languages or tools are installed on your system (along with their version):
|