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.
@@ -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:
@@ -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[-] No session opened after {max_poll}s\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
- return {'success': False, 'error': f'No session after {max_poll}s'}
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."""
@@ -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
- return str(alert.get('rule', {}).get('id', 'N/A'))
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
- return str(alert.get('rule_id', alert.get('id', 'N/A')))[:15]
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
- return alert.get('rule', {}).get('description', 'No description')[:45]
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
- desc = alert.get('rule_name', alert.get('description', 'No description'))
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': 'Rule', 'width': 8, 'key': 'rule_id'},
9739
+ {'name': 'Type', 'width': 14, 'key': 'rule_id'},
9722
9740
  {'name': 'Agent', 'width': 15, 'key': 'agent_name'},
9723
- {'name': 'Description', 'width': 45, 'key': 'description'},
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