souleyez 2.43.34__py3-none-any.whl → 3.0.7__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/ai/__init__.py +7 -7
- souleyez/ai/action_mapper.py +3 -2
- souleyez/ai/chain_advisor.py +2 -1
- souleyez/ai/claude_provider.py +2 -2
- souleyez/ai/context_builder.py +4 -2
- souleyez/ai/executor.py +9 -6
- souleyez/ai/feedback_handler.py +4 -2
- souleyez/ai/llm_provider.py +2 -2
- souleyez/ai/ollama_provider.py +2 -2
- souleyez/ai/ollama_service.py +10 -26
- souleyez/ai/path_scorer.py +2 -1
- souleyez/ai/recommender.py +6 -4
- souleyez/ai/report_context.py +2 -2
- souleyez/ai/report_service.py +5 -5
- souleyez/ai/result_parser.py +3 -2
- souleyez/ai/safety.py +5 -2
- souleyez/auth/__init__.py +6 -6
- souleyez/auth/audit.py +2 -2
- souleyez/auth/engagement_access.py +5 -7
- souleyez/auth/permissions.py +1 -1
- souleyez/auth/session_manager.py +5 -5
- souleyez/auth/user_manager.py +4 -5
- souleyez/commands/audit.py +6 -5
- souleyez/commands/auth.py +6 -5
- souleyez/commands/deliverables.py +2 -3
- souleyez/commands/engagement.py +3 -3
- souleyez/commands/license.py +3 -2
- souleyez/commands/screenshots.py +5 -4
- souleyez/commands/user.py +10 -8
- souleyez/config.py +4 -2
- souleyez/core/credential_tester.py +4 -2
- souleyez/core/cve_mappings.py +2 -1
- souleyez/core/cve_matcher.py +2 -1
- souleyez/core/msf_auto_mapper.py +2 -0
- souleyez/core/msf_chain_engine.py +3 -1
- souleyez/core/msf_database.py +7 -13
- souleyez/core/msf_integration.py +2 -2
- souleyez/core/msf_rpc_client.py +3 -2
- souleyez/core/msf_rpc_manager.py +4 -4
- souleyez/core/msf_sync_manager.py +7 -7
- souleyez/core/network_utils.py +1 -1
- souleyez/core/parser_handler.py +2 -1
- souleyez/core/pending_chains.py +4 -3
- souleyez/core/templates.py +5 -2
- souleyez/core/tool_chaining.py +297 -230
- souleyez/core/version_utils.py +1 -0
- souleyez/core/vuln_correlation.py +3 -2
- souleyez/core/web_utils.py +2 -1
- souleyez/detection/__init__.py +1 -1
- souleyez/detection/attack_signatures.py +1 -1
- souleyez/detection/mitre_mappings.py +1 -2
- souleyez/detection/validator.py +5 -4
- souleyez/devtools.py +4 -2
- souleyez/docs/README.md +2 -2
- souleyez/engine/background.py +168 -7
- souleyez/engine/base.py +2 -1
- souleyez/engine/loader.py +4 -2
- souleyez/engine/log_sanitizer.py +1 -0
- souleyez/engine/manager.py +3 -1
- souleyez/engine/result_handler.py +50 -67
- souleyez/engine/worker_manager.py +6 -4
- souleyez/export/evidence_bundle.py +1 -0
- souleyez/handlers/base.py +1 -0
- souleyez/handlers/bash_handler.py +1 -0
- souleyez/handlers/bloodhound_handler.py +1 -0
- souleyez/handlers/certipy_handler.py +1 -0
- souleyez/handlers/crackmapexec_handler.py +2 -20
- souleyez/handlers/dnsrecon_handler.py +2 -1
- souleyez/handlers/enum4linux_handler.py +65 -37
- souleyez/handlers/evil_winrm_handler.py +1 -0
- souleyez/handlers/ffuf_handler.py +3 -1
- souleyez/handlers/gobuster_handler.py +7 -6
- souleyez/handlers/gpp_extract_handler.py +1 -0
- souleyez/handlers/hashcat_handler.py +1 -0
- souleyez/handlers/hydra_handler.py +5 -1
- souleyez/handlers/impacket_getuserspns_handler.py +1 -0
- souleyez/handlers/impacket_psexec_handler.py +1 -0
- souleyez/handlers/impacket_secretsdump_handler.py +1 -0
- souleyez/handlers/john_handler.py +1 -0
- souleyez/handlers/katana_handler.py +39 -2
- souleyez/handlers/kerbrute_handler.py +1 -0
- souleyez/handlers/ldapsearch_handler.py +90 -17
- souleyez/handlers/lfi_extract_handler.py +1 -0
- souleyez/handlers/msf_auxiliary_handler.py +2 -0
- souleyez/handlers/msf_exploit_handler.py +1 -0
- souleyez/handlers/nikto_handler.py +2 -1
- souleyez/handlers/nmap_handler.py +2 -1
- souleyez/handlers/nuclei_handler.py +2 -1
- souleyez/handlers/nxc_handler.py +50 -19
- souleyez/handlers/rdp_sec_check_handler.py +1 -0
- souleyez/handlers/registry.py +1 -0
- souleyez/handlers/responder_handler.py +1 -0
- souleyez/handlers/service_explorer_handler.py +2 -1
- souleyez/handlers/smbclient_handler.py +1 -0
- souleyez/handlers/smbmap_handler.py +3 -2
- souleyez/handlers/sqlmap_handler.py +6 -4
- souleyez/handlers/theharvester_handler.py +2 -1
- souleyez/handlers/web_login_test_handler.py +1 -0
- souleyez/handlers/whois_handler.py +3 -2
- souleyez/handlers/wpscan_handler.py +2 -1
- souleyez/history.py +4 -3
- souleyez/importers/msf_importer.py +5 -3
- souleyez/importers/smart_importer.py +6 -4
- souleyez/integrations/siem/__init__.py +6 -6
- souleyez/integrations/siem/base.py +1 -1
- souleyez/integrations/siem/elastic.py +3 -3
- souleyez/integrations/siem/factory.py +1 -2
- souleyez/integrations/siem/googlesecops.py +4 -4
- souleyez/integrations/siem/rule_mappings/wazuh_rules.py +1 -1
- souleyez/integrations/siem/sentinel.py +3 -3
- souleyez/integrations/siem/splunk.py +3 -3
- souleyez/integrations/siem/wazuh.py +4 -4
- souleyez/integrations/wazuh/__init__.py +1 -1
- souleyez/integrations/wazuh/client.py +3 -2
- souleyez/integrations/wazuh/config.py +3 -2
- souleyez/integrations/wazuh/host_mapper.py +3 -1
- souleyez/integrations/wazuh/sync.py +4 -1
- souleyez/intelligence/__init__.py +1 -1
- souleyez/intelligence/correlation_analyzer.py +6 -5
- souleyez/intelligence/exploit_knowledge.py +4 -4
- souleyez/intelligence/exploit_suggestions.py +4 -3
- souleyez/intelligence/gap_analyzer.py +5 -3
- souleyez/intelligence/gap_detector.py +2 -0
- souleyez/intelligence/sensitive_tables.py +1 -1
- souleyez/intelligence/service_parser.py +1 -0
- souleyez/intelligence/surface_analyzer.py +9 -9
- souleyez/intelligence/target_parser.py +1 -0
- souleyez/licensing/__init__.py +3 -3
- souleyez/main.py +25 -18
- souleyez/migrations/fix_job_counter.py +2 -1
- souleyez/parsers/bloodhound_parser.py +1 -0
- souleyez/parsers/crackmapexec_parser.py +2 -1
- souleyez/parsers/dalfox_parser.py +3 -2
- souleyez/parsers/dnsrecon_parser.py +2 -1
- souleyez/parsers/enum4linux_parser.py +2 -1
- souleyez/parsers/ffuf_parser.py +2 -1
- souleyez/parsers/gobuster_parser.py +2 -1
- souleyez/parsers/hashcat_parser.py +3 -2
- souleyez/parsers/http_fingerprint_parser.py +2 -1
- souleyez/parsers/hydra_parser.py +2 -1
- souleyez/parsers/impacket_parser.py +2 -1
- souleyez/parsers/john_parser.py +4 -3
- souleyez/parsers/katana_parser.py +134 -2
- souleyez/parsers/msf_parser.py +2 -1
- souleyez/parsers/nikto_parser.py +2 -1
- souleyez/parsers/nmap_parser.py +14 -3
- souleyez/parsers/nuclei_parser.py +3 -2
- souleyez/parsers/responder_parser.py +1 -0
- souleyez/parsers/searchsploit_parser.py +3 -2
- souleyez/parsers/service_explorer_parser.py +1 -0
- souleyez/parsers/smbmap_parser.py +2 -1
- souleyez/parsers/sqlmap_parser.py +36 -2
- souleyez/parsers/theharvester_parser.py +2 -1
- souleyez/parsers/whois_parser.py +2 -1
- souleyez/parsers/wpscan_parser.py +3 -2
- souleyez/plugins/afp.py +3 -1
- souleyez/plugins/afp_brute.py +3 -1
- souleyez/plugins/ard.py +3 -1
- souleyez/plugins/bloodhound.py +3 -2
- souleyez/plugins/certipy.py +1 -0
- souleyez/plugins/crackmapexec.py +11 -7
- souleyez/plugins/dalfox.py +5 -2
- souleyez/plugins/dns_hijack.py +3 -1
- souleyez/plugins/dnsrecon.py +3 -1
- souleyez/plugins/enum4linux.py +3 -1
- souleyez/plugins/evil_winrm.py +1 -0
- souleyez/plugins/ffuf.py +3 -1
- souleyez/plugins/firmware_extract.py +3 -2
- souleyez/plugins/gobuster.py +6 -3
- souleyez/plugins/gpp_extract.py +1 -0
- souleyez/plugins/hashcat.py +2 -1
- souleyez/plugins/http_fingerprint.py +149 -40
- souleyez/plugins/hydra.py +5 -3
- souleyez/plugins/impacket_common.py +40 -0
- souleyez/plugins/impacket_getnpusers.py +19 -2
- souleyez/plugins/impacket_getuserspns.py +158 -0
- souleyez/plugins/impacket_psexec.py +19 -2
- souleyez/plugins/impacket_secretsdump.py +19 -2
- souleyez/plugins/impacket_smbclient.py +19 -2
- souleyez/plugins/john.py +2 -1
- souleyez/plugins/katana.py +48 -6
- souleyez/plugins/kerbrute.py +1 -0
- souleyez/plugins/lfi_extract.py +1 -0
- souleyez/plugins/macos_ssh.py +3 -1
- souleyez/plugins/mdns.py +3 -1
- souleyez/plugins/msf_auxiliary.py +3 -2
- souleyez/plugins/msf_exploit.py +6 -5
- souleyez/plugins/nikto.py +5 -2
- souleyez/plugins/nmap.py +6 -4
- souleyez/plugins/nuclei.py +3 -1
- souleyez/plugins/nxc.py +1 -0
- souleyez/plugins/plugin_base.py +3 -2
- souleyez/plugins/plugin_template.py +3 -2
- souleyez/plugins/rdp_sec_check.py +1 -0
- souleyez/plugins/responder.py +2 -1
- souleyez/plugins/router_http_brute.py +3 -1
- souleyez/plugins/router_ssh_brute.py +3 -1
- souleyez/plugins/router_telnet_brute.py +3 -1
- souleyez/plugins/routersploit.py +5 -3
- souleyez/plugins/routersploit_exploit.py +5 -3
- souleyez/plugins/searchsploit.py +1 -0
- souleyez/plugins/service_explorer.py +2 -1
- souleyez/plugins/smbmap.py +3 -1
- souleyez/plugins/smbpasswd.py +1 -0
- souleyez/plugins/sqlmap.py +3 -1
- souleyez/plugins/theharvester.py +3 -1
- souleyez/plugins/tr069.py +3 -1
- souleyez/plugins/upnp.py +3 -1
- souleyez/plugins/upnp_abuse.py +4 -2
- souleyez/plugins/vnc_access.py +4 -2
- souleyez/plugins/vnc_brute.py +3 -1
- souleyez/plugins/web_login_test.py +1 -0
- souleyez/plugins/whois.py +3 -1
- souleyez/plugins/wpscan.py +49 -1
- souleyez/reporting/attack_chain.py +2 -1
- souleyez/reporting/charts.py +1 -0
- souleyez/reporting/compliance_mappings.py +1 -0
- souleyez/reporting/detection_report.py +10 -10
- souleyez/reporting/formatters.py +7 -12
- souleyez/reporting/generator.py +34 -46
- souleyez/reporting/metrics.py +2 -1
- souleyez/scanner.py +6 -3
- souleyez/security/__init__.py +7 -5
- souleyez/security/scope_validator.py +5 -4
- souleyez/security/validation.py +14 -0
- souleyez/security.py +5 -2
- souleyez/storage/credentials.py +14 -19
- souleyez/storage/crypto.py +7 -4
- souleyez/storage/database.py +6 -6
- souleyez/storage/db.py +8 -8
- souleyez/storage/deliverable_evidence.py +2 -1
- souleyez/storage/deliverable_exporter.py +3 -2
- souleyez/storage/deliverable_templates.py +2 -1
- souleyez/storage/deliverables.py +2 -1
- souleyez/storage/engagements.py +6 -4
- souleyez/storage/evidence.py +5 -4
- souleyez/storage/execution_log.py +4 -2
- souleyez/storage/exploit_attempts.py +3 -2
- souleyez/storage/exploits.py +3 -1
- souleyez/storage/findings.py +3 -1
- souleyez/storage/hosts.py +5 -2
- souleyez/storage/migrate_to_engagements.py +14 -24
- souleyez/storage/migrations/_001_add_credential_enhancements.py +12 -21
- souleyez/storage/migrations/_003_add_execution_log.py +8 -13
- souleyez/storage/migrations/_005_screenshots.py +2 -4
- souleyez/storage/migrations/_006_deliverables.py +2 -4
- souleyez/storage/migrations/_007_deliverable_templates.py +4 -8
- souleyez/storage/migrations/_008_add_nuclei_table.py +2 -4
- souleyez/storage/migrations/_010_evidence_linking.py +6 -12
- souleyez/storage/migrations/_012_team_collaboration.py +12 -24
- souleyez/storage/migrations/_013_add_host_tags.py +2 -4
- souleyez/storage/migrations/_014_exploit_attempts.py +10 -20
- souleyez/storage/migrations/_015_add_mac_os_fields.py +4 -8
- souleyez/storage/migrations/_016_add_domain_field.py +2 -4
- souleyez/storage/migrations/_017_msf_sessions.py +8 -16
- souleyez/storage/migrations/_018_add_osint_target.py +4 -8
- souleyez/storage/migrations/_019_add_engagement_type.py +4 -8
- souleyez/storage/migrations/_020_add_rbac.py +9 -17
- souleyez/storage/migrations/_021_wazuh_integration.py +4 -8
- souleyez/storage/migrations/_023_fix_detection_results_fk.py +2 -4
- souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +4 -8
- souleyez/storage/migrations/_026_add_engagement_scope.py +4 -8
- souleyez/storage/migrations/_027_multi_siem_persistence.py +8 -16
- souleyez/storage/migrations/__init__.py +1 -4
- souleyez/storage/migrations/migration_manager.py +6 -9
- souleyez/storage/msf_sessions.py +1 -1
- souleyez/storage/osint.py +3 -1
- souleyez/storage/recommendation_engine.py +3 -2
- souleyez/storage/screenshots.py +2 -1
- souleyez/storage/smb_shares.py +3 -1
- souleyez/storage/sqlmap_data.py +6 -4
- souleyez/storage/team_collaboration.py +3 -2
- souleyez/storage/timeline_tracker.py +2 -1
- souleyez/storage/wazuh_vulns.py +3 -1
- souleyez/storage/web_paths.py +3 -1
- souleyez/testing/credential_tester.py +2 -0
- souleyez/ui/__init__.py +2 -1
- souleyez/ui/ai_quotes.py +1 -1
- souleyez/ui/attack_surface.py +50 -28
- souleyez/ui/chain_rules_view.py +6 -3
- souleyez/ui/correlation_view.py +3 -2
- souleyez/ui/dashboard.py +85 -139
- souleyez/ui/deliverables_view.py +1 -1
- souleyez/ui/design_system.py +5 -3
- souleyez/ui/errors.py +3 -1
- souleyez/ui/evidence_linking_view.py +2 -1
- souleyez/ui/evidence_vault.py +11 -6
- souleyez/ui/exploit_suggestions_view.py +11 -7
- souleyez/ui/export_view.py +3 -1
- souleyez/ui/gap_analysis_view.py +6 -3
- souleyez/ui/help_system.py +4 -1
- souleyez/ui/intelligence_view.py +7 -3
- souleyez/ui/interactive.py +1512 -584
- souleyez/ui/interactive_selector.py +3 -2
- souleyez/ui/log_formatter.py +1 -0
- souleyez/ui/menu_components.py +3 -1
- souleyez/ui/msf_auxiliary_menu.py +4 -1
- souleyez/ui/pending_chains_view.py +15 -12
- souleyez/ui/progress_indicators.py +5 -2
- souleyez/ui/recommendations_view.py +4 -2
- souleyez/ui/rule_builder.py +4 -1
- souleyez/ui/setup_wizard.py +10 -8
- souleyez/ui/shortcuts.py +1 -1
- souleyez/ui/splunk_gap_analysis_view.py +7 -4
- souleyez/ui/splunk_vulns_view.py +4 -1
- souleyez/ui/team_dashboard.py +7 -5
- souleyez/ui/template_selector.py +2 -1
- souleyez/ui/terminal.py +3 -2
- souleyez/ui/timeline_view.py +2 -1
- souleyez/ui/tool_setup.py +92 -31
- souleyez/ui/tutorial.py +7 -4
- souleyez/ui/tutorial_state.py +3 -2
- souleyez/ui/wazuh_vulns_view.py +5 -2
- souleyez/ui/wordlist_browser.py +4 -3
- souleyez/ui.py +13 -7
- souleyez/utils/tool_checker.py +61 -12
- souleyez/utils.py +4 -4
- souleyez/wordlists.py +1 -0
- {souleyez-2.43.34.dist-info → souleyez-3.0.7.dist-info}/METADATA +2 -2
- souleyez-3.0.7.dist-info/RECORD +445 -0
- souleyez-2.43.34.dist-info/RECORD +0 -443
- {souleyez-2.43.34.dist-info → souleyez-3.0.7.dist-info}/WHEEL +0 -0
- {souleyez-2.43.34.dist-info → souleyez-3.0.7.dist-info}/entry_points.txt +0 -0
- {souleyez-2.43.34.dist-info → souleyez-3.0.7.dist-info}/licenses/LICENSE +0 -0
- {souleyez-2.43.34.dist-info → souleyez-3.0.7.dist-info}/top_level.txt +0 -0
souleyez/ui/interactive.py
CHANGED
|
@@ -8,6 +8,7 @@ import os
|
|
|
8
8
|
import platform
|
|
9
9
|
import shlex
|
|
10
10
|
import shutil
|
|
11
|
+
import subprocess
|
|
11
12
|
import sys
|
|
12
13
|
import tempfile
|
|
13
14
|
import time
|
|
@@ -1947,78 +1948,132 @@ def show_tool_menu(tool_name: str) -> Optional[Dict[str, Any]]:
|
|
|
1947
1948
|
|
|
1948
1949
|
elif choice == 3:
|
|
1949
1950
|
# Select from credentials in current engagement
|
|
1950
|
-
from souleyez.storage.credentials import
|
|
1951
|
+
from souleyez.storage.credentials import CredentialsManager
|
|
1951
1952
|
from souleyez.storage.engagements import EngagementManager
|
|
1952
1953
|
|
|
1953
1954
|
em = EngagementManager()
|
|
1954
1955
|
current_eng = em.get_current()
|
|
1955
1956
|
|
|
1956
1957
|
if not current_eng:
|
|
1958
|
+
click.echo()
|
|
1957
1959
|
click.echo(click.style("No engagement selected!", fg="red"))
|
|
1958
1960
|
click.echo("Use 'souleyez engagement use <name>' first")
|
|
1961
|
+
click.echo()
|
|
1962
|
+
click.pause()
|
|
1959
1963
|
return None
|
|
1960
1964
|
|
|
1961
|
-
cm =
|
|
1965
|
+
cm = CredentialsManager()
|
|
1962
1966
|
creds = cm.list_credentials(current_eng["id"])
|
|
1963
1967
|
|
|
1964
|
-
# Filter for
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
+
# Filter for actual crackable hashes
|
|
1969
|
+
# Hash is stored in 'password' field, must look like a hash (not plaintext)
|
|
1970
|
+
def is_crackable_hash(cred):
|
|
1971
|
+
cred_type = cred.get("credential_type", "").lower()
|
|
1972
|
+
# Hash value is in 'password' field
|
|
1973
|
+
hash_value = cred.get("password", "") or ""
|
|
1974
|
+
|
|
1975
|
+
if not hash_value or hash_value == "None":
|
|
1976
|
+
return False
|
|
1977
|
+
|
|
1978
|
+
# Must have hash-related type
|
|
1979
|
+
if "hash" not in cred_type:
|
|
1980
|
+
return False
|
|
1981
|
+
|
|
1982
|
+
# Check if value looks like a hash (not plaintext)
|
|
1983
|
+
# Hex hash (32+ chars for MD5, mostly hex characters)
|
|
1984
|
+
hex_chars = sum(c in "0123456789abcdefABCDEF" for c in hash_value)
|
|
1985
|
+
if len(hash_value) >= 32 and hex_chars / len(hash_value) > 0.9:
|
|
1986
|
+
return True
|
|
1987
|
+
# Hash format like $1$salt$hash or $2a$... (bcrypt, etc.)
|
|
1988
|
+
if hash_value.startswith("$") and "$" in hash_value[1:]:
|
|
1989
|
+
return True
|
|
1990
|
+
# Kerberos hashes start with $krb5
|
|
1991
|
+
if hash_value.startswith("$krb5"):
|
|
1992
|
+
return True
|
|
1993
|
+
# NTLM:LM format (hash:hash)
|
|
1994
|
+
if ":" in hash_value and len(hash_value) >= 65:
|
|
1995
|
+
return True
|
|
1996
|
+
|
|
1997
|
+
# If it looks like plaintext (has spaces, common words), reject
|
|
1998
|
+
if " " in hash_value or len(hash_value) < 20:
|
|
1999
|
+
return False
|
|
2000
|
+
|
|
2001
|
+
return False
|
|
2002
|
+
|
|
2003
|
+
hashes = [c for c in creds if is_crackable_hash(c)]
|
|
1968
2004
|
|
|
1969
2005
|
if not hashes:
|
|
2006
|
+
click.echo()
|
|
1970
2007
|
click.echo(
|
|
1971
|
-
click.style(
|
|
2008
|
+
click.style(
|
|
2009
|
+
"No crackable hashes found in current engagement!", fg="yellow"
|
|
2010
|
+
)
|
|
1972
2011
|
)
|
|
2012
|
+
click.echo()
|
|
1973
2013
|
click.echo("Try discovering hashes first with:")
|
|
1974
2014
|
click.echo(" • secretsdump (extract SAM/NTDS)")
|
|
1975
2015
|
click.echo(" • GetNPUsers (AS-REP roasting)")
|
|
2016
|
+
click.echo(" • SQLMap (database password hashes)")
|
|
2017
|
+
click.echo()
|
|
2018
|
+
click.pause()
|
|
1976
2019
|
return None
|
|
1977
2020
|
|
|
1978
|
-
#
|
|
1979
|
-
|
|
1980
|
-
click.echo(
|
|
1981
|
-
click.style(
|
|
1982
|
-
f"Found {len(hashes)} hash(es) in current engagement:",
|
|
1983
|
-
fg="green",
|
|
1984
|
-
bold=True,
|
|
1985
|
-
)
|
|
1986
|
-
)
|
|
1987
|
-
click.echo()
|
|
2021
|
+
# Use interactive selector for hash list
|
|
2022
|
+
from souleyez.ui.interactive_selector import interactive_select
|
|
1988
2023
|
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
2024
|
+
# Prepare hash items with display-friendly fields
|
|
2025
|
+
hash_items = []
|
|
2026
|
+
for idx, cred in enumerate(hashes):
|
|
2027
|
+
hash_value = cred.get("password", "") or ""
|
|
2028
|
+
# Create preview of hash
|
|
2029
|
+
if len(hash_value) > 40:
|
|
2030
|
+
hash_preview = hash_value[:20] + "..." + hash_value[-10:]
|
|
2031
|
+
else:
|
|
2032
|
+
hash_preview = hash_value
|
|
1994
2033
|
|
|
1995
|
-
|
|
1996
|
-
|
|
2034
|
+
hash_items.append(
|
|
2035
|
+
{
|
|
2036
|
+
"idx": idx,
|
|
2037
|
+
"username": cred.get("username", "unknown")[:25],
|
|
2038
|
+
"hash_type": cred.get("credential_type", "unknown"),
|
|
2039
|
+
"hash_preview": hash_preview,
|
|
2040
|
+
"hash_value": hash_value, # Full hash for use
|
|
2041
|
+
"original": cred, # Keep original for later
|
|
2042
|
+
}
|
|
2043
|
+
)
|
|
1997
2044
|
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2045
|
+
columns = [
|
|
2046
|
+
{"name": "#", "width": 5, "key": "idx"},
|
|
2047
|
+
{"name": "Username", "width": 25, "key": "username"},
|
|
2048
|
+
{"name": "Type", "width": 20, "key": "hash_type"},
|
|
2049
|
+
{"name": "Hash Preview", "key": "hash_preview"},
|
|
2050
|
+
]
|
|
2051
|
+
|
|
2052
|
+
selected_ids = set()
|
|
2053
|
+
interactive_select(
|
|
2054
|
+
items=hash_items,
|
|
2055
|
+
columns=columns,
|
|
2056
|
+
selected_ids=selected_ids,
|
|
2057
|
+
get_id=lambda h: h["idx"],
|
|
2058
|
+
title="SELECT HASH TO CRACK",
|
|
2005
2059
|
)
|
|
2006
2060
|
|
|
2007
|
-
if
|
|
2061
|
+
if not selected_ids:
|
|
2008
2062
|
click.echo(click.style("Cancelled", fg="yellow"))
|
|
2009
2063
|
return None
|
|
2010
2064
|
|
|
2011
|
-
#
|
|
2012
|
-
|
|
2013
|
-
|
|
2065
|
+
# Get first selected hash
|
|
2066
|
+
selected_idx = list(selected_ids)[0]
|
|
2067
|
+
selected_item = hash_items[selected_idx]
|
|
2068
|
+
target = selected_item["hash_value"]
|
|
2014
2069
|
|
|
2015
|
-
if not target:
|
|
2070
|
+
if not target or target == "None":
|
|
2016
2071
|
click.echo(click.style("Hash is empty!", fg="red"))
|
|
2017
2072
|
return None
|
|
2018
2073
|
|
|
2019
2074
|
click.echo(
|
|
2020
2075
|
click.style(
|
|
2021
|
-
f"\n✓ Selected: {
|
|
2076
|
+
f"\n✓ Selected: {selected_item['username']} ({selected_item['hash_type']})",
|
|
2022
2077
|
fg="green",
|
|
2023
2078
|
)
|
|
2024
2079
|
)
|
|
@@ -2044,6 +2099,121 @@ def show_tool_menu(tool_name: str) -> Optional[Dict[str, Any]]:
|
|
|
2044
2099
|
em = EngagementManager()
|
|
2045
2100
|
current_eng = em.get_current()
|
|
2046
2101
|
|
|
2102
|
+
# Valid TLDs for filtering (common ones)
|
|
2103
|
+
valid_tlds = {
|
|
2104
|
+
"com",
|
|
2105
|
+
"org",
|
|
2106
|
+
"net",
|
|
2107
|
+
"edu",
|
|
2108
|
+
"gov",
|
|
2109
|
+
"mil",
|
|
2110
|
+
"int",
|
|
2111
|
+
"io",
|
|
2112
|
+
"co",
|
|
2113
|
+
"us",
|
|
2114
|
+
"uk",
|
|
2115
|
+
"ca",
|
|
2116
|
+
"au",
|
|
2117
|
+
"de",
|
|
2118
|
+
"fr",
|
|
2119
|
+
"jp",
|
|
2120
|
+
"cn",
|
|
2121
|
+
"ru",
|
|
2122
|
+
"br",
|
|
2123
|
+
"in",
|
|
2124
|
+
"mx",
|
|
2125
|
+
"es",
|
|
2126
|
+
"it",
|
|
2127
|
+
"nl",
|
|
2128
|
+
"se",
|
|
2129
|
+
"no",
|
|
2130
|
+
"fi",
|
|
2131
|
+
"dk",
|
|
2132
|
+
"pl",
|
|
2133
|
+
"cz",
|
|
2134
|
+
"at",
|
|
2135
|
+
"ch",
|
|
2136
|
+
"be",
|
|
2137
|
+
"ie",
|
|
2138
|
+
"nz",
|
|
2139
|
+
"za",
|
|
2140
|
+
"sg",
|
|
2141
|
+
"hk",
|
|
2142
|
+
"kr",
|
|
2143
|
+
"tw",
|
|
2144
|
+
"th",
|
|
2145
|
+
"my",
|
|
2146
|
+
"ph",
|
|
2147
|
+
"id",
|
|
2148
|
+
"vn",
|
|
2149
|
+
"ar",
|
|
2150
|
+
"cl",
|
|
2151
|
+
"co",
|
|
2152
|
+
"pe",
|
|
2153
|
+
"ve",
|
|
2154
|
+
"pt",
|
|
2155
|
+
"gr",
|
|
2156
|
+
"tr",
|
|
2157
|
+
"il",
|
|
2158
|
+
"ae",
|
|
2159
|
+
"sa",
|
|
2160
|
+
"eg",
|
|
2161
|
+
"ng",
|
|
2162
|
+
"ke",
|
|
2163
|
+
"info",
|
|
2164
|
+
"biz",
|
|
2165
|
+
"name",
|
|
2166
|
+
"pro",
|
|
2167
|
+
"mobi",
|
|
2168
|
+
"tv",
|
|
2169
|
+
"cc",
|
|
2170
|
+
"ws",
|
|
2171
|
+
"me",
|
|
2172
|
+
"ly",
|
|
2173
|
+
"to",
|
|
2174
|
+
"fm",
|
|
2175
|
+
"am",
|
|
2176
|
+
"gg",
|
|
2177
|
+
"xyz",
|
|
2178
|
+
"app",
|
|
2179
|
+
"dev",
|
|
2180
|
+
"cloud",
|
|
2181
|
+
"tech",
|
|
2182
|
+
"online",
|
|
2183
|
+
"site",
|
|
2184
|
+
"store",
|
|
2185
|
+
"shop",
|
|
2186
|
+
"blog",
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
# Local/invalid TLDs to exclude
|
|
2190
|
+
invalid_tlds = {"lan", "local", "home", "internal", "localdomain", "localhost"}
|
|
2191
|
+
|
|
2192
|
+
def is_valid_domain(domain):
|
|
2193
|
+
"""Check if domain is valid for OSINT tools."""
|
|
2194
|
+
if not domain or not isinstance(domain, str):
|
|
2195
|
+
return False
|
|
2196
|
+
# Must have at least one dot
|
|
2197
|
+
if "." not in domain:
|
|
2198
|
+
return False
|
|
2199
|
+
parts = domain.split(".")
|
|
2200
|
+
if len(parts) < 2:
|
|
2201
|
+
return False
|
|
2202
|
+
# Check TLD
|
|
2203
|
+
tld = parts[-1].lower()
|
|
2204
|
+
if tld in invalid_tlds:
|
|
2205
|
+
return False
|
|
2206
|
+
# Reject if TLD is all digits (partial IP)
|
|
2207
|
+
if tld.isdigit():
|
|
2208
|
+
return False
|
|
2209
|
+
# Reject if domain part is all digits (partial IP)
|
|
2210
|
+
if parts[-2].isdigit():
|
|
2211
|
+
return False
|
|
2212
|
+
# Must have valid TLD or at least 2 chars
|
|
2213
|
+
if tld not in valid_tlds and len(tld) < 2:
|
|
2214
|
+
return False
|
|
2215
|
+
return True
|
|
2216
|
+
|
|
2047
2217
|
discovered_domains = []
|
|
2048
2218
|
if current_eng:
|
|
2049
2219
|
hm = HostManager()
|
|
@@ -2054,11 +2224,10 @@ def show_tool_menu(tool_name: str) -> Optional[Dict[str, Any]]:
|
|
|
2054
2224
|
for host in all_hosts:
|
|
2055
2225
|
hostname = host.get("hostname")
|
|
2056
2226
|
if hostname and "." in hostname:
|
|
2057
|
-
# Extract domain (everything after first subdomain)
|
|
2058
2227
|
parts = hostname.split(".")
|
|
2059
2228
|
if len(parts) >= 2:
|
|
2060
|
-
domain = ".".join(parts[-2:])
|
|
2061
|
-
if domain not in discovered_domains:
|
|
2229
|
+
domain = ".".join(parts[-2:])
|
|
2230
|
+
if is_valid_domain(domain) and domain not in discovered_domains:
|
|
2062
2231
|
discovered_domains.append(domain)
|
|
2063
2232
|
|
|
2064
2233
|
# Also check OSINT data for hosts/domains (from theHarvester, etc.)
|
|
@@ -2067,77 +2236,109 @@ def show_tool_menu(tool_name: str) -> Optional[Dict[str, Any]]:
|
|
|
2067
2236
|
for osint_entry in osint_hosts:
|
|
2068
2237
|
hostname = osint_entry.get("value", "")
|
|
2069
2238
|
if hostname and "." in hostname:
|
|
2070
|
-
# Extract domain from hostname
|
|
2071
2239
|
parts = hostname.split(".")
|
|
2072
2240
|
if len(parts) >= 2:
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2241
|
+
domain = ".".join(parts[-2:])
|
|
2242
|
+
if (
|
|
2243
|
+
is_valid_domain(domain)
|
|
2244
|
+
and domain not in discovered_domains
|
|
2245
|
+
):
|
|
2078
2246
|
discovered_domains.append(domain)
|
|
2079
2247
|
except Exception:
|
|
2080
|
-
pass
|
|
2248
|
+
pass
|
|
2081
2249
|
|
|
2082
|
-
|
|
2250
|
+
# Show menu-based selection like Nmap
|
|
2251
|
+
domain_count = len(discovered_domains)
|
|
2252
|
+
if domain_count > 0:
|
|
2083
2253
|
click.echo(
|
|
2084
|
-
click.style(
|
|
2085
|
-
f"🎯 Discovered {len(discovered_domains)} domain(s) in current engagement:",
|
|
2086
|
-
fg="green",
|
|
2087
|
-
bold=True,
|
|
2088
|
-
)
|
|
2254
|
+
f"Found {click.style(str(domain_count), fg='green', bold=True)} valid domain(s) in current engagement."
|
|
2089
2255
|
)
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
click.echo(f" {idx}. {domain}")
|
|
2094
|
-
|
|
2095
|
-
if len(discovered_domains) > 10:
|
|
2096
|
-
click.echo(f" ... and {len(discovered_domains) - 10} more")
|
|
2256
|
+
else:
|
|
2257
|
+
click.echo(click.style("No valid domains discovered yet.", fg="yellow"))
|
|
2258
|
+
click.echo()
|
|
2097
2259
|
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
click.
|
|
2102
|
-
|
|
2103
|
-
click.style(" 💡 TIP: ", fg="yellow", bold=True)
|
|
2104
|
-
+ "Type "
|
|
2105
|
-
+ click.style("?", fg="cyan", bold=True)
|
|
2106
|
-
+ " for help guide"
|
|
2260
|
+
click.echo(
|
|
2261
|
+
" 1. Select from discovered domains"
|
|
2262
|
+
if domain_count > 0
|
|
2263
|
+
else click.style(
|
|
2264
|
+
" 1. Select from discovered domains (none found)", dim=True
|
|
2107
2265
|
)
|
|
2108
|
-
|
|
2266
|
+
)
|
|
2267
|
+
click.echo(" 2. Enter custom domain")
|
|
2268
|
+
click.echo(" [q] Back")
|
|
2269
|
+
click.echo()
|
|
2270
|
+
click.echo(
|
|
2271
|
+
click.style(" 💡 TIP: ", fg="yellow", bold=True)
|
|
2272
|
+
+ "Type "
|
|
2273
|
+
+ click.style("?", fg="cyan", bold=True)
|
|
2274
|
+
+ " for help guide"
|
|
2275
|
+
)
|
|
2276
|
+
click.echo()
|
|
2109
2277
|
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2278
|
+
choice = click.prompt(
|
|
2279
|
+
click.style("Select option", fg="green", bold=True),
|
|
2280
|
+
type=str,
|
|
2281
|
+
default="2" if domain_count == 0 else "1",
|
|
2282
|
+
show_default=False,
|
|
2283
|
+
).strip()
|
|
2116
2284
|
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2285
|
+
# Handle help command
|
|
2286
|
+
if choice == "?":
|
|
2287
|
+
show_tool_help(tool_name, help_info)
|
|
2288
|
+
DesignSystem.clear_screen()
|
|
2289
|
+
return {"action": "retry"}
|
|
2122
2290
|
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2291
|
+
# Handle back/quit
|
|
2292
|
+
if choice.lower() == "q":
|
|
2293
|
+
return {"action": "back"}
|
|
2126
2294
|
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2295
|
+
target = None
|
|
2296
|
+
if choice == "1" and domain_count > 0:
|
|
2297
|
+
# Use interactive selector for domain list
|
|
2298
|
+
from souleyez.ui.interactive_selector import interactive_select
|
|
2299
|
+
|
|
2300
|
+
# Prepare domain items
|
|
2301
|
+
domain_items = []
|
|
2302
|
+
for idx, domain in enumerate(discovered_domains):
|
|
2303
|
+
# Extract TLD for display
|
|
2304
|
+
parts = domain.split(".")
|
|
2305
|
+
tld = parts[-1].upper() if parts else ""
|
|
2306
|
+
domain_items.append(
|
|
2307
|
+
{
|
|
2308
|
+
"idx": idx,
|
|
2309
|
+
"domain": domain,
|
|
2310
|
+
"tld": f".{tld}",
|
|
2311
|
+
}
|
|
2137
2312
|
)
|
|
2313
|
+
|
|
2314
|
+
columns = [
|
|
2315
|
+
{"name": "#", "width": 5, "key": "idx"},
|
|
2316
|
+
{"name": "Domain", "key": "domain"},
|
|
2317
|
+
{"name": "TLD", "width": 8, "key": "tld"},
|
|
2318
|
+
]
|
|
2319
|
+
|
|
2320
|
+
selected_ids = set()
|
|
2321
|
+
interactive_select(
|
|
2322
|
+
items=domain_items,
|
|
2323
|
+
columns=columns,
|
|
2324
|
+
selected_ids=selected_ids,
|
|
2325
|
+
get_id=lambda d: d["idx"],
|
|
2326
|
+
title="SELECT TARGET DOMAIN",
|
|
2138
2327
|
)
|
|
2328
|
+
|
|
2329
|
+
if not selected_ids:
|
|
2330
|
+
return {"action": "back"}
|
|
2331
|
+
|
|
2332
|
+
# Get first selected domain
|
|
2333
|
+
selected_idx = list(selected_ids)[0]
|
|
2334
|
+
target = domain_items[selected_idx]["domain"]
|
|
2335
|
+
click.echo(click.style(f"\n✓ Selected: {target}", fg="green"))
|
|
2139
2336
|
click.echo()
|
|
2140
|
-
|
|
2337
|
+
elif choice == "2" or (choice == "1" and domain_count == 0):
|
|
2338
|
+
target = None # Will prompt for manual entry below
|
|
2339
|
+
else:
|
|
2340
|
+
click.echo(click.style("Invalid option!", fg="red"))
|
|
2341
|
+
return None
|
|
2141
2342
|
|
|
2142
2343
|
# Manual entry if not selected from list
|
|
2143
2344
|
if not target:
|
|
@@ -6144,10 +6345,12 @@ def view_jobs_menu():
|
|
|
6144
6345
|
"hashes"
|
|
6145
6346
|
):
|
|
6146
6347
|
critical_indicator = "[bold red]💀[/bold red]"
|
|
6147
|
-
# Shell access (evil_winrm, psexec success)
|
|
6148
|
-
elif tool in [
|
|
6149
|
-
"
|
|
6150
|
-
|
|
6348
|
+
# Shell access (evil_winrm, psexec, msf_exploit success)
|
|
6349
|
+
elif tool in [
|
|
6350
|
+
"evil_winrm",
|
|
6351
|
+
"impacket-psexec",
|
|
6352
|
+
"msf_exploit",
|
|
6353
|
+
] and parse_result.get("success"):
|
|
6151
6354
|
critical_indicator = "[bold green]🐚[/bold green]"
|
|
6152
6355
|
# Credentials found (hydra, hashcat cracked, smbpasswd, etc.)
|
|
6153
6356
|
else:
|
|
@@ -6229,8 +6432,8 @@ def view_jobs_menu():
|
|
|
6229
6432
|
click.echo()
|
|
6230
6433
|
|
|
6231
6434
|
# Build menu and legend side by side using Rich
|
|
6232
|
-
from rich.table import Table
|
|
6233
6435
|
from rich import box
|
|
6436
|
+
from rich.table import Table
|
|
6234
6437
|
|
|
6235
6438
|
console = Console()
|
|
6236
6439
|
|
|
@@ -11850,7 +12053,10 @@ def view_job_detail(job_id: int):
|
|
|
11850
12053
|
if web_login_test_handler:
|
|
11851
12054
|
try:
|
|
11852
12055
|
current_status = job.get("status", "")
|
|
11853
|
-
if
|
|
12056
|
+
if (
|
|
12057
|
+
current_status == "done"
|
|
12058
|
+
and web_login_test_handler.has_done_handler
|
|
12059
|
+
):
|
|
11854
12060
|
web_login_test_handler.display_done(
|
|
11855
12061
|
job, log_path, show_all_paths, show_passwords
|
|
11856
12062
|
)
|
|
@@ -14747,20 +14953,26 @@ def view_job_detail(job_id: int):
|
|
|
14747
14953
|
actions.append("n")
|
|
14748
14954
|
|
|
14749
14955
|
# [s] Spawn shell (for jobs with admin credentials)
|
|
14750
|
-
# Supported: evil_winrm, crackmapexec (Pwn3d!), nxc (Pwn3d!), secretsdump, psexec
|
|
14956
|
+
# Supported: evil_winrm, crackmapexec (Pwn3d!), nxc (Pwn3d! or SSH Shell access!), secretsdump, psexec
|
|
14751
14957
|
can_spawn_shell = False
|
|
14958
|
+
is_ssh_shell = False # Track if this is an SSH shell (for sshpass)
|
|
14752
14959
|
tool_name = job.get("tool", "")
|
|
14753
14960
|
|
|
14754
14961
|
if tool_name == "evil_winrm" and job.get("status") == "done":
|
|
14755
14962
|
if parse_result and parse_result.get("success"):
|
|
14756
14963
|
can_spawn_shell = True
|
|
14757
14964
|
elif tool_name in ["crackmapexec", "nxc"] and job.get("status") == "done":
|
|
14758
|
-
# Check if Pwn3d!
|
|
14965
|
+
# Check if Pwn3d! or SSH Shell access! in log
|
|
14759
14966
|
if log_path and os.path.exists(log_path):
|
|
14760
14967
|
try:
|
|
14761
14968
|
with open(log_path, "r", encoding="utf-8", errors="replace") as f:
|
|
14762
|
-
|
|
14969
|
+
log_content = f.read()
|
|
14970
|
+
if "Pwn3d!" in log_content:
|
|
14763
14971
|
can_spawn_shell = True
|
|
14972
|
+
elif "Shell access!" in log_content:
|
|
14973
|
+
# SSH shell access (Linux) - use sshpass
|
|
14974
|
+
can_spawn_shell = True
|
|
14975
|
+
is_ssh_shell = True
|
|
14764
14976
|
except Exception:
|
|
14765
14977
|
pass
|
|
14766
14978
|
elif tool_name == "impacket-secretsdump" and job.get("status") == "done":
|
|
@@ -14769,6 +14981,46 @@ def view_job_detail(job_id: int):
|
|
|
14769
14981
|
elif tool_name == "impacket-psexec" and job.get("status") == "done":
|
|
14770
14982
|
# psexec with done status means we have working shell access
|
|
14771
14983
|
can_spawn_shell = True
|
|
14984
|
+
elif tool_name == "msf_auxiliary" and job.get("status") == "done":
|
|
14985
|
+
# Check if msf_auxiliary found SSH or telnet credentials
|
|
14986
|
+
if parse_result:
|
|
14987
|
+
creds = parse_result.get("credentials", [])
|
|
14988
|
+
# Check for SSH or telnet credentials
|
|
14989
|
+
for cred in creds if isinstance(creds, list) else []:
|
|
14990
|
+
service = (
|
|
14991
|
+
cred.get("service", "").lower()
|
|
14992
|
+
if isinstance(cred, dict)
|
|
14993
|
+
else ""
|
|
14994
|
+
)
|
|
14995
|
+
if service in ["ssh", "telnet"]:
|
|
14996
|
+
can_spawn_shell = True
|
|
14997
|
+
if service == "ssh":
|
|
14998
|
+
is_ssh_shell = True
|
|
14999
|
+
break
|
|
15000
|
+
elif tool_name == "hydra" and job.get("status") == "done":
|
|
15001
|
+
# Hydra found valid credentials - check if SSH or telnet
|
|
15002
|
+
if parse_result:
|
|
15003
|
+
service = parse_result.get("service", "").lower()
|
|
15004
|
+
creds = parse_result.get("credentials", [])
|
|
15005
|
+
if service in ["ssh", "telnet", "ftp"] and creds:
|
|
15006
|
+
can_spawn_shell = True
|
|
15007
|
+
if service == "ssh":
|
|
15008
|
+
is_ssh_shell = True
|
|
15009
|
+
# Also check log for session opened
|
|
15010
|
+
if not can_spawn_shell and log_path and os.path.exists(log_path):
|
|
15011
|
+
try:
|
|
15012
|
+
with open(log_path, "r", encoding="utf-8", errors="replace") as f:
|
|
15013
|
+
log_content = f.read()
|
|
15014
|
+
if (
|
|
15015
|
+
"session" in log_content.lower()
|
|
15016
|
+
and "opened" in log_content.lower()
|
|
15017
|
+
):
|
|
15018
|
+
can_spawn_shell = True
|
|
15019
|
+
# Check if SSH or telnet
|
|
15020
|
+
if "ssh" in log_content.lower():
|
|
15021
|
+
is_ssh_shell = True
|
|
15022
|
+
except Exception:
|
|
15023
|
+
pass
|
|
14772
15024
|
|
|
14773
15025
|
if can_spawn_shell:
|
|
14774
15026
|
click.echo(
|
|
@@ -14986,6 +15238,39 @@ def view_job_detail(job_id: int):
|
|
|
14986
15238
|
if not target:
|
|
14987
15239
|
target = match.group(4)
|
|
14988
15240
|
|
|
15241
|
+
elif tool_name == "msf_auxiliary":
|
|
15242
|
+
# Get credentials from parse_result
|
|
15243
|
+
if parse_result:
|
|
15244
|
+
creds = parse_result.get("credentials", [])
|
|
15245
|
+
if isinstance(creds, list) and creds:
|
|
15246
|
+
# Find first SSH or telnet credential
|
|
15247
|
+
for cred in creds:
|
|
15248
|
+
if isinstance(cred, dict):
|
|
15249
|
+
service = cred.get("service", "").lower()
|
|
15250
|
+
if service in ["ssh", "telnet"]:
|
|
15251
|
+
username = cred.get("username")
|
|
15252
|
+
password = cred.get("password")
|
|
15253
|
+
break
|
|
15254
|
+
# If no SSH/telnet, use first credential
|
|
15255
|
+
if not username and creds:
|
|
15256
|
+
first_cred = creds[0]
|
|
15257
|
+
if isinstance(first_cred, dict):
|
|
15258
|
+
username = first_cred.get("username")
|
|
15259
|
+
password = first_cred.get("password")
|
|
15260
|
+
|
|
15261
|
+
elif tool_name == "hydra":
|
|
15262
|
+
# Get credentials from parse_result
|
|
15263
|
+
if parse_result:
|
|
15264
|
+
creds = parse_result.get("credentials", [])
|
|
15265
|
+
if isinstance(creds, list) and creds:
|
|
15266
|
+
# Use first credential
|
|
15267
|
+
first_cred = creds[0]
|
|
15268
|
+
if isinstance(first_cred, dict):
|
|
15269
|
+
username = first_cred.get("username") or first_cred.get(
|
|
15270
|
+
"login"
|
|
15271
|
+
)
|
|
15272
|
+
password = first_cred.get("password")
|
|
15273
|
+
|
|
14989
15274
|
if not username or (not password and not nt_hash):
|
|
14990
15275
|
click.echo(
|
|
14991
15276
|
click.style(
|
|
@@ -15025,47 +15310,185 @@ def view_job_detail(job_id: int):
|
|
|
15025
15310
|
f"impacket-psexec '{cred_prefix}:{password}@{target}'"
|
|
15026
15311
|
)
|
|
15027
15312
|
|
|
15028
|
-
|
|
15029
|
-
#
|
|
15030
|
-
|
|
15031
|
-
|
|
15032
|
-
|
|
15033
|
-
|
|
15034
|
-
|
|
15035
|
-
|
|
15036
|
-
|
|
15037
|
-
|
|
15038
|
-
click.echo(" [1] evil-winrm (WinRM - port 5985)")
|
|
15039
|
-
click.echo(" [2] psexec (SMB - port 445)")
|
|
15040
|
-
click.echo(" [q] Cancel")
|
|
15041
|
-
click.echo()
|
|
15313
|
+
elif tool_name == "msf_auxiliary":
|
|
15314
|
+
# msf_auxiliary found credentials - determine service type
|
|
15315
|
+
service_type = None
|
|
15316
|
+
if parse_result:
|
|
15317
|
+
creds = parse_result.get("credentials", [])
|
|
15318
|
+
for cred in creds if isinstance(creds, list) else []:
|
|
15319
|
+
if isinstance(cred, dict):
|
|
15320
|
+
service_type = cred.get("service", "").lower()
|
|
15321
|
+
if service_type in ["ssh", "telnet"]:
|
|
15322
|
+
break
|
|
15042
15323
|
|
|
15043
|
-
|
|
15044
|
-
|
|
15045
|
-
|
|
15324
|
+
if service_type == "ssh" or is_ssh_shell:
|
|
15325
|
+
# SSH - use sshpass
|
|
15326
|
+
shell_cmd = f"sshpass -p '{password}' ssh -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group1-sha1 -o HostKeyAlgorithms=+ssh-rsa {username}@{target}"
|
|
15327
|
+
elif service_type == "telnet":
|
|
15328
|
+
# Telnet - show command to run manually (telnet doesn't support password on cmdline easily)
|
|
15329
|
+
click.echo()
|
|
15330
|
+
click.echo(click.style("=" * 70, fg="green"))
|
|
15331
|
+
click.echo(click.style("TELNET SHELL", bold=True, fg="green"))
|
|
15332
|
+
click.echo(click.style("=" * 70, fg="green"))
|
|
15333
|
+
click.echo()
|
|
15334
|
+
click.echo(f" Target: {target}")
|
|
15335
|
+
click.echo(f" User: {username}")
|
|
15336
|
+
click.echo(f" Pass: {password}")
|
|
15337
|
+
click.echo()
|
|
15338
|
+
click.echo(
|
|
15339
|
+
" Launching telnet... Enter password when prompted."
|
|
15340
|
+
)
|
|
15341
|
+
click.echo()
|
|
15342
|
+
shell_cmd = f"telnet {target}"
|
|
15343
|
+
else:
|
|
15344
|
+
# Unknown service - show menu
|
|
15345
|
+
click.echo()
|
|
15346
|
+
click.echo(click.style("=" * 70, fg="green"))
|
|
15347
|
+
click.echo(click.style("SPAWN SHELL", bold=True, fg="green"))
|
|
15348
|
+
click.echo(click.style("=" * 70, fg="green"))
|
|
15349
|
+
click.echo()
|
|
15350
|
+
click.echo(f" Target: {target}")
|
|
15351
|
+
click.echo(f" User: {username}")
|
|
15352
|
+
click.echo()
|
|
15353
|
+
click.echo(" [1] ssh (SSH - port 22)")
|
|
15354
|
+
click.echo(" [2] telnet (Telnet - port 23)")
|
|
15355
|
+
click.echo(" [q] Cancel")
|
|
15356
|
+
click.echo()
|
|
15046
15357
|
|
|
15047
|
-
|
|
15048
|
-
|
|
15358
|
+
shell_choice = click.prompt(
|
|
15359
|
+
"Select shell type", type=str, default="1"
|
|
15360
|
+
).strip()
|
|
15049
15361
|
|
|
15050
|
-
|
|
15051
|
-
|
|
15052
|
-
|
|
15053
|
-
shell_cmd =
|
|
15054
|
-
|
|
15055
|
-
)
|
|
15362
|
+
if shell_choice == "q":
|
|
15363
|
+
continue
|
|
15364
|
+
elif shell_choice == "1":
|
|
15365
|
+
shell_cmd = f"sshpass -p '{password}' ssh -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group1-sha1 -o HostKeyAlgorithms=+ssh-rsa {username}@{target}"
|
|
15366
|
+
elif shell_choice == "2":
|
|
15367
|
+
click.echo(f"\n Password: {password}")
|
|
15368
|
+
shell_cmd = f"telnet {target}"
|
|
15056
15369
|
else:
|
|
15057
|
-
|
|
15058
|
-
|
|
15059
|
-
|
|
15060
|
-
|
|
15061
|
-
|
|
15370
|
+
click.echo(click.style(" Invalid choice", fg="red"))
|
|
15371
|
+
continue
|
|
15372
|
+
|
|
15373
|
+
elif tool_name == "hydra":
|
|
15374
|
+
# Hydra found valid credentials - determine service type
|
|
15375
|
+
service_type = (
|
|
15376
|
+
parse_result.get("service", "").lower() if parse_result else ""
|
|
15377
|
+
)
|
|
15378
|
+
|
|
15379
|
+
if service_type == "ssh" or is_ssh_shell:
|
|
15380
|
+
# SSH - use sshpass
|
|
15381
|
+
shell_cmd = f"sshpass -p '{password}' ssh -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group1-sha1 -o HostKeyAlgorithms=+ssh-rsa {username}@{target}"
|
|
15382
|
+
elif service_type == "telnet":
|
|
15383
|
+
# Telnet
|
|
15384
|
+
click.echo()
|
|
15385
|
+
click.echo(click.style("=" * 70, fg="green"))
|
|
15386
|
+
click.echo(click.style("TELNET SHELL", bold=True, fg="green"))
|
|
15387
|
+
click.echo(click.style("=" * 70, fg="green"))
|
|
15388
|
+
click.echo()
|
|
15389
|
+
click.echo(f" Target: {target}")
|
|
15390
|
+
click.echo(f" User: {username}")
|
|
15391
|
+
click.echo(f" Pass: {password}")
|
|
15392
|
+
click.echo()
|
|
15393
|
+
click.echo(
|
|
15394
|
+
" Launching telnet... Enter password when prompted."
|
|
15395
|
+
)
|
|
15396
|
+
click.echo()
|
|
15397
|
+
shell_cmd = f"telnet {target}"
|
|
15398
|
+
elif service_type == "ftp":
|
|
15399
|
+
# FTP
|
|
15400
|
+
click.echo()
|
|
15401
|
+
click.echo(click.style("=" * 70, fg="green"))
|
|
15402
|
+
click.echo(click.style("FTP SHELL", bold=True, fg="green"))
|
|
15403
|
+
click.echo(click.style("=" * 70, fg="green"))
|
|
15404
|
+
click.echo()
|
|
15405
|
+
click.echo(f" Target: {target}")
|
|
15406
|
+
click.echo(f" User: {username}")
|
|
15407
|
+
click.echo(f" Pass: {password}")
|
|
15408
|
+
click.echo()
|
|
15409
|
+
shell_cmd = f"ftp {target}"
|
|
15410
|
+
else:
|
|
15411
|
+
# Unknown service - show menu
|
|
15412
|
+
click.echo()
|
|
15413
|
+
click.echo(click.style("=" * 70, fg="green"))
|
|
15414
|
+
click.echo(click.style("SPAWN SHELL", bold=True, fg="green"))
|
|
15415
|
+
click.echo(click.style("=" * 70, fg="green"))
|
|
15416
|
+
click.echo()
|
|
15417
|
+
click.echo(f" Target: {target}")
|
|
15418
|
+
click.echo(f" User: {username}")
|
|
15419
|
+
click.echo(f" Service: {service_type or 'unknown'}")
|
|
15420
|
+
click.echo()
|
|
15421
|
+
click.echo(" [1] ssh (SSH - port 22)")
|
|
15422
|
+
click.echo(" [2] telnet (Telnet - port 23)")
|
|
15423
|
+
click.echo(" [3] ftp (FTP - port 21)")
|
|
15424
|
+
click.echo(" [q] Cancel")
|
|
15425
|
+
click.echo()
|
|
15426
|
+
|
|
15427
|
+
shell_choice = click.prompt(
|
|
15428
|
+
"Select shell type", type=str, default="1"
|
|
15429
|
+
).strip()
|
|
15430
|
+
|
|
15431
|
+
if shell_choice == "q":
|
|
15432
|
+
continue
|
|
15433
|
+
elif shell_choice == "1":
|
|
15434
|
+
shell_cmd = f"sshpass -p '{password}' ssh -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group1-sha1 -o HostKeyAlgorithms=+ssh-rsa {username}@{target}"
|
|
15435
|
+
elif shell_choice == "2":
|
|
15436
|
+
click.echo(f"\n Password: {password}")
|
|
15437
|
+
shell_cmd = f"telnet {target}"
|
|
15438
|
+
elif shell_choice == "3":
|
|
15439
|
+
click.echo(f"\n Password: {password}")
|
|
15440
|
+
shell_cmd = f"ftp {target}"
|
|
15062
15441
|
else:
|
|
15063
|
-
|
|
15064
|
-
|
|
15065
|
-
|
|
15442
|
+
click.echo(click.style(" Invalid choice", fg="red"))
|
|
15443
|
+
continue
|
|
15444
|
+
|
|
15445
|
+
else:
|
|
15446
|
+
# For credential jobs (crackmapexec, nxc, secretsdump) - show menu
|
|
15447
|
+
# Check if this is an SSH shell (set by detection logic above)
|
|
15448
|
+
if is_ssh_shell:
|
|
15449
|
+
# SSH shell - use sshpass directly
|
|
15450
|
+
shell_cmd = f"sshpass -p '{password}' ssh -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group1-sha1 -o HostKeyAlgorithms=+ssh-rsa {username}@{target}"
|
|
15066
15451
|
else:
|
|
15067
|
-
|
|
15068
|
-
|
|
15452
|
+
# Windows shell options menu
|
|
15453
|
+
click.echo()
|
|
15454
|
+
click.echo(click.style("=" * 70, fg="green"))
|
|
15455
|
+
click.echo(click.style("SPAWN SHELL", bold=True, fg="green"))
|
|
15456
|
+
click.echo(click.style("=" * 70, fg="green"))
|
|
15457
|
+
click.echo()
|
|
15458
|
+
click.echo(f" Target: {target}")
|
|
15459
|
+
click.echo(f" User: {cred_prefix}")
|
|
15460
|
+
click.echo()
|
|
15461
|
+
click.echo(" [1] evil-winrm (WinRM - port 5985)")
|
|
15462
|
+
click.echo(" [2] psexec (SMB - port 445)")
|
|
15463
|
+
click.echo(" [3] ssh (SSH - port 22, requires sshpass)")
|
|
15464
|
+
click.echo(" [q] Cancel")
|
|
15465
|
+
click.echo()
|
|
15466
|
+
|
|
15467
|
+
shell_choice = click.prompt(
|
|
15468
|
+
"Select shell type", type=str, default="2"
|
|
15469
|
+
).strip()
|
|
15470
|
+
|
|
15471
|
+
if shell_choice == "q":
|
|
15472
|
+
continue
|
|
15473
|
+
|
|
15474
|
+
if shell_choice == "1":
|
|
15475
|
+
# evil-winrm
|
|
15476
|
+
if nt_hash:
|
|
15477
|
+
shell_cmd = f"evil-winrm -i {target} -u '{username}' -H '{nt_hash}'"
|
|
15478
|
+
else:
|
|
15479
|
+
shell_cmd = f"evil-winrm -i {target} -u '{username}' -p '{password}'"
|
|
15480
|
+
elif shell_choice == "2":
|
|
15481
|
+
# psexec
|
|
15482
|
+
if nt_hash:
|
|
15483
|
+
shell_cmd = f"impacket-psexec '{cred_prefix}@{target}' -hashes ':{nt_hash}'"
|
|
15484
|
+
else:
|
|
15485
|
+
shell_cmd = f"impacket-psexec '{cred_prefix}:{password}@{target}'"
|
|
15486
|
+
elif shell_choice == "3":
|
|
15487
|
+
# SSH with sshpass
|
|
15488
|
+
shell_cmd = f"sshpass -p '{password}' ssh -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group1-sha1 -o HostKeyAlgorithms=+ssh-rsa {username}@{target}"
|
|
15489
|
+
else:
|
|
15490
|
+
click.echo(click.style(" Invalid choice", fg="red"))
|
|
15491
|
+
continue
|
|
15069
15492
|
|
|
15070
15493
|
if not shell_cmd:
|
|
15071
15494
|
click.echo(
|
|
@@ -16837,6 +17260,10 @@ def _license_management_menu():
|
|
|
16837
17260
|
_bypass_validation=True, # Already validated
|
|
16838
17261
|
)
|
|
16839
17262
|
if tier_success:
|
|
17263
|
+
# Refresh in-memory user cache so PRO is active immediately
|
|
17264
|
+
from souleyez.auth import get_session_manager
|
|
17265
|
+
|
|
17266
|
+
get_session_manager().set_current_user(None)
|
|
16840
17267
|
click.echo(
|
|
16841
17268
|
click.style(
|
|
16842
17269
|
f" ✓ User '{user.username}' upgraded to PRO tier",
|
|
@@ -16846,8 +17273,8 @@ def _license_management_menu():
|
|
|
16846
17273
|
click.echo()
|
|
16847
17274
|
click.echo(
|
|
16848
17275
|
click.style(
|
|
16849
|
-
"
|
|
16850
|
-
fg="
|
|
17276
|
+
" 💎 PRO features now unlocked!",
|
|
17277
|
+
fg="green",
|
|
16851
17278
|
)
|
|
16852
17279
|
)
|
|
16853
17280
|
else:
|
|
@@ -16982,13 +17409,18 @@ def _license_management_menu():
|
|
|
16982
17409
|
|
|
16983
17410
|
# Reset ALL users with PRO tier to FREE
|
|
16984
17411
|
try:
|
|
16985
|
-
from souleyez.auth import
|
|
17412
|
+
from souleyez.auth import (
|
|
17413
|
+
UserManager,
|
|
17414
|
+
get_session_manager,
|
|
17415
|
+
)
|
|
16986
17416
|
from souleyez.storage.database import get_db
|
|
16987
17417
|
|
|
16988
17418
|
user_mgr = UserManager(get_db().db_path)
|
|
16989
17419
|
count, _ = user_mgr.reset_all_pro_tiers()
|
|
16990
17420
|
if count > 0:
|
|
16991
17421
|
click.echo(f" Reset {count} user(s) to FREE tier.")
|
|
17422
|
+
# Refresh in-memory user cache
|
|
17423
|
+
get_session_manager().set_current_user(None)
|
|
16992
17424
|
except Exception:
|
|
16993
17425
|
pass
|
|
16994
17426
|
else:
|
|
@@ -23423,61 +23855,6 @@ def manage_credentials_menu():
|
|
|
23423
23855
|
break
|
|
23424
23856
|
|
|
23425
23857
|
|
|
23426
|
-
def view_additional_data_menu():
|
|
23427
|
-
"""Additional data viewing menu for OSINT and Web Paths."""
|
|
23428
|
-
em = EngagementManager()
|
|
23429
|
-
current_ws = em.get_current()
|
|
23430
|
-
|
|
23431
|
-
if not current_ws:
|
|
23432
|
-
click.echo(click.style("No engagement selected!", fg="red"))
|
|
23433
|
-
click.pause()
|
|
23434
|
-
return
|
|
23435
|
-
|
|
23436
|
-
engagement_id = current_ws["id"]
|
|
23437
|
-
|
|
23438
|
-
while True:
|
|
23439
|
-
DesignSystem.clear_screen()
|
|
23440
|
-
click.echo("\n" + "=" * 70)
|
|
23441
|
-
click.echo("ADDITIONAL DATA")
|
|
23442
|
-
click.echo("=" * 70 + "\n")
|
|
23443
|
-
|
|
23444
|
-
click.echo(" 1. Web Paths - View and manage discovered web paths")
|
|
23445
|
-
click.echo(" 2. SMB Shares - View and manage enumerated SMB shares")
|
|
23446
|
-
click.echo(
|
|
23447
|
-
" 3. SQLMap Data - View SQL injection discoveries and databases"
|
|
23448
|
-
)
|
|
23449
|
-
click.echo()
|
|
23450
|
-
click.echo(" [q] ← Back")
|
|
23451
|
-
click.echo()
|
|
23452
|
-
|
|
23453
|
-
try:
|
|
23454
|
-
choice_input = click.prompt(
|
|
23455
|
-
"Select data type", type=str, default="q"
|
|
23456
|
-
).strip()
|
|
23457
|
-
|
|
23458
|
-
if choice_input == "q":
|
|
23459
|
-
return
|
|
23460
|
-
try:
|
|
23461
|
-
choice = int(choice_input)
|
|
23462
|
-
except ValueError:
|
|
23463
|
-
click.echo(click.style("Invalid selection!", fg="red"))
|
|
23464
|
-
click.pause()
|
|
23465
|
-
continue
|
|
23466
|
-
|
|
23467
|
-
if choice == 1:
|
|
23468
|
-
view_web_paths(engagement_id)
|
|
23469
|
-
elif choice == 2:
|
|
23470
|
-
view_smb_shares(engagement_id)
|
|
23471
|
-
elif choice == 3:
|
|
23472
|
-
view_sqlmap_data(engagement_id)
|
|
23473
|
-
else:
|
|
23474
|
-
click.echo(click.style("Invalid selection!", fg="red"))
|
|
23475
|
-
click.pause()
|
|
23476
|
-
|
|
23477
|
-
except (KeyboardInterrupt, click.Abort):
|
|
23478
|
-
return
|
|
23479
|
-
|
|
23480
|
-
|
|
23481
23858
|
def view_additional_data_menu():
|
|
23482
23859
|
"""Additional data viewing menu for OSINT and Web Paths."""
|
|
23483
23860
|
em = EngagementManager()
|
|
@@ -27526,7 +27903,13 @@ def view_credentials(engagement_id: int):
|
|
|
27526
27903
|
return
|
|
27527
27904
|
|
|
27528
27905
|
# Active filters
|
|
27529
|
-
filters = {
|
|
27906
|
+
filters = {
|
|
27907
|
+
"service": None,
|
|
27908
|
+
"status": None,
|
|
27909
|
+
"host_id": None,
|
|
27910
|
+
"tool": None,
|
|
27911
|
+
"credential_type": None,
|
|
27912
|
+
}
|
|
27530
27913
|
|
|
27531
27914
|
# Pagination
|
|
27532
27915
|
PAGE_SIZE = 20
|
|
@@ -27574,6 +27957,14 @@ def view_credentials(engagement_id: int):
|
|
|
27574
27957
|
if filters["tool"] and credentials:
|
|
27575
27958
|
credentials = [c for c in credentials if c.get("tool") == filters["tool"]]
|
|
27576
27959
|
|
|
27960
|
+
# Apply credential_type filter
|
|
27961
|
+
if filters["credential_type"] and credentials:
|
|
27962
|
+
credentials = [
|
|
27963
|
+
c
|
|
27964
|
+
for c in credentials
|
|
27965
|
+
if c.get("credential_type") == filters["credential_type"]
|
|
27966
|
+
]
|
|
27967
|
+
|
|
27577
27968
|
# Pagination calculations
|
|
27578
27969
|
total_creds = len(credentials) if credentials else 0
|
|
27579
27970
|
total_pages = (
|
|
@@ -27697,6 +28088,7 @@ def view_credentials(engagement_id: int):
|
|
|
27697
28088
|
click.echo(" [a] Add - Manually add credential")
|
|
27698
28089
|
click.echo(" [f] Filter - Filter by service/status/host")
|
|
27699
28090
|
click.echo(" [o] Tool - Filter by tool")
|
|
28091
|
+
click.echo(" [y] Type - Filter by credential type (password/hash/username)")
|
|
27700
28092
|
click.echo(" [c] Clear filters - Reset all filters")
|
|
27701
28093
|
click.echo(" [q] Back")
|
|
27702
28094
|
click.echo()
|
|
@@ -27798,6 +28190,12 @@ def view_credentials(engagement_id: int):
|
|
|
27798
28190
|
# Filter by tool
|
|
27799
28191
|
filters["tool"] = _filter_credential_by_tool(engagement_id, cm)
|
|
27800
28192
|
current_page = 1
|
|
28193
|
+
elif choice == "y":
|
|
28194
|
+
# Filter by credential type
|
|
28195
|
+
filters["credential_type"] = _filter_credential_by_type(
|
|
28196
|
+
engagement_id, cm
|
|
28197
|
+
)
|
|
28198
|
+
current_page = 1
|
|
27801
28199
|
elif choice == "c":
|
|
27802
28200
|
filters = {k: None for k in filters}
|
|
27803
28201
|
current_page = 1
|
|
@@ -28102,6 +28500,50 @@ def _filter_credential_by_tool(engagement_id: int, cm: "CredentialsManager"):
|
|
|
28102
28500
|
return None
|
|
28103
28501
|
|
|
28104
28502
|
|
|
28503
|
+
def _filter_credential_by_type(engagement_id: int, cm: "CredentialsManager"):
|
|
28504
|
+
"""Prompt for credential type filter."""
|
|
28505
|
+
credentials = cm.list_credentials(engagement_id, decrypt=False)
|
|
28506
|
+
types = sorted(
|
|
28507
|
+
set([c.get("credential_type") for c in credentials if c.get("credential_type")])
|
|
28508
|
+
)
|
|
28509
|
+
|
|
28510
|
+
if not types:
|
|
28511
|
+
click.echo(click.style("\nNo credential types found.", fg="yellow"))
|
|
28512
|
+
click.pause()
|
|
28513
|
+
return None
|
|
28514
|
+
|
|
28515
|
+
# Add friendly names for common types
|
|
28516
|
+
type_labels = {
|
|
28517
|
+
"password": "password (tested credentials)",
|
|
28518
|
+
"hash": "hash (NTLM/other hashes)",
|
|
28519
|
+
"username": "username (enumerated users only)",
|
|
28520
|
+
"kerberos_tgs": "kerberos_tgs (Kerberoast hashes)",
|
|
28521
|
+
"plaintext": "plaintext (GPP/unencrypted)",
|
|
28522
|
+
"database": "database (DB credentials)",
|
|
28523
|
+
"smb": "smb (SMB credentials)",
|
|
28524
|
+
}
|
|
28525
|
+
|
|
28526
|
+
click.echo("\nAvailable credential types:")
|
|
28527
|
+
click.echo(" [q] Clear filter")
|
|
28528
|
+
for idx, ctype in enumerate(types, 1):
|
|
28529
|
+
count = sum(1 for c in credentials if c.get("credential_type") == ctype)
|
|
28530
|
+
label = type_labels.get(ctype, ctype)
|
|
28531
|
+
click.echo(f" [{idx}] {label} ({count})")
|
|
28532
|
+
|
|
28533
|
+
try:
|
|
28534
|
+
choice_input = click.prompt("Select type", type=str, default="q").strip()
|
|
28535
|
+
if choice_input == "q":
|
|
28536
|
+
return None
|
|
28537
|
+
try:
|
|
28538
|
+
choice = int(choice_input)
|
|
28539
|
+
if 1 <= choice <= len(types):
|
|
28540
|
+
return types[choice - 1]
|
|
28541
|
+
except ValueError:
|
|
28542
|
+
return None
|
|
28543
|
+
except (KeyboardInterrupt, click.Abort):
|
|
28544
|
+
return None
|
|
28545
|
+
|
|
28546
|
+
|
|
28105
28547
|
def _filter_credential_by_host(engagement_id: int):
|
|
28106
28548
|
"""Prompt for host filter."""
|
|
28107
28549
|
from souleyez.storage.hosts import HostManager
|
|
@@ -31065,7 +31507,9 @@ def _execute_webpath_quick_action(engagement_id: int, paths: list, hm: "HostMana
|
|
|
31065
31507
|
" ⚙️ Launching Gobuster for test directories...", fg="yellow"
|
|
31066
31508
|
)
|
|
31067
31509
|
)
|
|
31068
|
-
click.echo(
|
|
31510
|
+
click.echo(
|
|
31511
|
+
click.style(" ℹ️ Gobuster integration coming soon!", fg="cyan")
|
|
31512
|
+
)
|
|
31069
31513
|
click.echo(" For now, manually run:")
|
|
31070
31514
|
for path in test_dirs[:3]:
|
|
31071
31515
|
url = path.get("url", "")
|
|
@@ -34614,7 +35058,7 @@ def _view_sqlmap_dumped_data(engagement_id: int):
|
|
|
34614
35058
|
|
|
34615
35059
|
for table in tables_with_dumps:
|
|
34616
35060
|
console.print(
|
|
34617
|
-
f"
|
|
35061
|
+
f" [cyan]{table['table_name']}[/cyan] ({table['row_count']} rows)"
|
|
34618
35062
|
)
|
|
34619
35063
|
|
|
34620
35064
|
# Get dumped data
|
|
@@ -35267,18 +35711,24 @@ def view_sqlmap_data(engagement_id: int):
|
|
|
35267
35711
|
all_columns[key] = set()
|
|
35268
35712
|
all_columns[key].update(columns)
|
|
35269
35713
|
|
|
35270
|
-
# Aggregate dumped data
|
|
35714
|
+
# Aggregate dumped data (track by target URL)
|
|
35715
|
+
job_target = job.get("target", "")
|
|
35271
35716
|
if parsed.get("dumped_data"):
|
|
35272
35717
|
for key, data in parsed["dumped_data"].items():
|
|
35273
|
-
|
|
35274
|
-
|
|
35718
|
+
# Create target-specific key to avoid cross-host bleeding
|
|
35719
|
+
target_key = f"{job_target}::{key}"
|
|
35720
|
+
if target_key not in all_dumped:
|
|
35721
|
+
data["_target"] = job_target
|
|
35722
|
+
all_dumped[target_key] = data
|
|
35275
35723
|
else:
|
|
35276
35724
|
# Merge rows if same table dumped multiple times
|
|
35277
|
-
existing_rows = all_dumped[
|
|
35725
|
+
existing_rows = all_dumped[target_key].get("rows", [])
|
|
35278
35726
|
new_rows = data.get("rows", [])
|
|
35279
|
-
all_dumped[
|
|
35280
|
-
|
|
35281
|
-
|
|
35727
|
+
all_dumped[target_key]["rows"] = (
|
|
35728
|
+
existing_rows + new_rows
|
|
35729
|
+
)
|
|
35730
|
+
all_dumped[target_key]["row_count"] = len(
|
|
35731
|
+
all_dumped[target_key]["rows"]
|
|
35282
35732
|
)
|
|
35283
35733
|
|
|
35284
35734
|
for vuln in parsed.get("vulnerabilities", []):
|
|
@@ -35433,13 +35883,51 @@ def view_sqlmap_data(engagement_id: int):
|
|
|
35433
35883
|
row_num = int(choice)
|
|
35434
35884
|
if 1 <= row_num <= len(filtered_injections):
|
|
35435
35885
|
inj = filtered_injections[row_num - 1]
|
|
35886
|
+
inj_url = inj.get("url", "")
|
|
35887
|
+
|
|
35888
|
+
# Filter databases to only this injection's target
|
|
35889
|
+
filtered_dbs = set()
|
|
35890
|
+
filtered_db_to_dbms = {}
|
|
35891
|
+
for db in all_databases:
|
|
35892
|
+
db_target = db_to_target.get(db, "")
|
|
35893
|
+
if db_target in inj_url or inj_url in db_target:
|
|
35894
|
+
filtered_dbs.add(db)
|
|
35895
|
+
if db in db_to_dbms:
|
|
35896
|
+
filtered_db_to_dbms[db] = db_to_dbms[db]
|
|
35897
|
+
|
|
35898
|
+
# Filter tables to only this injection's target
|
|
35899
|
+
filtered_tables = {}
|
|
35900
|
+
filtered_columns = {}
|
|
35901
|
+
for db, tables in all_tables.items():
|
|
35902
|
+
for tbl in tables:
|
|
35903
|
+
table_key = f"{db}.{tbl}"
|
|
35904
|
+
tbl_target = table_to_target.get(table_key, "")
|
|
35905
|
+
if tbl_target in inj_url or inj_url in tbl_target:
|
|
35906
|
+
if db not in filtered_tables:
|
|
35907
|
+
filtered_tables[db] = set()
|
|
35908
|
+
filtered_tables[db].add(tbl)
|
|
35909
|
+
if table_key in all_columns:
|
|
35910
|
+
filtered_columns[table_key] = all_columns[table_key]
|
|
35911
|
+
|
|
35912
|
+
# Filter dumped data to only this injection's target
|
|
35913
|
+
filtered_dumped = {}
|
|
35914
|
+
for key, data in all_dumped.items():
|
|
35915
|
+
# Key format is "target_url::db.table"
|
|
35916
|
+
if "::" in key:
|
|
35917
|
+
target_part = key.split("::")[0]
|
|
35918
|
+
table_part = key.split("::", 1)[1]
|
|
35919
|
+
if target_part in inj_url or inj_url in target_part:
|
|
35920
|
+
filtered_dumped[table_part] = data
|
|
35921
|
+
elif data.get("_target", "") in inj_url:
|
|
35922
|
+
filtered_dumped[key] = data
|
|
35923
|
+
|
|
35436
35924
|
_view_sqlmap_injection_detail(
|
|
35437
35925
|
inj,
|
|
35438
|
-
|
|
35439
|
-
|
|
35440
|
-
|
|
35441
|
-
|
|
35442
|
-
|
|
35926
|
+
filtered_dbs,
|
|
35927
|
+
filtered_tables,
|
|
35928
|
+
filtered_columns,
|
|
35929
|
+
filtered_db_to_dbms,
|
|
35930
|
+
filtered_dumped,
|
|
35443
35931
|
engagement_id,
|
|
35444
35932
|
)
|
|
35445
35933
|
else:
|
|
@@ -35614,12 +36102,46 @@ def _view_sqlmap_injection_detail(
|
|
|
35614
36102
|
from rich.console import Console
|
|
35615
36103
|
from rich.table import Table
|
|
35616
36104
|
|
|
36105
|
+
from souleyez.ui.interactive_selector import _get_key
|
|
36106
|
+
|
|
35617
36107
|
console = Console()
|
|
35618
36108
|
|
|
35619
36109
|
if all_dumped is None:
|
|
35620
36110
|
all_dumped = {}
|
|
35621
36111
|
|
|
35622
|
-
|
|
36112
|
+
# View states: 'databases', 'tables:<db>', 'data:<table_key>'
|
|
36113
|
+
current_view = "databases"
|
|
36114
|
+
selected_db = None
|
|
36115
|
+
data_page = 1
|
|
36116
|
+
data_view_all = False
|
|
36117
|
+
db_cursor_pos = 0 # Cursor for database list
|
|
36118
|
+
tbl_cursor_pos = 0 # Cursor for table list
|
|
36119
|
+
show_empty_tables = False # Hide empty tables by default
|
|
36120
|
+
|
|
36121
|
+
# Build database list with stats
|
|
36122
|
+
db_list = []
|
|
36123
|
+
for db in sorted(all_databases) if all_databases else sorted(all_tables.keys()):
|
|
36124
|
+
tables = all_tables.get(db, set())
|
|
36125
|
+
table_count = len(tables)
|
|
36126
|
+
# Count extracted rows for this database
|
|
36127
|
+
extracted_count = 0
|
|
36128
|
+
extracted_tables = 0
|
|
36129
|
+
for tbl in tables:
|
|
36130
|
+
dumped_key = next((k for k in all_dumped.keys() if tbl in k), None)
|
|
36131
|
+
if dumped_key:
|
|
36132
|
+
extracted_tables += 1
|
|
36133
|
+
extracted_count += all_dumped[dumped_key].get(
|
|
36134
|
+
"row_count", len(all_dumped[dumped_key].get("rows", []))
|
|
36135
|
+
)
|
|
36136
|
+
db_list.append(
|
|
36137
|
+
{
|
|
36138
|
+
"name": db,
|
|
36139
|
+
"table_count": table_count,
|
|
36140
|
+
"extracted_tables": extracted_tables,
|
|
36141
|
+
"extracted_rows": extracted_count,
|
|
36142
|
+
"dbms": db_to_dbms.get(db, "Unknown"),
|
|
36143
|
+
}
|
|
36144
|
+
)
|
|
35623
36145
|
|
|
35624
36146
|
while True:
|
|
35625
36147
|
DesignSystem.clear_screen()
|
|
@@ -35649,412 +36171,729 @@ def _view_sqlmap_injection_detail(
|
|
|
35649
36171
|
)
|
|
35650
36172
|
click.echo()
|
|
35651
36173
|
|
|
35652
|
-
if current_view == "
|
|
35653
|
-
|
|
35654
|
-
|
|
35655
|
-
|
|
35656
|
-
all_tables,
|
|
35657
|
-
all_columns,
|
|
35658
|
-
db_to_dbms,
|
|
36174
|
+
if current_view == "databases":
|
|
36175
|
+
# Database selector view
|
|
36176
|
+
_sqli_database_selector(
|
|
36177
|
+
db_list,
|
|
35659
36178
|
all_dumped,
|
|
35660
36179
|
console,
|
|
36180
|
+
db_cursor_pos,
|
|
35661
36181
|
)
|
|
35662
36182
|
|
|
35663
|
-
#
|
|
35664
|
-
click.echo()
|
|
36183
|
+
# Navigation footer
|
|
36184
|
+
click.echo("─" * width)
|
|
36185
|
+
click.echo(" ↑↓/jk: Navigate | Enter: View tables | q: Back")
|
|
35665
36186
|
click.echo("─" * width)
|
|
35666
|
-
options = []
|
|
35667
|
-
if all_dumped:
|
|
35668
|
-
# List available tables with numbers
|
|
35669
|
-
table_keys = list(all_dumped.keys())
|
|
35670
|
-
click.echo(click.style(" BROWSE EXTRACTED DATA:", bold=True))
|
|
35671
|
-
for i, key in enumerate(table_keys, 1):
|
|
35672
|
-
data = all_dumped[key]
|
|
35673
|
-
row_count = data.get("row_count", len(data.get("rows", [])))
|
|
35674
|
-
# Highlight credential tables
|
|
35675
|
-
if any(
|
|
35676
|
-
x in key.lower()
|
|
35677
|
-
for x in [
|
|
35678
|
-
"user",
|
|
35679
|
-
"account",
|
|
35680
|
-
"login",
|
|
35681
|
-
"credential",
|
|
35682
|
-
"password",
|
|
35683
|
-
"card",
|
|
35684
|
-
"payment",
|
|
35685
|
-
]
|
|
35686
|
-
):
|
|
35687
|
-
click.echo(
|
|
35688
|
-
f" [{i}] {click.style(key, fg='red', bold=True)} ({row_count} rows) 🔐"
|
|
35689
|
-
)
|
|
35690
|
-
else:
|
|
35691
|
-
click.echo(f" [{i}] {key} ({row_count} rows)")
|
|
35692
|
-
options.extend([str(i) for i in range(1, len(table_keys) + 1)])
|
|
35693
|
-
|
|
35694
|
-
click.echo()
|
|
35695
|
-
click.echo(f" [t] View all tables structure")
|
|
35696
|
-
click.echo(f" [q] Back")
|
|
35697
|
-
click.echo()
|
|
35698
36187
|
|
|
36188
|
+
# Handle keyboard input
|
|
35699
36189
|
try:
|
|
35700
|
-
|
|
36190
|
+
key = _get_key()
|
|
35701
36191
|
|
|
35702
|
-
if
|
|
36192
|
+
if key in ("q", "Q", "\x1b"): # q or Escape
|
|
35703
36193
|
return
|
|
35704
|
-
elif
|
|
35705
|
-
|
|
35706
|
-
|
|
35707
|
-
|
|
35708
|
-
|
|
35709
|
-
|
|
35710
|
-
|
|
36194
|
+
elif key in ("\x1b[A", "k"): # Up arrow or k
|
|
36195
|
+
if db_cursor_pos > 0:
|
|
36196
|
+
db_cursor_pos -= 1
|
|
36197
|
+
elif key in ("\x1b[B", "j"): # Down arrow or j
|
|
36198
|
+
if db_cursor_pos < len(db_list) - 1:
|
|
36199
|
+
db_cursor_pos += 1
|
|
36200
|
+
elif key in ("\r", "\n"): # Enter
|
|
36201
|
+
if db_list:
|
|
36202
|
+
selected_db = db_list[db_cursor_pos]["name"]
|
|
36203
|
+
current_view = f"tables:{selected_db}"
|
|
36204
|
+
tbl_cursor_pos = 0
|
|
35711
36205
|
except (KeyboardInterrupt, click.Abort):
|
|
35712
36206
|
return
|
|
35713
36207
|
|
|
35714
|
-
elif current_view
|
|
35715
|
-
|
|
35716
|
-
|
|
36208
|
+
elif current_view.startswith("tables:"):
|
|
36209
|
+
db_name = current_view[7:]
|
|
36210
|
+
tables = all_tables.get(db_name, set())
|
|
36211
|
+
|
|
36212
|
+
# Build full table list for this database
|
|
36213
|
+
full_table_list = []
|
|
36214
|
+
for tbl in sorted(tables):
|
|
36215
|
+
dumped_key = next((k for k in all_dumped.keys() if tbl in k), None)
|
|
36216
|
+
# Check if table has data or is a high-value table
|
|
36217
|
+
tbl_lower = tbl.lower()
|
|
36218
|
+
is_high_value = any(
|
|
36219
|
+
x in tbl_lower
|
|
36220
|
+
for x in [
|
|
36221
|
+
"user",
|
|
36222
|
+
"account",
|
|
36223
|
+
"login",
|
|
36224
|
+
"credential",
|
|
36225
|
+
"password",
|
|
36226
|
+
"card",
|
|
36227
|
+
"payment",
|
|
36228
|
+
]
|
|
36229
|
+
)
|
|
36230
|
+
full_table_list.append(
|
|
36231
|
+
{
|
|
36232
|
+
"db": db_name,
|
|
36233
|
+
"table": tbl,
|
|
36234
|
+
"dumped_key": dumped_key,
|
|
36235
|
+
"has_data": dumped_key is not None,
|
|
36236
|
+
"is_high_value": is_high_value,
|
|
36237
|
+
}
|
|
36238
|
+
)
|
|
36239
|
+
|
|
36240
|
+
# Filter table list based on show_empty_tables
|
|
36241
|
+
if show_empty_tables:
|
|
36242
|
+
table_list = full_table_list
|
|
36243
|
+
else:
|
|
36244
|
+
# Show only tables with data or high-value tables
|
|
36245
|
+
table_list = [
|
|
36246
|
+
t for t in full_table_list if t["has_data"] or t["is_high_value"]
|
|
36247
|
+
]
|
|
36248
|
+
# If no tables match filter, show all
|
|
36249
|
+
if not table_list:
|
|
36250
|
+
table_list = full_table_list
|
|
36251
|
+
|
|
36252
|
+
# Ensure cursor is within bounds
|
|
36253
|
+
if tbl_cursor_pos >= len(table_list):
|
|
36254
|
+
tbl_cursor_pos = max(0, len(table_list) - 1)
|
|
36255
|
+
|
|
36256
|
+
# Count hidden tables
|
|
36257
|
+
hidden_count = len(full_table_list) - len(table_list)
|
|
36258
|
+
|
|
36259
|
+
# Show tables for this database
|
|
36260
|
+
_sqli_tables_for_database(
|
|
36261
|
+
db_name,
|
|
36262
|
+
table_list,
|
|
36263
|
+
all_columns,
|
|
36264
|
+
db_to_dbms,
|
|
36265
|
+
all_dumped,
|
|
36266
|
+
console,
|
|
36267
|
+
tbl_cursor_pos,
|
|
36268
|
+
hidden_count=hidden_count,
|
|
36269
|
+
show_all=show_empty_tables,
|
|
35717
36270
|
)
|
|
35718
36271
|
|
|
35719
|
-
|
|
36272
|
+
# Navigation footer
|
|
36273
|
+
click.echo("─" * width)
|
|
36274
|
+
if show_empty_tables:
|
|
36275
|
+
click.echo(
|
|
36276
|
+
" ↑↓/jk: Navigate | Enter: View data | a: Hide empty | q: Back"
|
|
36277
|
+
)
|
|
36278
|
+
else:
|
|
36279
|
+
click.echo(
|
|
36280
|
+
" ↑↓/jk: Navigate | Enter: View data | a: Show all | q: Back"
|
|
36281
|
+
)
|
|
35720
36282
|
click.echo("─" * width)
|
|
35721
|
-
click.echo(f" [b] Back to overview")
|
|
35722
|
-
click.echo()
|
|
35723
36283
|
|
|
36284
|
+
# Handle keyboard input
|
|
35724
36285
|
try:
|
|
35725
|
-
|
|
35726
|
-
|
|
35727
|
-
|
|
36286
|
+
key = _get_key()
|
|
36287
|
+
|
|
36288
|
+
if key in ("q", "Q", "\x1b"): # q or Escape - back to databases
|
|
36289
|
+
current_view = "databases"
|
|
36290
|
+
tbl_cursor_pos = 0
|
|
36291
|
+
elif key in ("\x1b[A", "k"): # Up arrow or k
|
|
36292
|
+
if tbl_cursor_pos > 0:
|
|
36293
|
+
tbl_cursor_pos -= 1
|
|
36294
|
+
elif key in ("\x1b[B", "j"): # Down arrow or j
|
|
36295
|
+
if tbl_cursor_pos < len(table_list) - 1:
|
|
36296
|
+
tbl_cursor_pos += 1
|
|
36297
|
+
elif key in ("a", "A"): # Toggle show all tables
|
|
36298
|
+
show_empty_tables = not show_empty_tables
|
|
36299
|
+
tbl_cursor_pos = 0 # Reset cursor when toggling
|
|
36300
|
+
elif key in ("\r", "\n"): # Enter
|
|
36301
|
+
if table_list and table_list[tbl_cursor_pos]["has_data"]:
|
|
36302
|
+
current_view = (
|
|
36303
|
+
f"data:{table_list[tbl_cursor_pos]['dumped_key']}"
|
|
36304
|
+
)
|
|
36305
|
+
data_page = 1
|
|
36306
|
+
data_view_all = False
|
|
35728
36307
|
except (KeyboardInterrupt, click.Abort):
|
|
35729
36308
|
return
|
|
35730
36309
|
|
|
35731
36310
|
elif current_view.startswith("data:"):
|
|
35732
36311
|
table_key = current_view[5:]
|
|
35733
36312
|
if table_key in all_dumped:
|
|
36313
|
+
table_data = all_dumped[table_key]
|
|
36314
|
+
total_rows = len(table_data.get("rows", []))
|
|
36315
|
+
page_size = 20
|
|
36316
|
+
total_pages = (total_rows + page_size - 1) // page_size
|
|
36317
|
+
|
|
35734
36318
|
_sqli_detail_data_table(
|
|
35735
|
-
table_key,
|
|
36319
|
+
table_key,
|
|
36320
|
+
table_data,
|
|
36321
|
+
console,
|
|
36322
|
+
width,
|
|
36323
|
+
page=data_page,
|
|
36324
|
+
page_size=page_size,
|
|
36325
|
+
view_all=data_view_all,
|
|
35736
36326
|
)
|
|
35737
36327
|
|
|
35738
|
-
click.echo()
|
|
35739
36328
|
click.echo("─" * width)
|
|
35740
|
-
click.echo(
|
|
36329
|
+
click.echo()
|
|
36330
|
+
if total_rows > page_size and not data_view_all:
|
|
36331
|
+
click.echo(" [n] Next - Next page")
|
|
36332
|
+
click.echo(" [p] Prev - Previous page")
|
|
36333
|
+
click.echo(" [a] All - View all records")
|
|
36334
|
+
elif data_view_all:
|
|
36335
|
+
click.echo(" [a] Paginate - Return to paginated view")
|
|
36336
|
+
click.echo(" [q] Back")
|
|
35741
36337
|
click.echo()
|
|
35742
36338
|
|
|
35743
36339
|
try:
|
|
35744
|
-
choice = click.prompt("
|
|
35745
|
-
if choice == "
|
|
35746
|
-
|
|
36340
|
+
choice = click.prompt("Select option", default="q").strip().lower()
|
|
36341
|
+
if choice == "q":
|
|
36342
|
+
# Go back to tables view for the selected database
|
|
36343
|
+
if selected_db:
|
|
36344
|
+
current_view = f"tables:{selected_db}"
|
|
36345
|
+
else:
|
|
36346
|
+
current_view = "databases"
|
|
36347
|
+
data_page = 1
|
|
36348
|
+
data_view_all = False
|
|
36349
|
+
elif choice == "n" and not data_view_all and data_page < total_pages:
|
|
36350
|
+
data_page += 1
|
|
36351
|
+
elif choice == "p" and not data_view_all and data_page > 1:
|
|
36352
|
+
data_page -= 1
|
|
36353
|
+
elif choice == "a":
|
|
36354
|
+
data_view_all = not data_view_all
|
|
36355
|
+
data_page = 1
|
|
35747
36356
|
except (KeyboardInterrupt, click.Abort):
|
|
35748
36357
|
return
|
|
35749
36358
|
|
|
35750
36359
|
|
|
35751
|
-
def
|
|
35752
|
-
|
|
36360
|
+
def _sqli_database_selector(db_list, all_dumped, console, cursor_pos):
|
|
36361
|
+
"""Show database selector with table counts and extracted data."""
|
|
36362
|
+
from rich.table import Table
|
|
36363
|
+
|
|
36364
|
+
# Count total extracted data
|
|
36365
|
+
total_extracted = 0
|
|
36366
|
+
cred_count = 0
|
|
36367
|
+
for key, data in all_dumped.items():
|
|
36368
|
+
row_count = data.get("row_count", len(data.get("rows", [])))
|
|
36369
|
+
total_extracted += row_count
|
|
36370
|
+
key_lower = key.lower()
|
|
36371
|
+
if any(x in key_lower for x in ["user", "account", "login", "credential"]):
|
|
36372
|
+
cred_count += row_count
|
|
36373
|
+
|
|
36374
|
+
# Summary alert if data extracted
|
|
36375
|
+
if total_extracted > 0:
|
|
36376
|
+
alerts = []
|
|
36377
|
+
if cred_count > 0:
|
|
36378
|
+
alerts.append(f"🔑 {cred_count} credentials")
|
|
36379
|
+
if total_extracted > cred_count:
|
|
36380
|
+
alerts.append(f"📊 {total_extracted} total rows")
|
|
36381
|
+
click.echo(
|
|
36382
|
+
click.style(" ⚠️ EXTRACTED: ", fg="red", bold=True)
|
|
36383
|
+
+ click.style(" | ".join(alerts), fg="red")
|
|
36384
|
+
)
|
|
36385
|
+
click.echo()
|
|
36386
|
+
|
|
36387
|
+
# Summary
|
|
36388
|
+
total_tables = sum(db["table_count"] for db in db_list)
|
|
36389
|
+
extracted_tables = sum(db["extracted_tables"] for db in db_list)
|
|
36390
|
+
click.echo(
|
|
36391
|
+
f" {click.style('Databases:', bold=True)} {len(db_list)} | "
|
|
36392
|
+
f"{click.style('Tables:', bold=True)} {total_tables} | "
|
|
36393
|
+
f"{click.style('Extracted:', bold=True, fg='green')} {extracted_tables}"
|
|
36394
|
+
)
|
|
36395
|
+
click.echo()
|
|
36396
|
+
|
|
36397
|
+
# Database table
|
|
36398
|
+
table = Table(
|
|
36399
|
+
show_header=True,
|
|
36400
|
+
header_style="bold cyan",
|
|
36401
|
+
box=DesignSystem.TABLE_BOX,
|
|
36402
|
+
padding=(0, 1),
|
|
36403
|
+
expand=True,
|
|
36404
|
+
)
|
|
36405
|
+
table.add_column("", width=3, no_wrap=True) # Cursor
|
|
36406
|
+
table.add_column("Database", no_wrap=True)
|
|
36407
|
+
table.add_column("DBMS", width=12)
|
|
36408
|
+
table.add_column("Tables", width=10, justify="right")
|
|
36409
|
+
table.add_column("Extracted", width=12, justify="right")
|
|
36410
|
+
table.add_column("Rows", width=10, justify="right")
|
|
36411
|
+
|
|
36412
|
+
for idx, db in enumerate(db_list):
|
|
36413
|
+
is_selected = idx == cursor_pos
|
|
36414
|
+
cursor = "▶" if is_selected else ""
|
|
36415
|
+
|
|
36416
|
+
name = db["name"]
|
|
36417
|
+
dbms_type = db["dbms"]
|
|
36418
|
+
table_count = db["table_count"]
|
|
36419
|
+
extracted_tables = db["extracted_tables"]
|
|
36420
|
+
extracted_rows = db["extracted_rows"]
|
|
36421
|
+
|
|
36422
|
+
# Highlight rows with extracted data
|
|
36423
|
+
if extracted_tables > 0:
|
|
36424
|
+
if is_selected:
|
|
36425
|
+
table.add_row(
|
|
36426
|
+
f"[bold yellow]{cursor}[/bold yellow]",
|
|
36427
|
+
f"[bold yellow]📂 {name}[/bold yellow]",
|
|
36428
|
+
f"[bold yellow]{dbms_type}[/bold yellow]",
|
|
36429
|
+
f"[bold yellow]{table_count}[/bold yellow]",
|
|
36430
|
+
f"[bold yellow]✓ {extracted_tables}[/bold yellow]",
|
|
36431
|
+
f"[bold yellow]{extracted_rows}[/bold yellow]",
|
|
36432
|
+
)
|
|
36433
|
+
else:
|
|
36434
|
+
table.add_row(
|
|
36435
|
+
cursor,
|
|
36436
|
+
f"[green bold]📂 {name}[/green bold]",
|
|
36437
|
+
dbms_type,
|
|
36438
|
+
str(table_count),
|
|
36439
|
+
f"[green]✓ {extracted_tables}[/green]",
|
|
36440
|
+
f"[green]{extracted_rows}[/green]",
|
|
36441
|
+
)
|
|
36442
|
+
else:
|
|
36443
|
+
if is_selected:
|
|
36444
|
+
table.add_row(
|
|
36445
|
+
f"[bold yellow]{cursor}[/bold yellow]",
|
|
36446
|
+
f"[bold yellow]📂 {name}[/bold yellow]",
|
|
36447
|
+
f"[bold yellow]{dbms_type}[/bold yellow]",
|
|
36448
|
+
f"[bold yellow]{table_count}[/bold yellow]",
|
|
36449
|
+
"[bold yellow]-[/bold yellow]",
|
|
36450
|
+
"[bold yellow]-[/bold yellow]",
|
|
36451
|
+
)
|
|
36452
|
+
else:
|
|
36453
|
+
table.add_row(
|
|
36454
|
+
cursor,
|
|
36455
|
+
f"[dim]📂 {name}[/dim]",
|
|
36456
|
+
f"[dim]{dbms_type}[/dim]",
|
|
36457
|
+
f"[dim]{table_count}[/dim]",
|
|
36458
|
+
"[dim]-[/dim]",
|
|
36459
|
+
"[dim]-[/dim]",
|
|
36460
|
+
)
|
|
36461
|
+
|
|
36462
|
+
console.print(table)
|
|
36463
|
+
click.echo()
|
|
36464
|
+
|
|
36465
|
+
|
|
36466
|
+
def _sqli_tables_for_database(
|
|
36467
|
+
db_name,
|
|
36468
|
+
table_list,
|
|
36469
|
+
all_columns,
|
|
36470
|
+
db_to_dbms,
|
|
36471
|
+
all_dumped,
|
|
36472
|
+
console,
|
|
36473
|
+
cursor_pos,
|
|
36474
|
+
hidden_count=0,
|
|
36475
|
+
show_all=False,
|
|
35753
36476
|
):
|
|
35754
|
-
"""Show
|
|
36477
|
+
"""Show tables for a specific database with cursor navigation."""
|
|
35755
36478
|
from rich.table import Table
|
|
35756
36479
|
|
|
35757
|
-
|
|
35758
|
-
|
|
36480
|
+
dbms_type = db_to_dbms.get(db_name, "Unknown")
|
|
36481
|
+
|
|
36482
|
+
# Count extracted data
|
|
36483
|
+
extracted_count = sum(1 for t in table_list if t["has_data"])
|
|
35759
36484
|
total_rows = sum(
|
|
35760
|
-
|
|
36485
|
+
all_dumped[t["dumped_key"]].get(
|
|
36486
|
+
"row_count", len(all_dumped[t["dumped_key"]].get("rows", []))
|
|
36487
|
+
)
|
|
36488
|
+
for t in table_list
|
|
36489
|
+
if t["has_data"]
|
|
35761
36490
|
)
|
|
35762
36491
|
|
|
35763
|
-
#
|
|
35764
|
-
|
|
35765
|
-
|
|
35766
|
-
|
|
35767
|
-
|
|
35768
|
-
|
|
35769
|
-
"card" in k.lower() or "payment" in k.lower() or "credit" in k.lower()
|
|
35770
|
-
for k in all_dumped.keys()
|
|
36492
|
+
# Database header
|
|
36493
|
+
click.echo(f" {click.style('📂 ' + db_name, bold=True, fg='cyan')} ({dbms_type})")
|
|
36494
|
+
summary_line = (
|
|
36495
|
+
f" {click.style('Tables:', bold=True)} {len(table_list)} | "
|
|
36496
|
+
f"{click.style('Extracted:', bold=True, fg='green')} {extracted_count} | "
|
|
36497
|
+
f"{click.style('Rows:', bold=True)} {total_rows}"
|
|
35771
36498
|
)
|
|
35772
|
-
|
|
35773
|
-
|
|
35774
|
-
|
|
36499
|
+
if hidden_count > 0 and not show_all:
|
|
36500
|
+
summary_line += click.style(f" | {hidden_count} hidden", dim=True)
|
|
36501
|
+
click.echo(summary_line)
|
|
36502
|
+
click.echo()
|
|
36503
|
+
|
|
36504
|
+
if not table_list:
|
|
36505
|
+
click.echo(" [dim]No tables discovered for this database.[/dim]")
|
|
36506
|
+
return
|
|
36507
|
+
|
|
36508
|
+
# Table list
|
|
36509
|
+
table = Table(
|
|
36510
|
+
show_header=True,
|
|
36511
|
+
header_style="bold cyan",
|
|
36512
|
+
box=DesignSystem.TABLE_BOX,
|
|
36513
|
+
padding=(0, 1),
|
|
36514
|
+
expand=True,
|
|
35775
36515
|
)
|
|
36516
|
+
table.add_column("", width=3, no_wrap=True) # Cursor
|
|
36517
|
+
table.add_column("○", width=3, justify="center", no_wrap=True)
|
|
36518
|
+
table.add_column("Table Name", no_wrap=True)
|
|
36519
|
+
table.add_column("Cols", width=8, justify="right")
|
|
36520
|
+
table.add_column("Rows", width=12, justify="right")
|
|
36521
|
+
table.add_column("Type", width=12)
|
|
36522
|
+
|
|
36523
|
+
for idx, tbl_info in enumerate(table_list):
|
|
36524
|
+
tbl = tbl_info["table"]
|
|
36525
|
+
is_selected = idx == cursor_pos
|
|
36526
|
+
cursor = "▶" if is_selected else ""
|
|
36527
|
+
|
|
36528
|
+
table_key = f"{db_name}.{tbl}"
|
|
36529
|
+
cols = all_columns.get(table_key, set())
|
|
36530
|
+
col_count = len(cols) if cols else 0
|
|
36531
|
+
|
|
36532
|
+
# Determine category
|
|
36533
|
+
tbl_lower = tbl.lower()
|
|
36534
|
+
if any(
|
|
36535
|
+
x in tbl_lower
|
|
36536
|
+
for x in ["user", "account", "login", "credential", "password"]
|
|
36537
|
+
):
|
|
36538
|
+
category = "🔑 Creds"
|
|
36539
|
+
elif any(x in tbl_lower for x in ["card", "payment", "credit"]):
|
|
36540
|
+
category = "💳 Cards"
|
|
36541
|
+
elif any(
|
|
36542
|
+
x in tbl_lower for x in ["address", "personal", "customer", "profile"]
|
|
36543
|
+
):
|
|
36544
|
+
category = "👤 PII"
|
|
36545
|
+
else:
|
|
36546
|
+
category = ""
|
|
36547
|
+
|
|
36548
|
+
has_data = tbl_info["has_data"]
|
|
36549
|
+
if has_data:
|
|
36550
|
+
dumped_key = tbl_info["dumped_key"]
|
|
36551
|
+
row_count = all_dumped[dumped_key].get(
|
|
36552
|
+
"row_count", len(all_dumped[dumped_key].get("rows", []))
|
|
36553
|
+
)
|
|
36554
|
+
if is_selected:
|
|
36555
|
+
table.add_row(
|
|
36556
|
+
f"[bold yellow]{cursor}[/bold yellow]",
|
|
36557
|
+
"[bold yellow]○[/bold yellow]",
|
|
36558
|
+
f"[bold yellow]{tbl}[/bold yellow]",
|
|
36559
|
+
f"[bold yellow]{col_count}[/bold yellow]",
|
|
36560
|
+
f"[bold yellow]✓ {row_count}[/bold yellow]",
|
|
36561
|
+
f"[bold yellow]{category}[/bold yellow]",
|
|
36562
|
+
)
|
|
36563
|
+
else:
|
|
36564
|
+
table.add_row(
|
|
36565
|
+
cursor,
|
|
36566
|
+
"○",
|
|
36567
|
+
f"[green bold]{tbl}[/green bold]",
|
|
36568
|
+
str(col_count),
|
|
36569
|
+
f"[green]✓ {row_count}[/green]",
|
|
36570
|
+
category,
|
|
36571
|
+
)
|
|
36572
|
+
else:
|
|
36573
|
+
if is_selected:
|
|
36574
|
+
table.add_row(
|
|
36575
|
+
f"[bold yellow]{cursor}[/bold yellow]",
|
|
36576
|
+
"[bold yellow]○[/bold yellow]",
|
|
36577
|
+
f"[bold yellow]{tbl}[/bold yellow]",
|
|
36578
|
+
f"[bold yellow]{col_count}[/bold yellow]",
|
|
36579
|
+
"[bold yellow]-[/bold yellow]",
|
|
36580
|
+
f"[bold yellow]{category}[/bold yellow]",
|
|
36581
|
+
)
|
|
36582
|
+
else:
|
|
36583
|
+
table.add_row(
|
|
36584
|
+
cursor,
|
|
36585
|
+
"○",
|
|
36586
|
+
tbl,
|
|
36587
|
+
str(col_count),
|
|
36588
|
+
"[dim]-[/dim]",
|
|
36589
|
+
category,
|
|
36590
|
+
)
|
|
35776
36591
|
|
|
35777
|
-
|
|
35778
|
-
click.echo(click.style(" 📊 EXPLOITATION SUMMARY", bold=True, fg="green"))
|
|
36592
|
+
console.print(table)
|
|
35779
36593
|
click.echo()
|
|
35780
36594
|
|
|
35781
|
-
|
|
35782
|
-
|
|
35783
|
-
|
|
35784
|
-
|
|
36595
|
+
|
|
36596
|
+
def _sqli_detail_overview_interactive(
|
|
36597
|
+
inj,
|
|
36598
|
+
all_databases,
|
|
36599
|
+
all_tables,
|
|
36600
|
+
all_columns,
|
|
36601
|
+
db_to_dbms,
|
|
36602
|
+
all_dumped,
|
|
36603
|
+
console,
|
|
36604
|
+
cursor_pos,
|
|
36605
|
+
all_table_list,
|
|
36606
|
+
):
|
|
36607
|
+
"""Show interactive overview with cursor navigation."""
|
|
36608
|
+
from rich.table import Table
|
|
36609
|
+
|
|
36610
|
+
# Count extracted data by category
|
|
36611
|
+
cred_count = 0
|
|
36612
|
+
card_count = 0
|
|
36613
|
+
pii_count = 0
|
|
36614
|
+
|
|
36615
|
+
for key, data in all_dumped.items():
|
|
36616
|
+
row_count = data.get("row_count", len(data.get("rows", [])))
|
|
36617
|
+
key_lower = key.lower()
|
|
36618
|
+
if any(x in key_lower for x in ["user", "account", "login", "credential"]):
|
|
36619
|
+
cred_count += row_count
|
|
36620
|
+
elif any(x in key_lower for x in ["card", "payment", "credit"]):
|
|
36621
|
+
card_count += row_count
|
|
36622
|
+
elif any(
|
|
36623
|
+
x in key_lower for x in ["address", "personal", "customer", "profile"]
|
|
36624
|
+
):
|
|
36625
|
+
pii_count += row_count
|
|
36626
|
+
|
|
36627
|
+
# Summary alert if data extracted
|
|
36628
|
+
if cred_count > 0 or card_count > 0 or pii_count > 0:
|
|
36629
|
+
alerts = []
|
|
36630
|
+
if cred_count > 0:
|
|
36631
|
+
alerts.append(f"🔑 {cred_count} credentials")
|
|
36632
|
+
if card_count > 0:
|
|
36633
|
+
alerts.append(f"💳 {card_count} cards")
|
|
36634
|
+
if pii_count > 0:
|
|
36635
|
+
alerts.append(f"👤 {pii_count} PII records")
|
|
35785
36636
|
click.echo(
|
|
35786
|
-
|
|
36637
|
+
click.style(" ⚠️ EXTRACTED: ", fg="red", bold=True)
|
|
36638
|
+
+ click.style(" | ".join(alerts), fg="red")
|
|
35787
36639
|
)
|
|
35788
36640
|
click.echo()
|
|
35789
36641
|
|
|
35790
|
-
|
|
35791
|
-
|
|
35792
|
-
|
|
35793
|
-
|
|
35794
|
-
|
|
35795
|
-
)
|
|
35796
|
-
if has_cards:
|
|
35797
|
-
click.echo(
|
|
35798
|
-
f" 💳 {click.style('PAYMENT DATA FOUND', fg='red', bold=True)} - Credit card information"
|
|
35799
|
-
)
|
|
35800
|
-
if has_pii:
|
|
35801
|
-
click.echo(
|
|
35802
|
-
f" 👤 {click.style('PII FOUND', fg='yellow', bold=True)} - Personal addresses/information"
|
|
35803
|
-
)
|
|
35804
|
-
if not (has_credentials or has_cards or has_pii):
|
|
35805
|
-
click.echo(f" 📄 Application data extracted")
|
|
35806
|
-
else:
|
|
35807
|
-
click.echo(f" {click.style('No data extracted yet', fg='yellow')}")
|
|
35808
|
-
click.echo()
|
|
35809
|
-
click.echo(
|
|
35810
|
-
" 💡 Tables discovered but not yet dumped. Select a table to dump data."
|
|
35811
|
-
)
|
|
36642
|
+
# Database structure
|
|
36643
|
+
if not all_databases and not all_tables:
|
|
36644
|
+
click.echo(" No database structure discovered yet.")
|
|
36645
|
+
click.echo(" 💡 Run SQLMap with --tables to enumerate tables.")
|
|
36646
|
+
return
|
|
35812
36647
|
|
|
36648
|
+
# Summary line
|
|
36649
|
+
total_tables = len(all_table_list)
|
|
36650
|
+
dumped_count = len(all_dumped)
|
|
36651
|
+
click.echo(
|
|
36652
|
+
f" {click.style('Total:', bold=True)} {total_tables} tables | "
|
|
36653
|
+
f"{click.style('Extracted:', bold=True, fg='green')} {dumped_count}"
|
|
36654
|
+
)
|
|
35813
36655
|
click.echo()
|
|
35814
36656
|
|
|
35815
|
-
# Show
|
|
35816
|
-
if
|
|
35817
|
-
|
|
36657
|
+
# Show database structure with Rich table and cursor
|
|
36658
|
+
for db in sorted(all_databases) if all_databases else list(all_tables.keys()):
|
|
36659
|
+
dbms_type = db_to_dbms.get(db, "SQLite")
|
|
36660
|
+
tables = all_tables.get(db, set())
|
|
36661
|
+
|
|
36662
|
+
click.echo(f" {click.style('📂 ' + db, bold=True, fg='cyan')} ({dbms_type})")
|
|
35818
36663
|
click.echo()
|
|
35819
36664
|
|
|
35820
|
-
|
|
35821
|
-
|
|
35822
|
-
|
|
35823
|
-
|
|
36665
|
+
if tables:
|
|
36666
|
+
# Create Rich table with cursor column
|
|
36667
|
+
table = Table(
|
|
36668
|
+
show_header=True,
|
|
36669
|
+
header_style="bold cyan",
|
|
36670
|
+
box=DesignSystem.TABLE_BOX,
|
|
36671
|
+
padding=(0, 1),
|
|
36672
|
+
expand=True,
|
|
36673
|
+
)
|
|
35824
36674
|
|
|
35825
|
-
|
|
35826
|
-
|
|
35827
|
-
|
|
35828
|
-
|
|
35829
|
-
|
|
35830
|
-
|
|
35831
|
-
|
|
35832
|
-
|
|
35833
|
-
|
|
35834
|
-
|
|
35835
|
-
|
|
35836
|
-
|
|
35837
|
-
|
|
36675
|
+
table.add_column("", width=3, no_wrap=True) # Cursor
|
|
36676
|
+
table.add_column("○", width=3, justify="center", no_wrap=True)
|
|
36677
|
+
table.add_column("Table Name", width=28, no_wrap=True)
|
|
36678
|
+
table.add_column("Cols", width=6, justify="right", no_wrap=True)
|
|
36679
|
+
table.add_column("Rows", width=12, justify="right", no_wrap=True)
|
|
36680
|
+
table.add_column("Type", width=12, no_wrap=True)
|
|
36681
|
+
|
|
36682
|
+
sorted_tables = sorted(tables)
|
|
36683
|
+
for tbl in sorted_tables:
|
|
36684
|
+
table_key = f"{db}.{tbl}"
|
|
36685
|
+
cols = all_columns.get(table_key, set())
|
|
36686
|
+
|
|
36687
|
+
# Find this table's index in all_table_list
|
|
36688
|
+
tbl_idx = next(
|
|
35838
36689
|
(
|
|
35839
|
-
|
|
35840
|
-
for
|
|
35841
|
-
if
|
|
35842
|
-
x in c.lower()
|
|
35843
|
-
for x in ["password", "passwd", "hash", "pwd"]
|
|
35844
|
-
)
|
|
36690
|
+
i
|
|
36691
|
+
for i, t in enumerate(all_table_list)
|
|
36692
|
+
if t["table"] == tbl and t["db"] == db
|
|
35845
36693
|
),
|
|
35846
|
-
|
|
36694
|
+
-1,
|
|
35847
36695
|
)
|
|
35848
|
-
|
|
36696
|
+
is_selected = tbl_idx == cursor_pos
|
|
35849
36697
|
|
|
35850
|
-
|
|
35851
|
-
|
|
35852
|
-
table = Table(
|
|
35853
|
-
show_header=True,
|
|
35854
|
-
header_style="bold red",
|
|
35855
|
-
box=None,
|
|
35856
|
-
padding=(0, 2),
|
|
35857
|
-
)
|
|
35858
|
-
if user_col:
|
|
35859
|
-
table.add_column("Username", style="yellow")
|
|
35860
|
-
if email_col and email_col != user_col:
|
|
35861
|
-
table.add_column("Email", style="cyan")
|
|
35862
|
-
if pass_col:
|
|
35863
|
-
table.add_column("Password/Hash", style="red")
|
|
35864
|
-
|
|
35865
|
-
for row in rows[:5]:
|
|
35866
|
-
row_data = []
|
|
35867
|
-
if user_col:
|
|
35868
|
-
val = str(row.get(user_col, ""))[:30]
|
|
35869
|
-
row_data.append(
|
|
35870
|
-
val
|
|
35871
|
-
if val and val not in ["<blank>", "None", "NULL"]
|
|
35872
|
-
else "-"
|
|
35873
|
-
)
|
|
35874
|
-
if email_col and email_col != user_col:
|
|
35875
|
-
val = str(row.get(email_col, ""))[:35]
|
|
35876
|
-
row_data.append(val if val else "-")
|
|
35877
|
-
if pass_col:
|
|
35878
|
-
val = str(row.get(pass_col, ""))[:40]
|
|
35879
|
-
row_data.append(val if val else "-")
|
|
35880
|
-
if row_data:
|
|
35881
|
-
table.add_row(*row_data)
|
|
36698
|
+
# Cursor indicator
|
|
36699
|
+
cursor = "▶" if is_selected else ""
|
|
35882
36700
|
|
|
35883
|
-
|
|
36701
|
+
# Check if this table has been dumped
|
|
36702
|
+
dumped_key = next((k for k in all_dumped.keys() if tbl in k), None)
|
|
36703
|
+
has_data = dumped_key is not None
|
|
35884
36704
|
|
|
35885
|
-
|
|
35886
|
-
|
|
35887
|
-
|
|
35888
|
-
|
|
36705
|
+
# Determine category
|
|
36706
|
+
tbl_lower = tbl.lower()
|
|
36707
|
+
if any(
|
|
36708
|
+
x in tbl_lower
|
|
36709
|
+
for x in ["user", "account", "login", "credential", "password"]
|
|
36710
|
+
):
|
|
36711
|
+
category = "🔑 Creds"
|
|
36712
|
+
elif any(x in tbl_lower for x in ["card", "payment", "credit"]):
|
|
36713
|
+
category = "💳 Cards"
|
|
36714
|
+
elif any(
|
|
36715
|
+
x in tbl_lower
|
|
36716
|
+
for x in ["address", "personal", "customer", "profile"]
|
|
36717
|
+
):
|
|
36718
|
+
category = "👤 PII"
|
|
36719
|
+
else:
|
|
36720
|
+
category = ""
|
|
36721
|
+
|
|
36722
|
+
col_count = len(cols) if cols else 0
|
|
36723
|
+
|
|
36724
|
+
if has_data:
|
|
36725
|
+
row_count = all_dumped[dumped_key].get(
|
|
36726
|
+
"row_count", len(all_dumped[dumped_key].get("rows", []))
|
|
36727
|
+
)
|
|
36728
|
+
if is_selected:
|
|
36729
|
+
table.add_row(
|
|
36730
|
+
f"[bold yellow]{cursor}[/bold yellow]",
|
|
36731
|
+
"[bold yellow]○[/bold yellow]",
|
|
36732
|
+
f"[bold yellow]{tbl}[/bold yellow]",
|
|
36733
|
+
f"[bold yellow]{col_count}[/bold yellow]",
|
|
36734
|
+
f"[bold yellow]✓ {row_count}[/bold yellow]",
|
|
36735
|
+
f"[bold yellow]{category}[/bold yellow]",
|
|
36736
|
+
)
|
|
36737
|
+
else:
|
|
36738
|
+
table.add_row(
|
|
36739
|
+
cursor,
|
|
36740
|
+
"○",
|
|
36741
|
+
f"[green bold]{tbl}[/green bold]",
|
|
36742
|
+
str(col_count),
|
|
36743
|
+
f"[green]✓ {row_count}[/green]",
|
|
36744
|
+
category,
|
|
36745
|
+
)
|
|
36746
|
+
else:
|
|
36747
|
+
if is_selected:
|
|
36748
|
+
table.add_row(
|
|
36749
|
+
f"[bold yellow]{cursor}[/bold yellow]",
|
|
36750
|
+
"[bold yellow]○[/bold yellow]",
|
|
36751
|
+
f"[bold yellow]{tbl}[/bold yellow]",
|
|
36752
|
+
f"[bold yellow]{col_count}[/bold yellow]",
|
|
36753
|
+
"[bold yellow]-[/bold yellow]",
|
|
36754
|
+
f"[bold yellow]{category}[/bold yellow]",
|
|
36755
|
+
)
|
|
36756
|
+
else:
|
|
36757
|
+
table.add_row(
|
|
36758
|
+
cursor,
|
|
36759
|
+
"○",
|
|
36760
|
+
tbl,
|
|
36761
|
+
str(col_count),
|
|
36762
|
+
"[dim]-[/dim]",
|
|
36763
|
+
category,
|
|
36764
|
+
)
|
|
36765
|
+
|
|
36766
|
+
console.print(table)
|
|
35889
36767
|
|
|
35890
|
-
# Show card preview if available
|
|
35891
|
-
if has_cards:
|
|
35892
|
-
click.echo(click.style(" 💳 PAYMENT DATA PREVIEW", bold=True, fg="red"))
|
|
35893
36768
|
click.echo()
|
|
35894
36769
|
|
|
35895
|
-
for table_key, data in all_dumped.items():
|
|
35896
|
-
if any(x in table_key.lower() for x in ["card", "payment", "credit"]):
|
|
35897
|
-
rows = data.get("rows", [])
|
|
35898
|
-
columns = data.get("columns", [])
|
|
35899
36770
|
|
|
35900
|
-
|
|
35901
|
-
|
|
35902
|
-
|
|
35903
|
-
|
|
35904
|
-
|
|
35905
|
-
if any(x in c.lower() for x in ["name", "holder", "fullname"])
|
|
35906
|
-
),
|
|
35907
|
-
None,
|
|
35908
|
-
)
|
|
35909
|
-
card_col = next(
|
|
35910
|
-
(
|
|
35911
|
-
c
|
|
35912
|
-
for c in columns
|
|
35913
|
-
if any(
|
|
35914
|
-
x in c.lower()
|
|
35915
|
-
for x in ["cardnum", "card_number", "ccnumber", "number"]
|
|
35916
|
-
)
|
|
35917
|
-
),
|
|
35918
|
-
None,
|
|
35919
|
-
)
|
|
35920
|
-
exp_col = next(
|
|
35921
|
-
(
|
|
35922
|
-
c
|
|
35923
|
-
for c in columns
|
|
35924
|
-
if any(x in c.lower() for x in ["exp", "expir"])
|
|
35925
|
-
),
|
|
35926
|
-
None,
|
|
35927
|
-
)
|
|
36771
|
+
def _sqli_detail_overview(
|
|
36772
|
+
inj, all_databases, all_tables, all_columns, db_to_dbms, all_dumped, console
|
|
36773
|
+
):
|
|
36774
|
+
"""Show overview of SQLi exploitation results with database structure."""
|
|
36775
|
+
from rich.table import Table
|
|
35928
36776
|
|
|
35929
|
-
|
|
35930
|
-
|
|
35931
|
-
|
|
35932
|
-
|
|
35933
|
-
|
|
35934
|
-
|
|
35935
|
-
|
|
35936
|
-
|
|
35937
|
-
|
|
35938
|
-
|
|
35939
|
-
|
|
35940
|
-
|
|
35941
|
-
|
|
35942
|
-
|
|
35943
|
-
|
|
35944
|
-
|
|
35945
|
-
|
|
35946
|
-
|
|
35947
|
-
|
|
35948
|
-
|
|
36777
|
+
# Count extracted data by category
|
|
36778
|
+
cred_count = 0
|
|
36779
|
+
card_count = 0
|
|
36780
|
+
pii_count = 0
|
|
36781
|
+
|
|
36782
|
+
for key, data in all_dumped.items():
|
|
36783
|
+
row_count = data.get("row_count", len(data.get("rows", [])))
|
|
36784
|
+
key_lower = key.lower()
|
|
36785
|
+
if any(x in key_lower for x in ["user", "account", "login", "credential"]):
|
|
36786
|
+
cred_count += row_count
|
|
36787
|
+
elif any(x in key_lower for x in ["card", "payment", "credit"]):
|
|
36788
|
+
card_count += row_count
|
|
36789
|
+
elif any(
|
|
36790
|
+
x in key_lower for x in ["address", "personal", "customer", "profile"]
|
|
36791
|
+
):
|
|
36792
|
+
pii_count += row_count
|
|
36793
|
+
|
|
36794
|
+
# Summary alert if data extracted
|
|
36795
|
+
if cred_count > 0 or card_count > 0 or pii_count > 0:
|
|
36796
|
+
alerts = []
|
|
36797
|
+
if cred_count > 0:
|
|
36798
|
+
alerts.append(f"🔑 {cred_count} credentials")
|
|
36799
|
+
if card_count > 0:
|
|
36800
|
+
alerts.append(f"💳 {card_count} cards")
|
|
36801
|
+
if pii_count > 0:
|
|
36802
|
+
alerts.append(f"👤 {pii_count} PII records")
|
|
36803
|
+
click.echo(
|
|
36804
|
+
click.style(" ⚠️ EXTRACTED: ", fg="red", bold=True)
|
|
36805
|
+
+ click.style(" | ".join(alerts), fg="red")
|
|
36806
|
+
)
|
|
36807
|
+
click.echo()
|
|
35949
36808
|
|
|
35950
|
-
|
|
36809
|
+
# Database structure
|
|
36810
|
+
if not all_databases and not all_tables:
|
|
36811
|
+
click.echo(" No database structure discovered yet.")
|
|
36812
|
+
click.echo(" 💡 Run SQLMap with --tables to enumerate tables.")
|
|
36813
|
+
return
|
|
35951
36814
|
|
|
35952
|
-
|
|
35953
|
-
|
|
35954
|
-
|
|
35955
|
-
|
|
36815
|
+
# Show database structure with Rich table (like Host Management)
|
|
36816
|
+
for db in sorted(all_databases) if all_databases else list(all_tables.keys()):
|
|
36817
|
+
dbms_type = db_to_dbms.get(db, "SQLite")
|
|
36818
|
+
tables = all_tables.get(db, set())
|
|
35956
36819
|
|
|
35957
|
-
|
|
35958
|
-
if has_pii:
|
|
35959
|
-
click.echo(click.style(" 👤 PERSONAL DATA PREVIEW", bold=True, fg="yellow"))
|
|
36820
|
+
click.echo(f" {click.style('📂 ' + db, bold=True, fg='cyan')} ({dbms_type})")
|
|
35960
36821
|
click.echo()
|
|
35961
36822
|
|
|
35962
|
-
|
|
35963
|
-
|
|
35964
|
-
|
|
35965
|
-
|
|
35966
|
-
|
|
35967
|
-
|
|
35968
|
-
|
|
36823
|
+
if tables:
|
|
36824
|
+
# Create Rich table like Host Management
|
|
36825
|
+
table = Table(
|
|
36826
|
+
show_header=True,
|
|
36827
|
+
header_style="bold cyan",
|
|
36828
|
+
box=DesignSystem.TABLE_BOX,
|
|
36829
|
+
padding=(0, 1),
|
|
36830
|
+
expand=True,
|
|
36831
|
+
)
|
|
35969
36832
|
|
|
35970
|
-
|
|
35971
|
-
|
|
35972
|
-
|
|
35973
|
-
|
|
35974
|
-
|
|
35975
|
-
if any(
|
|
35976
|
-
x in c.lower() for x in ["name", "fullname", "full_name"]
|
|
35977
|
-
)
|
|
35978
|
-
),
|
|
35979
|
-
None,
|
|
35980
|
-
)
|
|
35981
|
-
city_col = next((c for c in columns if "city" in c.lower()), None)
|
|
35982
|
-
country_col = next((c for c in columns if "country" in c.lower()), None)
|
|
35983
|
-
address_col = next(
|
|
35984
|
-
(
|
|
35985
|
-
c
|
|
35986
|
-
for c in columns
|
|
35987
|
-
if any(x in c.lower() for x in ["address", "street"])
|
|
35988
|
-
),
|
|
35989
|
-
None,
|
|
35990
|
-
)
|
|
35991
|
-
phone_col = next(
|
|
35992
|
-
(
|
|
35993
|
-
c
|
|
35994
|
-
for c in columns
|
|
35995
|
-
if any(x in c.lower() for x in ["phone", "mobile", "tel"])
|
|
35996
|
-
),
|
|
35997
|
-
None,
|
|
35998
|
-
)
|
|
35999
|
-
zip_col = next(
|
|
36000
|
-
(
|
|
36001
|
-
c
|
|
36002
|
-
for c in columns
|
|
36003
|
-
if any(x in c.lower() for x in ["zip", "postal"])
|
|
36004
|
-
),
|
|
36005
|
-
None,
|
|
36006
|
-
)
|
|
36833
|
+
table.add_column("○", width=5, justify="center", no_wrap=True)
|
|
36834
|
+
table.add_column("Table Name", width=28, no_wrap=True)
|
|
36835
|
+
table.add_column("Cols", width=6, justify="right", no_wrap=True)
|
|
36836
|
+
table.add_column("Rows", width=12, justify="right", no_wrap=True)
|
|
36837
|
+
table.add_column("Type", width=12, no_wrap=True)
|
|
36007
36838
|
|
|
36008
|
-
|
|
36009
|
-
|
|
36010
|
-
|
|
36011
|
-
|
|
36012
|
-
padding=(0, 2),
|
|
36013
|
-
)
|
|
36014
|
-
if name_col:
|
|
36015
|
-
table.add_column("Name", style="yellow")
|
|
36016
|
-
if address_col:
|
|
36017
|
-
table.add_column("Address", style="white")
|
|
36018
|
-
if city_col:
|
|
36019
|
-
table.add_column("City", style="cyan")
|
|
36020
|
-
if country_col:
|
|
36021
|
-
table.add_column("Country", style="cyan")
|
|
36022
|
-
if zip_col:
|
|
36023
|
-
table.add_column("ZIP", style="dim")
|
|
36024
|
-
if phone_col:
|
|
36025
|
-
table.add_column("Phone", style="green")
|
|
36026
|
-
|
|
36027
|
-
for row in rows[:5]:
|
|
36028
|
-
row_data = []
|
|
36029
|
-
if name_col:
|
|
36030
|
-
row_data.append(str(row.get(name_col, "-"))[:25])
|
|
36031
|
-
if address_col:
|
|
36032
|
-
row_data.append(str(row.get(address_col, "-"))[:30])
|
|
36033
|
-
if city_col:
|
|
36034
|
-
row_data.append(str(row.get(city_col, "-"))[:15])
|
|
36035
|
-
if country_col:
|
|
36036
|
-
row_data.append(str(row.get(country_col, "-"))[:15])
|
|
36037
|
-
if zip_col:
|
|
36038
|
-
row_data.append(str(row.get(zip_col, "-"))[:10])
|
|
36039
|
-
if phone_col:
|
|
36040
|
-
row_data.append(str(row.get(phone_col, "-"))[:15])
|
|
36041
|
-
if row_data:
|
|
36042
|
-
table.add_row(*row_data)
|
|
36839
|
+
sorted_tables = sorted(tables)
|
|
36840
|
+
for tbl in sorted_tables:
|
|
36841
|
+
table_key = f"{db}.{tbl}"
|
|
36842
|
+
cols = all_columns.get(table_key, set())
|
|
36043
36843
|
|
|
36044
|
-
|
|
36844
|
+
# Check if this table has been dumped
|
|
36845
|
+
dumped_key = next((k for k in all_dumped.keys() if tbl in k), None)
|
|
36846
|
+
has_data = dumped_key is not None
|
|
36045
36847
|
|
|
36046
|
-
|
|
36047
|
-
|
|
36048
|
-
|
|
36049
|
-
|
|
36848
|
+
# Determine category
|
|
36849
|
+
tbl_lower = tbl.lower()
|
|
36850
|
+
if any(
|
|
36851
|
+
x in tbl_lower
|
|
36852
|
+
for x in ["user", "account", "login", "credential", "password"]
|
|
36853
|
+
):
|
|
36854
|
+
category = "🔑 Creds"
|
|
36855
|
+
elif any(x in tbl_lower for x in ["card", "payment", "credit"]):
|
|
36856
|
+
category = "💳 Cards"
|
|
36857
|
+
elif any(
|
|
36858
|
+
x in tbl_lower
|
|
36859
|
+
for x in ["address", "personal", "customer", "profile"]
|
|
36860
|
+
):
|
|
36861
|
+
category = "👤 PII"
|
|
36862
|
+
else:
|
|
36863
|
+
category = ""
|
|
36864
|
+
|
|
36865
|
+
col_count = len(cols) if cols else 0
|
|
36866
|
+
|
|
36867
|
+
if has_data:
|
|
36868
|
+
row_count = all_dumped[dumped_key].get(
|
|
36869
|
+
"row_count", len(all_dumped[dumped_key].get("rows", []))
|
|
36870
|
+
)
|
|
36871
|
+
table.add_row(
|
|
36872
|
+
"○",
|
|
36873
|
+
f"[green bold]{tbl}[/green bold]",
|
|
36874
|
+
str(col_count),
|
|
36875
|
+
f"[green]✓ {row_count}[/green]",
|
|
36876
|
+
category,
|
|
36877
|
+
)
|
|
36878
|
+
else:
|
|
36879
|
+
table.add_row(
|
|
36880
|
+
"○",
|
|
36881
|
+
tbl,
|
|
36882
|
+
str(col_count),
|
|
36883
|
+
"[dim]-[/dim]",
|
|
36884
|
+
category,
|
|
36885
|
+
)
|
|
36886
|
+
|
|
36887
|
+
console.print(table)
|
|
36888
|
+
|
|
36889
|
+
click.echo()
|
|
36050
36890
|
|
|
36051
36891
|
|
|
36052
36892
|
def _sqli_detail_tables(
|
|
36053
36893
|
all_databases, all_tables, all_columns, db_to_dbms, all_dumped, console
|
|
36054
36894
|
):
|
|
36055
36895
|
"""Show database structure overview."""
|
|
36056
|
-
from rich.
|
|
36057
|
-
from rich.tree import Tree
|
|
36896
|
+
from rich.table import Table
|
|
36058
36897
|
|
|
36059
36898
|
click.echo(click.style(" 📁 DATABASE STRUCTURE", bold=True, fg="cyan"))
|
|
36060
36899
|
click.echo()
|
|
@@ -36064,14 +36903,29 @@ def _sqli_detail_tables(
|
|
|
36064
36903
|
click.echo(" 💡 Run SQLMap with --tables to enumerate tables.")
|
|
36065
36904
|
return
|
|
36066
36905
|
|
|
36067
|
-
# Build tree structure
|
|
36068
36906
|
for db in sorted(all_databases) if all_databases else list(all_tables.keys()):
|
|
36069
36907
|
dbms_type = db_to_dbms.get(db, "SQLite")
|
|
36070
36908
|
tables = all_tables.get(db, set())
|
|
36071
36909
|
|
|
36072
36910
|
click.echo(f" {click.style('📂 ' + db, bold=True, fg='cyan')} ({dbms_type})")
|
|
36911
|
+
click.echo()
|
|
36073
36912
|
|
|
36074
36913
|
if tables:
|
|
36914
|
+
# Create Rich table like Host Management
|
|
36915
|
+
table = Table(
|
|
36916
|
+
show_header=True,
|
|
36917
|
+
header_style="bold cyan",
|
|
36918
|
+
box=DesignSystem.TABLE_BOX,
|
|
36919
|
+
padding=(0, 1),
|
|
36920
|
+
expand=True,
|
|
36921
|
+
)
|
|
36922
|
+
|
|
36923
|
+
table.add_column("○", width=5, justify="center", no_wrap=True)
|
|
36924
|
+
table.add_column("Table Name", width=28, no_wrap=True)
|
|
36925
|
+
table.add_column("Cols", width=6, justify="right", no_wrap=True)
|
|
36926
|
+
table.add_column("Rows", width=10, justify="right", no_wrap=True)
|
|
36927
|
+
table.add_column("Type", width=12, no_wrap=True)
|
|
36928
|
+
|
|
36075
36929
|
sorted_tables = sorted(tables)
|
|
36076
36930
|
for tbl in sorted_tables:
|
|
36077
36931
|
table_key = f"{db}.{tbl}"
|
|
@@ -36081,55 +36935,74 @@ def _sqli_detail_tables(
|
|
|
36081
36935
|
dumped_key = next((k for k in all_dumped.keys() if tbl in k), None)
|
|
36082
36936
|
has_data = dumped_key is not None
|
|
36083
36937
|
|
|
36084
|
-
#
|
|
36085
|
-
|
|
36086
|
-
|
|
36087
|
-
|
|
36088
|
-
|
|
36089
|
-
|
|
36090
|
-
|
|
36091
|
-
|
|
36092
|
-
|
|
36093
|
-
|
|
36094
|
-
|
|
36095
|
-
|
|
36096
|
-
|
|
36097
|
-
|
|
36938
|
+
# Determine category
|
|
36939
|
+
tbl_lower = tbl.lower()
|
|
36940
|
+
if any(
|
|
36941
|
+
x in tbl_lower
|
|
36942
|
+
for x in ["user", "account", "login", "credential", "password"]
|
|
36943
|
+
):
|
|
36944
|
+
category = "🔑 Creds"
|
|
36945
|
+
elif any(x in tbl_lower for x in ["card", "payment", "credit"]):
|
|
36946
|
+
category = "💳 Cards"
|
|
36947
|
+
elif any(
|
|
36948
|
+
x in tbl_lower
|
|
36949
|
+
for x in ["address", "personal", "customer", "profile"]
|
|
36950
|
+
):
|
|
36951
|
+
category = "👤 PII"
|
|
36952
|
+
else:
|
|
36953
|
+
category = ""
|
|
36954
|
+
|
|
36955
|
+
col_count = len(cols) if cols else 0
|
|
36098
36956
|
|
|
36099
36957
|
if has_data:
|
|
36100
36958
|
row_count = all_dumped[dumped_key].get("row_count", 0)
|
|
36101
|
-
|
|
36102
|
-
|
|
36103
|
-
|
|
36104
|
-
|
|
36105
|
-
|
|
36106
|
-
|
|
36107
|
-
f" ├─ {click.style(tbl, fg='red', bold=True)} ({len(cols)} cols) {status}"
|
|
36959
|
+
table.add_row(
|
|
36960
|
+
"○",
|
|
36961
|
+
f"[green bold]{tbl}[/green bold]",
|
|
36962
|
+
str(col_count),
|
|
36963
|
+
f"[green]✓ {row_count}[/green]",
|
|
36964
|
+
category,
|
|
36108
36965
|
)
|
|
36109
36966
|
else:
|
|
36110
|
-
|
|
36967
|
+
table.add_row(
|
|
36968
|
+
"○",
|
|
36969
|
+
tbl,
|
|
36970
|
+
str(col_count),
|
|
36971
|
+
"[dim]-[/dim]",
|
|
36972
|
+
category,
|
|
36973
|
+
)
|
|
36111
36974
|
|
|
36112
|
-
|
|
36113
|
-
if has_data and cols:
|
|
36114
|
-
col_list = sorted(cols)[:8]
|
|
36115
|
-
col_str = ", ".join(col_list)
|
|
36116
|
-
if len(cols) > 8:
|
|
36117
|
-
col_str += f" +{len(cols) - 8} more"
|
|
36118
|
-
click.echo(f" │ └─ {click.style(col_str, dim=True)}")
|
|
36975
|
+
console.print(table)
|
|
36119
36976
|
|
|
36120
36977
|
click.echo()
|
|
36121
36978
|
|
|
36122
36979
|
|
|
36123
|
-
def _sqli_detail_data_table(
|
|
36124
|
-
|
|
36980
|
+
def _sqli_detail_data_table(
|
|
36981
|
+
table_key: str,
|
|
36982
|
+
data: dict,
|
|
36983
|
+
console,
|
|
36984
|
+
width: int,
|
|
36985
|
+
page: int = 1,
|
|
36986
|
+
page_size: int = 20,
|
|
36987
|
+
view_all: bool = False,
|
|
36988
|
+
):
|
|
36989
|
+
"""Show full data for a specific table with pagination."""
|
|
36125
36990
|
from rich.table import Table
|
|
36126
36991
|
|
|
36127
36992
|
rows = data.get("rows", [])
|
|
36128
36993
|
columns = data.get("columns", [])
|
|
36129
36994
|
row_count = data.get("row_count", len(rows))
|
|
36995
|
+
total_pages = (len(rows) + page_size - 1) // page_size if not view_all else 1
|
|
36130
36996
|
|
|
36131
36997
|
click.echo(click.style(f" 📋 TABLE: {table_key}", bold=True, fg="green"))
|
|
36132
|
-
|
|
36998
|
+
if view_all:
|
|
36999
|
+
click.echo(
|
|
37000
|
+
f" {row_count} records | {len(columns)} columns | Showing ALL"
|
|
37001
|
+
)
|
|
37002
|
+
else:
|
|
37003
|
+
click.echo(
|
|
37004
|
+
f" {row_count} records | {len(columns)} columns | Page {page}/{total_pages}"
|
|
37005
|
+
)
|
|
36133
37006
|
click.echo()
|
|
36134
37007
|
|
|
36135
37008
|
if not rows:
|
|
@@ -36192,8 +37065,14 @@ def _sqli_detail_data_table(table_key: str, data: dict, console, width: int):
|
|
|
36192
37065
|
else:
|
|
36193
37066
|
table.add_column(col, max_width=col_width, overflow="ellipsis")
|
|
36194
37067
|
|
|
36195
|
-
# Add rows
|
|
36196
|
-
|
|
37068
|
+
# Add rows with pagination
|
|
37069
|
+
if view_all:
|
|
37070
|
+
display_rows = rows
|
|
37071
|
+
else:
|
|
37072
|
+
start_idx = (page - 1) * page_size
|
|
37073
|
+
end_idx = start_idx + page_size
|
|
37074
|
+
display_rows = rows[start_idx:end_idx]
|
|
37075
|
+
|
|
36197
37076
|
for row in display_rows:
|
|
36198
37077
|
row_data = []
|
|
36199
37078
|
for col in display_cols:
|
|
@@ -36206,9 +37085,11 @@ def _sqli_detail_data_table(table_key: str, data: dict, console, width: int):
|
|
|
36206
37085
|
|
|
36207
37086
|
console.print(table)
|
|
36208
37087
|
|
|
36209
|
-
if len(rows) >
|
|
37088
|
+
if not view_all and len(rows) > page_size:
|
|
37089
|
+
start_idx = (page - 1) * page_size
|
|
37090
|
+
end_idx = min(start_idx + page_size, len(rows))
|
|
36210
37091
|
click.echo()
|
|
36211
|
-
click.echo(f"
|
|
37092
|
+
click.echo(f" Showing rows {start_idx + 1}-{end_idx} of {len(rows)}")
|
|
36212
37093
|
|
|
36213
37094
|
# Show hidden columns
|
|
36214
37095
|
if len(columns) > max_cols:
|
|
@@ -40131,6 +41012,30 @@ def _launch_interactive_msfconsole(engagement_id: int):
|
|
|
40131
41012
|
rc_path = rc.name
|
|
40132
41013
|
|
|
40133
41014
|
try:
|
|
41015
|
+
# Auto-copy database.yml to /root/.msf4/ if needed
|
|
41016
|
+
# When msfdb init runs as normal user, config is in ~/.msf4/
|
|
41017
|
+
# But sudo msfconsole looks in /root/.msf4/, so we need to copy it
|
|
41018
|
+
from pathlib import Path
|
|
41019
|
+
|
|
41020
|
+
user_db = Path.home() / ".msf4" / "database.yml"
|
|
41021
|
+
root_db = Path("/root/.msf4/database.yml")
|
|
41022
|
+
if user_db.exists():
|
|
41023
|
+
# Check if root's config exists (use sudo to check)
|
|
41024
|
+
check_result = subprocess.run(
|
|
41025
|
+
["sudo", "test", "-f", str(root_db)], capture_output=True
|
|
41026
|
+
)
|
|
41027
|
+
if check_result.returncode != 0:
|
|
41028
|
+
# Root's config doesn't exist, copy it
|
|
41029
|
+
click.echo(
|
|
41030
|
+
click.style(" Configuring database for sudo access...", fg="cyan")
|
|
41031
|
+
)
|
|
41032
|
+
subprocess.run(
|
|
41033
|
+
["sudo", "mkdir", "-p", "/root/.msf4"], capture_output=True
|
|
41034
|
+
)
|
|
41035
|
+
subprocess.run(
|
|
41036
|
+
["sudo", "cp", str(user_db), str(root_db)], capture_output=True
|
|
41037
|
+
)
|
|
41038
|
+
|
|
40134
41039
|
click.pause("Press Enter to launch msfconsole...")
|
|
40135
41040
|
|
|
40136
41041
|
# Launch msfconsole with our resource script
|
|
@@ -40284,18 +41189,27 @@ def _ensure_msf_database_ready():
|
|
|
40284
41189
|
click.style(" MSF database needs initialization...", fg="yellow")
|
|
40285
41190
|
)
|
|
40286
41191
|
|
|
40287
|
-
# Initialize msfdb
|
|
41192
|
+
# Initialize msfdb - distro-aware (Kali needs sudo, Ubuntu doesn't)
|
|
41193
|
+
from souleyez.utils.tool_checker import detect_distro
|
|
41194
|
+
|
|
41195
|
+
distro = detect_distro()
|
|
41196
|
+
use_sudo = distro in ("kali", "parrot")
|
|
41197
|
+
|
|
40288
41198
|
click.echo(" Running msfdb init (this may take a moment)...")
|
|
41199
|
+
msfdb_cmd = ["sudo", "msfdb", "init"] if use_sudo else ["msfdb", "init"]
|
|
40289
41200
|
result = subprocess.run(
|
|
40290
|
-
|
|
41201
|
+
msfdb_cmd, capture_output=True, text=True, timeout=120
|
|
40291
41202
|
)
|
|
40292
41203
|
if result.returncode == 0:
|
|
40293
41204
|
click.echo(click.style(" ✓ MSF database initialized", fg="green"))
|
|
40294
41205
|
else:
|
|
40295
41206
|
# Try msfdb reinit if init fails
|
|
40296
41207
|
click.echo(click.style(" Init failed, trying reinit...", fg="yellow"))
|
|
41208
|
+
msfdb_reinit_cmd = (
|
|
41209
|
+
["sudo", "msfdb", "reinit"] if use_sudo else ["msfdb", "reinit"]
|
|
41210
|
+
)
|
|
40297
41211
|
result = subprocess.run(
|
|
40298
|
-
|
|
41212
|
+
msfdb_reinit_cmd,
|
|
40299
41213
|
capture_output=True,
|
|
40300
41214
|
text=True,
|
|
40301
41215
|
timeout=120,
|
|
@@ -40534,7 +41448,14 @@ def _guided_msf_setup():
|
|
|
40534
41448
|
click.echo()
|
|
40535
41449
|
click.echo(" Please run these commands manually:")
|
|
40536
41450
|
click.echo(click.style(" sudo systemctl start postgresql", fg="cyan"))
|
|
40537
|
-
|
|
41451
|
+
# Show distro-appropriate msfdb command
|
|
41452
|
+
from souleyez.utils.tool_checker import detect_distro
|
|
41453
|
+
|
|
41454
|
+
distro = detect_distro()
|
|
41455
|
+
if distro in ("kali", "parrot"):
|
|
41456
|
+
click.echo(click.style(" sudo msfdb init", fg="cyan"))
|
|
41457
|
+
else:
|
|
41458
|
+
click.echo(click.style(" msfdb init", fg="cyan"))
|
|
40538
41459
|
click.echo()
|
|
40539
41460
|
if not click.confirm(" Continue anyway?", default=False):
|
|
40540
41461
|
click.pause()
|
|
@@ -43199,15 +44120,22 @@ def _check_msfdb_ready() -> bool:
|
|
|
43199
44120
|
" Without it, you won't be able to store hosts, credentials, or loot."
|
|
43200
44121
|
)
|
|
43201
44122
|
click.echo()
|
|
44123
|
+
|
|
44124
|
+
# Distro-aware msfdb init (Kali needs sudo, Ubuntu doesn't)
|
|
44125
|
+
from souleyez.utils.tool_checker import detect_distro
|
|
44126
|
+
|
|
44127
|
+
distro = detect_distro()
|
|
44128
|
+
use_sudo = distro in ("kali", "parrot")
|
|
44129
|
+
msfdb_cmd_str = "sudo msfdb init" if use_sudo else "msfdb init"
|
|
44130
|
+
msfdb_cmd = ["sudo", "msfdb", "init"] if use_sudo else ["msfdb", "init"]
|
|
44131
|
+
|
|
43202
44132
|
if click.confirm(
|
|
43203
|
-
" Initialize database now? (runs:
|
|
44133
|
+
f" Initialize database now? (runs: {msfdb_cmd_str})", default=True
|
|
43204
44134
|
):
|
|
43205
44135
|
click.echo()
|
|
43206
|
-
click.echo(click.style(" Running
|
|
44136
|
+
click.echo(click.style(f" Running {msfdb_cmd_str}...", fg="cyan"))
|
|
43207
44137
|
try:
|
|
43208
|
-
result = subprocess.run(
|
|
43209
|
-
["sudo", "msfdb", "init"], capture_output=False, text=True
|
|
43210
|
-
)
|
|
44138
|
+
result = subprocess.run(msfdb_cmd, capture_output=False, text=True)
|
|
43211
44139
|
if result.returncode == 0:
|
|
43212
44140
|
click.echo(
|
|
43213
44141
|
click.style(" Database initialized successfully!", fg="green")
|