souleyez 2.17.0__py3-none-any.whl → 2.23.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- souleyez/__init__.py +1 -1
- souleyez/core/tool_chaining.py +99 -7
- souleyez/detection/validator.py +4 -2
- souleyez/docs/README.md +2 -2
- souleyez/docs/user-guide/installation.md +3 -1
- souleyez/engine/background.py +9 -1
- souleyez/engine/result_handler.py +4 -0
- souleyez/integrations/siem/splunk.py +58 -11
- souleyez/main.py +1 -1
- souleyez/parsers/smbmap_parser.py +30 -2
- souleyez/parsers/sqlmap_parser.py +54 -17
- souleyez/plugins/gobuster.py +96 -3
- souleyez/plugins/msf_exploit.py +6 -3
- souleyez/ui/interactive.py +34 -16
- souleyez/ui/setup_wizard.py +353 -58
- souleyez/ui/tool_setup.py +49 -52
- souleyez/utils/tool_checker.py +33 -11
- {souleyez-2.17.0.dist-info → souleyez-2.23.0.dist-info}/METADATA +5 -3
- {souleyez-2.17.0.dist-info → souleyez-2.23.0.dist-info}/RECORD +23 -23
- {souleyez-2.17.0.dist-info → souleyez-2.23.0.dist-info}/WHEEL +0 -0
- {souleyez-2.17.0.dist-info → souleyez-2.23.0.dist-info}/entry_points.txt +0 -0
- {souleyez-2.17.0.dist-info → souleyez-2.23.0.dist-info}/licenses/LICENSE +0 -0
- {souleyez-2.17.0.dist-info → souleyez-2.23.0.dist-info}/top_level.txt +0 -0
souleyez/plugins/gobuster.py
CHANGED
|
@@ -154,6 +154,83 @@ class GobusterPlugin(PluginBase):
|
|
|
154
154
|
category = "scanning"
|
|
155
155
|
HELP = HELP
|
|
156
156
|
|
|
157
|
+
# Minimum required version (v3.x uses subcommands like 'dir', 'dns', 'vhost')
|
|
158
|
+
MIN_VERSION = "3.0.0"
|
|
159
|
+
|
|
160
|
+
def _check_version(self) -> tuple:
|
|
161
|
+
"""
|
|
162
|
+
Check gobuster version meets minimum requirements.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
(meets_requirement: bool, version: str, error_msg: str or None)
|
|
166
|
+
"""
|
|
167
|
+
try:
|
|
168
|
+
# Use -v flag (not 'version' subcommand) - works on v3.x
|
|
169
|
+
result = subprocess.run(
|
|
170
|
+
["gobuster", "-v"],
|
|
171
|
+
capture_output=True,
|
|
172
|
+
text=True,
|
|
173
|
+
timeout=10
|
|
174
|
+
)
|
|
175
|
+
output = result.stdout + result.stderr
|
|
176
|
+
|
|
177
|
+
# Parse version from output like "gobuster version 3.8.2"
|
|
178
|
+
version_match = re.search(r'version\s+(\d+\.\d+\.\d+)', output, re.IGNORECASE)
|
|
179
|
+
if version_match:
|
|
180
|
+
version = version_match.group(1)
|
|
181
|
+
major = int(version.split('.')[0])
|
|
182
|
+
if major >= 3:
|
|
183
|
+
return (True, version, None)
|
|
184
|
+
else:
|
|
185
|
+
return (False, version, self._upgrade_message(version))
|
|
186
|
+
|
|
187
|
+
# Also try --version flag as fallback
|
|
188
|
+
result2 = subprocess.run(
|
|
189
|
+
["gobuster", "--version"],
|
|
190
|
+
capture_output=True,
|
|
191
|
+
text=True,
|
|
192
|
+
timeout=10
|
|
193
|
+
)
|
|
194
|
+
output2 = result2.stdout + result2.stderr
|
|
195
|
+
version_match2 = re.search(r'version\s+(\d+\.\d+\.\d+)', output2, re.IGNORECASE)
|
|
196
|
+
if version_match2:
|
|
197
|
+
version = version_match2.group(1)
|
|
198
|
+
major = int(version.split('.')[0])
|
|
199
|
+
if major >= 3:
|
|
200
|
+
return (True, version, None)
|
|
201
|
+
else:
|
|
202
|
+
return (False, version, self._upgrade_message(version))
|
|
203
|
+
|
|
204
|
+
# If neither flag shows version info, check if v2.x by looking for subcommand error
|
|
205
|
+
# v2.x will show "Usage: gobuster [OPTIONS] ..." without subcommands
|
|
206
|
+
if "dir" not in output.lower() and "dns" not in output.lower():
|
|
207
|
+
return (False, "2.x", self._upgrade_message("2.x"))
|
|
208
|
+
|
|
209
|
+
# If we see dir/dns subcommands mentioned, assume v3.x
|
|
210
|
+
return (True, "3.x", None)
|
|
211
|
+
|
|
212
|
+
except FileNotFoundError:
|
|
213
|
+
return (False, None, "ERROR: gobuster not found. Install with: sudo apt install gobuster")
|
|
214
|
+
except subprocess.TimeoutExpired:
|
|
215
|
+
return (True, "unknown", None) # Assume it works
|
|
216
|
+
except Exception as e:
|
|
217
|
+
return (True, "unknown", None) # Assume it works
|
|
218
|
+
|
|
219
|
+
def _upgrade_message(self, current_version: str) -> str:
|
|
220
|
+
"""Generate upgrade instructions for old gobuster versions."""
|
|
221
|
+
return (
|
|
222
|
+
f"ERROR: gobuster {current_version} is too old. Version 3.0.0+ required.\n\n"
|
|
223
|
+
f"Gobuster v2.x doesn't support the 'dir/dns/vhost' subcommands used by SoulEyez.\n\n"
|
|
224
|
+
f"UPGRADE OPTIONS:\n"
|
|
225
|
+
f" Option 1 - Install from Go (recommended, gets latest):\n"
|
|
226
|
+
f" go install github.com/OJ/gobuster/v3@latest\n\n"
|
|
227
|
+
f" Option 2 - Download binary from GitHub:\n"
|
|
228
|
+
f" https://github.com/OJ/gobuster/releases\n\n"
|
|
229
|
+
f" Option 3 - On Kali Linux:\n"
|
|
230
|
+
f" sudo apt update && sudo apt install gobuster\n\n"
|
|
231
|
+
f"After upgrading, verify with: gobuster version\n"
|
|
232
|
+
)
|
|
233
|
+
|
|
157
234
|
def _preflight_check(self, base_url: str, timeout: float = 5.0, log_path: str = None) -> Dict[str, Optional[str]]:
|
|
158
235
|
"""
|
|
159
236
|
Probe target with random UUID path to detect false positive responses.
|
|
@@ -225,7 +302,15 @@ class GobusterPlugin(PluginBase):
|
|
|
225
302
|
def build_command(self, target: str, args: List[str] = None, label: str = "", log_path: str = None):
|
|
226
303
|
"""Build gobuster command for background execution with PID tracking."""
|
|
227
304
|
args = args or []
|
|
228
|
-
|
|
305
|
+
|
|
306
|
+
# Check gobuster version meets requirements (v3.x+ required for subcommands)
|
|
307
|
+
meets_req, version, error_msg = self._check_version()
|
|
308
|
+
if not meets_req:
|
|
309
|
+
if log_path:
|
|
310
|
+
with open(log_path, 'w') as f:
|
|
311
|
+
f.write(error_msg)
|
|
312
|
+
return None
|
|
313
|
+
|
|
229
314
|
# Detect the mode from args
|
|
230
315
|
mode = None
|
|
231
316
|
if 'dir' in args:
|
|
@@ -323,9 +408,17 @@ class GobusterPlugin(PluginBase):
|
|
|
323
408
|
|
|
324
409
|
def run(self, target: str, args: List[str] = None, label: str = "", log_path: str = None) -> int:
|
|
325
410
|
"""Execute gobuster scan and write output to log_path."""
|
|
326
|
-
|
|
411
|
+
|
|
327
412
|
args = args or []
|
|
328
|
-
|
|
413
|
+
|
|
414
|
+
# Check gobuster version meets requirements (v3.x+ required for subcommands)
|
|
415
|
+
meets_req, version, error_msg = self._check_version()
|
|
416
|
+
if not meets_req:
|
|
417
|
+
if log_path:
|
|
418
|
+
with open(log_path, 'w') as f:
|
|
419
|
+
f.write(error_msg)
|
|
420
|
+
return 1
|
|
421
|
+
|
|
329
422
|
# Detect the mode from args
|
|
330
423
|
mode = None
|
|
331
424
|
if 'dir' in args:
|
souleyez/plugins/msf_exploit.py
CHANGED
|
@@ -295,13 +295,16 @@ class MsfExploitPlugin(PluginBase):
|
|
|
295
295
|
with open(log_path, 'a') as f:
|
|
296
296
|
f.write(f"[!] Poll error: {e}\n")
|
|
297
297
|
|
|
298
|
-
# Timeout - no session opened
|
|
298
|
+
# Timeout - no session opened (not an error, just means target likely not vulnerable)
|
|
299
299
|
if log_path:
|
|
300
300
|
with open(log_path, 'a') as f:
|
|
301
|
-
f.write(f"\n[
|
|
301
|
+
f.write(f"\n[*] No session opened after {max_poll}s\n")
|
|
302
|
+
f.write(f"[*] Target may not be vulnerable or exploit conditions not met\n")
|
|
303
|
+
f.write(f"[*] Try re-running the exploit if needed\n")
|
|
302
304
|
f.write(f"Completed: {time.strftime('%Y-%m-%d %H:%M:%S UTC', time.gmtime())}\n")
|
|
303
305
|
|
|
304
|
-
|
|
306
|
+
# Return success=False but no 'error' key - this is a "no results" case, not an error
|
|
307
|
+
return {'success': False, 'no_session': True, 'reason': f'No session after {max_poll}s'}
|
|
305
308
|
|
|
306
309
|
def _get_local_ip(self, target: str) -> str:
|
|
307
310
|
"""Get local IP that can reach the target."""
|
souleyez/ui/interactive.py
CHANGED
|
@@ -9694,9 +9694,19 @@ def _view_all_job_alerts(item: dict):
|
|
|
9694
9694
|
return f"{icon} {severity[:6].upper()}"
|
|
9695
9695
|
elif key == 'rule_id':
|
|
9696
9696
|
if is_wazuh_style:
|
|
9697
|
-
|
|
9697
|
+
# Wazuh: show first rule group (more descriptive) or rule ID
|
|
9698
|
+
rule_data = alert.get('rule', {})
|
|
9699
|
+
groups = rule_data.get('groups', [])
|
|
9700
|
+
if groups:
|
|
9701
|
+
# Get most specific group (often last is most specific)
|
|
9702
|
+
return str(groups[-1])[:12]
|
|
9703
|
+
return str(rule_data.get('id', 'N/A'))[:12]
|
|
9698
9704
|
else:
|
|
9699
|
-
|
|
9705
|
+
# Splunk: show MITRE tactic if available, else sourcetype
|
|
9706
|
+
mitre_tactics = alert.get('mitre_tactics', [])
|
|
9707
|
+
if mitre_tactics:
|
|
9708
|
+
return str(mitre_tactics[0])[:12]
|
|
9709
|
+
return str(alert.get('rule_id', 'N/A'))[:12]
|
|
9700
9710
|
elif key == 'agent_name':
|
|
9701
9711
|
if is_wazuh_style:
|
|
9702
9712
|
return alert.get('agent', {}).get('name', 'N/A')
|
|
@@ -9704,9 +9714,17 @@ def _view_all_job_alerts(item: dict):
|
|
|
9704
9714
|
return str(alert.get('source_ip', alert.get('host', 'N/A')))[:15]
|
|
9705
9715
|
elif key == 'description':
|
|
9706
9716
|
if is_wazuh_style:
|
|
9707
|
-
|
|
9717
|
+
# Wazuh: use rule description, or rule groups if more descriptive
|
|
9718
|
+
rule_data = alert.get('rule', {})
|
|
9719
|
+
desc = rule_data.get('description', '')
|
|
9720
|
+
if not desc:
|
|
9721
|
+
groups = rule_data.get('groups', [])
|
|
9722
|
+
if groups:
|
|
9723
|
+
desc = ', '.join(groups[:2])
|
|
9724
|
+
return str(desc)[:45] if desc else 'No description'
|
|
9708
9725
|
else:
|
|
9709
|
-
|
|
9726
|
+
# Splunk: prefer actual description (log content) over rule_name
|
|
9727
|
+
desc = alert.get('description', '') or alert.get('rule_name', '')
|
|
9710
9728
|
return str(desc)[:45] if desc else 'No description'
|
|
9711
9729
|
elif key == 'timestamp':
|
|
9712
9730
|
ts = alert.get('timestamp', 'N/A')
|
|
@@ -9718,9 +9736,9 @@ def _view_all_job_alerts(item: dict):
|
|
|
9718
9736
|
columns = [
|
|
9719
9737
|
{'name': '#', 'width': 5, 'key': '_idx'},
|
|
9720
9738
|
{'name': 'Level', 'width': 10, 'key': 'level_display'},
|
|
9721
|
-
{'name': '
|
|
9739
|
+
{'name': 'Type', 'width': 14, 'key': 'rule_id'},
|
|
9722
9740
|
{'name': 'Agent', 'width': 15, 'key': 'agent_name'},
|
|
9723
|
-
{'name': 'Description', 'width':
|
|
9741
|
+
{'name': 'Description', 'width': 42, 'key': 'description'},
|
|
9724
9742
|
{'name': 'Time', 'width': 20, 'key': 'timestamp'},
|
|
9725
9743
|
]
|
|
9726
9744
|
|
|
@@ -29508,11 +29526,11 @@ def _check_msfdb_ready() -> bool:
|
|
|
29508
29526
|
click.echo(" The Metasploit database needs to be initialized for full functionality.")
|
|
29509
29527
|
click.echo(" Without it, you won't be able to store hosts, credentials, or loot.")
|
|
29510
29528
|
click.echo()
|
|
29511
|
-
if click.confirm(" Initialize database now? (runs: msfdb init)", default=True):
|
|
29529
|
+
if click.confirm(" Initialize database now? (runs: sudo msfdb init)", default=True):
|
|
29512
29530
|
click.echo()
|
|
29513
|
-
click.echo(click.style(" Running msfdb init...", fg='cyan'))
|
|
29531
|
+
click.echo(click.style(" Running sudo msfdb init...", fg='cyan'))
|
|
29514
29532
|
try:
|
|
29515
|
-
result = subprocess.run(['msfdb', 'init'], capture_output=False, text=True)
|
|
29533
|
+
result = subprocess.run(['sudo', 'msfdb', 'init'], capture_output=False, text=True)
|
|
29516
29534
|
if result.returncode == 0:
|
|
29517
29535
|
click.echo(click.style(" Database initialized successfully!", fg='green'))
|
|
29518
29536
|
click.echo()
|
|
@@ -31347,13 +31365,13 @@ def run_interactive_menu():
|
|
|
31347
31365
|
click.echo("└" + "─" * (width - 2) + "┘")
|
|
31348
31366
|
click.echo("\n")
|
|
31349
31367
|
|
|
31350
|
-
# ASCII Art Banner - SOULEYEZ
|
|
31351
|
-
click.echo(click.style(" ███████╗ ██████╗ ██╗ ██╗██╗ ███████╗██╗ ██╗███████╗███████╗", fg='bright_cyan', bold=True))
|
|
31352
|
-
click.echo(click.style(" ██╔════╝██╔═══██╗██║ ██║██║ ██╔════╝╚██╗ ██╔╝██╔════╝╚══███╔╝", fg='bright_cyan', bold=True))
|
|
31353
|
-
click.echo(click.style(" ███████╗██║ ██║██║ ██║██║ █████╗ ╚████╔╝ █████╗ ███╔╝ ", fg='bright_cyan', bold=True))
|
|
31354
|
-
click.echo(click.style(" ╚════██║██║ ██║██║ ██║██║ ██╔══╝ ╚██╔╝ ██╔══╝ ███╔╝ ", fg='bright_cyan', bold=True))
|
|
31355
|
-
click.echo(click.style(" ███████║╚██████╔╝╚██████╔╝███████╗███████╗ ██║ ███████╗███████╗", fg='bright_cyan', bold=True))
|
|
31356
|
-
click.echo(click.style(" ╚══════╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝ ╚═╝ ╚══════╝╚══════╝", fg='bright_cyan', bold=True))
|
|
31368
|
+
# ASCII Art Banner - SOULEYEZ with all-seeing eye on the right
|
|
31369
|
+
click.echo(click.style(" ███████╗ ██████╗ ██╗ ██╗██╗ ███████╗██╗ ██╗███████╗███████╗", fg='bright_cyan', bold=True) + click.style(" ▄██▄", fg='bright_blue', bold=True))
|
|
31370
|
+
click.echo(click.style(" ██╔════╝██╔═══██╗██║ ██║██║ ██╔════╝╚██╗ ██╔╝██╔════╝╚══███╔╝", fg='bright_cyan', bold=True) + click.style(" ▄█▀ ▀█▄", fg='bright_blue', bold=True))
|
|
31371
|
+
click.echo(click.style(" ███████╗██║ ██║██║ ██║██║ █████╗ ╚████╔╝ █████╗ ███╔╝ ", fg='bright_cyan', bold=True) + click.style(" █ ◉ █", fg='bright_blue', bold=True))
|
|
31372
|
+
click.echo(click.style(" ╚════██║██║ ██║██║ ██║██║ ██╔══╝ ╚██╔╝ ██╔══╝ ███╔╝ ", fg='bright_cyan', bold=True) + click.style(" █ ═══ █", fg='bright_blue', bold=True))
|
|
31373
|
+
click.echo(click.style(" ███████║╚██████╔╝╚██████╔╝███████╗███████╗ ██║ ███████╗███████╗", fg='bright_cyan', bold=True) + click.style(" ▀█▄ ▄█▀", fg='bright_blue', bold=True))
|
|
31374
|
+
click.echo(click.style(" ╚══════╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝ ╚═╝ ╚══════╝╚══════╝", fg='bright_cyan', bold=True) + click.style(" ▀██▀", fg='bright_blue', bold=True))
|
|
31357
31375
|
click.echo()
|
|
31358
31376
|
|
|
31359
31377
|
# Tagline and description
|