souleyez 2.43.26__py3-none-any.whl → 2.43.34__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 +9526 -2879
- 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 +563 -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 +408 -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 +371 -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 +854 -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 +173 -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 +223 -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 +23434 -10286
- 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.26.dist-info → souleyez-2.43.34.dist-info}/METADATA +1 -1
- souleyez-2.43.34.dist-info/RECORD +443 -0
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/WHEEL +1 -1
- souleyez-2.43.26.dist-info/RECORD +0 -379
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/entry_points.txt +0 -0
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/licenses/LICENSE +0 -0
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/top_level.txt +0 -0
souleyez/ui/intelligence_view.py
CHANGED
|
@@ -18,13 +18,13 @@ def view_intelligence(engagement_id: int):
|
|
|
18
18
|
from souleyez.intelligence.surface_analyzer import AttackSurfaceAnalyzer
|
|
19
19
|
from souleyez.storage.engagements import EngagementManager
|
|
20
20
|
from souleyez.ui.design_system import DesignSystem
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
em = EngagementManager()
|
|
23
23
|
analyzer = AttackSurfaceAnalyzer()
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
engagement = em.get_by_id(engagement_id)
|
|
26
26
|
if not engagement:
|
|
27
|
-
click.echo(click.style("Error: Engagement not found", fg=
|
|
27
|
+
click.echo(click.style("Error: Engagement not found", fg="red"))
|
|
28
28
|
click.pause()
|
|
29
29
|
return
|
|
30
30
|
|
|
@@ -34,11 +34,11 @@ def view_intelligence(engagement_id: int):
|
|
|
34
34
|
show_all_recommendations = False # Track if all recommendations should be shown
|
|
35
35
|
|
|
36
36
|
# Analyze once before entering loop
|
|
37
|
-
click.echo(click.style("\n🔍 Analyzing attack surface...", fg=
|
|
37
|
+
click.echo(click.style("\n🔍 Analyzing attack surface...", fg="yellow", bold=True))
|
|
38
38
|
try:
|
|
39
39
|
analysis = analyzer.analyze_engagement(engagement_id)
|
|
40
40
|
except Exception as e:
|
|
41
|
-
click.echo(click.style(f"Error analyzing: {e}", fg=
|
|
41
|
+
click.echo(click.style(f"Error analyzing: {e}", fg="red"))
|
|
42
42
|
click.pause()
|
|
43
43
|
return
|
|
44
44
|
|
|
@@ -48,73 +48,91 @@ def view_intelligence(engagement_id: int):
|
|
|
48
48
|
# Header
|
|
49
49
|
width = DesignSystem.get_terminal_width()
|
|
50
50
|
click.echo("\n┌" + "─" * (width - 2) + "┐")
|
|
51
|
-
click.echo(
|
|
51
|
+
click.echo(
|
|
52
|
+
"│"
|
|
53
|
+
+ click.style(
|
|
54
|
+
" ATTACK SURFACE DASHBOARD ".center(width - 2), bold=True, fg="cyan"
|
|
55
|
+
)
|
|
56
|
+
+ "│"
|
|
57
|
+
)
|
|
52
58
|
click.echo("└" + "─" * (width - 2) + "┘")
|
|
53
59
|
click.echo()
|
|
54
|
-
click.echo(
|
|
60
|
+
click.echo(
|
|
61
|
+
f"Engagement: {click.style(engagement['name'], fg='green', bold=True)}"
|
|
62
|
+
)
|
|
55
63
|
click.echo()
|
|
56
|
-
|
|
64
|
+
|
|
57
65
|
# Overview section
|
|
58
|
-
display_overview(analysis[
|
|
59
|
-
|
|
66
|
+
display_overview(analysis["overview"], width)
|
|
67
|
+
|
|
60
68
|
# Top targets
|
|
61
|
-
display_top_targets(analysis[
|
|
62
|
-
|
|
69
|
+
display_top_targets(analysis["hosts"], width, show_all_targets)
|
|
70
|
+
|
|
63
71
|
# Detailed service status for #1 host
|
|
64
72
|
has_more_services = False
|
|
65
|
-
if analysis[
|
|
66
|
-
has_more_services = display_service_status(
|
|
67
|
-
|
|
73
|
+
if analysis["hosts"]:
|
|
74
|
+
has_more_services = display_service_status(
|
|
75
|
+
analysis["hosts"][0], width, show_all_services
|
|
76
|
+
)
|
|
77
|
+
|
|
68
78
|
# Exploit suggestions
|
|
69
|
-
has_more_exploits = display_exploit_suggestions(
|
|
70
|
-
|
|
79
|
+
has_more_exploits = display_exploit_suggestions(
|
|
80
|
+
engagement_id, analysis["hosts"], width, show_all_exploits
|
|
81
|
+
)
|
|
82
|
+
|
|
71
83
|
# Recommendations
|
|
72
|
-
has_more_recommendations = display_recommendations(
|
|
73
|
-
|
|
84
|
+
has_more_recommendations = display_recommendations(
|
|
85
|
+
analysis["recommendations"], width, show_all_recommendations
|
|
86
|
+
)
|
|
87
|
+
|
|
74
88
|
# Menu
|
|
75
89
|
display_menu(width)
|
|
76
|
-
|
|
90
|
+
|
|
77
91
|
try:
|
|
78
92
|
choice = input("\n Select option: ").strip()
|
|
79
|
-
|
|
80
|
-
if choice ==
|
|
93
|
+
|
|
94
|
+
if choice == "q" or choice.lower() == "q":
|
|
81
95
|
return
|
|
82
|
-
elif choice ==
|
|
83
|
-
view_host_details(engagement_id, analysis[
|
|
84
|
-
elif choice ==
|
|
96
|
+
elif choice == "1":
|
|
97
|
+
view_host_details(engagement_id, analysis["hosts"])
|
|
98
|
+
elif choice == "2":
|
|
85
99
|
filter_services(engagement_id, analysis)
|
|
86
|
-
elif choice ==
|
|
100
|
+
elif choice == "3":
|
|
87
101
|
export_attack_surface_report(engagement_id, engagement, analysis)
|
|
88
|
-
elif choice ==
|
|
89
|
-
auto_exploit_untried(engagement_id, analysis[
|
|
90
|
-
elif choice ==
|
|
102
|
+
elif choice == "4":
|
|
103
|
+
auto_exploit_untried(engagement_id, analysis["hosts"])
|
|
104
|
+
elif choice == "5":
|
|
91
105
|
continue # Refresh
|
|
92
|
-
elif choice ==
|
|
106
|
+
elif choice == "6":
|
|
93
107
|
show_all_targets = not show_all_targets # Toggle all targets
|
|
94
108
|
continue
|
|
95
|
-
elif choice ==
|
|
109
|
+
elif choice == "7":
|
|
96
110
|
show_all_exploits = not show_all_exploits # Toggle all exploits
|
|
97
111
|
continue
|
|
98
|
-
elif choice ==
|
|
99
|
-
show_all_recommendations =
|
|
112
|
+
elif choice == "8":
|
|
113
|
+
show_all_recommendations = (
|
|
114
|
+
not show_all_recommendations
|
|
115
|
+
) # Toggle all recommendations
|
|
100
116
|
continue
|
|
101
|
-
elif choice.lower() ==
|
|
117
|
+
elif choice.lower() == "s":
|
|
102
118
|
show_all_services = not show_all_services # Toggle all services
|
|
103
119
|
continue
|
|
104
|
-
elif choice.lower() ==
|
|
120
|
+
elif choice.lower() == "w":
|
|
105
121
|
# Wazuh Vulnerabilities view
|
|
106
122
|
from souleyez.ui.wazuh_vulns_view import show_wazuh_vulns_view
|
|
107
|
-
|
|
123
|
+
|
|
124
|
+
show_wazuh_vulns_view(engagement_id, engagement.get("name", ""))
|
|
108
125
|
continue
|
|
109
|
-
elif choice.lower() ==
|
|
126
|
+
elif choice.lower() == "g":
|
|
110
127
|
# Gap Analysis view
|
|
111
128
|
from souleyez.ui.gap_analysis_view import show_gap_analysis_view
|
|
112
|
-
|
|
129
|
+
|
|
130
|
+
show_gap_analysis_view(engagement_id, engagement.get("name", ""))
|
|
113
131
|
continue
|
|
114
132
|
else:
|
|
115
|
-
click.echo(click.style("Invalid option", fg=
|
|
133
|
+
click.echo(click.style("Invalid option", fg="red"))
|
|
116
134
|
click.pause()
|
|
117
|
-
|
|
135
|
+
|
|
118
136
|
except (KeyboardInterrupt, EOFError):
|
|
119
137
|
return
|
|
120
138
|
|
|
@@ -122,18 +140,21 @@ def view_intelligence(engagement_id: int):
|
|
|
122
140
|
def display_overview(overview: Dict, width: int):
|
|
123
141
|
"""Display overview statistics."""
|
|
124
142
|
click.echo("═" * width)
|
|
125
|
-
click.echo(click.style("📊 OVERVIEW", bold=True, fg=
|
|
143
|
+
click.echo(click.style("📊 OVERVIEW", bold=True, fg="cyan"))
|
|
126
144
|
click.echo("─" * width)
|
|
127
145
|
click.echo()
|
|
128
|
-
|
|
146
|
+
|
|
129
147
|
click.echo(f"Total Hosts: {overview['total_hosts']}")
|
|
130
148
|
click.echo(f"Total Services: {overview['total_services']}")
|
|
131
|
-
|
|
132
|
-
exploited_pct = overview[
|
|
133
|
-
color =
|
|
134
|
-
click.echo(
|
|
149
|
+
|
|
150
|
+
exploited_pct = overview["exploitation_percentage"]
|
|
151
|
+
color = "green" if exploited_pct > 50 else "yellow" if exploited_pct > 20 else "red"
|
|
152
|
+
click.echo(
|
|
153
|
+
f"Exploited: {overview['exploited_services']} / {overview['total_services']} ",
|
|
154
|
+
nl=False,
|
|
155
|
+
)
|
|
135
156
|
click.echo(click.style(f"({exploited_pct}%)", fg=color))
|
|
136
|
-
|
|
157
|
+
|
|
137
158
|
click.echo(f"Credentials Found: {overview['credentials_found']}")
|
|
138
159
|
click.echo(f"Critical Findings: {overview['critical_findings']}")
|
|
139
160
|
click.echo()
|
|
@@ -142,69 +163,92 @@ def display_overview(overview: Dict, width: int):
|
|
|
142
163
|
def display_top_targets(hosts: List[Dict], width: int, show_all: bool = False):
|
|
143
164
|
"""Display top targets by attack surface."""
|
|
144
165
|
click.echo("═" * width)
|
|
145
|
-
click.echo(click.style("🎯 TOP TARGETS (by attack surface)", bold=True, fg=
|
|
166
|
+
click.echo(click.style("🎯 TOP TARGETS (by attack surface)", bold=True, fg="cyan"))
|
|
146
167
|
click.echo("─" * width)
|
|
147
168
|
click.echo()
|
|
148
|
-
|
|
169
|
+
|
|
149
170
|
if not hosts:
|
|
150
|
-
click.echo(" " + click.style("No hosts found", fg=
|
|
171
|
+
click.echo(" " + click.style("No hosts found", fg="yellow"))
|
|
151
172
|
return False
|
|
152
|
-
|
|
173
|
+
|
|
153
174
|
# Show top 3 or all
|
|
154
175
|
display_count = len(hosts) if show_all else min(3, len(hosts))
|
|
155
176
|
has_more = len(hosts) > 3
|
|
156
|
-
|
|
177
|
+
|
|
157
178
|
for idx, host in enumerate(hosts[:display_count], 1):
|
|
158
179
|
# Host header - Color thresholds match help system documentation
|
|
159
180
|
# 80-100: CRITICAL (red), 60-79: HIGH (yellow), 40-59: MEDIUM (white), 0-39: LOW (dim)
|
|
160
|
-
score_color =
|
|
181
|
+
score_color = (
|
|
182
|
+
"red"
|
|
183
|
+
if host["score"] >= 80
|
|
184
|
+
else (
|
|
185
|
+
"yellow"
|
|
186
|
+
if host["score"] >= 60
|
|
187
|
+
else "white" if host["score"] >= 40 else "white"
|
|
188
|
+
)
|
|
189
|
+
)
|
|
161
190
|
click.echo(f"#{idx} {click.style(host['host'], bold=True)} ", nl=False)
|
|
162
|
-
if host.get(
|
|
191
|
+
if host.get("hostname"):
|
|
163
192
|
click.echo(f"({host['hostname']}) ", nl=False)
|
|
164
193
|
click.echo(f"[Score: {click.style(str(host['score']), fg=score_color)}]")
|
|
165
|
-
|
|
194
|
+
|
|
166
195
|
# Stats
|
|
167
|
-
num_services = len(host.get(
|
|
168
|
-
click.echo(
|
|
196
|
+
num_services = len(host.get("services", []))
|
|
197
|
+
click.echo(
|
|
198
|
+
f" ├─ {host['open_ports']} open ports | {num_services} services | ",
|
|
199
|
+
nl=False,
|
|
200
|
+
)
|
|
169
201
|
click.echo(f"{host['findings']} findings ", nl=False)
|
|
170
|
-
if host[
|
|
171
|
-
click.echo(click.style(f"({host['critical_findings']} critical)", fg=
|
|
202
|
+
if host["critical_findings"] > 0:
|
|
203
|
+
click.echo(click.style(f"({host['critical_findings']} critical)", fg="red"))
|
|
172
204
|
else:
|
|
173
205
|
click.echo()
|
|
174
|
-
|
|
206
|
+
|
|
175
207
|
# Exploitation progress
|
|
176
|
-
prog = host[
|
|
177
|
-
pct = round(
|
|
178
|
-
|
|
179
|
-
|
|
208
|
+
prog = host["exploitation_progress"]
|
|
209
|
+
pct = round(
|
|
210
|
+
(prog["exploited"] / prog["total"] * 100) if prog["total"] > 0 else 0, 0
|
|
211
|
+
)
|
|
212
|
+
prog_color = "green" if pct > 50 else "yellow" if pct > 20 else "red"
|
|
213
|
+
click.echo(
|
|
214
|
+
f" ├─ Exploitation: {prog['exploited']}/{prog['total']} services ",
|
|
215
|
+
nl=False,
|
|
216
|
+
)
|
|
180
217
|
click.echo(click.style(f"({int(pct)}%)", fg=prog_color))
|
|
181
|
-
|
|
218
|
+
|
|
182
219
|
# Actions
|
|
183
220
|
click.echo(f" └─ ", nl=False)
|
|
184
|
-
click.echo(click.style("[View Details]", fg=
|
|
221
|
+
click.echo(click.style("[View Details]", fg="cyan"), nl=False)
|
|
185
222
|
click.echo(" ", nl=False)
|
|
186
|
-
click.echo(click.style("[Scan More]", fg=
|
|
187
|
-
if prog[
|
|
223
|
+
click.echo(click.style("[Scan More]", fg="cyan"), nl=False)
|
|
224
|
+
if prog["not_tried"] > 0:
|
|
188
225
|
click.echo(" ", nl=False)
|
|
189
|
-
click.echo(click.style("[Auto-Exploit]", fg=
|
|
226
|
+
click.echo(click.style("[Auto-Exploit]", fg="green"))
|
|
190
227
|
else:
|
|
191
228
|
click.echo()
|
|
192
|
-
|
|
229
|
+
|
|
193
230
|
click.echo()
|
|
194
|
-
|
|
231
|
+
|
|
195
232
|
# Show "show more" indicator
|
|
196
233
|
if has_more and not show_all:
|
|
197
234
|
remaining = len(hosts) - display_count
|
|
198
|
-
click.echo(
|
|
235
|
+
click.echo(
|
|
236
|
+
click.style(
|
|
237
|
+
f" ... and {remaining} more hosts (option [6] to show all)",
|
|
238
|
+
fg="bright_black",
|
|
239
|
+
)
|
|
240
|
+
)
|
|
199
241
|
click.echo()
|
|
200
|
-
|
|
242
|
+
|
|
201
243
|
return has_more
|
|
202
244
|
|
|
203
245
|
|
|
204
246
|
def display_service_status(host: Dict, width: int, show_all: bool = False):
|
|
205
247
|
"""Display detailed service status for a host."""
|
|
206
248
|
click.echo("═" * width)
|
|
207
|
-
click.echo(
|
|
249
|
+
click.echo(
|
|
250
|
+
click.style(f"🔓 EXPLOITATION STATUS - {host['host']}", bold=True, fg="cyan")
|
|
251
|
+
)
|
|
208
252
|
click.echo("─" * width)
|
|
209
253
|
click.echo()
|
|
210
254
|
|
|
@@ -214,35 +258,36 @@ def display_service_status(host: Dict, width: int, show_all: bool = False):
|
|
|
214
258
|
click.echo("─" * 170)
|
|
215
259
|
|
|
216
260
|
# Show services (10 or all)
|
|
217
|
-
all_services = host.get(
|
|
261
|
+
all_services = host.get("services", [])
|
|
218
262
|
services_to_show = all_services if show_all else all_services[:10]
|
|
219
263
|
|
|
220
264
|
if not services_to_show:
|
|
221
|
-
click.echo(" " + click.style("No services found", fg=
|
|
265
|
+
click.echo(" " + click.style("No services found", fg="yellow"))
|
|
222
266
|
click.echo()
|
|
223
267
|
return False
|
|
224
268
|
|
|
225
269
|
for service in services_to_show:
|
|
226
|
-
port = str(service[
|
|
227
|
-
svc = (service.get(
|
|
270
|
+
port = str(service["port"]).ljust(7)
|
|
271
|
+
svc = (service.get("service") or "unknown")[:17].ljust(18)
|
|
228
272
|
|
|
229
273
|
# Clean version: remove "syn-ack ttl XX" prefix
|
|
230
|
-
version = service[
|
|
274
|
+
version = service["version"] or ""
|
|
231
275
|
import re
|
|
232
|
-
|
|
276
|
+
|
|
277
|
+
version = re.sub(r"^syn-ack ttl \d+\s*", "", version)
|
|
233
278
|
ver = version[:34].ljust(35)
|
|
234
279
|
|
|
235
280
|
# Status with emoji
|
|
236
|
-
status = service.get(
|
|
237
|
-
if status ==
|
|
281
|
+
status = service.get("status", "unknown")
|
|
282
|
+
if status == "exploited":
|
|
238
283
|
status_display = "✅ EXPLOITED".ljust(17)
|
|
239
|
-
elif status ==
|
|
284
|
+
elif status == "attempted":
|
|
240
285
|
status_display = "🔄 ATTEMPTED".ljust(17)
|
|
241
286
|
else:
|
|
242
287
|
status_display = "⚠️ NOT TRIED".ljust(17)
|
|
243
288
|
|
|
244
289
|
# Actions
|
|
245
|
-
actions = (service.get(
|
|
290
|
+
actions = (service.get("suggested_actions") or [])[:2]
|
|
246
291
|
actions_str = " | ".join([f"[{a}]" for a in actions]) if actions else ""
|
|
247
292
|
|
|
248
293
|
click.echo(f"{port} {svc} {ver} {status_display} {actions_str}")
|
|
@@ -250,21 +295,38 @@ def display_service_status(host: Dict, width: int, show_all: bool = False):
|
|
|
250
295
|
total_services = len(all_services)
|
|
251
296
|
has_more = total_services > 10 and not show_all
|
|
252
297
|
if has_more:
|
|
253
|
-
click.echo(
|
|
298
|
+
click.echo(
|
|
299
|
+
f"\n... and {total_services - 10} more services "
|
|
300
|
+
+ click.style("[Press 's' to show all]", fg="cyan")
|
|
301
|
+
)
|
|
254
302
|
|
|
255
303
|
click.echo()
|
|
256
304
|
|
|
257
305
|
# Legend
|
|
258
306
|
click.echo("Legend:")
|
|
259
|
-
click.echo(
|
|
260
|
-
|
|
261
|
-
|
|
307
|
+
click.echo(
|
|
308
|
+
" "
|
|
309
|
+
+ click.style("✅ EXPLOITED", fg="green")
|
|
310
|
+
+ " - Successfully exploited, session/creds obtained"
|
|
311
|
+
)
|
|
312
|
+
click.echo(
|
|
313
|
+
" "
|
|
314
|
+
+ click.style("🔄 ATTEMPTED", fg="yellow")
|
|
315
|
+
+ " - Exploit tried but failed or no results yet"
|
|
316
|
+
)
|
|
317
|
+
click.echo(
|
|
318
|
+
" "
|
|
319
|
+
+ click.style("⚠️ NOT TRIED", fg="red")
|
|
320
|
+
+ " - No exploitation attempts logged"
|
|
321
|
+
)
|
|
262
322
|
click.echo()
|
|
263
323
|
|
|
264
324
|
return has_more
|
|
265
325
|
|
|
266
326
|
|
|
267
|
-
def display_exploit_suggestions(
|
|
327
|
+
def display_exploit_suggestions(
|
|
328
|
+
engagement_id: int, top_hosts: List[Dict], width: int, show_all: bool = False
|
|
329
|
+
):
|
|
268
330
|
"""Display exploit suggestions for top hosts."""
|
|
269
331
|
from souleyez.intelligence.exploit_suggestions import ExploitSuggestionEngine
|
|
270
332
|
from rich.console import Console
|
|
@@ -272,214 +334,254 @@ def display_exploit_suggestions(engagement_id: int, top_hosts: List[Dict], width
|
|
|
272
334
|
console = Console()
|
|
273
335
|
# Disable SearchSploit in dashboard to prevent UI hangs - use manual Exploit Suggestions menu instead
|
|
274
336
|
engine = ExploitSuggestionEngine(use_searchsploit=False)
|
|
275
|
-
|
|
337
|
+
|
|
276
338
|
click.echo("═" * width)
|
|
277
|
-
click.echo(click.style("💣 EXPLOIT SUGGESTIONS", bold=True, fg=
|
|
339
|
+
click.echo(click.style("💣 EXPLOIT SUGGESTIONS", bold=True, fg="red"))
|
|
278
340
|
click.echo("─" * width)
|
|
279
341
|
click.echo()
|
|
280
|
-
|
|
342
|
+
|
|
281
343
|
# Get suggestions for top 1 or all hosts
|
|
282
344
|
display_count = len(top_hosts) if show_all else 1
|
|
283
345
|
hosts_with_exploits = 0
|
|
284
|
-
|
|
346
|
+
|
|
285
347
|
for host_data in top_hosts[:display_count]:
|
|
286
|
-
host_ip = host_data.get(
|
|
348
|
+
host_ip = host_data.get("host") or host_data.get("ip_address")
|
|
287
349
|
if not host_ip:
|
|
288
350
|
continue
|
|
289
|
-
|
|
351
|
+
|
|
290
352
|
# Get host_id from IP
|
|
291
353
|
from souleyez.storage.hosts import HostManager
|
|
354
|
+
|
|
292
355
|
hm = HostManager()
|
|
293
356
|
host_obj = hm.get_host_by_ip(engagement_id, host_ip)
|
|
294
357
|
if not host_obj:
|
|
295
358
|
continue
|
|
296
|
-
|
|
297
|
-
host_id = host_obj[
|
|
359
|
+
|
|
360
|
+
host_id = host_obj["id"]
|
|
298
361
|
suggestions = engine.generate_suggestions(engagement_id, host_id)
|
|
299
|
-
|
|
300
|
-
if not suggestions[
|
|
362
|
+
|
|
363
|
+
if not suggestions["hosts"]:
|
|
301
364
|
continue
|
|
302
|
-
|
|
303
|
-
host_suggestions = suggestions[
|
|
304
|
-
ip = host_suggestions[
|
|
305
|
-
hostname = host_suggestions.get(
|
|
306
|
-
|
|
365
|
+
|
|
366
|
+
host_suggestions = suggestions["hosts"][0]
|
|
367
|
+
ip = host_suggestions["ip"]
|
|
368
|
+
hostname = host_suggestions.get("hostname", "")
|
|
369
|
+
|
|
307
370
|
# Count services with exploits
|
|
308
|
-
services_with_exploits = [
|
|
371
|
+
services_with_exploits = [
|
|
372
|
+
s for s in host_suggestions["services"] if s.get("exploits")
|
|
373
|
+
]
|
|
309
374
|
if not services_with_exploits:
|
|
310
375
|
continue
|
|
311
|
-
|
|
376
|
+
|
|
312
377
|
hosts_with_exploits += 1
|
|
313
|
-
|
|
378
|
+
|
|
314
379
|
# Host header
|
|
315
380
|
display_name = f"{ip}" + (f" ({hostname})" if hostname else "")
|
|
316
381
|
console.print(f"[cyan]┌─ {display_name}[/cyan]")
|
|
317
|
-
|
|
382
|
+
|
|
318
383
|
# Collect all exploits from all services with their service info
|
|
319
384
|
all_exploits = []
|
|
320
385
|
for svc in services_with_exploits:
|
|
321
|
-
for exploit in svc.get(
|
|
322
|
-
all_exploits.append(
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
386
|
+
for exploit in svc.get("exploits", []):
|
|
387
|
+
all_exploits.append(
|
|
388
|
+
{
|
|
389
|
+
"exploit": exploit,
|
|
390
|
+
"port": svc["port"],
|
|
391
|
+
"service": svc["service"],
|
|
392
|
+
"version": svc.get("version", "unknown"),
|
|
393
|
+
}
|
|
394
|
+
)
|
|
395
|
+
|
|
329
396
|
# Sort by severity (critical > high > medium > low > info)
|
|
330
|
-
severity_order = {
|
|
331
|
-
all_exploits.sort(
|
|
332
|
-
|
|
397
|
+
severity_order = {"critical": 0, "high": 1, "medium": 2, "low": 3, "info": 4}
|
|
398
|
+
all_exploits.sort(
|
|
399
|
+
key=lambda x: severity_order.get(x["exploit"].get("severity", "info"), 5)
|
|
400
|
+
)
|
|
401
|
+
|
|
333
402
|
# Show top 2 exploits when collapsed, all when expanded
|
|
334
|
-
display_exploit_count =
|
|
403
|
+
display_exploit_count = (
|
|
404
|
+
len(all_exploits) if show_all else min(2, len(all_exploits))
|
|
405
|
+
)
|
|
335
406
|
has_more_exploits = len(all_exploits) > 2
|
|
336
|
-
|
|
407
|
+
|
|
337
408
|
for idx, item in enumerate(all_exploits[:display_exploit_count]):
|
|
338
|
-
exploit = item[
|
|
339
|
-
port = item[
|
|
340
|
-
service = item[
|
|
341
|
-
version = item[
|
|
342
|
-
|
|
343
|
-
severity = exploit.get(
|
|
409
|
+
exploit = item["exploit"]
|
|
410
|
+
port = item["port"]
|
|
411
|
+
service = item["service"]
|
|
412
|
+
version = item["version"]
|
|
413
|
+
|
|
414
|
+
severity = exploit.get("severity", "info")
|
|
344
415
|
severity_colors = {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
416
|
+
"critical": "red",
|
|
417
|
+
"high": "yellow",
|
|
418
|
+
"medium": "blue",
|
|
419
|
+
"low": "white",
|
|
420
|
+
"info": "dim",
|
|
350
421
|
}
|
|
351
|
-
color = severity_colors.get(severity,
|
|
352
|
-
|
|
422
|
+
color = severity_colors.get(severity, "white")
|
|
423
|
+
|
|
353
424
|
# Service info
|
|
354
425
|
console.print(f" ├─ Port {port}/{service} [dim]({version})[/dim]")
|
|
355
|
-
|
|
426
|
+
|
|
356
427
|
# Title and severity
|
|
357
|
-
title = exploit[
|
|
428
|
+
title = exploit["title"][:60]
|
|
358
429
|
severity_display = severity.upper()
|
|
359
|
-
console.print(
|
|
360
|
-
|
|
430
|
+
console.print(
|
|
431
|
+
f" ├─ [{color}]{title}[/{color}] [{color}][{severity_display}][/{color}]"
|
|
432
|
+
)
|
|
433
|
+
|
|
361
434
|
# MSF module
|
|
362
|
-
msf_module = exploit.get(
|
|
435
|
+
msf_module = exploit.get("msf_module", "N/A")
|
|
363
436
|
console.print(f" │ MSF: [cyan]{msf_module}[/cyan]")
|
|
364
|
-
|
|
437
|
+
|
|
365
438
|
# CVE if available
|
|
366
|
-
if exploit.get(
|
|
439
|
+
if exploit.get("cve"):
|
|
367
440
|
console.print(f" │ CVE: [yellow]{exploit['cve']}[/yellow]")
|
|
368
|
-
|
|
441
|
+
|
|
369
442
|
# Description (truncated)
|
|
370
|
-
desc = exploit.get(
|
|
443
|
+
desc = exploit.get("description", "")[:80]
|
|
371
444
|
console.print(f" │ [dim]{desc}[/dim]")
|
|
372
445
|
console.print()
|
|
373
|
-
|
|
446
|
+
|
|
374
447
|
click.echo()
|
|
375
448
|
|
|
376
449
|
# Count total hosts with exploits (do this BEFORE showing "no exploits" message)
|
|
377
450
|
total_with_exploits = 0
|
|
378
451
|
for host_data in top_hosts:
|
|
379
|
-
host_ip = host_data.get(
|
|
452
|
+
host_ip = host_data.get("host") or host_data.get("ip_address")
|
|
380
453
|
if not host_ip:
|
|
381
454
|
continue
|
|
382
455
|
from souleyez.storage.hosts import HostManager
|
|
456
|
+
|
|
383
457
|
hm = HostManager()
|
|
384
458
|
host_obj = hm.get_host_by_ip(engagement_id, host_ip)
|
|
385
459
|
if not host_obj:
|
|
386
460
|
continue
|
|
387
|
-
suggestions = engine.generate_suggestions(engagement_id, host_obj[
|
|
388
|
-
if suggestions[
|
|
389
|
-
services_with_exploits = [
|
|
461
|
+
suggestions = engine.generate_suggestions(engagement_id, host_obj["id"])
|
|
462
|
+
if suggestions["hosts"] and suggestions["hosts"][0]["services"]:
|
|
463
|
+
services_with_exploits = [
|
|
464
|
+
s for s in suggestions["hosts"][0]["services"] if s.get("exploits")
|
|
465
|
+
]
|
|
390
466
|
if services_with_exploits:
|
|
391
467
|
total_with_exploits += 1
|
|
392
468
|
|
|
393
469
|
# Show message based on whether ANY hosts have exploits (not just displayed ones)
|
|
394
470
|
if total_with_exploits == 0:
|
|
395
|
-
click.echo(click.style(" No exploit suggestions available yet", fg=
|
|
471
|
+
click.echo(click.style(" No exploit suggestions available yet", fg="yellow"))
|
|
396
472
|
click.echo()
|
|
397
|
-
click.echo(click.style(" 💡 Possible reasons:", fg=
|
|
473
|
+
click.echo(click.style(" 💡 Possible reasons:", fg="cyan"))
|
|
398
474
|
click.echo(" • Service versions not detected - Run: nmap -sV <target>")
|
|
399
475
|
click.echo(" • Services are up-to-date with no known exploits")
|
|
400
476
|
click.echo(" • SearchSploit database needs updating")
|
|
401
477
|
click.echo()
|
|
402
|
-
|
|
478
|
+
|
|
403
479
|
# Show diagnostic info
|
|
404
|
-
total_services = sum(len(h.get(
|
|
405
|
-
services_with_version = sum(
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
480
|
+
total_services = sum(len(h.get("services", [])) for h in top_hosts)
|
|
481
|
+
services_with_version = sum(
|
|
482
|
+
1
|
|
483
|
+
for h in top_hosts
|
|
484
|
+
for s in h.get("services", [])
|
|
485
|
+
if s.get("version") and s.get("version") != "Unknown"
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
click.echo(click.style(" Diagnostics:", fg="cyan"))
|
|
409
489
|
click.echo(f" • Total services scanned: {total_services}")
|
|
410
490
|
click.echo(f" • Services with version info: {services_with_version}")
|
|
411
491
|
if services_with_version < total_services:
|
|
412
|
-
click.echo(
|
|
492
|
+
click.echo(
|
|
493
|
+
f" • Missing version info: {total_services - services_with_version}"
|
|
494
|
+
)
|
|
413
495
|
click.echo()
|
|
414
496
|
elif hosts_with_exploits == 0 and total_with_exploits > 0:
|
|
415
497
|
# Exploits exist but not in the displayed hosts
|
|
416
|
-
click.echo(
|
|
417
|
-
|
|
498
|
+
click.echo(
|
|
499
|
+
click.style(
|
|
500
|
+
f" 💡 {total_with_exploits} host(s) with exploits available - use option [7] to show all",
|
|
501
|
+
fg="cyan",
|
|
502
|
+
)
|
|
503
|
+
)
|
|
504
|
+
|
|
418
505
|
has_more = total_with_exploits > 1 and not show_all and hosts_with_exploits > 0
|
|
419
506
|
if has_more and not show_all:
|
|
420
507
|
remaining_hosts = total_with_exploits - display_count
|
|
421
508
|
# Also count total remaining exploits
|
|
422
509
|
total_exploits = 0
|
|
423
510
|
for host_data in top_hosts:
|
|
424
|
-
host_ip = host_data.get(
|
|
511
|
+
host_ip = host_data.get("host") or host_data.get("ip_address")
|
|
425
512
|
if not host_ip:
|
|
426
513
|
continue
|
|
427
514
|
from souleyez.storage.hosts import HostManager
|
|
515
|
+
|
|
428
516
|
hm = HostManager()
|
|
429
517
|
host_obj = hm.get_host_by_ip(engagement_id, host_ip)
|
|
430
518
|
if not host_obj:
|
|
431
519
|
continue
|
|
432
|
-
suggestions = engine.generate_suggestions(engagement_id, host_obj[
|
|
433
|
-
if suggestions[
|
|
434
|
-
for svc in suggestions[
|
|
435
|
-
total_exploits += len(svc.get(
|
|
436
|
-
|
|
520
|
+
suggestions = engine.generate_suggestions(engagement_id, host_obj["id"])
|
|
521
|
+
if suggestions["hosts"] and suggestions["hosts"][0]["services"]:
|
|
522
|
+
for svc in suggestions["hosts"][0]["services"]:
|
|
523
|
+
total_exploits += len(svc.get("exploits", []))
|
|
524
|
+
|
|
437
525
|
# Calculate shown exploits (top 2 from top host)
|
|
438
526
|
shown_exploits = display_exploit_count if hosts_with_exploits > 0 else 0
|
|
439
527
|
remaining_exploits = max(0, total_exploits - shown_exploits)
|
|
440
|
-
|
|
441
|
-
click.echo(
|
|
442
|
-
|
|
528
|
+
|
|
529
|
+
click.echo(
|
|
530
|
+
click.style(
|
|
531
|
+
f" ... and {remaining_exploits} more exploits from {remaining_hosts} more hosts (option [7] to show all)",
|
|
532
|
+
fg="bright_black",
|
|
533
|
+
)
|
|
534
|
+
)
|
|
535
|
+
|
|
443
536
|
click.echo()
|
|
444
537
|
return has_more
|
|
445
538
|
|
|
446
539
|
|
|
447
|
-
def display_recommendations(
|
|
540
|
+
def display_recommendations(
|
|
541
|
+
recommendations: List[Dict], width: int, show_all: bool = False
|
|
542
|
+
):
|
|
448
543
|
"""Display recommended next steps."""
|
|
449
544
|
if not recommendations:
|
|
450
545
|
return False
|
|
451
|
-
|
|
546
|
+
|
|
452
547
|
click.echo("═" * width)
|
|
453
|
-
click.echo(click.style("💡 RECOMMENDED NEXT STEPS", bold=True, fg=
|
|
548
|
+
click.echo(click.style("💡 RECOMMENDED NEXT STEPS", bold=True, fg="cyan"))
|
|
454
549
|
click.echo("─" * width)
|
|
455
550
|
click.echo()
|
|
456
|
-
|
|
457
|
-
priority_emoji = {
|
|
458
|
-
|
|
551
|
+
|
|
552
|
+
priority_emoji = {"high": "🎯", "medium": "🔍", "low": "📝"}
|
|
553
|
+
|
|
459
554
|
# Show top 3 or all
|
|
460
555
|
display_count = len(recommendations) if show_all else min(3, len(recommendations))
|
|
461
556
|
has_more = len(recommendations) > 3
|
|
462
|
-
|
|
557
|
+
|
|
463
558
|
for idx, rec in enumerate(recommendations[:display_count], 1):
|
|
464
|
-
emoji = priority_emoji.get(rec[
|
|
465
|
-
click.echo(
|
|
559
|
+
emoji = priority_emoji.get(rec["priority"], "•")
|
|
560
|
+
click.echo(
|
|
561
|
+
f"{idx}. {emoji} {rec['action']} - {rec['service']} on {rec['host']}:{rec['port']}"
|
|
562
|
+
)
|
|
466
563
|
click.echo(f" {rec['reason']}")
|
|
467
564
|
click.echo(f" {click.style('[Enqueue Action]', fg='green')}")
|
|
468
565
|
click.echo()
|
|
469
|
-
|
|
566
|
+
|
|
470
567
|
# Show "show more" indicator
|
|
471
568
|
if has_more and not show_all:
|
|
472
569
|
remaining = len(recommendations) - display_count
|
|
473
|
-
click.echo(
|
|
570
|
+
click.echo(
|
|
571
|
+
click.style(
|
|
572
|
+
f" ... and {remaining} more recommendations (option [8] to show all)",
|
|
573
|
+
fg="bright_black",
|
|
574
|
+
)
|
|
575
|
+
)
|
|
474
576
|
click.echo()
|
|
475
|
-
|
|
577
|
+
|
|
476
578
|
return has_more
|
|
477
579
|
|
|
478
580
|
|
|
479
581
|
def display_menu(width: int):
|
|
480
582
|
"""Display menu options."""
|
|
481
583
|
click.echo("═" * width)
|
|
482
|
-
click.echo(click.style("OPTIONS", bold=True, fg=
|
|
584
|
+
click.echo(click.style("OPTIONS", bold=True, fg="yellow"))
|
|
483
585
|
click.echo("─" * width)
|
|
484
586
|
click.echo()
|
|
485
587
|
click.echo(" [1] View Host Details - Select and view specific host")
|
|
@@ -492,7 +594,7 @@ def display_menu(width: int):
|
|
|
492
594
|
click.echo(" [7] Show All Exploits - Display all exploit suggestions")
|
|
493
595
|
click.echo(" [8] Show All Recommendations - Display all next steps")
|
|
494
596
|
click.echo()
|
|
495
|
-
click.echo(click.style(" WAZUH INTEGRATION", bold=True, fg=
|
|
597
|
+
click.echo(click.style(" WAZUH INTEGRATION", bold=True, fg="blue"))
|
|
496
598
|
click.echo(" [w] Wazuh Vulnerabilities - View agent-detected CVEs")
|
|
497
599
|
click.echo(" [g] Gap Analysis - Compare Wazuh vs scan findings")
|
|
498
600
|
click.echo()
|
|
@@ -504,54 +606,74 @@ def display_menu(width: int):
|
|
|
504
606
|
def filter_services(engagement_id: int, analysis: Dict):
|
|
505
607
|
"""Filter services by criteria."""
|
|
506
608
|
from souleyez.ui.design_system import DesignSystem
|
|
507
|
-
|
|
609
|
+
|
|
508
610
|
DesignSystem.clear_screen()
|
|
509
611
|
click.echo()
|
|
510
|
-
click.echo(click.style("🔍 FILTER SERVICES", bold=True, fg=
|
|
612
|
+
click.echo(click.style("🔍 FILTER SERVICES", bold=True, fg="cyan"))
|
|
511
613
|
click.echo("=" * 80)
|
|
512
614
|
click.echo()
|
|
513
|
-
|
|
615
|
+
|
|
514
616
|
# Prompt for filter criteria
|
|
515
617
|
click.echo("Filter by:")
|
|
516
|
-
service_filter = click.prompt(
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
618
|
+
service_filter = click.prompt(
|
|
619
|
+
" Service name (or press Enter to skip)", default="", show_default=False
|
|
620
|
+
)
|
|
621
|
+
port_filter = click.prompt(
|
|
622
|
+
" Port number (or press Enter to skip)", default="", show_default=False
|
|
623
|
+
)
|
|
624
|
+
protocol_filter = click.prompt(
|
|
625
|
+
" Protocol (tcp/udp, or press Enter to skip)", default="", show_default=False
|
|
626
|
+
)
|
|
627
|
+
|
|
520
628
|
# Apply filters
|
|
521
629
|
filtered_hosts = []
|
|
522
|
-
for host in analysis[
|
|
630
|
+
for host in analysis["hosts"]:
|
|
523
631
|
filtered_services = []
|
|
524
|
-
for svc in host.get(
|
|
632
|
+
for svc in host.get("services", []):
|
|
525
633
|
# Apply filters
|
|
526
|
-
if
|
|
634
|
+
if (
|
|
635
|
+
service_filter
|
|
636
|
+
and service_filter.lower() not in (svc.get("service") or "").lower()
|
|
637
|
+
):
|
|
527
638
|
continue
|
|
528
|
-
if port_filter and str(svc.get(
|
|
639
|
+
if port_filter and str(svc.get("port", "")) != port_filter:
|
|
529
640
|
continue
|
|
530
|
-
if
|
|
641
|
+
if (
|
|
642
|
+
protocol_filter
|
|
643
|
+
and svc.get("protocol", "").lower() != protocol_filter.lower()
|
|
644
|
+
):
|
|
531
645
|
continue
|
|
532
646
|
filtered_services.append(svc)
|
|
533
|
-
|
|
647
|
+
|
|
534
648
|
if filtered_services:
|
|
535
649
|
host_copy = host.copy()
|
|
536
|
-
host_copy[
|
|
650
|
+
host_copy["services"] = filtered_services
|
|
537
651
|
filtered_hosts.append(host_copy)
|
|
538
|
-
|
|
652
|
+
|
|
539
653
|
# Display results
|
|
540
654
|
click.echo()
|
|
541
|
-
click.echo(
|
|
655
|
+
click.echo(
|
|
656
|
+
click.style(
|
|
657
|
+
f"📊 Found {len(filtered_hosts)} host(s) with matching services",
|
|
658
|
+
fg="green",
|
|
659
|
+
bold=True,
|
|
660
|
+
)
|
|
661
|
+
)
|
|
542
662
|
click.echo()
|
|
543
|
-
|
|
663
|
+
|
|
544
664
|
if not filtered_hosts:
|
|
545
|
-
click.echo(click.style(" No services match your filters", fg=
|
|
665
|
+
click.echo(click.style(" No services match your filters", fg="yellow"))
|
|
546
666
|
else:
|
|
547
667
|
for host in filtered_hosts:
|
|
548
668
|
click.echo(f" {host['host']} - {len(host['services'])} service(s)")
|
|
549
|
-
for svc in host[
|
|
550
|
-
service_name = (svc.get(
|
|
551
|
-
click.echo(
|
|
552
|
-
|
|
669
|
+
for svc in host["services"][:5]: # Show first 5
|
|
670
|
+
service_name = (svc.get("service") or "unknown")[:30]
|
|
671
|
+
click.echo(
|
|
672
|
+
f" • {svc['port']}/{svc.get('protocol', 'tcp')} - {service_name}"
|
|
673
|
+
)
|
|
674
|
+
if len(host["services"]) > 5:
|
|
553
675
|
click.echo(f" ... and {len(host['services']) - 5} more")
|
|
554
|
-
|
|
676
|
+
|
|
555
677
|
click.echo()
|
|
556
678
|
click.pause()
|
|
557
679
|
|
|
@@ -559,17 +681,17 @@ def filter_services(engagement_id: int, analysis: Dict):
|
|
|
559
681
|
def view_host_details(engagement_id: int, hosts: List[Dict]):
|
|
560
682
|
"""View detailed information for a specific host."""
|
|
561
683
|
from souleyez.ui.design_system import DesignSystem
|
|
562
|
-
|
|
684
|
+
|
|
563
685
|
if not hosts:
|
|
564
|
-
click.echo(click.style("\nNo hosts available", fg=
|
|
686
|
+
click.echo(click.style("\nNo hosts available", fg="yellow"))
|
|
565
687
|
click.pause()
|
|
566
688
|
return
|
|
567
|
-
|
|
689
|
+
|
|
568
690
|
click.echo("\n" + click.style("Select host:", bold=True))
|
|
569
691
|
for idx, host in enumerate(hosts, 1):
|
|
570
692
|
click.echo(f" [{idx}] {host['host']} (Score: {host['score']})")
|
|
571
693
|
click.echo(" [q] Cancel")
|
|
572
|
-
|
|
694
|
+
|
|
573
695
|
try:
|
|
574
696
|
choice = int(input("\n Select option: ").strip())
|
|
575
697
|
if choice > 0 and choice <= len(hosts):
|
|
@@ -577,58 +699,68 @@ def view_host_details(engagement_id: int, hosts: List[Dict]):
|
|
|
577
699
|
# Display full service table
|
|
578
700
|
DesignSystem.clear_screen()
|
|
579
701
|
width = get_terminal_width()
|
|
580
|
-
|
|
702
|
+
|
|
581
703
|
click.echo("\n" + "=" * width)
|
|
582
|
-
click.echo(
|
|
583
|
-
|
|
584
|
-
|
|
704
|
+
click.echo(
|
|
705
|
+
click.style(f"Host: {host['host']}", bold=True, fg="cyan").center(width)
|
|
706
|
+
)
|
|
707
|
+
if host.get("hostname"):
|
|
708
|
+
click.echo(
|
|
709
|
+
click.style(f"({host['hostname']})", fg="bright_black").center(
|
|
710
|
+
width
|
|
711
|
+
)
|
|
712
|
+
)
|
|
585
713
|
click.echo("=" * width + "\n")
|
|
586
|
-
|
|
587
|
-
click.echo(
|
|
714
|
+
|
|
715
|
+
click.echo(
|
|
716
|
+
f"Attack Surface Score: {click.style(str(host['score']), bold=True)}"
|
|
717
|
+
)
|
|
588
718
|
click.echo(f"Open Ports: {host['open_ports']}")
|
|
589
719
|
click.echo(f"Services: {len(host.get('services', []))}")
|
|
590
|
-
click.echo(
|
|
720
|
+
click.echo(
|
|
721
|
+
f"Findings: {host['findings']} ({host['critical_findings']} critical)"
|
|
722
|
+
)
|
|
591
723
|
click.echo()
|
|
592
|
-
|
|
724
|
+
|
|
593
725
|
# Show all services in a table
|
|
594
726
|
from rich.console import Console
|
|
595
727
|
from rich.table import Table
|
|
596
728
|
from souleyez.ui.design_system import DesignSystem
|
|
597
|
-
|
|
729
|
+
|
|
598
730
|
console = Console(width=width - 4)
|
|
599
731
|
table = Table(
|
|
600
732
|
show_header=True,
|
|
601
733
|
header_style="bold cyan",
|
|
602
734
|
box=DesignSystem.TABLE_BOX,
|
|
603
735
|
padding=(0, 1),
|
|
604
|
-
expand=True
|
|
736
|
+
expand=True,
|
|
605
737
|
)
|
|
606
|
-
|
|
738
|
+
|
|
607
739
|
table.add_column("Port", width=8)
|
|
608
740
|
table.add_column("Service", width=20)
|
|
609
741
|
table.add_column("Version", width=30)
|
|
610
742
|
table.add_column("Status", width=15)
|
|
611
743
|
table.add_column("Creds", width=8, justify="center")
|
|
612
744
|
table.add_column("Findings", width=10, justify="center")
|
|
613
|
-
|
|
614
|
-
for svc in host.get(
|
|
615
|
-
status_emoji = {
|
|
616
|
-
emoji = status_emoji.get(svc[
|
|
745
|
+
|
|
746
|
+
for svc in host.get("services", []):
|
|
747
|
+
status_emoji = {"exploited": "✅", "attempted": "🔄", "not_tried": "⚠️"}
|
|
748
|
+
emoji = status_emoji.get(svc["status"], "•")
|
|
617
749
|
status_text = f"{emoji} {svc['status'].replace('_', ' ').upper()}"
|
|
618
|
-
|
|
619
|
-
version = svc.get(
|
|
620
|
-
|
|
750
|
+
|
|
751
|
+
version = svc.get("version") or "-"
|
|
752
|
+
|
|
621
753
|
table.add_row(
|
|
622
|
-
str(svc[
|
|
623
|
-
svc.get(
|
|
754
|
+
str(svc["port"]),
|
|
755
|
+
svc.get("service") or "unknown",
|
|
624
756
|
version,
|
|
625
757
|
status_text,
|
|
626
|
-
str(svc.get(
|
|
627
|
-
str(svc.get(
|
|
758
|
+
str(svc.get("credentials", 0)),
|
|
759
|
+
str(svc.get("findings", 0)),
|
|
628
760
|
)
|
|
629
|
-
|
|
761
|
+
|
|
630
762
|
console.print(table)
|
|
631
|
-
|
|
763
|
+
|
|
632
764
|
click.pause("\nPress any key to return...")
|
|
633
765
|
except (ValueError, KeyboardInterrupt, EOFError):
|
|
634
766
|
pass
|
|
@@ -636,23 +768,26 @@ def view_host_details(engagement_id: int, hosts: List[Dict]):
|
|
|
636
768
|
|
|
637
769
|
def auto_exploit_untried(engagement_id: int, hosts: List[Dict]):
|
|
638
770
|
"""Auto-enqueue exploits for untried services."""
|
|
639
|
-
click.echo(
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
771
|
+
click.echo(
|
|
772
|
+
click.style(
|
|
773
|
+
"\n⚠️ WARNING: This will enqueue multiple exploit jobs",
|
|
774
|
+
fg="yellow",
|
|
775
|
+
bold=True,
|
|
776
|
+
)
|
|
645
777
|
)
|
|
646
|
-
|
|
778
|
+
|
|
779
|
+
# Count untried services
|
|
780
|
+
untried_count = sum(h["exploitation_progress"]["not_tried"] for h in hosts)
|
|
781
|
+
|
|
647
782
|
if untried_count == 0:
|
|
648
|
-
click.echo(click.style("\nNo untried services found!", fg=
|
|
783
|
+
click.echo(click.style("\nNo untried services found!", fg="green"))
|
|
649
784
|
click.echo()
|
|
650
|
-
|
|
785
|
+
|
|
651
786
|
# Show diagnostic info
|
|
652
|
-
total_services = sum(len(h.get(
|
|
653
|
-
exploited = sum(h[
|
|
654
|
-
attempted = sum(h[
|
|
655
|
-
|
|
787
|
+
total_services = sum(len(h.get("services", [])) for h in hosts)
|
|
788
|
+
exploited = sum(h["exploitation_progress"]["exploited"] for h in hosts)
|
|
789
|
+
attempted = sum(h["exploitation_progress"]["attempted"] for h in hosts)
|
|
790
|
+
|
|
656
791
|
click.echo("Service Status Summary:")
|
|
657
792
|
click.echo(f" Total services: {total_services}")
|
|
658
793
|
click.echo(f" ✅ Exploited: {exploited}")
|
|
@@ -660,40 +795,40 @@ def auto_exploit_untried(engagement_id: int, hosts: List[Dict]):
|
|
|
660
795
|
click.echo(f" ⚠️ Not tried: {untried_count}")
|
|
661
796
|
click.echo()
|
|
662
797
|
click.echo("All services have already been tested!")
|
|
663
|
-
|
|
798
|
+
|
|
664
799
|
click.pause()
|
|
665
800
|
return
|
|
666
|
-
|
|
801
|
+
|
|
667
802
|
click.echo(f"Found {untried_count} untried services")
|
|
668
|
-
|
|
803
|
+
|
|
669
804
|
try:
|
|
670
805
|
if not click.confirm("\nContinue?", default=False):
|
|
671
806
|
return
|
|
672
807
|
except (KeyboardInterrupt, EOFError):
|
|
673
808
|
return
|
|
674
|
-
|
|
809
|
+
|
|
675
810
|
from souleyez.engine.background import enqueue_job
|
|
676
|
-
|
|
811
|
+
|
|
677
812
|
queued = 0
|
|
678
813
|
for host in hosts:
|
|
679
|
-
for service in host.get(
|
|
680
|
-
if service[
|
|
814
|
+
for service in host.get("services", []):
|
|
815
|
+
if service["status"] == "not_tried":
|
|
681
816
|
# Suggest tool for service
|
|
682
|
-
tool = suggest_tool_for_service(service.get(
|
|
817
|
+
tool = suggest_tool_for_service(service.get("service") or "unknown")
|
|
683
818
|
if tool:
|
|
684
819
|
try:
|
|
685
820
|
enqueue_job(
|
|
686
821
|
tool=tool,
|
|
687
|
-
target=host[
|
|
688
|
-
args=[
|
|
822
|
+
target=host["host"],
|
|
823
|
+
args=["-p", str(service["port"])],
|
|
689
824
|
label=f"Auto-exploit: {service.get('service') or 'unknown'}",
|
|
690
|
-
engagement_id=engagement_id
|
|
825
|
+
engagement_id=engagement_id,
|
|
691
826
|
)
|
|
692
827
|
queued += 1
|
|
693
828
|
except:
|
|
694
829
|
pass # Skip if enqueue fails
|
|
695
|
-
|
|
696
|
-
click.echo(click.style(f"\n✓ Queued {queued} jobs", fg=
|
|
830
|
+
|
|
831
|
+
click.echo(click.style(f"\n✓ Queued {queued} jobs", fg="green"))
|
|
697
832
|
click.pause()
|
|
698
833
|
|
|
699
834
|
|
|
@@ -701,22 +836,22 @@ def suggest_tool_for_service(service: str) -> str:
|
|
|
701
836
|
"""Suggest appropriate tool for a service."""
|
|
702
837
|
service_lower = service.lower()
|
|
703
838
|
mapping = {
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
839
|
+
"http": "gobuster",
|
|
840
|
+
"https": "gobuster",
|
|
841
|
+
"ssh": "hydra",
|
|
842
|
+
"ftp": "hydra",
|
|
843
|
+
"telnet": "hydra",
|
|
844
|
+
"mysql": "hydra",
|
|
845
|
+
"postgresql": "hydra",
|
|
846
|
+
"smb": "enum4linux",
|
|
847
|
+
"netbios-ssn": "enum4linux",
|
|
848
|
+
"microsoft-ds": "enum4linux",
|
|
714
849
|
}
|
|
715
|
-
|
|
850
|
+
|
|
716
851
|
for key, tool in mapping.items():
|
|
717
852
|
if key in service_lower:
|
|
718
853
|
return tool
|
|
719
|
-
|
|
854
|
+
|
|
720
855
|
return None
|
|
721
856
|
|
|
722
857
|
|
|
@@ -724,17 +859,17 @@ def export_attack_surface_report(engagement_id: int, engagement: Dict, analysis:
|
|
|
724
859
|
"""Export attack surface analysis to text report."""
|
|
725
860
|
import os
|
|
726
861
|
from datetime import datetime
|
|
727
|
-
|
|
862
|
+
|
|
728
863
|
# Create output directory
|
|
729
864
|
output_dir = os.path.expanduser("~/.souleyez/exports")
|
|
730
865
|
os.makedirs(output_dir, exist_ok=True)
|
|
731
|
-
|
|
866
|
+
|
|
732
867
|
# Generate filename
|
|
733
868
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
734
|
-
safe_name = engagement[
|
|
869
|
+
safe_name = engagement["name"].replace(" ", "_").replace("/", "_")
|
|
735
870
|
filename = f"{safe_name}_attack_surface_{timestamp}.txt"
|
|
736
871
|
filepath = os.path.join(output_dir, filename)
|
|
737
|
-
|
|
872
|
+
|
|
738
873
|
# Generate report
|
|
739
874
|
lines = []
|
|
740
875
|
lines.append("=" * 70)
|
|
@@ -742,43 +877,51 @@ def export_attack_surface_report(engagement_id: int, engagement: Dict, analysis:
|
|
|
742
877
|
lines.append("=" * 70)
|
|
743
878
|
lines.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
744
879
|
lines.append("")
|
|
745
|
-
|
|
880
|
+
|
|
746
881
|
# Overview
|
|
747
|
-
overview = analysis[
|
|
882
|
+
overview = analysis["overview"]
|
|
748
883
|
lines.append("OVERVIEW")
|
|
749
884
|
lines.append("-" * 70)
|
|
750
885
|
lines.append(f"Total Hosts: {overview['total_hosts']}")
|
|
751
886
|
lines.append(f"Total Services: {overview['total_services']}")
|
|
752
|
-
lines.append(
|
|
887
|
+
lines.append(
|
|
888
|
+
f"Exploited: {overview['exploited_services']} / {overview['total_services']} ({overview['exploitation_percentage']}%)"
|
|
889
|
+
)
|
|
753
890
|
lines.append(f"Credentials Found: {overview['credentials_found']}")
|
|
754
891
|
lines.append(f"Critical Findings: {overview['critical_findings']}")
|
|
755
892
|
lines.append("")
|
|
756
|
-
|
|
893
|
+
|
|
757
894
|
# Top targets
|
|
758
895
|
lines.append("TOP TARGETS (by attack surface)")
|
|
759
896
|
lines.append("-" * 70)
|
|
760
|
-
for idx, host in enumerate(analysis[
|
|
897
|
+
for idx, host in enumerate(analysis["hosts"][:5], 1):
|
|
761
898
|
lines.append(f"\n#{idx} {host['host']} (Score: {host['score']})")
|
|
762
|
-
lines.append(
|
|
763
|
-
|
|
764
|
-
|
|
899
|
+
lines.append(
|
|
900
|
+
f" Ports: {host['open_ports']} | Services: {host['services']} | Findings: {host['findings']}"
|
|
901
|
+
)
|
|
902
|
+
prog = host["exploitation_progress"]
|
|
903
|
+
pct = round(
|
|
904
|
+
(prog["exploited"] / prog["total"] * 100) if prog["total"] > 0 else 0, 1
|
|
905
|
+
)
|
|
765
906
|
lines.append(f" Exploitation: {prog['exploited']}/{prog['total']} ({pct}%)")
|
|
766
|
-
|
|
907
|
+
|
|
767
908
|
lines.append("")
|
|
768
|
-
|
|
909
|
+
|
|
769
910
|
# Recommendations
|
|
770
|
-
if analysis[
|
|
911
|
+
if analysis["recommendations"]:
|
|
771
912
|
lines.append("RECOMMENDED NEXT STEPS")
|
|
772
913
|
lines.append("-" * 70)
|
|
773
|
-
for idx, rec in enumerate(analysis[
|
|
774
|
-
lines.append(
|
|
914
|
+
for idx, rec in enumerate(analysis["recommendations"], 1):
|
|
915
|
+
lines.append(
|
|
916
|
+
f"\n{idx}. {rec['action']} - {rec['service']} on {rec['host']}:{rec['port']}"
|
|
917
|
+
)
|
|
775
918
|
lines.append(f" Priority: {rec['priority'].upper()}")
|
|
776
919
|
lines.append(f" Reason: {rec['reason']}")
|
|
777
|
-
|
|
920
|
+
|
|
778
921
|
# Write file
|
|
779
|
-
with open(filepath,
|
|
780
|
-
f.write(
|
|
781
|
-
|
|
782
|
-
click.echo(click.style(f"\n✓ Report exported to:", fg=
|
|
922
|
+
with open(filepath, "w") as f:
|
|
923
|
+
f.write("\n".join(lines))
|
|
924
|
+
|
|
925
|
+
click.echo(click.style(f"\n✓ Report exported to:", fg="green"))
|
|
783
926
|
click.echo(f" {filepath}")
|
|
784
927
|
click.pause()
|