souleyez 2.43.29__py3-none-any.whl → 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- souleyez/__init__.py +1 -2
- souleyez/ai/__init__.py +21 -15
- souleyez/ai/action_mapper.py +249 -150
- souleyez/ai/chain_advisor.py +116 -100
- souleyez/ai/claude_provider.py +29 -28
- souleyez/ai/context_builder.py +80 -62
- souleyez/ai/executor.py +158 -117
- souleyez/ai/feedback_handler.py +136 -121
- souleyez/ai/llm_factory.py +27 -20
- souleyez/ai/llm_provider.py +4 -2
- souleyez/ai/ollama_provider.py +6 -9
- souleyez/ai/ollama_service.py +44 -37
- souleyez/ai/path_scorer.py +91 -76
- souleyez/ai/recommender.py +176 -144
- souleyez/ai/report_context.py +74 -73
- souleyez/ai/report_service.py +84 -66
- souleyez/ai/result_parser.py +222 -229
- souleyez/ai/safety.py +67 -44
- souleyez/auth/__init__.py +23 -22
- souleyez/auth/audit.py +36 -26
- souleyez/auth/engagement_access.py +65 -48
- souleyez/auth/permissions.py +14 -3
- souleyez/auth/session_manager.py +54 -37
- souleyez/auth/user_manager.py +109 -64
- souleyez/commands/audit.py +40 -43
- souleyez/commands/auth.py +35 -15
- souleyez/commands/deliverables.py +55 -50
- souleyez/commands/engagement.py +47 -28
- souleyez/commands/license.py +32 -23
- souleyez/commands/screenshots.py +36 -32
- souleyez/commands/user.py +82 -36
- souleyez/config.py +52 -44
- souleyez/core/credential_tester.py +87 -81
- souleyez/core/cve_mappings.py +179 -192
- souleyez/core/cve_matcher.py +162 -148
- souleyez/core/msf_auto_mapper.py +100 -83
- souleyez/core/msf_chain_engine.py +294 -256
- souleyez/core/msf_database.py +153 -70
- souleyez/core/msf_integration.py +679 -673
- souleyez/core/msf_rpc_client.py +40 -42
- souleyez/core/msf_rpc_manager.py +77 -79
- souleyez/core/msf_sync_manager.py +241 -181
- souleyez/core/network_utils.py +22 -15
- souleyez/core/parser_handler.py +34 -25
- souleyez/core/pending_chains.py +114 -63
- souleyez/core/templates.py +158 -107
- souleyez/core/tool_chaining.py +9564 -2881
- souleyez/core/version_utils.py +79 -94
- souleyez/core/vuln_correlation.py +136 -89
- souleyez/core/web_utils.py +33 -32
- souleyez/data/wordlists/ad_users.txt +378 -0
- souleyez/data/wordlists/api_endpoints_large.txt +769 -0
- souleyez/data/wordlists/home_dir_sensitive.txt +39 -0
- souleyez/data/wordlists/lfi_payloads.txt +82 -0
- souleyez/data/wordlists/passwords_brute.txt +1548 -0
- souleyez/data/wordlists/passwords_crack.txt +2479 -0
- souleyez/data/wordlists/passwords_spray.txt +386 -0
- souleyez/data/wordlists/subdomains_large.txt +5057 -0
- souleyez/data/wordlists/usernames_common.txt +694 -0
- souleyez/data/wordlists/web_dirs_large.txt +4769 -0
- souleyez/detection/__init__.py +1 -1
- souleyez/detection/attack_signatures.py +12 -17
- souleyez/detection/mitre_mappings.py +61 -55
- souleyez/detection/validator.py +97 -86
- souleyez/devtools.py +23 -10
- souleyez/docs/README.md +4 -4
- souleyez/docs/api-reference/cli-commands.md +2 -2
- souleyez/docs/developer-guide/adding-new-tools.md +562 -0
- souleyez/docs/user-guide/auto-chaining.md +30 -8
- souleyez/docs/user-guide/getting-started.md +1 -1
- souleyez/docs/user-guide/installation.md +26 -3
- souleyez/docs/user-guide/metasploit-integration.md +2 -2
- souleyez/docs/user-guide/rbac.md +1 -1
- souleyez/docs/user-guide/scope-management.md +1 -1
- souleyez/docs/user-guide/siem-integration.md +1 -1
- souleyez/docs/user-guide/tools-reference.md +1 -8
- souleyez/docs/user-guide/worker-management.md +1 -1
- souleyez/engine/background.py +1239 -535
- souleyez/engine/base.py +4 -1
- souleyez/engine/job_status.py +17 -49
- souleyez/engine/log_sanitizer.py +103 -77
- souleyez/engine/manager.py +38 -7
- souleyez/engine/result_handler.py +2200 -1550
- souleyez/engine/worker_manager.py +50 -41
- souleyez/export/evidence_bundle.py +72 -62
- souleyez/feature_flags/features.py +16 -20
- souleyez/feature_flags.py +5 -9
- souleyez/handlers/__init__.py +11 -0
- souleyez/handlers/base.py +188 -0
- souleyez/handlers/bash_handler.py +277 -0
- souleyez/handlers/bloodhound_handler.py +243 -0
- souleyez/handlers/certipy_handler.py +311 -0
- souleyez/handlers/crackmapexec_handler.py +486 -0
- souleyez/handlers/dnsrecon_handler.py +344 -0
- souleyez/handlers/enum4linux_handler.py +400 -0
- souleyez/handlers/evil_winrm_handler.py +493 -0
- souleyez/handlers/ffuf_handler.py +815 -0
- souleyez/handlers/gobuster_handler.py +1114 -0
- souleyez/handlers/gpp_extract_handler.py +334 -0
- souleyez/handlers/hashcat_handler.py +444 -0
- souleyez/handlers/hydra_handler.py +564 -0
- souleyez/handlers/impacket_getuserspns_handler.py +343 -0
- souleyez/handlers/impacket_psexec_handler.py +222 -0
- souleyez/handlers/impacket_secretsdump_handler.py +426 -0
- souleyez/handlers/john_handler.py +286 -0
- souleyez/handlers/katana_handler.py +425 -0
- souleyez/handlers/kerbrute_handler.py +298 -0
- souleyez/handlers/ldapsearch_handler.py +636 -0
- souleyez/handlers/lfi_extract_handler.py +464 -0
- souleyez/handlers/msf_auxiliary_handler.py +409 -0
- souleyez/handlers/msf_exploit_handler.py +380 -0
- souleyez/handlers/nikto_handler.py +413 -0
- souleyez/handlers/nmap_handler.py +821 -0
- souleyez/handlers/nuclei_handler.py +359 -0
- souleyez/handlers/nxc_handler.py +417 -0
- souleyez/handlers/rdp_sec_check_handler.py +353 -0
- souleyez/handlers/registry.py +292 -0
- souleyez/handlers/responder_handler.py +232 -0
- souleyez/handlers/service_explorer_handler.py +434 -0
- souleyez/handlers/smbclient_handler.py +344 -0
- souleyez/handlers/smbmap_handler.py +510 -0
- souleyez/handlers/smbpasswd_handler.py +296 -0
- souleyez/handlers/sqlmap_handler.py +1116 -0
- souleyez/handlers/theharvester_handler.py +601 -0
- souleyez/handlers/web_login_test_handler.py +327 -0
- souleyez/handlers/whois_handler.py +277 -0
- souleyez/handlers/wpscan_handler.py +554 -0
- souleyez/history.py +32 -16
- souleyez/importers/msf_importer.py +106 -75
- souleyez/importers/smart_importer.py +208 -147
- souleyez/integrations/siem/__init__.py +10 -10
- souleyez/integrations/siem/base.py +17 -18
- souleyez/integrations/siem/elastic.py +108 -122
- souleyez/integrations/siem/factory.py +207 -80
- souleyez/integrations/siem/googlesecops.py +146 -154
- souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
- souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
- souleyez/integrations/siem/sentinel.py +107 -109
- souleyez/integrations/siem/splunk.py +246 -212
- souleyez/integrations/siem/wazuh.py +65 -71
- souleyez/integrations/wazuh/__init__.py +5 -5
- souleyez/integrations/wazuh/client.py +70 -93
- souleyez/integrations/wazuh/config.py +85 -57
- souleyez/integrations/wazuh/host_mapper.py +28 -36
- souleyez/integrations/wazuh/sync.py +78 -68
- souleyez/intelligence/__init__.py +4 -5
- souleyez/intelligence/correlation_analyzer.py +309 -295
- souleyez/intelligence/exploit_knowledge.py +661 -623
- souleyez/intelligence/exploit_suggestions.py +159 -139
- souleyez/intelligence/gap_analyzer.py +132 -97
- souleyez/intelligence/gap_detector.py +251 -214
- souleyez/intelligence/sensitive_tables.py +266 -129
- souleyez/intelligence/service_parser.py +137 -123
- souleyez/intelligence/surface_analyzer.py +407 -268
- souleyez/intelligence/target_parser.py +159 -162
- souleyez/licensing/__init__.py +6 -6
- souleyez/licensing/validator.py +17 -19
- souleyez/log_config.py +79 -54
- souleyez/main.py +1505 -687
- souleyez/migrations/fix_job_counter.py +16 -14
- souleyez/parsers/bloodhound_parser.py +41 -39
- souleyez/parsers/crackmapexec_parser.py +178 -111
- souleyez/parsers/dalfox_parser.py +72 -77
- souleyez/parsers/dnsrecon_parser.py +103 -91
- souleyez/parsers/enum4linux_parser.py +183 -153
- souleyez/parsers/ffuf_parser.py +29 -25
- souleyez/parsers/gobuster_parser.py +301 -41
- souleyez/parsers/hashcat_parser.py +324 -79
- souleyez/parsers/http_fingerprint_parser.py +350 -103
- souleyez/parsers/hydra_parser.py +131 -111
- souleyez/parsers/impacket_parser.py +231 -178
- souleyez/parsers/john_parser.py +98 -86
- souleyez/parsers/katana_parser.py +316 -0
- souleyez/parsers/msf_parser.py +943 -498
- souleyez/parsers/nikto_parser.py +346 -65
- souleyez/parsers/nmap_parser.py +262 -174
- souleyez/parsers/nuclei_parser.py +40 -44
- souleyez/parsers/responder_parser.py +26 -26
- souleyez/parsers/searchsploit_parser.py +74 -74
- souleyez/parsers/service_explorer_parser.py +279 -0
- souleyez/parsers/smbmap_parser.py +180 -124
- souleyez/parsers/sqlmap_parser.py +434 -308
- souleyez/parsers/theharvester_parser.py +75 -57
- souleyez/parsers/whois_parser.py +135 -94
- souleyez/parsers/wpscan_parser.py +278 -190
- souleyez/plugins/afp.py +44 -36
- souleyez/plugins/afp_brute.py +114 -46
- souleyez/plugins/ard.py +48 -37
- souleyez/plugins/bloodhound.py +95 -61
- souleyez/plugins/certipy.py +303 -0
- souleyez/plugins/crackmapexec.py +186 -85
- souleyez/plugins/dalfox.py +120 -59
- souleyez/plugins/dns_hijack.py +146 -41
- souleyez/plugins/dnsrecon.py +97 -61
- souleyez/plugins/enum4linux.py +91 -66
- souleyez/plugins/evil_winrm.py +291 -0
- souleyez/plugins/ffuf.py +166 -90
- souleyez/plugins/firmware_extract.py +133 -29
- souleyez/plugins/gobuster.py +387 -190
- souleyez/plugins/gpp_extract.py +393 -0
- souleyez/plugins/hashcat.py +100 -73
- souleyez/plugins/http_fingerprint.py +913 -267
- souleyez/plugins/hydra.py +566 -200
- souleyez/plugins/impacket_getnpusers.py +117 -69
- souleyez/plugins/impacket_psexec.py +84 -64
- souleyez/plugins/impacket_secretsdump.py +103 -69
- souleyez/plugins/impacket_smbclient.py +89 -75
- souleyez/plugins/john.py +86 -69
- souleyez/plugins/katana.py +313 -0
- souleyez/plugins/kerbrute.py +237 -0
- souleyez/plugins/lfi_extract.py +541 -0
- souleyez/plugins/macos_ssh.py +117 -48
- souleyez/plugins/mdns.py +35 -30
- souleyez/plugins/msf_auxiliary.py +253 -130
- souleyez/plugins/msf_exploit.py +239 -161
- souleyez/plugins/nikto.py +134 -78
- souleyez/plugins/nmap.py +275 -91
- souleyez/plugins/nuclei.py +180 -89
- souleyez/plugins/nxc.py +285 -0
- souleyez/plugins/plugin_base.py +35 -36
- souleyez/plugins/plugin_template.py +13 -5
- souleyez/plugins/rdp_sec_check.py +130 -0
- souleyez/plugins/responder.py +112 -71
- souleyez/plugins/router_http_brute.py +76 -65
- souleyez/plugins/router_ssh_brute.py +118 -41
- souleyez/plugins/router_telnet_brute.py +124 -42
- souleyez/plugins/routersploit.py +91 -59
- souleyez/plugins/routersploit_exploit.py +77 -55
- souleyez/plugins/searchsploit.py +91 -77
- souleyez/plugins/service_explorer.py +1160 -0
- souleyez/plugins/smbmap.py +122 -72
- souleyez/plugins/smbpasswd.py +215 -0
- souleyez/plugins/sqlmap.py +301 -113
- souleyez/plugins/theharvester.py +127 -75
- souleyez/plugins/tr069.py +79 -57
- souleyez/plugins/upnp.py +65 -47
- souleyez/plugins/upnp_abuse.py +73 -55
- souleyez/plugins/vnc_access.py +129 -42
- souleyez/plugins/vnc_brute.py +109 -38
- souleyez/plugins/web_login_test.py +417 -0
- souleyez/plugins/whois.py +77 -58
- souleyez/plugins/wpscan.py +219 -69
- souleyez/reporting/__init__.py +2 -1
- souleyez/reporting/attack_chain.py +411 -346
- souleyez/reporting/charts.py +436 -501
- souleyez/reporting/compliance_mappings.py +334 -201
- souleyez/reporting/detection_report.py +126 -125
- souleyez/reporting/formatters.py +828 -591
- souleyez/reporting/generator.py +386 -302
- souleyez/reporting/metrics.py +72 -75
- souleyez/scanner.py +35 -29
- souleyez/security/__init__.py +37 -11
- souleyez/security/scope_validator.py +175 -106
- souleyez/security/validation.py +237 -149
- souleyez/security.py +22 -6
- souleyez/storage/credentials.py +247 -186
- souleyez/storage/crypto.py +296 -129
- souleyez/storage/database.py +73 -50
- souleyez/storage/db.py +58 -36
- souleyez/storage/deliverable_evidence.py +177 -128
- souleyez/storage/deliverable_exporter.py +282 -246
- souleyez/storage/deliverable_templates.py +134 -116
- souleyez/storage/deliverables.py +135 -130
- souleyez/storage/engagements.py +109 -56
- souleyez/storage/evidence.py +181 -152
- souleyez/storage/execution_log.py +31 -17
- souleyez/storage/exploit_attempts.py +93 -57
- souleyez/storage/exploits.py +67 -36
- souleyez/storage/findings.py +48 -61
- souleyez/storage/hosts.py +176 -144
- souleyez/storage/migrate_to_engagements.py +43 -19
- souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
- souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
- souleyez/storage/migrations/_003_add_execution_log.py +14 -8
- souleyez/storage/migrations/_005_screenshots.py +13 -5
- souleyez/storage/migrations/_006_deliverables.py +13 -5
- souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
- souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
- souleyez/storage/migrations/_010_evidence_linking.py +17 -10
- souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
- souleyez/storage/migrations/_012_team_collaboration.py +34 -21
- souleyez/storage/migrations/_013_add_host_tags.py +12 -6
- souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
- souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
- souleyez/storage/migrations/_016_add_domain_field.py +10 -4
- souleyez/storage/migrations/_017_msf_sessions.py +16 -8
- souleyez/storage/migrations/_018_add_osint_target.py +10 -6
- souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
- souleyez/storage/migrations/_020_add_rbac.py +36 -15
- souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
- souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
- souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
- souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
- souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
- souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
- souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
- souleyez/storage/migrations/__init__.py +26 -26
- souleyez/storage/migrations/migration_manager.py +19 -19
- souleyez/storage/msf_sessions.py +100 -65
- souleyez/storage/osint.py +17 -24
- souleyez/storage/recommendation_engine.py +269 -235
- souleyez/storage/screenshots.py +33 -32
- souleyez/storage/smb_shares.py +136 -92
- souleyez/storage/sqlmap_data.py +183 -128
- souleyez/storage/team_collaboration.py +135 -141
- souleyez/storage/timeline_tracker.py +122 -94
- souleyez/storage/wazuh_vulns.py +64 -66
- souleyez/storage/web_paths.py +33 -37
- souleyez/testing/credential_tester.py +221 -205
- souleyez/ui/__init__.py +1 -1
- souleyez/ui/ai_quotes.py +12 -12
- souleyez/ui/attack_surface.py +2439 -1516
- souleyez/ui/chain_rules_view.py +914 -382
- souleyez/ui/correlation_view.py +312 -230
- souleyez/ui/dashboard.py +2382 -1130
- souleyez/ui/deliverables_view.py +148 -62
- souleyez/ui/design_system.py +13 -13
- souleyez/ui/errors.py +49 -49
- souleyez/ui/evidence_linking_view.py +284 -179
- souleyez/ui/evidence_vault.py +393 -285
- souleyez/ui/exploit_suggestions_view.py +555 -349
- souleyez/ui/export_view.py +100 -66
- souleyez/ui/gap_analysis_view.py +315 -171
- souleyez/ui/help_system.py +105 -97
- souleyez/ui/intelligence_view.py +436 -293
- souleyez/ui/interactive.py +23034 -10679
- souleyez/ui/interactive_selector.py +75 -68
- souleyez/ui/log_formatter.py +47 -39
- souleyez/ui/menu_components.py +22 -13
- souleyez/ui/msf_auxiliary_menu.py +184 -133
- souleyez/ui/pending_chains_view.py +336 -172
- souleyez/ui/progress_indicators.py +5 -3
- souleyez/ui/recommendations_view.py +195 -137
- souleyez/ui/rule_builder.py +343 -225
- souleyez/ui/setup_wizard.py +678 -284
- souleyez/ui/shortcuts.py +217 -165
- souleyez/ui/splunk_gap_analysis_view.py +452 -270
- souleyez/ui/splunk_vulns_view.py +139 -86
- souleyez/ui/team_dashboard.py +498 -335
- souleyez/ui/template_selector.py +196 -105
- souleyez/ui/terminal.py +6 -6
- souleyez/ui/timeline_view.py +198 -127
- souleyez/ui/tool_setup.py +264 -164
- souleyez/ui/tutorial.py +202 -72
- souleyez/ui/tutorial_state.py +40 -40
- souleyez/ui/wazuh_vulns_view.py +235 -141
- souleyez/ui/wordlist_browser.py +260 -107
- souleyez/ui.py +464 -312
- souleyez/utils/tool_checker.py +427 -367
- souleyez/utils.py +33 -29
- souleyez/wordlists.py +134 -167
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/METADATA +2 -2
- souleyez-3.0.0.dist-info/RECORD +443 -0
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/WHEEL +1 -1
- souleyez-2.43.29.dist-info/RECORD +0 -379
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/entry_points.txt +0 -0
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/top_level.txt +0 -0
|
@@ -10,6 +10,7 @@ Features:
|
|
|
10
10
|
- Direct MSF execution integration
|
|
11
11
|
- Detailed info views with full context
|
|
12
12
|
"""
|
|
13
|
+
|
|
13
14
|
import click
|
|
14
15
|
import os
|
|
15
16
|
import tempfile
|
|
@@ -25,57 +26,54 @@ from souleyez.core.msf_integration import MSFResourceGenerator, MSFConsoleManage
|
|
|
25
26
|
console = Console()
|
|
26
27
|
|
|
27
28
|
# Status indicators
|
|
28
|
-
STATUS_ICONS = {
|
|
29
|
-
'not_tried': '⚪',
|
|
30
|
-
'attempted': '🔄',
|
|
31
|
-
'failed': '❌',
|
|
32
|
-
'success': '✅'
|
|
33
|
-
}
|
|
29
|
+
STATUS_ICONS = {"not_tried": "⚪", "attempted": "🔄", "failed": "❌", "success": "✅"}
|
|
34
30
|
|
|
35
31
|
# Actionability indicators
|
|
36
32
|
ACTION_ICONS = {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
"one_click": "🎯", # Has MSF module, ready to execute
|
|
34
|
+
"setup_needed": "🔧", # Requires configuration/parameters
|
|
35
|
+
"manual": "📄", # Manual exploitation required
|
|
36
|
+
"research": "🔍", # Needs more research
|
|
41
37
|
}
|
|
42
38
|
|
|
43
39
|
|
|
44
40
|
def _determine_actionability(exploit: Dict) -> str:
|
|
45
41
|
"""Determine how actionable an exploit is."""
|
|
46
42
|
# One-click if has MSF module and no obvious parameters needed
|
|
47
|
-
if exploit.get(
|
|
43
|
+
if exploit.get("msf_module"):
|
|
48
44
|
# Check if it needs creds or configuration (heuristic)
|
|
49
|
-
title_lower = exploit.get(
|
|
50
|
-
if any(
|
|
51
|
-
|
|
45
|
+
title_lower = exploit.get("title", "").lower()
|
|
46
|
+
if any(
|
|
47
|
+
word in title_lower for word in ["auth", "credential", "login", "password"]
|
|
48
|
+
):
|
|
49
|
+
return "setup_needed"
|
|
52
50
|
# Check description for complexity indicators
|
|
53
|
-
desc = exploit.get(
|
|
54
|
-
if any(word in desc for word in [
|
|
55
|
-
return
|
|
56
|
-
return
|
|
51
|
+
desc = exploit.get("description", "").lower()
|
|
52
|
+
if any(word in desc for word in ["requires", "must configure", "needs"]):
|
|
53
|
+
return "setup_needed"
|
|
54
|
+
return "one_click"
|
|
57
55
|
|
|
58
56
|
# Has EDB but no MSF module - likely manual
|
|
59
|
-
if exploit.get(
|
|
60
|
-
return
|
|
57
|
+
if exploit.get("edb_id"):
|
|
58
|
+
return "manual"
|
|
61
59
|
|
|
62
60
|
# Has CVE but no module - research needed
|
|
63
|
-
if exploit.get(
|
|
64
|
-
return
|
|
61
|
+
if exploit.get("cve"):
|
|
62
|
+
return "research"
|
|
65
63
|
|
|
66
|
-
return
|
|
64
|
+
return "research"
|
|
67
65
|
|
|
68
66
|
|
|
69
67
|
def _render_status_badge(status: str) -> str:
|
|
70
68
|
"""Render a status badge with icon."""
|
|
71
|
-
icon = STATUS_ICONS.get(status,
|
|
72
|
-
if status ==
|
|
69
|
+
icon = STATUS_ICONS.get(status, "⚪")
|
|
70
|
+
if status == "not_tried":
|
|
73
71
|
return f"{icon}"
|
|
74
|
-
elif status ==
|
|
72
|
+
elif status == "attempted":
|
|
75
73
|
return f"[yellow]{icon}[/yellow]"
|
|
76
|
-
elif status ==
|
|
74
|
+
elif status == "failed":
|
|
77
75
|
return f"[red]{icon}[/red]"
|
|
78
|
-
elif status ==
|
|
76
|
+
elif status == "success":
|
|
79
77
|
return f"[green]{icon}[/green]"
|
|
80
78
|
return icon
|
|
81
79
|
|
|
@@ -83,24 +81,24 @@ def _render_status_badge(status: str) -> str:
|
|
|
83
81
|
def _render_actionability_badge(actionability: str) -> str:
|
|
84
82
|
"""Render actionability badge with text label."""
|
|
85
83
|
labels = {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
84
|
+
"one_click": "[green]READY[/green]",
|
|
85
|
+
"setup_needed": "[yellow]SETUP[/yellow]",
|
|
86
|
+
"manual": "[cyan]MANUAL[/cyan]",
|
|
87
|
+
"research": "[dim]RESEARCH[/dim]",
|
|
90
88
|
}
|
|
91
|
-
return labels.get(actionability,
|
|
89
|
+
return labels.get(actionability, "[dim]UNKNOWN[/dim]")
|
|
92
90
|
|
|
93
91
|
|
|
94
92
|
def _format_severity_badge(severity: str) -> str:
|
|
95
93
|
"""Format severity with color - full names, no abbreviations."""
|
|
96
|
-
severity = severity or
|
|
97
|
-
if severity ==
|
|
94
|
+
severity = severity or "info"
|
|
95
|
+
if severity == "critical":
|
|
98
96
|
return "[red bold]CRITICAL[/red bold]"
|
|
99
|
-
elif severity ==
|
|
97
|
+
elif severity == "high":
|
|
100
98
|
return "[yellow bold]HIGH[/yellow bold]"
|
|
101
|
-
elif severity ==
|
|
99
|
+
elif severity == "medium":
|
|
102
100
|
return "[white]MEDIUM[/white]"
|
|
103
|
-
elif severity ==
|
|
101
|
+
elif severity == "low":
|
|
104
102
|
return "[dim]LOW[/dim]"
|
|
105
103
|
else:
|
|
106
104
|
return "[dim]INFO[/dim]"
|
|
@@ -112,7 +110,7 @@ def _render_exploits_table(
|
|
|
112
110
|
show_all: bool = False,
|
|
113
111
|
filter_untried: bool = False,
|
|
114
112
|
start_index: int = 1,
|
|
115
|
-
selected_exploits: set = None
|
|
113
|
+
selected_exploits: set = None,
|
|
116
114
|
) -> None:
|
|
117
115
|
"""Render exploits in an enhanced table format."""
|
|
118
116
|
if not exploits:
|
|
@@ -120,7 +118,7 @@ def _render_exploits_table(
|
|
|
120
118
|
|
|
121
119
|
# Filter if needed
|
|
122
120
|
if filter_untried:
|
|
123
|
-
exploits = [e for e in exploits if e.get(
|
|
121
|
+
exploits = [e for e in exploits if e.get("attempt_status") == "not_tried"]
|
|
124
122
|
|
|
125
123
|
if not exploits:
|
|
126
124
|
console.print(f" [dim]No untried exploits in this category[/dim]")
|
|
@@ -134,11 +132,13 @@ def _render_exploits_table(
|
|
|
134
132
|
header_style="bold cyan",
|
|
135
133
|
box=None,
|
|
136
134
|
padding=(0, 1),
|
|
137
|
-
width=min(width - 6, 140)
|
|
135
|
+
width=min(width - 6, 140),
|
|
138
136
|
)
|
|
139
137
|
|
|
140
138
|
table.add_column("○", width=3, justify="center") # Selection checkbox
|
|
141
|
-
table.add_column(
|
|
139
|
+
table.add_column(
|
|
140
|
+
"ID", width=4, style="cyan"
|
|
141
|
+
) # Row ID for selection - leftmost for easy reference
|
|
142
142
|
table.add_column("STATUS", width=7) # Attempt status
|
|
143
143
|
table.add_column("ACTION", width=7) # Actionability indicator
|
|
144
144
|
table.add_column("SEVERITY", width=9)
|
|
@@ -148,46 +148,52 @@ def _render_exploits_table(
|
|
|
148
148
|
table.add_column("MODULE/EDB", width=24)
|
|
149
149
|
|
|
150
150
|
for idx, exploit in enumerate(display_exploits, start_index):
|
|
151
|
-
status = exploit.get(
|
|
151
|
+
status = exploit.get("attempt_status", "not_tried")
|
|
152
152
|
actionability = _determine_actionability(exploit)
|
|
153
|
-
severity = _format_severity_badge(exploit.get(
|
|
153
|
+
severity = _format_severity_badge(exploit.get("severity", "info"))
|
|
154
154
|
|
|
155
|
-
title = exploit.get(
|
|
156
|
-
cve = (exploit.get(
|
|
155
|
+
title = exploit.get("title", "Unknown")[:58]
|
|
156
|
+
cve = (exploit.get("cve") or "-")[:14]
|
|
157
157
|
|
|
158
|
-
source = exploit.get(
|
|
159
|
-
source_display =
|
|
158
|
+
source = exploit.get("source", "msf_kb")
|
|
159
|
+
source_display = "MSF" if source == "msf_kb" else "EDB"
|
|
160
160
|
|
|
161
161
|
# Module/EDB display with smart truncation
|
|
162
|
-
if exploit.get(
|
|
163
|
-
module_parts = exploit[
|
|
164
|
-
module_display =
|
|
162
|
+
if exploit.get("msf_module"):
|
|
163
|
+
module_parts = exploit["msf_module"].split("/")
|
|
164
|
+
module_display = (
|
|
165
|
+
"/".join(module_parts[-2:])
|
|
166
|
+
if len(module_parts) >= 2
|
|
167
|
+
else module_parts[-1]
|
|
168
|
+
)
|
|
165
169
|
|
|
166
170
|
# Smart truncation at word boundaries
|
|
167
171
|
if len(module_display) > 24:
|
|
168
172
|
# Try to truncate at last underscore or slash before limit
|
|
169
173
|
truncate_at = 21 # Leave room for "..."
|
|
170
174
|
last_sep = max(
|
|
171
|
-
module_display.rfind(
|
|
172
|
-
module_display.rfind(
|
|
175
|
+
module_display.rfind("_", 0, truncate_at),
|
|
176
|
+
module_display.rfind("/", 0, truncate_at),
|
|
173
177
|
)
|
|
174
178
|
if last_sep > 10: # Don't truncate too early
|
|
175
179
|
module_display = module_display[:last_sep] + "..."
|
|
176
180
|
else:
|
|
177
181
|
module_display = module_display[:21] + "..."
|
|
178
|
-
elif exploit.get(
|
|
182
|
+
elif exploit.get("edb_id"):
|
|
179
183
|
module_display = f"EDB-{exploit['edb_id']}"[:24]
|
|
180
184
|
else:
|
|
181
|
-
module_display =
|
|
185
|
+
module_display = "-"
|
|
182
186
|
|
|
183
187
|
# Add description as subtle hint if available
|
|
184
|
-
description = exploit.get(
|
|
188
|
+
description = exploit.get("description", "")
|
|
185
189
|
if description and len(title) < 50:
|
|
186
190
|
title = f"{title}\n[dim]{description[:50]}[/dim]"
|
|
187
191
|
|
|
188
192
|
# Determine checkbox state
|
|
189
|
-
exploit_id = exploit.get(
|
|
190
|
-
checkbox =
|
|
193
|
+
exploit_id = exploit.get("identifier", "")
|
|
194
|
+
checkbox = (
|
|
195
|
+
"●" if (selected_exploits and exploit_id in selected_exploits) else "○"
|
|
196
|
+
)
|
|
191
197
|
|
|
192
198
|
table.add_row(
|
|
193
199
|
checkbox,
|
|
@@ -198,29 +204,32 @@ def _render_exploits_table(
|
|
|
198
204
|
title,
|
|
199
205
|
cve,
|
|
200
206
|
source_display,
|
|
201
|
-
module_display
|
|
207
|
+
module_display,
|
|
202
208
|
)
|
|
203
209
|
|
|
204
210
|
console.print(table)
|
|
205
211
|
|
|
206
212
|
if not show_all and len(exploits) > 5:
|
|
207
|
-
console.print(
|
|
213
|
+
console.print(
|
|
214
|
+
f" [dim]... and {len(exploits) - 5} more. Press [x] to expand.[/dim]"
|
|
215
|
+
)
|
|
208
216
|
|
|
209
217
|
|
|
210
218
|
def _render_quick_wins(host: Dict, width: int, selected_exploits: set = None) -> None:
|
|
211
219
|
"""Render Quick Wins section - critical + one-click + untried."""
|
|
212
220
|
all_exploits = []
|
|
213
|
-
for service in host.get(
|
|
214
|
-
for exploit in service.get(
|
|
215
|
-
exploit[
|
|
221
|
+
for service in host.get("services", []):
|
|
222
|
+
for exploit in service.get("exploits", []):
|
|
223
|
+
exploit["_service"] = service # Track which service
|
|
216
224
|
all_exploits.append(exploit)
|
|
217
225
|
|
|
218
226
|
# Filter for quick wins
|
|
219
227
|
quick_wins = [
|
|
220
|
-
e
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
and
|
|
228
|
+
e
|
|
229
|
+
for e in all_exploits
|
|
230
|
+
if e.get("severity") == "critical"
|
|
231
|
+
and e.get("attempt_status") == "not_tried"
|
|
232
|
+
and _determine_actionability(e) in ["one_click", "setup_needed"]
|
|
224
233
|
]
|
|
225
234
|
|
|
226
235
|
if not quick_wins:
|
|
@@ -229,7 +238,9 @@ def _render_quick_wins(host: Dict, width: int, selected_exploits: set = None) ->
|
|
|
229
238
|
# Header
|
|
230
239
|
separator = "━" * (width - 4)
|
|
231
240
|
console.print(f"\n {separator}")
|
|
232
|
-
console.print(
|
|
241
|
+
console.print(
|
|
242
|
+
f" 🎯 [red bold]QUICK WINS[/red bold] - [white]Critical untried exploits ready to execute[/white]"
|
|
243
|
+
)
|
|
233
244
|
console.print(f" {separator}\n")
|
|
234
245
|
|
|
235
246
|
# Table
|
|
@@ -238,7 +249,7 @@ def _render_quick_wins(host: Dict, width: int, selected_exploits: set = None) ->
|
|
|
238
249
|
header_style="bold red",
|
|
239
250
|
box=None,
|
|
240
251
|
padding=(0, 1),
|
|
241
|
-
width=min(width - 6, 140)
|
|
252
|
+
width=min(width - 6, 140),
|
|
242
253
|
)
|
|
243
254
|
|
|
244
255
|
table.add_column("○", width=3, justify="center") # Selection checkbox
|
|
@@ -251,22 +262,24 @@ def _render_quick_wins(host: Dict, width: int, selected_exploits: set = None) ->
|
|
|
251
262
|
table.add_column("MODULE", width=24)
|
|
252
263
|
|
|
253
264
|
for idx, exploit in enumerate(quick_wins[:10], 1):
|
|
254
|
-
status = exploit.get(
|
|
255
|
-
severity = _format_severity_badge(exploit.get(
|
|
265
|
+
status = exploit.get("attempt_status", "not_tried")
|
|
266
|
+
severity = _format_severity_badge(exploit.get("severity", "info"))
|
|
256
267
|
actionability = _render_actionability_badge(_determine_actionability(exploit))
|
|
257
|
-
service = exploit[
|
|
268
|
+
service = exploit["_service"]
|
|
258
269
|
service_name = f"{service.get('service')}:{service.get('port')}"
|
|
259
|
-
title = exploit.get(
|
|
270
|
+
title = exploit.get("title", "Unknown")[:43]
|
|
260
271
|
|
|
261
|
-
if exploit.get(
|
|
262
|
-
module_parts = exploit[
|
|
263
|
-
module_display =
|
|
272
|
+
if exploit.get("msf_module"):
|
|
273
|
+
module_parts = exploit["msf_module"].split("/")
|
|
274
|
+
module_display = "/".join(module_parts[-2:])[:22]
|
|
264
275
|
else:
|
|
265
|
-
module_display =
|
|
276
|
+
module_display = "-"
|
|
266
277
|
|
|
267
278
|
# Determine checkbox state
|
|
268
|
-
exploit_id = exploit.get(
|
|
269
|
-
checkbox =
|
|
279
|
+
exploit_id = exploit.get("identifier", "")
|
|
280
|
+
checkbox = (
|
|
281
|
+
"●" if (selected_exploits and exploit_id in selected_exploits) else "○"
|
|
282
|
+
)
|
|
270
283
|
|
|
271
284
|
table.add_row(
|
|
272
285
|
checkbox,
|
|
@@ -276,11 +289,13 @@ def _render_quick_wins(host: Dict, width: int, selected_exploits: set = None) ->
|
|
|
276
289
|
actionability,
|
|
277
290
|
service_name,
|
|
278
291
|
title,
|
|
279
|
-
module_display
|
|
292
|
+
module_display,
|
|
280
293
|
)
|
|
281
294
|
|
|
282
295
|
console.print(table)
|
|
283
|
-
console.print(
|
|
296
|
+
console.print(
|
|
297
|
+
f"\n [dim]💡 Press \\[e] to execute | Full SearchSploit via menu \\[o][/dim]\n"
|
|
298
|
+
)
|
|
284
299
|
|
|
285
300
|
|
|
286
301
|
def _render_severity_group(
|
|
@@ -288,7 +303,7 @@ def _render_severity_group(
|
|
|
288
303
|
exploits: List[Dict],
|
|
289
304
|
width: int,
|
|
290
305
|
collapsed: bool,
|
|
291
|
-
filter_untried: bool = False
|
|
306
|
+
filter_untried: bool = False,
|
|
292
307
|
) -> None:
|
|
293
308
|
"""Render a collapsible severity group."""
|
|
294
309
|
if not exploits:
|
|
@@ -297,7 +312,9 @@ def _render_severity_group(
|
|
|
297
312
|
# Filter if needed
|
|
298
313
|
display_exploits = exploits
|
|
299
314
|
if filter_untried:
|
|
300
|
-
display_exploits = [
|
|
315
|
+
display_exploits = [
|
|
316
|
+
e for e in exploits if e.get("attempt_status") == "not_tried"
|
|
317
|
+
]
|
|
301
318
|
|
|
302
319
|
if not display_exploits:
|
|
303
320
|
return
|
|
@@ -307,31 +324,49 @@ def _render_severity_group(
|
|
|
307
324
|
collapse_icon = "▼" if not collapsed else "▶"
|
|
308
325
|
|
|
309
326
|
severity_display = severity.upper()
|
|
310
|
-
color =
|
|
327
|
+
color = (
|
|
328
|
+
"red"
|
|
329
|
+
if severity == "critical"
|
|
330
|
+
else (
|
|
331
|
+
"yellow"
|
|
332
|
+
if severity == "high"
|
|
333
|
+
else "white" if severity == "medium" else "dim"
|
|
334
|
+
)
|
|
335
|
+
)
|
|
311
336
|
|
|
312
337
|
console.print(f"\n {separator}")
|
|
313
|
-
console.print(
|
|
338
|
+
console.print(
|
|
339
|
+
f" {collapse_icon} [{color} bold]{severity_display}[/{color} bold] [{color}]({len(display_exploits)} exploits)[/{color}]"
|
|
340
|
+
)
|
|
314
341
|
console.print(f" {separator}\n")
|
|
315
342
|
|
|
316
343
|
if not collapsed:
|
|
317
|
-
_render_exploits_table(
|
|
344
|
+
_render_exploits_table(
|
|
345
|
+
display_exploits, width, show_all=False, filter_untried=False
|
|
346
|
+
)
|
|
318
347
|
else:
|
|
319
348
|
console.print(f" [dim]Collapsed. Press [{severity[0]}] to expand.[/dim]\n")
|
|
320
349
|
|
|
321
350
|
|
|
322
|
-
def _render_service_groups(
|
|
351
|
+
def _render_service_groups(
|
|
352
|
+
host: Dict,
|
|
353
|
+
width: int,
|
|
354
|
+
collapsed_services: set,
|
|
355
|
+
filter_untried: bool,
|
|
356
|
+
selected_exploits: set = None,
|
|
357
|
+
) -> None:
|
|
323
358
|
"""Render exploits grouped by service with collapse capability."""
|
|
324
359
|
global_index = 1 # Track global exploit ID across all services
|
|
325
|
-
services = host.get(
|
|
326
|
-
services_with_exploits = [s for s in services if s.get(
|
|
360
|
+
services = host.get("services", [])
|
|
361
|
+
services_with_exploits = [s for s in services if s.get("exploits")]
|
|
327
362
|
|
|
328
363
|
for service in services_with_exploits:
|
|
329
364
|
service_key = f"{service.get('service')}:{service.get('port')}"
|
|
330
365
|
collapsed = service_key in collapsed_services
|
|
331
366
|
|
|
332
|
-
exploits = service.get(
|
|
367
|
+
exploits = service.get("exploits", [])
|
|
333
368
|
if filter_untried:
|
|
334
|
-
exploits = [e for e in exploits if e.get(
|
|
369
|
+
exploits = [e for e in exploits if e.get("attempt_status") == "not_tried"]
|
|
335
370
|
|
|
336
371
|
if not exploits:
|
|
337
372
|
continue
|
|
@@ -340,20 +375,22 @@ def _render_service_groups(host: Dict, width: int, collapsed_services: set, filt
|
|
|
340
375
|
separator = "━" * (width - 4)
|
|
341
376
|
collapse_icon = "▼" if not collapsed else "▶"
|
|
342
377
|
|
|
343
|
-
service_name = service.get(
|
|
344
|
-
port = service.get(
|
|
345
|
-
version = service.get(
|
|
378
|
+
service_name = service.get("service", "unknown").upper()
|
|
379
|
+
port = service.get("port", "?")
|
|
380
|
+
version = service.get("version", "version unknown")
|
|
346
381
|
|
|
347
382
|
console.print(f"\n {separator}")
|
|
348
|
-
console.print(
|
|
383
|
+
console.print(
|
|
384
|
+
f" {collapse_icon} [bright_white bold]{service_name}:{port}[/bright_white bold] - [cyan]{version}[/cyan]"
|
|
385
|
+
)
|
|
349
386
|
console.print(f" {separator}\n")
|
|
350
387
|
|
|
351
388
|
if not collapsed:
|
|
352
389
|
# Group by severity
|
|
353
|
-
critical = [e for e in exploits if e.get(
|
|
354
|
-
high = [e for e in exploits if e.get(
|
|
355
|
-
medium = [e for e in exploits if e.get(
|
|
356
|
-
low = [e for e in exploits if e.get(
|
|
390
|
+
critical = [e for e in exploits if e.get("severity") == "critical"]
|
|
391
|
+
high = [e for e in exploits if e.get("severity") == "high"]
|
|
392
|
+
medium = [e for e in exploits if e.get("severity") == "medium"]
|
|
393
|
+
low = [e for e in exploits if e.get("severity") == "low"]
|
|
357
394
|
|
|
358
395
|
all_sev_exploits = critical + high + medium + low
|
|
359
396
|
|
|
@@ -364,7 +401,7 @@ def _render_service_groups(host: Dict, width: int, collapsed_services: set, filt
|
|
|
364
401
|
show_all=True,
|
|
365
402
|
filter_untried=False,
|
|
366
403
|
start_index=global_index,
|
|
367
|
-
selected_exploits=selected_exploits
|
|
404
|
+
selected_exploits=selected_exploits,
|
|
368
405
|
)
|
|
369
406
|
|
|
370
407
|
# Increment global index for next service
|
|
@@ -378,17 +415,17 @@ def _render_additional_attack_vectors(host: Dict, width: int, show: bool) -> Non
|
|
|
378
415
|
if not show:
|
|
379
416
|
return
|
|
380
417
|
|
|
381
|
-
services = host.get(
|
|
418
|
+
services = host.get("services", [])
|
|
382
419
|
technique_map = {}
|
|
383
420
|
|
|
384
421
|
for service in services:
|
|
385
|
-
service_type = service.get(
|
|
386
|
-
techniques = service.get(
|
|
422
|
+
service_type = service.get("service", "unknown")
|
|
423
|
+
techniques = service.get("techniques", [])
|
|
387
424
|
|
|
388
425
|
if techniques and service_type not in technique_map:
|
|
389
426
|
technique_map[service_type] = {
|
|
390
|
-
|
|
391
|
-
|
|
427
|
+
"port": service.get("port"),
|
|
428
|
+
"techniques": techniques,
|
|
392
429
|
}
|
|
393
430
|
|
|
394
431
|
if not technique_map:
|
|
@@ -396,12 +433,14 @@ def _render_additional_attack_vectors(host: Dict, width: int, show: bool) -> Non
|
|
|
396
433
|
|
|
397
434
|
separator = "━" * (width - 4)
|
|
398
435
|
console.print(f"\n {separator}")
|
|
399
|
-
console.print(
|
|
436
|
+
console.print(
|
|
437
|
+
f" 💡 [cyan bold]ADDITIONAL ATTACK VECTORS[/cyan bold] [dim](Generic Techniques)[/dim]"
|
|
438
|
+
)
|
|
400
439
|
console.print(f" {separator}\n")
|
|
401
440
|
|
|
402
441
|
for service_type, data in sorted(technique_map.items()):
|
|
403
|
-
port = data[
|
|
404
|
-
techniques = data[
|
|
442
|
+
port = data["port"]
|
|
443
|
+
techniques = data["techniques"]
|
|
405
444
|
|
|
406
445
|
console.print(f" [cyan bold]{service_type.upper()}:{port}[/cyan bold]\n")
|
|
407
446
|
|
|
@@ -411,7 +450,7 @@ def _render_additional_attack_vectors(host: Dict, width: int, show: bool) -> Non
|
|
|
411
450
|
header_style="bold cyan",
|
|
412
451
|
box=None,
|
|
413
452
|
padding=(0, 1),
|
|
414
|
-
width=min(width - 6, 110)
|
|
453
|
+
width=min(width - 6, 110),
|
|
415
454
|
)
|
|
416
455
|
|
|
417
456
|
table.add_column("TECHNIQUE", width=40)
|
|
@@ -419,10 +458,10 @@ def _render_additional_attack_vectors(host: Dict, width: int, show: bool) -> Non
|
|
|
419
458
|
table.add_column("MODULE", width=45)
|
|
420
459
|
|
|
421
460
|
for tech in techniques:
|
|
422
|
-
name = tech.get(
|
|
423
|
-
severity = _format_severity_badge(tech.get(
|
|
424
|
-
modules = tech.get(
|
|
425
|
-
module = modules[0].split(
|
|
461
|
+
name = tech.get("name", "Unknown")[:38]
|
|
462
|
+
severity = _format_severity_badge(tech.get("severity", "info"))
|
|
463
|
+
modules = tech.get("msf_modules", [])
|
|
464
|
+
module = modules[0].split("/")[-1][:43] if modules else "-"
|
|
426
465
|
|
|
427
466
|
table.add_row(name, severity, module)
|
|
428
467
|
|
|
@@ -449,39 +488,47 @@ def _show_detailed_info(exploit: Dict) -> None:
|
|
|
449
488
|
console.print(f" [bold]{exploit.get('title', 'Unknown Exploit')}[/bold]\n")
|
|
450
489
|
|
|
451
490
|
# Metadata
|
|
452
|
-
console.print(
|
|
453
|
-
|
|
454
|
-
|
|
491
|
+
console.print(
|
|
492
|
+
f" [cyan]Severity:[/cyan] {_format_severity_badge(exploit.get('severity', 'info'))}"
|
|
493
|
+
)
|
|
494
|
+
console.print(
|
|
495
|
+
f" [cyan]Actionability:[/cyan] {_render_actionability_badge(_determine_actionability(exploit))}"
|
|
496
|
+
)
|
|
497
|
+
console.print(
|
|
498
|
+
f" [cyan]Attempt Status:[/cyan] {_render_status_badge(exploit.get('attempt_status', 'not_tried'))}"
|
|
499
|
+
)
|
|
455
500
|
|
|
456
|
-
if exploit.get(
|
|
501
|
+
if exploit.get("cve"):
|
|
457
502
|
console.print(f" [cyan]CVE:[/cyan] {exploit['cve']}")
|
|
458
503
|
|
|
459
|
-
if exploit.get(
|
|
504
|
+
if exploit.get("msf_module"):
|
|
460
505
|
console.print(f" [cyan]Metasploit Module:[/cyan] {exploit['msf_module']}")
|
|
461
506
|
|
|
462
|
-
if exploit.get(
|
|
507
|
+
if exploit.get("edb_id"):
|
|
463
508
|
console.print(f" [cyan]Exploit-DB ID:[/cyan] {exploit['edb_id']}")
|
|
464
509
|
|
|
465
510
|
console.print()
|
|
466
511
|
|
|
467
512
|
# Description
|
|
468
|
-
if exploit.get(
|
|
513
|
+
if exploit.get("description"):
|
|
469
514
|
console.print(f" [cyan bold]Description:[/cyan bold]")
|
|
470
515
|
console.print(f" {exploit['description']}\n")
|
|
471
516
|
|
|
472
517
|
# Match type
|
|
473
|
-
match_type = exploit.get(
|
|
518
|
+
match_type = exploit.get("match_type", "unknown")
|
|
474
519
|
console.print(f" [cyan]Match Type:[/cyan] {match_type.upper()}\n")
|
|
475
520
|
|
|
476
521
|
# Actionability assessment
|
|
477
522
|
actionability = _determine_actionability(exploit)
|
|
478
523
|
console.print(f" [cyan bold]Actionability Assessment:[/cyan bold]")
|
|
479
|
-
if actionability ==
|
|
524
|
+
if actionability == "one_click":
|
|
480
525
|
console.print(f" 🎯 One-Click Ready - Has MSF module, can execute immediately")
|
|
481
|
-
elif actionability ==
|
|
526
|
+
elif actionability == "setup_needed":
|
|
482
527
|
console.print(f" 🔧 Setup Needed - May require credentials or configuration")
|
|
483
|
-
elif actionability ==
|
|
484
|
-
console.print(
|
|
528
|
+
elif actionability == "manual":
|
|
529
|
+
console.print(
|
|
530
|
+
f" 📄 Manual Exploitation - Requires manual steps, check Exploit-DB"
|
|
531
|
+
)
|
|
485
532
|
else:
|
|
486
533
|
console.print(f" 🔍 Research Required - No ready-to-use module available")
|
|
487
534
|
|
|
@@ -494,7 +541,7 @@ def _show_detailed_info(exploit: Dict) -> None:
|
|
|
494
541
|
console.print(f" • Network connectivity to target")
|
|
495
542
|
|
|
496
543
|
# Check if MSF is available
|
|
497
|
-
if exploit.get(
|
|
544
|
+
if exploit.get("msf_module"):
|
|
498
545
|
msf = MSFConsoleManager()
|
|
499
546
|
if msf.is_available():
|
|
500
547
|
console.print(f" • [green]✓[/green] Metasploit Framework installed")
|
|
@@ -502,8 +549,11 @@ def _show_detailed_info(exploit: Dict) -> None:
|
|
|
502
549
|
console.print(f" • [red]✗[/red] Metasploit Framework required (not found)")
|
|
503
550
|
|
|
504
551
|
# Check for credential requirements
|
|
505
|
-
if any(
|
|
506
|
-
|
|
552
|
+
if any(
|
|
553
|
+
word in exploit.get("title", "").lower()
|
|
554
|
+
or word in exploit.get("description", "").lower()
|
|
555
|
+
for word in ["auth", "credential", "login", "password", "authenticated"]
|
|
556
|
+
):
|
|
507
557
|
console.print(f" • Valid credentials may be required")
|
|
508
558
|
console.print(f" [dim](Check credentials database for this host)[/dim]")
|
|
509
559
|
|
|
@@ -511,10 +561,10 @@ def _show_detailed_info(exploit: Dict) -> None:
|
|
|
511
561
|
|
|
512
562
|
# Expected impact
|
|
513
563
|
console.print(f" [cyan bold]Expected Impact:[/cyan bold]")
|
|
514
|
-
if exploit.get(
|
|
564
|
+
if exploit.get("severity") == "critical":
|
|
515
565
|
console.print(f" • Full system compromise possible")
|
|
516
566
|
console.print(f" • Remote code execution likely")
|
|
517
|
-
elif exploit.get(
|
|
567
|
+
elif exploit.get("severity") == "high":
|
|
518
568
|
console.print(f" • Significant access or information disclosure")
|
|
519
569
|
else:
|
|
520
570
|
console.print(f" • Limited impact, may require chaining with other exploits")
|
|
@@ -522,10 +572,14 @@ def _show_detailed_info(exploit: Dict) -> None:
|
|
|
522
572
|
console.print()
|
|
523
573
|
|
|
524
574
|
# Related URLs
|
|
525
|
-
if exploit.get(
|
|
526
|
-
console.print(
|
|
527
|
-
|
|
528
|
-
|
|
575
|
+
if exploit.get("cve"):
|
|
576
|
+
console.print(
|
|
577
|
+
f" [cyan]NVD URL:[/cyan] https://nvd.nist.gov/vuln/detail/{exploit['cve']}"
|
|
578
|
+
)
|
|
579
|
+
if exploit.get("edb_id"):
|
|
580
|
+
console.print(
|
|
581
|
+
f" [cyan]Exploit-DB URL:[/cyan] https://www.exploit-db.com/exploits/{exploit['edb_id']}"
|
|
582
|
+
)
|
|
529
583
|
|
|
530
584
|
console.print()
|
|
531
585
|
console.print(f" [dim]Press any key to return...[/dim]")
|
|
@@ -586,19 +640,33 @@ def _show_help_overlay() -> None:
|
|
|
586
640
|
|
|
587
641
|
console.print(f" [dim]STATUS Indicators:[/dim]")
|
|
588
642
|
console.print(f" [green]✅[/green] SUCCESS - Exploit succeeded")
|
|
589
|
-
console.print(
|
|
643
|
+
console.print(
|
|
644
|
+
f" [yellow]🔄[/yellow] TRIED - Exploit attempted (result unknown)"
|
|
645
|
+
)
|
|
590
646
|
console.print(f" [red]❌[/red] FAILED - Exploit failed")
|
|
591
647
|
console.print(f" ⚪ NOT TRIED - Not yet attempted\n")
|
|
592
648
|
|
|
593
649
|
console.print(f" [dim]ACTION Types:[/dim]")
|
|
594
|
-
console.print(
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
console.print(
|
|
650
|
+
console.print(
|
|
651
|
+
f" [green]READY[/green] - Execute immediately (one-click, zero prep)"
|
|
652
|
+
)
|
|
653
|
+
console.print(
|
|
654
|
+
f" [yellow]SETUP[/yellow] - Needs configuration first (install tools, set options, generate payload)"
|
|
655
|
+
)
|
|
656
|
+
console.print(
|
|
657
|
+
f" [cyan]MANUAL[/cyan] - Requires hands-on work (can't be fully automated)"
|
|
658
|
+
)
|
|
659
|
+
console.print(
|
|
660
|
+
f" [dim]RESEARCH[/dim] - Needs investigation to determine if applicable\n"
|
|
661
|
+
)
|
|
598
662
|
|
|
599
663
|
console.print(f" [dim]SEVERITY Levels:[/dim]")
|
|
600
|
-
console.print(
|
|
601
|
-
|
|
664
|
+
console.print(
|
|
665
|
+
f" [red bold]CRITICAL[/red bold] - Immediate exploitation, full system compromise"
|
|
666
|
+
)
|
|
667
|
+
console.print(
|
|
668
|
+
f" [yellow bold]HIGH[/yellow bold] - Significant security impact, privilege escalation"
|
|
669
|
+
)
|
|
602
670
|
console.print(f" [white]MEDIUM[/white] - Moderate risk, limited access")
|
|
603
671
|
console.print(f" [dim]LOW[/dim] - Minor security issue")
|
|
604
672
|
console.print(f" [dim]INFO[/dim] - Informational only\n")
|
|
@@ -607,7 +675,9 @@ def _show_help_overlay() -> None:
|
|
|
607
675
|
click.getchar()
|
|
608
676
|
|
|
609
677
|
|
|
610
|
-
def _check_prerequisites(
|
|
678
|
+
def _check_prerequisites(
|
|
679
|
+
exploit: Dict, engagement_id: int, host: Dict
|
|
680
|
+
) -> Tuple[bool, List[str]]:
|
|
611
681
|
"""
|
|
612
682
|
Check if exploit prerequisites are met.
|
|
613
683
|
|
|
@@ -617,20 +687,25 @@ def _check_prerequisites(exploit: Dict, engagement_id: int, host: Dict) -> Tuple
|
|
|
617
687
|
missing = []
|
|
618
688
|
|
|
619
689
|
# Check if exploit needs credentials
|
|
620
|
-
title_lower = exploit.get(
|
|
621
|
-
desc_lower = exploit.get(
|
|
690
|
+
title_lower = exploit.get("title", "").lower()
|
|
691
|
+
desc_lower = exploit.get("description", "").lower()
|
|
622
692
|
|
|
623
|
-
if any(
|
|
693
|
+
if any(
|
|
694
|
+
word in title_lower or word in desc_lower
|
|
695
|
+
for word in ["auth", "credential", "login", "password", "authenticated"]
|
|
696
|
+
):
|
|
624
697
|
# Check if we have credentials for this host/service
|
|
625
698
|
try:
|
|
626
699
|
from souleyez.storage.credentials import CredentialsManager
|
|
700
|
+
|
|
627
701
|
cm = CredentialsManager()
|
|
628
702
|
creds = cm.list_credentials(engagement_id)
|
|
629
703
|
|
|
630
704
|
# Look for credentials matching this host or service
|
|
631
|
-
host_ip = host.get(
|
|
705
|
+
host_ip = host.get("ip")
|
|
632
706
|
has_creds = any(
|
|
633
|
-
cred.get(
|
|
707
|
+
cred.get("host") == host_ip
|
|
708
|
+
or cred.get("source", "").startswith(host_ip)
|
|
634
709
|
for cred in creds
|
|
635
710
|
)
|
|
636
711
|
|
|
@@ -640,7 +715,7 @@ def _check_prerequisites(exploit: Dict, engagement_id: int, host: Dict) -> Tuple
|
|
|
640
715
|
missing.append("Credentials (unable to verify)")
|
|
641
716
|
|
|
642
717
|
# Check for MSF module availability
|
|
643
|
-
if exploit.get(
|
|
718
|
+
if exploit.get("msf_module"):
|
|
644
719
|
msf = MSFConsoleManager()
|
|
645
720
|
if not msf.is_available():
|
|
646
721
|
missing.append("Metasploit Framework installation")
|
|
@@ -652,10 +727,7 @@ def _check_prerequisites(exploit: Dict, engagement_id: int, host: Dict) -> Tuple
|
|
|
652
727
|
|
|
653
728
|
|
|
654
729
|
def _execute_msf_module(
|
|
655
|
-
exploit: Dict,
|
|
656
|
-
host: Dict,
|
|
657
|
-
service: Dict,
|
|
658
|
-
engagement_id: int
|
|
730
|
+
exploit: Dict, host: Dict, service: Dict, engagement_id: int
|
|
659
731
|
) -> bool:
|
|
660
732
|
"""
|
|
661
733
|
Execute MSF module for an exploit.
|
|
@@ -663,7 +735,7 @@ def _execute_msf_module(
|
|
|
663
735
|
Returns:
|
|
664
736
|
True if execution started successfully, False otherwise
|
|
665
737
|
"""
|
|
666
|
-
msf_module = exploit.get(
|
|
738
|
+
msf_module = exploit.get("msf_module")
|
|
667
739
|
if not msf_module:
|
|
668
740
|
console.print(" [red]No MSF module available for this exploit[/red]")
|
|
669
741
|
click.pause()
|
|
@@ -697,21 +769,32 @@ def _execute_msf_module(
|
|
|
697
769
|
# Build resource script
|
|
698
770
|
script = generator.generate_header()
|
|
699
771
|
script += f"# Exploit: {exploit.get('title', 'Unknown')}\n"
|
|
700
|
-
script +=
|
|
772
|
+
script += (
|
|
773
|
+
f"# Target: {host.get('ip')} - {service.get('service')}:{service.get('port')}\n"
|
|
774
|
+
)
|
|
701
775
|
script += f"# Severity: {exploit.get('severity', 'unknown').upper()}\n\n"
|
|
702
776
|
|
|
703
777
|
script += f"use {msf_module}\n"
|
|
704
778
|
script += f"set RHOSTS {host.get('ip')}\n"
|
|
705
779
|
|
|
706
|
-
if service.get(
|
|
780
|
+
if service.get("port"):
|
|
707
781
|
script += f"set RPORT {service.get('port')}\n"
|
|
708
782
|
|
|
709
783
|
# Check if module needs credentials (login/bruteforce scanners)
|
|
710
|
-
login_modules = [
|
|
711
|
-
|
|
712
|
-
|
|
784
|
+
login_modules = [
|
|
785
|
+
"ssh_login",
|
|
786
|
+
"telnet_login",
|
|
787
|
+
"ftp_login",
|
|
788
|
+
"smb_login",
|
|
789
|
+
"mysql_login",
|
|
790
|
+
"postgres_login",
|
|
791
|
+
"vnc_login",
|
|
792
|
+
"rdp_scanner",
|
|
793
|
+
"http_login",
|
|
794
|
+
]
|
|
795
|
+
enumuser_modules = ["ssh_enumusers", "smb_enumusers"]
|
|
713
796
|
|
|
714
|
-
module_name = msf_module.split(
|
|
797
|
+
module_name = msf_module.split("/")[-1] if "/" in msf_module else msf_module
|
|
715
798
|
|
|
716
799
|
if any(login_mod in module_name for login_mod in login_modules):
|
|
717
800
|
# Login modules need username file and USER_AS_PASS
|
|
@@ -725,7 +808,9 @@ def _execute_msf_module(
|
|
|
725
808
|
# User enumeration modules need username file
|
|
726
809
|
script += "# Username wordlist for enumeration\n"
|
|
727
810
|
script += "set USER_FILE data/wordlists/soul_users.txt\n"
|
|
728
|
-
console.print(
|
|
811
|
+
console.print(
|
|
812
|
+
" [yellow]⚠️ Using default username wordlist (soul_users.txt)[/yellow]"
|
|
813
|
+
)
|
|
729
814
|
|
|
730
815
|
# Check if module needs payload
|
|
731
816
|
if generator._module_needs_payload(msf_module):
|
|
@@ -743,16 +828,16 @@ def _execute_msf_module(
|
|
|
743
828
|
# Show script preview
|
|
744
829
|
console.print("\n [cyan bold]Resource Script Preview:[/cyan bold]")
|
|
745
830
|
console.print(" " + "─" * 60)
|
|
746
|
-
for line in script.split(
|
|
831
|
+
for line in script.split("\n")[:15]:
|
|
747
832
|
console.print(f" {line}")
|
|
748
|
-
script_lines = script.split(
|
|
833
|
+
script_lines = script.split("\n")
|
|
749
834
|
if len(script_lines) > 15:
|
|
750
835
|
console.print(f" ... ({len(script_lines) - 15} more lines)")
|
|
751
836
|
console.print(" " + "─" * 60)
|
|
752
837
|
console.print()
|
|
753
838
|
|
|
754
839
|
# Save script
|
|
755
|
-
with tempfile.NamedTemporaryFile(mode=
|
|
840
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".rc", delete=False) as f:
|
|
756
841
|
f.write(script)
|
|
757
842
|
rc_file = f.name
|
|
758
843
|
|
|
@@ -766,9 +851,11 @@ def _execute_msf_module(
|
|
|
766
851
|
console.print(" [q] Cancel")
|
|
767
852
|
console.print()
|
|
768
853
|
|
|
769
|
-
choice = click.prompt(
|
|
854
|
+
choice = click.prompt(
|
|
855
|
+
" Select option", type=str, default="1", show_default=False
|
|
856
|
+
).strip()
|
|
770
857
|
|
|
771
|
-
if choice ==
|
|
858
|
+
if choice == "1":
|
|
772
859
|
console.print("\n [cyan]Launching msfconsole...[/cyan]")
|
|
773
860
|
console.print(" [dim]The script will load but won't auto-execute.[/dim]")
|
|
774
861
|
console.print(" [dim]Review settings and type 'exploit' when ready.[/dim]\n")
|
|
@@ -782,12 +869,12 @@ def _execute_msf_module(
|
|
|
782
869
|
# Mark as attempted
|
|
783
870
|
exploit_attempts.record_attempt(
|
|
784
871
|
engagement_id=engagement_id,
|
|
785
|
-
host_id=host.get(
|
|
786
|
-
exploit_identifier=exploit.get(
|
|
787
|
-
exploit_title=exploit.get(
|
|
788
|
-
status=
|
|
789
|
-
service_id=service.get(
|
|
790
|
-
notes=f"Executed via MSF module: {msf_module}"
|
|
872
|
+
host_id=host.get("host_id"),
|
|
873
|
+
exploit_identifier=exploit.get("identifier"),
|
|
874
|
+
exploit_title=exploit.get("title", "Unknown"),
|
|
875
|
+
status="attempted",
|
|
876
|
+
service_id=service.get("service_id"),
|
|
877
|
+
notes=f"Executed via MSF module: {msf_module}",
|
|
791
878
|
)
|
|
792
879
|
|
|
793
880
|
console.print("\n [green]✓ Exploit execution completed[/green]")
|
|
@@ -806,12 +893,15 @@ def _execute_msf_module(
|
|
|
806
893
|
except:
|
|
807
894
|
pass
|
|
808
895
|
|
|
809
|
-
elif choice ==
|
|
896
|
+
elif choice == "2":
|
|
810
897
|
# Just save the script
|
|
811
|
-
save_path = click.prompt(
|
|
898
|
+
save_path = click.prompt(
|
|
899
|
+
" Save as",
|
|
900
|
+
default=f"exploit_{exploit.get('identifier', 'unknown').replace(':', '_')}.rc",
|
|
901
|
+
)
|
|
812
902
|
|
|
813
903
|
try:
|
|
814
|
-
with open(save_path,
|
|
904
|
+
with open(save_path, "w") as f:
|
|
815
905
|
f.write(script)
|
|
816
906
|
console.print(f"\n [green]✓ Script saved to: {save_path}[/green]")
|
|
817
907
|
console.print(f" Run with: msfconsole -r {save_path}")
|
|
@@ -840,11 +930,13 @@ def _batch_execute_critical(engagement_id: int, host: Dict) -> int:
|
|
|
840
930
|
"""
|
|
841
931
|
# Get all critical one-click exploits that are untried
|
|
842
932
|
all_exploits = []
|
|
843
|
-
for service in host.get(
|
|
844
|
-
for exploit in service.get(
|
|
845
|
-
if (
|
|
846
|
-
exploit.get(
|
|
847
|
-
|
|
933
|
+
for service in host.get("services", []):
|
|
934
|
+
for exploit in service.get("exploits", []):
|
|
935
|
+
if (
|
|
936
|
+
exploit.get("severity") == "critical"
|
|
937
|
+
and exploit.get("attempt_status") == "not_tried"
|
|
938
|
+
and _determine_actionability(exploit) == "one_click"
|
|
939
|
+
):
|
|
848
940
|
all_exploits.append((exploit, service))
|
|
849
941
|
|
|
850
942
|
if not all_exploits:
|
|
@@ -853,7 +945,9 @@ def _batch_execute_critical(engagement_id: int, host: Dict) -> int:
|
|
|
853
945
|
return 0
|
|
854
946
|
|
|
855
947
|
# Show what will be executed
|
|
856
|
-
console.print(
|
|
948
|
+
console.print(
|
|
949
|
+
f"\n [red bold]⚠️ BATCH EXECUTION - {len(all_exploits)} Exploits[/red bold]\n"
|
|
950
|
+
)
|
|
857
951
|
console.print(" The following critical exploits will be executed:\n")
|
|
858
952
|
|
|
859
953
|
for idx, (exploit, service) in enumerate(all_exploits, 1):
|
|
@@ -861,7 +955,9 @@ def _batch_execute_critical(engagement_id: int, host: Dict) -> int:
|
|
|
861
955
|
console.print(f" Target: {service.get('service')}:{service.get('port')}")
|
|
862
956
|
console.print()
|
|
863
957
|
|
|
864
|
-
console.print(
|
|
958
|
+
console.print(
|
|
959
|
+
" [yellow]This will launch MSF console for each exploit sequentially.[/yellow]"
|
|
960
|
+
)
|
|
865
961
|
console.print(" [yellow]Each exploit will require manual confirmation.[/yellow]\n")
|
|
866
962
|
|
|
867
963
|
if not click.confirm(" Proceed with batch execution?", default=False):
|
|
@@ -880,7 +976,9 @@ def _batch_execute_critical(engagement_id: int, host: Dict) -> int:
|
|
|
880
976
|
if not click.confirm("\n Continue to next exploit?", default=True):
|
|
881
977
|
break
|
|
882
978
|
|
|
883
|
-
console.print(
|
|
979
|
+
console.print(
|
|
980
|
+
f"\n [green]✓ Batch execution complete: {executed}/{len(all_exploits)} executed[/green]"
|
|
981
|
+
)
|
|
884
982
|
click.pause()
|
|
885
983
|
return executed
|
|
886
984
|
|
|
@@ -888,8 +986,8 @@ def _batch_execute_critical(engagement_id: int, host: Dict) -> int:
|
|
|
888
986
|
def _build_exploit_index(host: Dict) -> List[Tuple[Dict, Dict]]:
|
|
889
987
|
"""Build a flat index of exploits with their service context for easy selection."""
|
|
890
988
|
exploit_index = []
|
|
891
|
-
for service in host.get(
|
|
892
|
-
for exploit in service.get(
|
|
989
|
+
for service in host.get("services", []):
|
|
990
|
+
for exploit in service.get("exploits", []):
|
|
893
991
|
exploit_index.append((exploit, service))
|
|
894
992
|
return exploit_index
|
|
895
993
|
|
|
@@ -916,14 +1014,20 @@ def _mark_exploit_interactive(engagement_id: int, host: Dict) -> bool:
|
|
|
916
1014
|
exploit_num = click.prompt(" Enter exploit ID", type=int)
|
|
917
1015
|
|
|
918
1016
|
if exploit_num < 1 or exploit_num > len(exploit_index):
|
|
919
|
-
console.print(
|
|
1017
|
+
console.print(
|
|
1018
|
+
f" [red]Invalid ID. Must be between 1 and {len(exploit_index)}[/red]"
|
|
1019
|
+
)
|
|
920
1020
|
click.pause()
|
|
921
1021
|
return False
|
|
922
1022
|
|
|
923
1023
|
exploit, service = exploit_index[exploit_num - 1]
|
|
924
1024
|
|
|
925
|
-
console.print(
|
|
926
|
-
|
|
1025
|
+
console.print(
|
|
1026
|
+
f"\n Selected: [cyan]{exploit.get('title', 'Unknown')[:60]}[/cyan]"
|
|
1027
|
+
)
|
|
1028
|
+
console.print(
|
|
1029
|
+
f" Current status: {_render_status_badge(exploit.get('attempt_status', 'not_tried'))}\n"
|
|
1030
|
+
)
|
|
927
1031
|
|
|
928
1032
|
console.print(" New status:")
|
|
929
1033
|
console.print(" [n] Not tried (⚪)")
|
|
@@ -934,12 +1038,7 @@ def _mark_exploit_interactive(engagement_id: int, host: Dict) -> bool:
|
|
|
934
1038
|
|
|
935
1039
|
status_choice = click.prompt(" Select status", type=str).strip().lower()
|
|
936
1040
|
|
|
937
|
-
status_map = {
|
|
938
|
-
'n': 'not_tried',
|
|
939
|
-
't': 'attempted',
|
|
940
|
-
'f': 'failed',
|
|
941
|
-
's': 'success'
|
|
942
|
-
}
|
|
1041
|
+
status_map = {"n": "not_tried", "t": "attempted", "f": "failed", "s": "success"}
|
|
943
1042
|
|
|
944
1043
|
if status_choice not in status_map:
|
|
945
1044
|
console.print(" [red]Invalid status selection[/red]")
|
|
@@ -949,19 +1048,23 @@ def _mark_exploit_interactive(engagement_id: int, host: Dict) -> bool:
|
|
|
949
1048
|
new_status = status_map[status_choice]
|
|
950
1049
|
|
|
951
1050
|
# Optional notes
|
|
952
|
-
notes = click.prompt(
|
|
953
|
-
|
|
1051
|
+
notes = click.prompt(
|
|
1052
|
+
" Add notes (optional, press Enter to skip)",
|
|
1053
|
+
type=str,
|
|
1054
|
+
default="",
|
|
1055
|
+
show_default=False,
|
|
1056
|
+
)
|
|
954
1057
|
|
|
955
1058
|
# Record the attempt
|
|
956
|
-
host_id = host.get(
|
|
1059
|
+
host_id = host.get("host_id")
|
|
957
1060
|
exploit_attempts.record_attempt(
|
|
958
1061
|
engagement_id=engagement_id,
|
|
959
1062
|
host_id=host_id,
|
|
960
|
-
exploit_identifier=exploit.get(
|
|
961
|
-
exploit_title=exploit.get(
|
|
1063
|
+
exploit_identifier=exploit.get("identifier"),
|
|
1064
|
+
exploit_title=exploit.get("title", "Unknown"),
|
|
962
1065
|
status=new_status,
|
|
963
|
-
service_id=service.get(
|
|
964
|
-
notes=notes if notes else None
|
|
1066
|
+
service_id=service.get("service_id"),
|
|
1067
|
+
notes=notes if notes else None,
|
|
965
1068
|
)
|
|
966
1069
|
|
|
967
1070
|
console.print(f"\n [green]✓ Exploit marked as {new_status}[/green]")
|
|
@@ -996,63 +1099,66 @@ def _interactive_exploit_select(host: Dict, selected_exploits: set) -> set:
|
|
|
996
1099
|
# Build items for selector
|
|
997
1100
|
items = []
|
|
998
1101
|
for idx, (exploit, service) in enumerate(exploit_index, 1):
|
|
999
|
-
items.append(
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1102
|
+
items.append(
|
|
1103
|
+
{
|
|
1104
|
+
"idx": idx,
|
|
1105
|
+
"identifier": exploit.get("identifier", ""),
|
|
1106
|
+
"title": exploit.get("title", "Unknown")[:45],
|
|
1107
|
+
"severity": exploit.get("severity", "info"),
|
|
1108
|
+
"status": exploit.get("attempt_status", "not_tried"),
|
|
1109
|
+
"service": f"{service.get('service', '?')}:{service.get('port', '?')}",
|
|
1110
|
+
"msf_module": exploit.get("msf_module", ""),
|
|
1111
|
+
"actionability": _determine_actionability(exploit),
|
|
1112
|
+
}
|
|
1113
|
+
)
|
|
1009
1114
|
|
|
1010
1115
|
# Column definitions
|
|
1011
1116
|
columns = [
|
|
1012
|
-
{
|
|
1013
|
-
{
|
|
1014
|
-
{
|
|
1015
|
-
{
|
|
1016
|
-
{
|
|
1117
|
+
{"name": "ID", "width": 4, "key": "idx", "justify": "right"},
|
|
1118
|
+
{"name": "Severity", "width": 10, "key": "severity"},
|
|
1119
|
+
{"name": "Status", "width": 8, "key": "status"},
|
|
1120
|
+
{"name": "Service", "width": 15, "key": "service"},
|
|
1121
|
+
{"name": "Exploit", "width": 45, "key": "title"},
|
|
1017
1122
|
]
|
|
1018
1123
|
|
|
1019
1124
|
def format_cell(item: dict, key: str) -> str:
|
|
1020
1125
|
value = item.get(key)
|
|
1021
|
-
if key ==
|
|
1126
|
+
if key == "severity":
|
|
1022
1127
|
return _format_severity_badge(value)
|
|
1023
|
-
elif key ==
|
|
1128
|
+
elif key == "status":
|
|
1024
1129
|
return _render_status_badge(value)
|
|
1025
|
-
return str(value) if value else
|
|
1130
|
+
return str(value) if value else "-"
|
|
1026
1131
|
|
|
1027
1132
|
while True:
|
|
1028
1133
|
interactive_select(
|
|
1029
1134
|
items=items,
|
|
1030
1135
|
columns=columns,
|
|
1031
1136
|
selected_ids=selected_exploits,
|
|
1032
|
-
get_id=lambda e: e[
|
|
1033
|
-
title=
|
|
1034
|
-
format_cell=format_cell
|
|
1137
|
+
get_id=lambda e: e["identifier"],
|
|
1138
|
+
title="SELECT EXPLOITS",
|
|
1139
|
+
format_cell=format_cell,
|
|
1035
1140
|
)
|
|
1036
1141
|
|
|
1037
1142
|
if not selected_exploits:
|
|
1038
1143
|
return selected_exploits
|
|
1039
1144
|
|
|
1040
1145
|
from souleyez.core.models import get_current_engagement
|
|
1146
|
+
|
|
1041
1147
|
current = get_current_engagement()
|
|
1042
1148
|
if not current:
|
|
1043
1149
|
return selected_exploits
|
|
1044
1150
|
|
|
1045
|
-
result = _exploit_bulk_action_menu(
|
|
1046
|
-
|
|
1151
|
+
result = _exploit_bulk_action_menu(
|
|
1152
|
+
current["engagement_id"], host, selected_exploits
|
|
1153
|
+
)
|
|
1154
|
+
if result == "back":
|
|
1047
1155
|
return selected_exploits
|
|
1048
|
-
elif result ==
|
|
1156
|
+
elif result == "clear":
|
|
1049
1157
|
selected_exploits.clear()
|
|
1050
1158
|
|
|
1051
1159
|
|
|
1052
1160
|
def _exploit_bulk_action_menu(
|
|
1053
|
-
engagement_id: int,
|
|
1054
|
-
host: Dict,
|
|
1055
|
-
selected_exploits: set
|
|
1161
|
+
engagement_id: int, host: Dict, selected_exploits: set
|
|
1056
1162
|
) -> str:
|
|
1057
1163
|
"""
|
|
1058
1164
|
Show bulk action menu for selected exploits.
|
|
@@ -1066,20 +1172,25 @@ def _exploit_bulk_action_menu(
|
|
|
1066
1172
|
'continue' to stay in view, 'clear' to clear selection
|
|
1067
1173
|
"""
|
|
1068
1174
|
if not selected_exploits:
|
|
1069
|
-
return
|
|
1175
|
+
return "continue"
|
|
1070
1176
|
|
|
1071
1177
|
# Get exploit details for selected items
|
|
1072
1178
|
exploit_index = _build_exploit_index(host)
|
|
1073
1179
|
selected_items = [
|
|
1074
|
-
(exploit, service)
|
|
1075
|
-
|
|
1180
|
+
(exploit, service)
|
|
1181
|
+
for exploit, service in exploit_index
|
|
1182
|
+
if exploit.get("identifier") in selected_exploits
|
|
1076
1183
|
]
|
|
1077
1184
|
|
|
1078
1185
|
# Count by category
|
|
1079
|
-
executable_count = len(
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1186
|
+
executable_count = len(
|
|
1187
|
+
[
|
|
1188
|
+
e
|
|
1189
|
+
for e, s in selected_items
|
|
1190
|
+
if e.get("msf_module")
|
|
1191
|
+
and _determine_actionability(e) in ["one_click", "setup_needed"]
|
|
1192
|
+
]
|
|
1193
|
+
)
|
|
1083
1194
|
|
|
1084
1195
|
console.print()
|
|
1085
1196
|
console.print(f" [bold]Selected: {len(selected_exploits)} exploit(s)[/bold]")
|
|
@@ -1093,58 +1204,68 @@ def _exploit_bulk_action_menu(
|
|
|
1093
1204
|
console.print(" \\[q] Back")
|
|
1094
1205
|
|
|
1095
1206
|
try:
|
|
1096
|
-
action =
|
|
1207
|
+
action = (
|
|
1208
|
+
click.prompt(" Select option", default="q", show_default=False)
|
|
1209
|
+
.strip()
|
|
1210
|
+
.lower()
|
|
1211
|
+
)
|
|
1097
1212
|
|
|
1098
|
-
if action in [
|
|
1213
|
+
if action in ["s", "t", "f", "n"]:
|
|
1099
1214
|
# Mark exploits with status
|
|
1100
|
-
status_map = {
|
|
1215
|
+
status_map = {
|
|
1216
|
+
"s": "success",
|
|
1217
|
+
"t": "attempted",
|
|
1218
|
+
"f": "failed",
|
|
1219
|
+
"n": "not_tried",
|
|
1220
|
+
}
|
|
1101
1221
|
new_status = status_map[action]
|
|
1102
1222
|
|
|
1103
1223
|
marked = 0
|
|
1104
1224
|
for exploit, service in selected_items:
|
|
1105
1225
|
exploit_attempts.record_attempt(
|
|
1106
1226
|
engagement_id=engagement_id,
|
|
1107
|
-
host_id=host.get(
|
|
1108
|
-
exploit_identifier=exploit.get(
|
|
1109
|
-
exploit_title=exploit.get(
|
|
1227
|
+
host_id=host.get("host_id"),
|
|
1228
|
+
exploit_identifier=exploit.get("identifier"),
|
|
1229
|
+
exploit_title=exploit.get("title", "Unknown"),
|
|
1110
1230
|
status=new_status,
|
|
1111
|
-
service_id=service.get(
|
|
1112
|
-
notes=f"Bulk marked via multi-select"
|
|
1231
|
+
service_id=service.get("service_id"),
|
|
1232
|
+
notes=f"Bulk marked via multi-select",
|
|
1113
1233
|
)
|
|
1114
1234
|
marked += 1
|
|
1115
1235
|
|
|
1116
|
-
console.print(
|
|
1236
|
+
console.print(
|
|
1237
|
+
f"\n [green]Marked {marked} exploit(s) as {new_status}[/green]"
|
|
1238
|
+
)
|
|
1117
1239
|
click.pause()
|
|
1118
1240
|
|
|
1119
1241
|
# Clear cache to force refresh
|
|
1120
1242
|
from souleyez.intelligence.exploit_suggestions import _SUGGESTION_CACHE
|
|
1243
|
+
|
|
1121
1244
|
_SUGGESTION_CACHE.clear()
|
|
1122
1245
|
|
|
1123
|
-
return
|
|
1246
|
+
return "clear" # Clear selection after marking
|
|
1124
1247
|
|
|
1125
|
-
elif action ==
|
|
1248
|
+
elif action == "q":
|
|
1126
1249
|
if executable_count > 0:
|
|
1127
1250
|
# Queue exploits for execution
|
|
1128
1251
|
return _queue_selected_exploits(engagement_id, host, selected_items)
|
|
1129
1252
|
else:
|
|
1130
1253
|
# Back
|
|
1131
|
-
return
|
|
1254
|
+
return "back"
|
|
1132
1255
|
|
|
1133
|
-
elif action ==
|
|
1256
|
+
elif action == "c":
|
|
1134
1257
|
selected_exploits.clear()
|
|
1135
1258
|
console.print("\n [dim]Selection cleared[/dim]")
|
|
1136
|
-
return
|
|
1259
|
+
return "continue"
|
|
1137
1260
|
|
|
1138
1261
|
except (KeyboardInterrupt, click.Abort):
|
|
1139
1262
|
pass
|
|
1140
1263
|
|
|
1141
|
-
return
|
|
1264
|
+
return "continue"
|
|
1142
1265
|
|
|
1143
1266
|
|
|
1144
1267
|
def _queue_selected_exploits(
|
|
1145
|
-
engagement_id: int,
|
|
1146
|
-
host: Dict,
|
|
1147
|
-
selected_items: List[Tuple[Dict, Dict]]
|
|
1268
|
+
engagement_id: int, host: Dict, selected_items: List[Tuple[Dict, Dict]]
|
|
1148
1269
|
) -> str:
|
|
1149
1270
|
"""
|
|
1150
1271
|
Queue selected exploits as background jobs for sequential execution.
|
|
@@ -1161,17 +1282,23 @@ def _queue_selected_exploits(
|
|
|
1161
1282
|
|
|
1162
1283
|
# Filter to executable exploits
|
|
1163
1284
|
executable = [
|
|
1164
|
-
(e, s)
|
|
1165
|
-
|
|
1285
|
+
(e, s)
|
|
1286
|
+
for e, s in selected_items
|
|
1287
|
+
if e.get("msf_module")
|
|
1288
|
+
and _determine_actionability(e) in ["one_click", "setup_needed"]
|
|
1166
1289
|
]
|
|
1167
1290
|
|
|
1168
1291
|
if not executable:
|
|
1169
|
-
console.print(
|
|
1292
|
+
console.print(
|
|
1293
|
+
" [yellow]No executable exploits selected (need MSF module)[/yellow]"
|
|
1294
|
+
)
|
|
1170
1295
|
click.pause()
|
|
1171
|
-
return
|
|
1296
|
+
return "continue"
|
|
1172
1297
|
|
|
1173
1298
|
# Show confirmation
|
|
1174
|
-
console.print(
|
|
1299
|
+
console.print(
|
|
1300
|
+
f"\n [cyan]Queue {len(executable)} exploit(s) for execution?[/cyan]\n"
|
|
1301
|
+
)
|
|
1175
1302
|
|
|
1176
1303
|
for idx, (exploit, service) in enumerate(executable[:5], 1):
|
|
1177
1304
|
console.print(f" {idx}. {exploit.get('title', 'Unknown')[:50]}")
|
|
@@ -1181,12 +1308,14 @@ def _queue_selected_exploits(
|
|
|
1181
1308
|
console.print(f" ... and {len(executable) - 5} more")
|
|
1182
1309
|
|
|
1183
1310
|
console.print()
|
|
1184
|
-
console.print(
|
|
1311
|
+
console.print(
|
|
1312
|
+
" [yellow]Note: Exploits will run sequentially as background jobs[/yellow]"
|
|
1313
|
+
)
|
|
1185
1314
|
console.print(" [dim]Check Job Queue to monitor progress[/dim]")
|
|
1186
1315
|
console.print()
|
|
1187
1316
|
|
|
1188
1317
|
if not click.confirm(" Proceed?", default=False):
|
|
1189
|
-
return
|
|
1318
|
+
return "continue"
|
|
1190
1319
|
|
|
1191
1320
|
# Queue each exploit as a job
|
|
1192
1321
|
queued = 0
|
|
@@ -1195,9 +1324,9 @@ def _queue_selected_exploits(
|
|
|
1195
1324
|
|
|
1196
1325
|
for exploit, service in executable:
|
|
1197
1326
|
# Create MSF resource script content
|
|
1198
|
-
msf_module = exploit.get(
|
|
1199
|
-
target_ip = host.get(
|
|
1200
|
-
target_port = service.get(
|
|
1327
|
+
msf_module = exploit.get("msf_module")
|
|
1328
|
+
target_ip = host.get("ip")
|
|
1329
|
+
target_port = service.get("port")
|
|
1201
1330
|
|
|
1202
1331
|
# Build args for MSF execution
|
|
1203
1332
|
args = f"--module {msf_module} --rhost {target_ip}"
|
|
@@ -1206,12 +1335,12 @@ def _queue_selected_exploits(
|
|
|
1206
1335
|
|
|
1207
1336
|
# Queue the job with metadata
|
|
1208
1337
|
job_id = enqueue_job(
|
|
1209
|
-
tool=
|
|
1338
|
+
tool="msf_exploit",
|
|
1210
1339
|
target=target_ip,
|
|
1211
1340
|
args=args,
|
|
1212
1341
|
label=f"Exploit: {exploit.get('title', 'Unknown')[:40]}",
|
|
1213
1342
|
engagement_id=engagement_id,
|
|
1214
|
-
parent_id=prev_job_id if prev_job_id else None
|
|
1343
|
+
parent_id=prev_job_id if prev_job_id else None,
|
|
1215
1344
|
)
|
|
1216
1345
|
|
|
1217
1346
|
if first_job_id is None:
|
|
@@ -1222,12 +1351,12 @@ def _queue_selected_exploits(
|
|
|
1222
1351
|
# Mark as attempted
|
|
1223
1352
|
exploit_attempts.record_attempt(
|
|
1224
1353
|
engagement_id=engagement_id,
|
|
1225
|
-
host_id=host.get(
|
|
1226
|
-
exploit_identifier=exploit.get(
|
|
1227
|
-
exploit_title=exploit.get(
|
|
1228
|
-
status=
|
|
1229
|
-
service_id=service.get(
|
|
1230
|
-
notes=f"Queued as job #{job_id}"
|
|
1354
|
+
host_id=host.get("host_id"),
|
|
1355
|
+
exploit_identifier=exploit.get("identifier"),
|
|
1356
|
+
exploit_title=exploit.get("title", "Unknown"),
|
|
1357
|
+
status="attempted",
|
|
1358
|
+
service_id=service.get("service_id"),
|
|
1359
|
+
notes=f"Queued as job #{job_id}",
|
|
1231
1360
|
)
|
|
1232
1361
|
|
|
1233
1362
|
console.print(f"\n [green]Queued {queued} exploit(s) as background jobs[/green]")
|
|
@@ -1237,9 +1366,10 @@ def _queue_selected_exploits(
|
|
|
1237
1366
|
|
|
1238
1367
|
# Clear cache
|
|
1239
1368
|
from souleyez.intelligence.exploit_suggestions import _SUGGESTION_CACHE
|
|
1369
|
+
|
|
1240
1370
|
_SUGGESTION_CACHE.clear()
|
|
1241
1371
|
|
|
1242
|
-
return
|
|
1372
|
+
return "clear"
|
|
1243
1373
|
|
|
1244
1374
|
|
|
1245
1375
|
def view_exploit_suggestions(engagement_id):
|
|
@@ -1248,11 +1378,13 @@ def view_exploit_suggestions(engagement_id):
|
|
|
1248
1378
|
from souleyez.storage.engagements import EngagementManager
|
|
1249
1379
|
|
|
1250
1380
|
em = EngagementManager()
|
|
1251
|
-
engine = ExploitSuggestionEngine(
|
|
1381
|
+
engine = ExploitSuggestionEngine(
|
|
1382
|
+
use_searchsploit=False
|
|
1383
|
+
) # Disable SearchSploit for instant loading
|
|
1252
1384
|
|
|
1253
1385
|
current = em.get_by_id(engagement_id)
|
|
1254
1386
|
if not current:
|
|
1255
|
-
click.echo(click.style("Engagement not found", fg=
|
|
1387
|
+
click.echo(click.style("Engagement not found", fg="red"))
|
|
1256
1388
|
click.pause()
|
|
1257
1389
|
return
|
|
1258
1390
|
|
|
@@ -1276,13 +1408,15 @@ def view_exploit_suggestions(engagement_id):
|
|
|
1276
1408
|
padding_total = width - 2 - text_display_width
|
|
1277
1409
|
padding_left = padding_total // 2
|
|
1278
1410
|
padding_right = padding_total - padding_left
|
|
1279
|
-
console.print(
|
|
1411
|
+
console.print(
|
|
1412
|
+
"│" + " " * padding_left + header_text + " " * padding_right + "│"
|
|
1413
|
+
)
|
|
1280
1414
|
console.print("└" + "─" * (width - 2) + "┘\n")
|
|
1281
1415
|
|
|
1282
1416
|
# Get suggestions
|
|
1283
1417
|
suggestions = engine.generate_suggestions(engagement_id)
|
|
1284
1418
|
|
|
1285
|
-
if not suggestions or not suggestions.get(
|
|
1419
|
+
if not suggestions or not suggestions.get("hosts"):
|
|
1286
1420
|
# No suggestions available
|
|
1287
1421
|
hm = HostManager()
|
|
1288
1422
|
all_hosts = hm.list_hosts(engagement_id=engagement_id)
|
|
@@ -1297,22 +1431,28 @@ def view_exploit_suggestions(engagement_id):
|
|
|
1297
1431
|
console.print(" • nmap for network scanning")
|
|
1298
1432
|
else:
|
|
1299
1433
|
console.print(" 💡 [cyan]Reason:[/cyan]")
|
|
1300
|
-
console.print(
|
|
1301
|
-
|
|
1434
|
+
console.print(
|
|
1435
|
+
f" • {len(all_hosts)} host(s) discovered but no exploits found"
|
|
1436
|
+
)
|
|
1437
|
+
console.print(
|
|
1438
|
+
" • Services may be patched or require manual research\n"
|
|
1439
|
+
)
|
|
1302
1440
|
|
|
1303
1441
|
console.print()
|
|
1304
1442
|
else:
|
|
1305
1443
|
if selected_host is None:
|
|
1306
1444
|
# Summary view
|
|
1307
1445
|
console.print(f" Engagement: [cyan]{current['name']}[/cyan]")
|
|
1308
|
-
console.print(
|
|
1446
|
+
console.print(
|
|
1447
|
+
f" Filter: {'[yellow]Untried Only[/yellow]' if filter_untried else 'All Exploits'}\n"
|
|
1448
|
+
)
|
|
1309
1449
|
|
|
1310
1450
|
# Summary table
|
|
1311
1451
|
table = Table(
|
|
1312
1452
|
show_header=True,
|
|
1313
1453
|
header_style="bold cyan",
|
|
1314
1454
|
box=DesignSystem.TABLE_BOX,
|
|
1315
|
-
padding=(0, 1)
|
|
1455
|
+
padding=(0, 1),
|
|
1316
1456
|
)
|
|
1317
1457
|
table.add_column("#", justify="right", width=4)
|
|
1318
1458
|
table.add_column("Host", style="cyan")
|
|
@@ -1321,16 +1461,20 @@ def view_exploit_suggestions(engagement_id):
|
|
|
1321
1461
|
table.add_column("High", justify="center", width=6, style="yellow")
|
|
1322
1462
|
table.add_column("Med", justify="center", width=6)
|
|
1323
1463
|
table.add_column("Low", justify="center", width=6, style="dim")
|
|
1324
|
-
table.add_column(
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1464
|
+
table.add_column(
|
|
1465
|
+
"⚪", justify="center", width=4, style="dim"
|
|
1466
|
+
) # Untried
|
|
1467
|
+
table.add_column(
|
|
1468
|
+
"✅", justify="center", width=4, style="green"
|
|
1469
|
+
) # Success
|
|
1470
|
+
|
|
1471
|
+
for idx, host in enumerate(suggestions["hosts"], 1):
|
|
1472
|
+
ip = host.get("ip", "Unknown")
|
|
1473
|
+
hostname = host.get("hostname", "")
|
|
1330
1474
|
display_name = f"{ip} ({hostname})" if hostname else ip
|
|
1331
1475
|
|
|
1332
|
-
services = host.get(
|
|
1333
|
-
services_with_exploits = [s for s in services if s.get(
|
|
1476
|
+
services = host.get("services", [])
|
|
1477
|
+
services_with_exploits = [s for s in services if s.get("exploits")]
|
|
1334
1478
|
|
|
1335
1479
|
# Count by severity and status
|
|
1336
1480
|
crit_count = 0
|
|
@@ -1341,22 +1485,22 @@ def view_exploit_suggestions(engagement_id):
|
|
|
1341
1485
|
success_count = 0
|
|
1342
1486
|
|
|
1343
1487
|
for service in services_with_exploits:
|
|
1344
|
-
for exploit in service.get(
|
|
1345
|
-
severity = exploit.get(
|
|
1346
|
-
status = exploit.get(
|
|
1488
|
+
for exploit in service.get("exploits", []):
|
|
1489
|
+
severity = exploit.get("severity", "")
|
|
1490
|
+
status = exploit.get("attempt_status", "not_tried")
|
|
1347
1491
|
|
|
1348
|
-
if severity ==
|
|
1492
|
+
if severity == "critical":
|
|
1349
1493
|
crit_count += 1
|
|
1350
|
-
elif severity ==
|
|
1494
|
+
elif severity == "high":
|
|
1351
1495
|
high_count += 1
|
|
1352
|
-
elif severity ==
|
|
1496
|
+
elif severity == "medium":
|
|
1353
1497
|
med_count += 1
|
|
1354
|
-
elif severity ==
|
|
1498
|
+
elif severity == "low":
|
|
1355
1499
|
low_count += 1
|
|
1356
1500
|
|
|
1357
|
-
if status ==
|
|
1501
|
+
if status == "not_tried":
|
|
1358
1502
|
untried_count += 1
|
|
1359
|
-
elif status ==
|
|
1503
|
+
elif status == "success":
|
|
1360
1504
|
success_count += 1
|
|
1361
1505
|
|
|
1362
1506
|
table.add_row(
|
|
@@ -1368,7 +1512,7 @@ def view_exploit_suggestions(engagement_id):
|
|
|
1368
1512
|
str(med_count) if med_count > 0 else "-",
|
|
1369
1513
|
str(low_count) if low_count > 0 else "-",
|
|
1370
1514
|
str(untried_count) if untried_count > 0 else "-",
|
|
1371
|
-
str(success_count) if success_count > 0 else "-"
|
|
1515
|
+
str(success_count) if success_count > 0 else "-",
|
|
1372
1516
|
)
|
|
1373
1517
|
|
|
1374
1518
|
console.print(table)
|
|
@@ -1376,24 +1520,30 @@ def view_exploit_suggestions(engagement_id):
|
|
|
1376
1520
|
|
|
1377
1521
|
else:
|
|
1378
1522
|
# Detail view for selected host
|
|
1379
|
-
host = suggestions[
|
|
1380
|
-
ip = host.get(
|
|
1381
|
-
hostname = host.get(
|
|
1523
|
+
host = suggestions["hosts"][selected_host - 1]
|
|
1524
|
+
ip = host.get("ip") or host.get("host") or "Unknown"
|
|
1525
|
+
hostname = host.get("hostname", "")
|
|
1382
1526
|
display_name = f"{ip} ({hostname})" if hostname else ip
|
|
1383
1527
|
|
|
1384
1528
|
console.print(f" 🎯 [cyan bold]{display_name}[/cyan bold]")
|
|
1385
|
-
console.print(
|
|
1529
|
+
console.print(
|
|
1530
|
+
f" Filter: {'[yellow]Untried Only[/yellow]' if filter_untried else 'All Exploits'}"
|
|
1531
|
+
)
|
|
1386
1532
|
|
|
1387
1533
|
# Show selection count if any
|
|
1388
1534
|
if selected_exploits:
|
|
1389
|
-
console.print(
|
|
1535
|
+
console.print(
|
|
1536
|
+
f" [cyan bold]Selected:[/cyan bold] {len(selected_exploits)} exploit(s) [dim](\\[x] to manage)[/dim]"
|
|
1537
|
+
)
|
|
1390
1538
|
console.print()
|
|
1391
1539
|
|
|
1392
1540
|
# Quick Wins section
|
|
1393
1541
|
_render_quick_wins(host, width, selected_exploits)
|
|
1394
1542
|
|
|
1395
1543
|
# Service groups with exploits
|
|
1396
|
-
_render_service_groups(
|
|
1544
|
+
_render_service_groups(
|
|
1545
|
+
host, width, collapsed_services, filter_untried, selected_exploits
|
|
1546
|
+
)
|
|
1397
1547
|
|
|
1398
1548
|
# Additional attack vectors
|
|
1399
1549
|
if show_additional_vectors:
|
|
@@ -1407,10 +1557,18 @@ def view_exploit_suggestions(engagement_id):
|
|
|
1407
1557
|
console.print()
|
|
1408
1558
|
|
|
1409
1559
|
if selected_host is None:
|
|
1410
|
-
console.print(
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
console.print(
|
|
1560
|
+
console.print(
|
|
1561
|
+
" [cyan]\\[1-9][/cyan] Select Host - View exploit suggestions for specific target"
|
|
1562
|
+
)
|
|
1563
|
+
console.print(
|
|
1564
|
+
" [cyan]\\[u][/cyan] Toggle Untried Filter - Show only unexploited vulnerabilities"
|
|
1565
|
+
)
|
|
1566
|
+
console.print(
|
|
1567
|
+
" [cyan]\\[r][/cyan] Refresh - Reload exploit suggestions from database"
|
|
1568
|
+
)
|
|
1569
|
+
console.print(
|
|
1570
|
+
" [cyan]\\[h][/cyan] Help - Show keyboard shortcuts and usage guide"
|
|
1571
|
+
)
|
|
1414
1572
|
console.print()
|
|
1415
1573
|
console.print(" " + "═" * (width - 4))
|
|
1416
1574
|
console.print()
|
|
@@ -1418,19 +1576,41 @@ def view_exploit_suggestions(engagement_id):
|
|
|
1418
1576
|
else:
|
|
1419
1577
|
# Multi-select options
|
|
1420
1578
|
if selected_exploits:
|
|
1421
|
-
console.print(
|
|
1422
|
-
|
|
1423
|
-
|
|
1579
|
+
console.print(
|
|
1580
|
+
f" [cyan]\\[x][/cyan] Bulk Actions - Manage {len(selected_exploits)} selected exploit(s)"
|
|
1581
|
+
)
|
|
1582
|
+
console.print(
|
|
1583
|
+
" [cyan]\\[i][/cyan] Interactive Select - Multi-select exploits with keyboard"
|
|
1584
|
+
)
|
|
1585
|
+
console.print(
|
|
1586
|
+
" [cyan]\\[c][/cyan] Clear Selection - Deselect all exploits"
|
|
1587
|
+
)
|
|
1424
1588
|
console.print()
|
|
1425
|
-
console.print(
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
console.print(
|
|
1429
|
-
|
|
1589
|
+
console.print(
|
|
1590
|
+
" [cyan]\\[m][/cyan] Mark Exploit - Record attempt status (tried/failed/success)"
|
|
1591
|
+
)
|
|
1592
|
+
console.print(
|
|
1593
|
+
" [cyan]\\[v][/cyan] View Details - Full exploit information and prerequisites"
|
|
1594
|
+
)
|
|
1595
|
+
console.print(
|
|
1596
|
+
" [cyan]\\[e][/cyan] Execute Exploit - Launch MSF module for selected exploit"
|
|
1597
|
+
)
|
|
1598
|
+
console.print(
|
|
1599
|
+
" [cyan]\\[a][/cyan] Execute All Critical - Batch execute all critical exploits"
|
|
1600
|
+
)
|
|
1601
|
+
console.print(
|
|
1602
|
+
" [cyan]\\[g][/cyan] Toggle Vectors - Show/hide additional attack techniques"
|
|
1603
|
+
)
|
|
1430
1604
|
console.print()
|
|
1431
|
-
console.print(
|
|
1432
|
-
|
|
1433
|
-
|
|
1605
|
+
console.print(
|
|
1606
|
+
" [cyan]\\[u][/cyan] Toggle Untried Filter - Show only unexploited vulnerabilities"
|
|
1607
|
+
)
|
|
1608
|
+
console.print(
|
|
1609
|
+
" [cyan]\\[b][/cyan] Back to Host List - Return to target selection"
|
|
1610
|
+
)
|
|
1611
|
+
console.print(
|
|
1612
|
+
" [cyan]\\[h][/cyan] Help - Show keyboard shortcuts and usage guide"
|
|
1613
|
+
)
|
|
1434
1614
|
console.print()
|
|
1435
1615
|
console.print(" " + "═" * (width - 4))
|
|
1436
1616
|
console.print()
|
|
@@ -1439,56 +1619,70 @@ def view_exploit_suggestions(engagement_id):
|
|
|
1439
1619
|
console.print()
|
|
1440
1620
|
|
|
1441
1621
|
try:
|
|
1442
|
-
choice =
|
|
1622
|
+
choice = (
|
|
1623
|
+
click.prompt(
|
|
1624
|
+
" Select option", type=str, default="q", show_default=False
|
|
1625
|
+
)
|
|
1626
|
+
.strip()
|
|
1627
|
+
.lower()
|
|
1628
|
+
)
|
|
1443
1629
|
|
|
1444
|
-
if choice ==
|
|
1630
|
+
if choice == "q":
|
|
1445
1631
|
return
|
|
1446
|
-
elif choice ==
|
|
1632
|
+
elif choice == "h" or choice == "?":
|
|
1447
1633
|
_show_help_overlay()
|
|
1448
|
-
elif choice ==
|
|
1634
|
+
elif choice == "r":
|
|
1449
1635
|
continue # Refresh
|
|
1450
|
-
elif choice ==
|
|
1636
|
+
elif choice == "u":
|
|
1451
1637
|
filter_untried = not filter_untried
|
|
1452
|
-
elif choice ==
|
|
1638
|
+
elif choice == "b" and selected_host is not None:
|
|
1453
1639
|
selected_host = None
|
|
1454
1640
|
collapsed_services.clear()
|
|
1455
1641
|
selected_exploits.clear() # Clear selection when leaving host
|
|
1456
|
-
elif choice ==
|
|
1642
|
+
elif choice == "g" and selected_host is not None:
|
|
1457
1643
|
show_additional_vectors = not show_additional_vectors
|
|
1458
1644
|
elif choice.isdigit() and selected_host is None:
|
|
1459
1645
|
host_num = int(choice)
|
|
1460
|
-
if 1 <= host_num <= len(suggestions.get(
|
|
1646
|
+
if 1 <= host_num <= len(suggestions.get("hosts", [])):
|
|
1461
1647
|
selected_host = host_num
|
|
1462
1648
|
selected_exploits.clear() # Clear selection when switching hosts
|
|
1463
1649
|
else:
|
|
1464
1650
|
console.print("[red]Invalid host number[/red]")
|
|
1465
1651
|
click.pause()
|
|
1466
|
-
elif choice ==
|
|
1652
|
+
elif choice == "m" and selected_host is not None:
|
|
1467
1653
|
# Mark exploit interactively
|
|
1468
|
-
host = suggestions[
|
|
1654
|
+
host = suggestions["hosts"][selected_host - 1]
|
|
1469
1655
|
if _mark_exploit_interactive(engagement_id, host):
|
|
1470
1656
|
# Refresh suggestions after marking
|
|
1471
|
-
from souleyez.intelligence.exploit_suggestions import
|
|
1657
|
+
from souleyez.intelligence.exploit_suggestions import (
|
|
1658
|
+
_SUGGESTION_CACHE,
|
|
1659
|
+
)
|
|
1660
|
+
|
|
1472
1661
|
_SUGGESTION_CACHE.clear() # Clear cache to force refresh
|
|
1473
|
-
elif choice ==
|
|
1662
|
+
elif choice == "i" and selected_host is not None:
|
|
1474
1663
|
# Interactive mode - launch interactive selector
|
|
1475
|
-
host = suggestions[
|
|
1664
|
+
host = suggestions["hosts"][selected_host - 1]
|
|
1476
1665
|
_interactive_exploit_select(host, selected_exploits)
|
|
1477
|
-
elif choice ==
|
|
1666
|
+
elif choice == "x" and selected_host is not None and selected_exploits:
|
|
1478
1667
|
# Bulk actions menu
|
|
1479
|
-
host = suggestions[
|
|
1480
|
-
result = _exploit_bulk_action_menu(
|
|
1481
|
-
|
|
1668
|
+
host = suggestions["hosts"][selected_host - 1]
|
|
1669
|
+
result = _exploit_bulk_action_menu(
|
|
1670
|
+
engagement_id, host, selected_exploits
|
|
1671
|
+
)
|
|
1672
|
+
if result == "clear":
|
|
1482
1673
|
selected_exploits.clear()
|
|
1483
|
-
from souleyez.intelligence.exploit_suggestions import
|
|
1674
|
+
from souleyez.intelligence.exploit_suggestions import (
|
|
1675
|
+
_SUGGESTION_CACHE,
|
|
1676
|
+
)
|
|
1677
|
+
|
|
1484
1678
|
_SUGGESTION_CACHE.clear()
|
|
1485
|
-
elif choice ==
|
|
1679
|
+
elif choice == "c" and selected_host is not None:
|
|
1486
1680
|
# Clear selection
|
|
1487
1681
|
selected_exploits.clear()
|
|
1488
1682
|
console.print(" [dim]Selection cleared[/dim]")
|
|
1489
|
-
elif choice ==
|
|
1683
|
+
elif choice == "v" and selected_host is not None:
|
|
1490
1684
|
# Show detailed info for an exploit (renamed from [i])
|
|
1491
|
-
host = suggestions[
|
|
1685
|
+
host = suggestions["hosts"][selected_host - 1]
|
|
1492
1686
|
exploit_index = _build_exploit_index(host)
|
|
1493
1687
|
|
|
1494
1688
|
if not exploit_index:
|
|
@@ -1509,9 +1703,9 @@ def view_exploit_suggestions(engagement_id):
|
|
|
1509
1703
|
click.pause()
|
|
1510
1704
|
except (ValueError, click.Abort):
|
|
1511
1705
|
pass
|
|
1512
|
-
elif choice ==
|
|
1706
|
+
elif choice == "e" and selected_host is not None:
|
|
1513
1707
|
# Execute MSF module
|
|
1514
|
-
host = suggestions[
|
|
1708
|
+
host = suggestions["hosts"][selected_host - 1]
|
|
1515
1709
|
exploit_index = _build_exploit_index(host)
|
|
1516
1710
|
|
|
1517
1711
|
if not exploit_index:
|
|
@@ -1527,26 +1721,38 @@ def view_exploit_suggestions(engagement_id):
|
|
|
1527
1721
|
if 1 <= exploit_num <= len(exploit_index):
|
|
1528
1722
|
exploit, service = exploit_index[exploit_num - 1]
|
|
1529
1723
|
|
|
1530
|
-
if not exploit.get(
|
|
1531
|
-
console.print(
|
|
1532
|
-
|
|
1724
|
+
if not exploit.get("msf_module"):
|
|
1725
|
+
console.print(
|
|
1726
|
+
" [red]This exploit doesn't have an MSF module[/red]"
|
|
1727
|
+
)
|
|
1728
|
+
console.print(
|
|
1729
|
+
f" [dim]Actionability: {_render_actionability_badge(_determine_actionability(exploit))}[/dim]"
|
|
1730
|
+
)
|
|
1533
1731
|
click.pause()
|
|
1534
1732
|
else:
|
|
1535
|
-
if _execute_msf_module(
|
|
1733
|
+
if _execute_msf_module(
|
|
1734
|
+
exploit, host, service, engagement_id
|
|
1735
|
+
):
|
|
1536
1736
|
# Refresh after execution
|
|
1537
|
-
from souleyez.intelligence.exploit_suggestions import
|
|
1737
|
+
from souleyez.intelligence.exploit_suggestions import (
|
|
1738
|
+
_SUGGESTION_CACHE,
|
|
1739
|
+
)
|
|
1740
|
+
|
|
1538
1741
|
_SUGGESTION_CACHE.clear()
|
|
1539
1742
|
else:
|
|
1540
1743
|
console.print(" [red]Invalid ID[/red]")
|
|
1541
1744
|
click.pause()
|
|
1542
1745
|
except (ValueError, click.Abort):
|
|
1543
1746
|
pass
|
|
1544
|
-
elif choice ==
|
|
1747
|
+
elif choice == "a" and selected_host is not None:
|
|
1545
1748
|
# Batch execute all critical
|
|
1546
|
-
host = suggestions[
|
|
1749
|
+
host = suggestions["hosts"][selected_host - 1]
|
|
1547
1750
|
if _batch_execute_critical(engagement_id, host) > 0:
|
|
1548
1751
|
# Refresh after batch execution
|
|
1549
|
-
from souleyez.intelligence.exploit_suggestions import
|
|
1752
|
+
from souleyez.intelligence.exploit_suggestions import (
|
|
1753
|
+
_SUGGESTION_CACHE,
|
|
1754
|
+
)
|
|
1755
|
+
|
|
1550
1756
|
_SUGGESTION_CACHE.clear()
|
|
1551
1757
|
else:
|
|
1552
1758
|
console.print("[red]Invalid command[/red]")
|