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
souleyez/ui/correlation_view.py
CHANGED
|
@@ -18,119 +18,142 @@ def show_correlation_view(engagement_id: int):
|
|
|
18
18
|
console = Console()
|
|
19
19
|
analyzer = CorrelationAnalyzer()
|
|
20
20
|
detector = GapDetector()
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
while True:
|
|
23
23
|
DesignSystem.clear_screen()
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
# Header
|
|
26
26
|
width = DesignSystem.get_terminal_width()
|
|
27
27
|
click.echo("\n┌" + "─" * (width - 2) + "┐")
|
|
28
|
-
click.echo(
|
|
28
|
+
click.echo(
|
|
29
|
+
"│"
|
|
30
|
+
+ click.style(
|
|
31
|
+
" ATTACK CORRELATION & GAP ANALYSIS ".center(width - 2),
|
|
32
|
+
bold=True,
|
|
33
|
+
fg="cyan",
|
|
34
|
+
)
|
|
35
|
+
+ "│"
|
|
36
|
+
)
|
|
29
37
|
click.echo("└" + "─" * (width - 2) + "┘")
|
|
30
38
|
click.echo()
|
|
31
|
-
|
|
39
|
+
|
|
32
40
|
# Run analysis
|
|
33
41
|
try:
|
|
34
42
|
analysis = analyzer.analyze_engagement(engagement_id)
|
|
35
43
|
except Exception as e:
|
|
36
44
|
import traceback
|
|
37
|
-
|
|
45
|
+
|
|
46
|
+
click.echo(click.style(f"Error analyzing engagement: {e}", fg="red"))
|
|
38
47
|
click.echo()
|
|
39
|
-
click.echo(click.style("Full traceback:", fg=
|
|
48
|
+
click.echo(click.style("Full traceback:", fg="yellow"))
|
|
40
49
|
click.echo(traceback.format_exc())
|
|
41
50
|
click.pause()
|
|
42
51
|
return
|
|
43
|
-
|
|
44
|
-
summary = analysis[
|
|
45
|
-
|
|
52
|
+
|
|
53
|
+
summary = analysis["summary"]
|
|
54
|
+
|
|
46
55
|
# Exploitation Summary
|
|
47
56
|
console.print("[bold cyan]📊 EXPLOITATION SUMMARY[/bold cyan]")
|
|
48
57
|
console.print(f" ├─ Total Services: {summary['total_services']}")
|
|
49
|
-
|
|
58
|
+
|
|
50
59
|
# Calculate percentages
|
|
51
|
-
total = summary[
|
|
60
|
+
total = summary["total_services"]
|
|
52
61
|
if total > 0:
|
|
53
|
-
exploited_pct = int((summary[
|
|
54
|
-
attempted_pct = int((summary[
|
|
55
|
-
not_attempted_pct = int((summary[
|
|
62
|
+
exploited_pct = int((summary["exploited_services"] / total) * 100)
|
|
63
|
+
attempted_pct = int((summary["attempted_services"] / total) * 100)
|
|
64
|
+
not_attempted_pct = int((summary["not_attempted_services"] / total) * 100)
|
|
56
65
|
else:
|
|
57
66
|
exploited_pct = attempted_pct = not_attempted_pct = 0
|
|
58
|
-
|
|
59
|
-
console.print(
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
|
|
68
|
+
console.print(
|
|
69
|
+
f" ├─ [green]✅ Exploited: {summary['exploited_services']} ({exploited_pct}%)[/green]"
|
|
70
|
+
)
|
|
71
|
+
console.print(
|
|
72
|
+
f" ├─ [yellow]🔄 Attempted: {summary['attempted_services']} ({attempted_pct}%)[/yellow]"
|
|
73
|
+
)
|
|
74
|
+
console.print(
|
|
75
|
+
f" └─ [red]⚠️ Not Attempted: {summary['not_attempted_services']} ({not_attempted_pct}%)[/red]"
|
|
76
|
+
)
|
|
62
77
|
console.print()
|
|
63
|
-
|
|
78
|
+
|
|
64
79
|
# Compromised Hosts
|
|
65
|
-
if summary[
|
|
80
|
+
if summary["compromised_hosts"] > 0:
|
|
66
81
|
console.print("[bold green]🎯 COMPROMISED HOSTS[/bold green]")
|
|
67
|
-
|
|
68
|
-
for host_analysis in analysis[
|
|
69
|
-
host_summary = host_analysis[
|
|
70
|
-
if host_summary[
|
|
71
|
-
host = host_analysis[
|
|
72
|
-
access_emoji = {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
|
|
83
|
+
for host_analysis in analysis["hosts"]:
|
|
84
|
+
host_summary = host_analysis["summary"]
|
|
85
|
+
if host_summary["access_level"] != "none":
|
|
86
|
+
host = host_analysis["host"]
|
|
87
|
+
access_emoji = {"user": "👤", "root": "👑", "admin": "🔑"}.get(
|
|
88
|
+
host_summary["access_level"], "❓"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
console.print(
|
|
92
|
+
f" ├─ {access_emoji} {host['ip_address']} [{host_summary['access_level'].upper()} ACCESS] - {host_summary['exploited']} services exploited"
|
|
93
|
+
)
|
|
94
|
+
|
|
80
95
|
console.print()
|
|
81
|
-
|
|
96
|
+
|
|
82
97
|
# Exploitation Gaps
|
|
83
|
-
gaps = analysis[
|
|
84
|
-
|
|
98
|
+
gaps = analysis["gaps"]
|
|
99
|
+
|
|
85
100
|
if gaps:
|
|
86
101
|
# Prioritize gaps
|
|
87
102
|
prioritized_gaps = detector.prioritize_gaps(gaps)
|
|
88
|
-
|
|
103
|
+
|
|
89
104
|
gap_summary = detector.get_gap_summary(engagement_id)
|
|
90
|
-
|
|
91
|
-
console.print(
|
|
105
|
+
|
|
106
|
+
console.print(
|
|
107
|
+
f"[bold yellow]⚠️ EXPLOITATION GAPS ({len(gaps)} total)[/bold yellow]"
|
|
108
|
+
)
|
|
92
109
|
console.print(f" ├─ 🔴 Critical: {gap_summary['by_severity']['critical']}")
|
|
93
110
|
console.print(f" ├─ 🟠 High: {gap_summary['by_severity']['high']}")
|
|
94
111
|
console.print(f" ├─ 🟡 Medium: {gap_summary['by_severity']['medium']}")
|
|
95
112
|
console.print(f" └─ ⚪ Low: {gap_summary['by_severity']['low']}")
|
|
96
113
|
console.print()
|
|
97
|
-
|
|
114
|
+
|
|
98
115
|
# Show top 5 gaps
|
|
99
116
|
console.print("[bold]🎯 TOP PRIORITY GAPS:[/bold]")
|
|
100
|
-
|
|
117
|
+
|
|
101
118
|
for idx, gap in enumerate(prioritized_gaps[:5], 1):
|
|
102
119
|
severity_emoji = {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}.get(gap[
|
|
108
|
-
|
|
109
|
-
service_display = gap[
|
|
110
|
-
if gap.get(
|
|
120
|
+
"critical": "🔴",
|
|
121
|
+
"high": "🟠",
|
|
122
|
+
"medium": "🟡",
|
|
123
|
+
"low": "⚪",
|
|
124
|
+
}.get(gap["severity"], "⚪")
|
|
125
|
+
|
|
126
|
+
service_display = gap["service"]
|
|
127
|
+
if gap.get("version"):
|
|
111
128
|
service_display += f" ({gap['version']})"
|
|
112
|
-
|
|
113
|
-
console.print(
|
|
129
|
+
|
|
130
|
+
console.print(
|
|
131
|
+
f"\n {severity_emoji} [bold]#{idx} {gap['host']}:{gap['port']}[/bold] ({service_display})"
|
|
132
|
+
)
|
|
114
133
|
console.print(f" Priority Score: {gap['priority_score']}/100")
|
|
115
134
|
console.print(f" Reason: {gap['reason']}")
|
|
116
|
-
|
|
135
|
+
|
|
117
136
|
# Show top 2 suggested actions
|
|
118
|
-
if gap.get(
|
|
137
|
+
if gap.get("suggested_actions"):
|
|
119
138
|
console.print(" 💡 Suggested:")
|
|
120
|
-
for action in gap[
|
|
139
|
+
for action in gap["suggested_actions"][:2]:
|
|
121
140
|
console.print(f" • {action}")
|
|
122
141
|
else:
|
|
123
|
-
console.print(
|
|
142
|
+
console.print(
|
|
143
|
+
"[bold green]✅ NO GAPS - All discovered services have been attempted![/bold green]"
|
|
144
|
+
)
|
|
124
145
|
console.print()
|
|
125
|
-
|
|
146
|
+
|
|
126
147
|
# Credentials Summary
|
|
127
|
-
if summary[
|
|
128
|
-
console.print(
|
|
148
|
+
if summary["total_credentials"] > 0:
|
|
149
|
+
console.print(
|
|
150
|
+
f"[bold green]🔑 {summary['total_credentials']} credential(s) discovered[/bold green]"
|
|
151
|
+
)
|
|
129
152
|
console.print()
|
|
130
|
-
|
|
153
|
+
|
|
131
154
|
# Menu
|
|
132
155
|
click.echo(DesignSystem.separator())
|
|
133
|
-
click.echo(click.style("ACTIONS", bold=True, fg=
|
|
156
|
+
click.echo(click.style("ACTIONS", bold=True, fg="yellow"))
|
|
134
157
|
click.echo("─" * 170)
|
|
135
158
|
click.echo()
|
|
136
159
|
click.echo(" [1] View Detailed Host Breakdown - See per-host gap analysis")
|
|
@@ -144,23 +167,25 @@ def show_correlation_view(engagement_id: int):
|
|
|
144
167
|
click.echo()
|
|
145
168
|
click.echo(" [q] ← Back")
|
|
146
169
|
click.echo()
|
|
147
|
-
|
|
148
|
-
choice = click.prompt(
|
|
149
|
-
|
|
150
|
-
|
|
170
|
+
|
|
171
|
+
choice = click.prompt(
|
|
172
|
+
"Select option", type=str, default="q", show_default=False
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if choice == "q":
|
|
151
176
|
return
|
|
152
|
-
elif choice ==
|
|
177
|
+
elif choice == "1":
|
|
153
178
|
show_host_breakdown(engagement_id, analysis)
|
|
154
|
-
elif choice ==
|
|
179
|
+
elif choice == "2":
|
|
155
180
|
show_all_gaps(engagement_id, detector)
|
|
156
|
-
elif choice ==
|
|
181
|
+
elif choice == "3":
|
|
157
182
|
show_service_timeline_menu(engagement_id, analysis)
|
|
158
|
-
elif choice ==
|
|
183
|
+
elif choice == "4":
|
|
159
184
|
export_gap_report(engagement_id, gaps)
|
|
160
|
-
elif choice ==
|
|
185
|
+
elif choice == "5":
|
|
161
186
|
continue
|
|
162
187
|
else:
|
|
163
|
-
click.echo(click.style("Invalid choice", fg=
|
|
188
|
+
click.echo(click.style("Invalid choice", fg="red"))
|
|
164
189
|
click.pause()
|
|
165
190
|
|
|
166
191
|
|
|
@@ -176,15 +201,23 @@ def show_host_breakdown(engagement_id: int, analysis: dict):
|
|
|
176
201
|
while True:
|
|
177
202
|
# Filter hosts based on show_empty_hosts setting
|
|
178
203
|
if show_empty_hosts:
|
|
179
|
-
filtered_hosts = analysis[
|
|
204
|
+
filtered_hosts = analysis["hosts"]
|
|
180
205
|
else:
|
|
181
|
-
filtered_hosts = [
|
|
206
|
+
filtered_hosts = [
|
|
207
|
+
h for h in analysis["hosts"] if h["summary"]["total_services"] > 0
|
|
208
|
+
]
|
|
182
209
|
|
|
183
210
|
if not filtered_hosts:
|
|
184
211
|
DesignSystem.clear_screen()
|
|
185
212
|
width = DesignSystem.get_terminal_width()
|
|
186
213
|
click.echo("\n┌" + "─" * (width - 2) + "┐")
|
|
187
|
-
click.echo(
|
|
214
|
+
click.echo(
|
|
215
|
+
"│"
|
|
216
|
+
+ click.style(
|
|
217
|
+
" HOST BREAKDOWN ".center(width - 2), bold=True, fg="cyan"
|
|
218
|
+
)
|
|
219
|
+
+ "│"
|
|
220
|
+
)
|
|
188
221
|
click.echo("└" + "─" * (width - 2) + "┘")
|
|
189
222
|
click.echo()
|
|
190
223
|
console.print("[yellow]No hosts with services found.[/yellow]")
|
|
@@ -203,7 +236,11 @@ def show_host_breakdown(engagement_id: int, analysis: dict):
|
|
|
203
236
|
DesignSystem.clear_screen()
|
|
204
237
|
width = DesignSystem.get_terminal_width()
|
|
205
238
|
click.echo("\n┌" + "─" * (width - 2) + "┐")
|
|
206
|
-
click.echo(
|
|
239
|
+
click.echo(
|
|
240
|
+
"│"
|
|
241
|
+
+ click.style(" HOST BREAKDOWN ".center(width - 2), bold=True, fg="cyan")
|
|
242
|
+
+ "│"
|
|
243
|
+
)
|
|
207
244
|
click.echo("└" + "─" * (width - 2) + "┘")
|
|
208
245
|
click.echo()
|
|
209
246
|
|
|
@@ -214,61 +251,70 @@ def show_host_breakdown(engagement_id: int, analysis: dict):
|
|
|
214
251
|
|
|
215
252
|
# Display hosts for current page
|
|
216
253
|
for host_analysis in page_hosts:
|
|
217
|
-
host = host_analysis[
|
|
218
|
-
summary = host_analysis[
|
|
254
|
+
host = host_analysis["host"]
|
|
255
|
+
summary = host_analysis["summary"]
|
|
219
256
|
|
|
220
257
|
console.print(f"\n[bold cyan]{'═' * 80}[/bold cyan]")
|
|
221
|
-
console.print(f"[bold]HOST: {host['ip_address']}[/bold]", end=
|
|
222
|
-
if host.get(
|
|
223
|
-
console.print(f" ({host['hostname']})", end=
|
|
258
|
+
console.print(f"[bold]HOST: {host['ip_address']}[/bold]", end="")
|
|
259
|
+
if host.get("hostname"):
|
|
260
|
+
console.print(f" ({host['hostname']})", end="")
|
|
224
261
|
console.print()
|
|
225
|
-
console.print(
|
|
262
|
+
console.print(
|
|
263
|
+
f"Access Level: [{_get_access_color(summary['access_level'])}]{summary['access_level'].upper()}[/{_get_access_color(summary['access_level'])}]"
|
|
264
|
+
)
|
|
226
265
|
console.print()
|
|
227
266
|
|
|
228
267
|
# Service summary
|
|
229
|
-
console.print(
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
268
|
+
console.print(
|
|
269
|
+
f"Services: {summary['total_services']} total | "
|
|
270
|
+
f"[green]{summary['exploited']} exploited[/green] | "
|
|
271
|
+
f"[yellow]{summary['attempted']} attempted[/yellow] | "
|
|
272
|
+
f"[red]{summary['not_attempted']} not attempted[/red]"
|
|
273
|
+
)
|
|
233
274
|
console.print(f"Credentials: {summary['credentials_found']}")
|
|
234
275
|
console.print()
|
|
235
276
|
|
|
236
277
|
# Show services
|
|
237
|
-
for svc_analysis in host_analysis[
|
|
238
|
-
service = svc_analysis[
|
|
239
|
-
status = svc_analysis[
|
|
278
|
+
for svc_analysis in host_analysis["services"]:
|
|
279
|
+
service = svc_analysis["service"]
|
|
280
|
+
status = svc_analysis["exploitation_status"]
|
|
240
281
|
|
|
241
282
|
status_emoji = {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
283
|
+
"EXPLOITED": "✅",
|
|
284
|
+
"ATTEMPTED": "🔄",
|
|
285
|
+
"NOT_ATTEMPTED": "⚠️",
|
|
245
286
|
}[status]
|
|
246
287
|
|
|
247
288
|
service_display = f"{service['port']}/{service['protocol']} ({service.get('service_name') or 'unknown'})"
|
|
248
|
-
if service.get(
|
|
289
|
+
if service.get("version"):
|
|
249
290
|
service_display += f" - {service['version']}"
|
|
250
291
|
|
|
251
292
|
console.print(f" {status_emoji} {service_display}")
|
|
252
293
|
|
|
253
294
|
# Show job count
|
|
254
|
-
job_count = len(svc_analysis[
|
|
255
|
-
cred_count = len(svc_analysis[
|
|
295
|
+
job_count = len(svc_analysis["jobs"])
|
|
296
|
+
cred_count = len(svc_analysis["credentials"])
|
|
256
297
|
|
|
257
298
|
if job_count > 0:
|
|
258
|
-
success_rate = int(svc_analysis[
|
|
259
|
-
console.print(
|
|
299
|
+
success_rate = int(svc_analysis["success_rate"] * 100)
|
|
300
|
+
console.print(
|
|
301
|
+
f" Jobs: {job_count} | Success Rate: {success_rate}% | Credentials: {cred_count}"
|
|
302
|
+
)
|
|
260
303
|
|
|
261
304
|
# Show top recommendation
|
|
262
|
-
if svc_analysis[
|
|
305
|
+
if svc_analysis["recommendations"]:
|
|
263
306
|
console.print(f" 💡 {svc_analysis['recommendations'][0]}")
|
|
264
307
|
|
|
265
308
|
console.print(f"\n[bold cyan]{'═' * 80}[/bold cyan]")
|
|
266
309
|
click.echo()
|
|
267
310
|
|
|
268
311
|
# Pagination info
|
|
269
|
-
click.echo(
|
|
270
|
-
|
|
271
|
-
|
|
312
|
+
click.echo(
|
|
313
|
+
click.style(f"Page {current_page} of {total_pages}", fg="cyan", bold=True)
|
|
314
|
+
+ f" (Showing {start_idx + 1}-{end_idx} of {total_hosts} hosts"
|
|
315
|
+
+ ("" if show_empty_hosts else " with services")
|
|
316
|
+
+ ")"
|
|
317
|
+
)
|
|
272
318
|
click.echo()
|
|
273
319
|
|
|
274
320
|
# Navigation options
|
|
@@ -286,15 +332,17 @@ def show_host_breakdown(engagement_id: int, analysis: dict):
|
|
|
286
332
|
click.echo(" " + " | ".join(nav_options))
|
|
287
333
|
click.echo()
|
|
288
334
|
|
|
289
|
-
choice = click.prompt(
|
|
335
|
+
choice = click.prompt(
|
|
336
|
+
"Select option", type=str, default="q", show_default=False
|
|
337
|
+
).lower()
|
|
290
338
|
|
|
291
|
-
if choice ==
|
|
339
|
+
if choice == "q":
|
|
292
340
|
return
|
|
293
|
-
elif choice ==
|
|
341
|
+
elif choice == "n" and current_page < total_pages:
|
|
294
342
|
current_page += 1
|
|
295
|
-
elif choice ==
|
|
343
|
+
elif choice == "p" and current_page > 1:
|
|
296
344
|
current_page -= 1
|
|
297
|
-
elif choice ==
|
|
345
|
+
elif choice == "a":
|
|
298
346
|
show_empty_hosts = not show_empty_hosts
|
|
299
347
|
current_page = 1 # Reset to first page when toggling filter
|
|
300
348
|
elif choice.isdigit():
|
|
@@ -302,10 +350,10 @@ def show_host_breakdown(engagement_id: int, analysis: dict):
|
|
|
302
350
|
if 1 <= page_num <= total_pages:
|
|
303
351
|
current_page = page_num
|
|
304
352
|
else:
|
|
305
|
-
click.echo(click.style("Invalid page number", fg=
|
|
353
|
+
click.echo(click.style("Invalid page number", fg="red"))
|
|
306
354
|
click.pause()
|
|
307
355
|
else:
|
|
308
|
-
click.echo(click.style("Invalid option", fg=
|
|
356
|
+
click.echo(click.style("Invalid option", fg="red"))
|
|
309
357
|
click.pause()
|
|
310
358
|
|
|
311
359
|
|
|
@@ -320,24 +368,44 @@ def show_all_gaps(engagement_id: int, detector: GapDetector):
|
|
|
320
368
|
DesignSystem.clear_screen()
|
|
321
369
|
width = DesignSystem.get_terminal_width()
|
|
322
370
|
click.echo("\n┌" + "─" * (width - 2) + "┐")
|
|
323
|
-
click.echo(
|
|
371
|
+
click.echo(
|
|
372
|
+
"│"
|
|
373
|
+
+ click.style(
|
|
374
|
+
" EXPLOITATION GAPS - DETAILED VIEW ".center(width - 2),
|
|
375
|
+
bold=True,
|
|
376
|
+
fg="yellow",
|
|
377
|
+
)
|
|
378
|
+
+ "│"
|
|
379
|
+
)
|
|
324
380
|
click.echo("└" + "─" * (width - 2) + "┘")
|
|
325
381
|
click.echo()
|
|
326
|
-
console.print(
|
|
382
|
+
console.print(
|
|
383
|
+
"[bold green]✅ No gaps found! All services have been attempted.[/bold green]"
|
|
384
|
+
)
|
|
327
385
|
click.pause()
|
|
328
386
|
return
|
|
329
387
|
|
|
330
388
|
# Pagination settings
|
|
331
389
|
items_per_page = 20
|
|
332
390
|
total_items = len(prioritized)
|
|
333
|
-
total_pages = (
|
|
391
|
+
total_pages = (
|
|
392
|
+
total_items + items_per_page - 1
|
|
393
|
+
) // items_per_page # Ceiling division
|
|
334
394
|
current_page = 1
|
|
335
395
|
|
|
336
396
|
while True:
|
|
337
397
|
DesignSystem.clear_screen()
|
|
338
398
|
width = DesignSystem.get_terminal_width()
|
|
339
399
|
click.echo("\n┌" + "─" * (width - 2) + "┐")
|
|
340
|
-
click.echo(
|
|
400
|
+
click.echo(
|
|
401
|
+
"│"
|
|
402
|
+
+ click.style(
|
|
403
|
+
" EXPLOITATION GAPS - DETAILED VIEW ".center(width - 2),
|
|
404
|
+
bold=True,
|
|
405
|
+
fg="yellow",
|
|
406
|
+
)
|
|
407
|
+
+ "│"
|
|
408
|
+
)
|
|
341
409
|
click.echo("└" + "─" * (width - 2) + "┘")
|
|
342
410
|
click.echo()
|
|
343
411
|
|
|
@@ -347,7 +415,12 @@ def show_all_gaps(engagement_id: int, detector: GapDetector):
|
|
|
347
415
|
page_items = prioritized[start_idx:end_idx]
|
|
348
416
|
|
|
349
417
|
# Create table
|
|
350
|
-
table = Table(
|
|
418
|
+
table = Table(
|
|
419
|
+
show_header=True,
|
|
420
|
+
header_style="bold",
|
|
421
|
+
box=DesignSystem.TABLE_BOX,
|
|
422
|
+
expand=True,
|
|
423
|
+
)
|
|
351
424
|
table.add_column("#", width=8)
|
|
352
425
|
table.add_column("Host", width=22)
|
|
353
426
|
table.add_column("Port", width=10)
|
|
@@ -359,31 +432,37 @@ def show_all_gaps(engagement_id: int, detector: GapDetector):
|
|
|
359
432
|
for idx in range(start_idx, end_idx):
|
|
360
433
|
gap = prioritized[idx]
|
|
361
434
|
severity_emoji = {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
}[gap[
|
|
435
|
+
"critical": "🔴",
|
|
436
|
+
"high": "🟠",
|
|
437
|
+
"medium": "🟡",
|
|
438
|
+
"low": "⚪",
|
|
439
|
+
}[gap["severity"]]
|
|
367
440
|
|
|
368
441
|
# Get first suggested action
|
|
369
|
-
first_action =
|
|
442
|
+
first_action = (
|
|
443
|
+
gap["suggested_actions"][0]
|
|
444
|
+
if gap["suggested_actions"]
|
|
445
|
+
else "No suggestions"
|
|
446
|
+
)
|
|
370
447
|
|
|
371
448
|
table.add_row(
|
|
372
449
|
str(idx + 1),
|
|
373
|
-
gap[
|
|
374
|
-
str(gap[
|
|
375
|
-
gap[
|
|
376
|
-
(gap.get(
|
|
450
|
+
gap["host"],
|
|
451
|
+
str(gap["port"]),
|
|
452
|
+
gap["service"],
|
|
453
|
+
(gap.get("version") or "Unknown")[:26],
|
|
377
454
|
f"{severity_emoji} {gap['priority_score']}",
|
|
378
|
-
first_action[:40]
|
|
455
|
+
first_action[:40],
|
|
379
456
|
)
|
|
380
457
|
|
|
381
458
|
console.print(table)
|
|
382
459
|
click.echo()
|
|
383
460
|
|
|
384
461
|
# Pagination info
|
|
385
|
-
click.echo(
|
|
386
|
-
|
|
462
|
+
click.echo(
|
|
463
|
+
click.style(f"Page {current_page} of {total_pages}", fg="cyan", bold=True)
|
|
464
|
+
+ f" (Showing {start_idx + 1}-{end_idx} of {total_items} gaps)"
|
|
465
|
+
)
|
|
387
466
|
click.echo()
|
|
388
467
|
|
|
389
468
|
# Navigation options
|
|
@@ -397,155 +476,167 @@ def show_all_gaps(engagement_id: int, detector: GapDetector):
|
|
|
397
476
|
click.echo(" " + " | ".join(nav_options))
|
|
398
477
|
click.echo()
|
|
399
478
|
|
|
400
|
-
choice = click.prompt(
|
|
479
|
+
choice = click.prompt(
|
|
480
|
+
"Select option", type=str, default="q", show_default=False
|
|
481
|
+
).lower()
|
|
401
482
|
|
|
402
|
-
if choice ==
|
|
483
|
+
if choice == "q":
|
|
403
484
|
return
|
|
404
|
-
elif choice ==
|
|
485
|
+
elif choice == "n" and current_page < total_pages:
|
|
405
486
|
current_page += 1
|
|
406
|
-
elif choice ==
|
|
487
|
+
elif choice == "p" and current_page > 1:
|
|
407
488
|
current_page -= 1
|
|
408
489
|
elif choice.isdigit():
|
|
409
490
|
page_num = int(choice)
|
|
410
491
|
if 1 <= page_num <= total_pages:
|
|
411
492
|
current_page = page_num
|
|
412
493
|
else:
|
|
413
|
-
click.echo(click.style("Invalid page number", fg=
|
|
494
|
+
click.echo(click.style("Invalid page number", fg="red"))
|
|
414
495
|
click.pause()
|
|
415
496
|
else:
|
|
416
|
-
click.echo(click.style("Invalid option", fg=
|
|
497
|
+
click.echo(click.style("Invalid option", fg="red"))
|
|
417
498
|
click.pause()
|
|
418
499
|
|
|
419
500
|
|
|
420
501
|
def show_service_timeline_menu(engagement_id: int, analysis: dict):
|
|
421
502
|
"""Select a service to view timeline."""
|
|
422
503
|
console = Console()
|
|
423
|
-
|
|
504
|
+
|
|
424
505
|
DesignSystem.clear_screen()
|
|
425
506
|
width = DesignSystem.get_terminal_width()
|
|
426
507
|
click.echo("\n┌" + "─" * (width - 2) + "┐")
|
|
427
|
-
click.echo(
|
|
508
|
+
click.echo(
|
|
509
|
+
"│"
|
|
510
|
+
+ click.style(
|
|
511
|
+
" SELECT SERVICE FOR TIMELINE ".center(width - 2), bold=True, fg="cyan"
|
|
512
|
+
)
|
|
513
|
+
+ "│"
|
|
514
|
+
)
|
|
428
515
|
click.echo("└" + "─" * (width - 2) + "┘")
|
|
429
516
|
click.echo()
|
|
430
|
-
|
|
517
|
+
|
|
431
518
|
# Build service list
|
|
432
519
|
services = []
|
|
433
|
-
for host_analysis in analysis[
|
|
434
|
-
host = host_analysis[
|
|
435
|
-
for svc_analysis in host_analysis[
|
|
436
|
-
if len(svc_analysis[
|
|
437
|
-
services.append(
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
520
|
+
for host_analysis in analysis["hosts"]:
|
|
521
|
+
host = host_analysis["host"]
|
|
522
|
+
for svc_analysis in host_analysis["services"]:
|
|
523
|
+
if len(svc_analysis["jobs"]) > 0: # Only show services with jobs
|
|
524
|
+
services.append(
|
|
525
|
+
{
|
|
526
|
+
"host": host,
|
|
527
|
+
"service": svc_analysis["service"],
|
|
528
|
+
"analysis": svc_analysis,
|
|
529
|
+
}
|
|
530
|
+
)
|
|
531
|
+
|
|
443
532
|
if not services:
|
|
444
533
|
console.print("[yellow]No services with exploitation attempts found.[/yellow]")
|
|
445
534
|
click.pause()
|
|
446
535
|
return
|
|
447
|
-
|
|
536
|
+
|
|
448
537
|
# Display services
|
|
449
538
|
for idx, svc_data in enumerate(services, 1):
|
|
450
|
-
host = svc_data[
|
|
451
|
-
service = svc_data[
|
|
452
|
-
analysis = svc_data[
|
|
453
|
-
|
|
454
|
-
status_emoji = {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
539
|
+
host = svc_data["host"]
|
|
540
|
+
service = svc_data["service"]
|
|
541
|
+
analysis = svc_data["analysis"]
|
|
542
|
+
|
|
543
|
+
status_emoji = {"EXPLOITED": "✅", "ATTEMPTED": "🔄", "NOT_ATTEMPTED": "⚠️"}[
|
|
544
|
+
analysis["exploitation_status"]
|
|
545
|
+
]
|
|
546
|
+
|
|
547
|
+
console.print(
|
|
548
|
+
f"[{idx}] {status_emoji} {host['ip_address']}:{service['port']} ({service['service_name']}) - {len(analysis['jobs'])} attempts"
|
|
549
|
+
)
|
|
550
|
+
|
|
462
551
|
click.echo()
|
|
463
552
|
choice = click.prompt("Select option", type=int, default=0, show_default=False)
|
|
464
|
-
|
|
553
|
+
|
|
465
554
|
if choice > 0 and choice <= len(services):
|
|
466
555
|
selected = services[choice - 1]
|
|
467
556
|
show_service_timeline(
|
|
468
|
-
selected[
|
|
469
|
-
selected['service'],
|
|
470
|
-
selected['analysis']
|
|
557
|
+
selected["host"], selected["service"], selected["analysis"]
|
|
471
558
|
)
|
|
472
559
|
|
|
473
560
|
|
|
474
561
|
def show_service_timeline(host: dict, service: dict, analysis: dict):
|
|
475
562
|
"""Show attack progression timeline for a service."""
|
|
476
563
|
console = Console()
|
|
477
|
-
|
|
564
|
+
|
|
478
565
|
DesignSystem.clear_screen()
|
|
479
566
|
width = DesignSystem.get_terminal_width()
|
|
480
567
|
click.echo("\n┌" + "─" * (width - 2) + "┐")
|
|
481
568
|
service_title = f" ATTACK TIMELINE: {host['ip_address']}:{service['port']} ({service['service_name']}) "
|
|
482
|
-
click.echo(
|
|
569
|
+
click.echo(
|
|
570
|
+
"│" + click.style(service_title.center(width - 2), bold=True, fg="cyan") + "│"
|
|
571
|
+
)
|
|
483
572
|
click.echo("└" + "─" * (width - 2) + "┘")
|
|
484
573
|
click.echo()
|
|
485
|
-
|
|
574
|
+
|
|
486
575
|
# Service details
|
|
487
576
|
console.print(f"[bold]Service:[/bold] {service['service_name']}")
|
|
488
|
-
if service.get(
|
|
577
|
+
if service.get("version"):
|
|
489
578
|
console.print(f"[bold]Version:[/bold] {service['version']}")
|
|
490
|
-
console.print(
|
|
579
|
+
console.print(
|
|
580
|
+
f"[bold]Status:[/bold] [{_get_status_color(analysis['exploitation_status'])}]{analysis['exploitation_status']}[/{_get_status_color(analysis['exploitation_status'])}]"
|
|
581
|
+
)
|
|
491
582
|
console.print()
|
|
492
|
-
|
|
583
|
+
|
|
493
584
|
# Timeline
|
|
494
585
|
console.print("[bold cyan]📅 TIMELINE[/bold cyan]")
|
|
495
586
|
console.print()
|
|
496
|
-
|
|
587
|
+
|
|
497
588
|
# Show jobs chronologically
|
|
498
|
-
for idx, job in enumerate(analysis[
|
|
499
|
-
timestamp = job.get(
|
|
500
|
-
|
|
589
|
+
for idx, job in enumerate(analysis["jobs"], 1):
|
|
590
|
+
timestamp = job.get("created_at", "Unknown")[:19].replace("T", " ")
|
|
591
|
+
|
|
501
592
|
# Job status emoji
|
|
502
|
-
if job.get(
|
|
503
|
-
emoji =
|
|
504
|
-
color =
|
|
505
|
-
elif job[
|
|
506
|
-
emoji =
|
|
507
|
-
color =
|
|
508
|
-
elif job[
|
|
509
|
-
emoji =
|
|
510
|
-
color =
|
|
593
|
+
if job.get("success"):
|
|
594
|
+
emoji = "✅"
|
|
595
|
+
color = "green"
|
|
596
|
+
elif job["status"] == "failed":
|
|
597
|
+
emoji = "❌"
|
|
598
|
+
color = "red"
|
|
599
|
+
elif job["status"] == "killed":
|
|
600
|
+
emoji = "🛑"
|
|
601
|
+
color = "yellow"
|
|
511
602
|
else:
|
|
512
|
-
emoji =
|
|
513
|
-
color =
|
|
514
|
-
|
|
603
|
+
emoji = "🔄"
|
|
604
|
+
color = "blue"
|
|
605
|
+
|
|
515
606
|
console.print(f"[{color}][{timestamp}] {emoji} Attempt #{idx}[/{color}]")
|
|
516
607
|
console.print(f" ├─ Tool: {job['tool']}")
|
|
517
|
-
|
|
518
|
-
if job.get(
|
|
608
|
+
|
|
609
|
+
if job.get("label"):
|
|
519
610
|
console.print(f" ├─ Label: {job['label']}")
|
|
520
|
-
|
|
611
|
+
|
|
521
612
|
console.print(f" ├─ Status: {job['status'].upper()}")
|
|
522
|
-
|
|
523
|
-
if job.get(
|
|
613
|
+
|
|
614
|
+
if job.get("success"):
|
|
524
615
|
console.print(" └─ Result: [bold green]SUCCESS ✅[/bold green]")
|
|
525
616
|
else:
|
|
526
617
|
console.print(" └─ Result: [yellow]No credentials found[/yellow]")
|
|
527
|
-
|
|
618
|
+
|
|
528
619
|
console.print()
|
|
529
|
-
|
|
620
|
+
|
|
530
621
|
# Show credentials found
|
|
531
|
-
if analysis[
|
|
622
|
+
if analysis["credentials"]:
|
|
532
623
|
console.print("[bold green]🔐 CREDENTIALS OBTAINED[/bold green]")
|
|
533
|
-
for cred in analysis[
|
|
534
|
-
username = cred.get(
|
|
535
|
-
password = cred.get(
|
|
536
|
-
status = cred.get(
|
|
537
|
-
|
|
538
|
-
status_emoji =
|
|
624
|
+
for cred in analysis["credentials"]:
|
|
625
|
+
username = cred.get("username", "N/A")
|
|
626
|
+
password = cred.get("password", "N/A")
|
|
627
|
+
status = cred.get("status", "unknown")
|
|
628
|
+
|
|
629
|
+
status_emoji = "✅" if status == "valid" else "❓"
|
|
539
630
|
console.print(f" {status_emoji} {username}:{password} [{status}]")
|
|
540
631
|
console.print()
|
|
541
|
-
|
|
632
|
+
|
|
542
633
|
# Show recommendations
|
|
543
|
-
if analysis[
|
|
634
|
+
if analysis["recommendations"]:
|
|
544
635
|
console.print("[bold yellow]💡 RECOMMENDATIONS[/bold yellow]")
|
|
545
|
-
for rec in analysis[
|
|
636
|
+
for rec in analysis["recommendations"]:
|
|
546
637
|
console.print(f" • {rec}")
|
|
547
638
|
console.print()
|
|
548
|
-
|
|
639
|
+
|
|
549
640
|
click.pause()
|
|
550
641
|
|
|
551
642
|
|
|
@@ -553,78 +644,69 @@ def export_gap_report(engagement_id: int, gaps: list):
|
|
|
553
644
|
"""Export gaps to a text report."""
|
|
554
645
|
from datetime import datetime
|
|
555
646
|
from pathlib import Path
|
|
556
|
-
|
|
647
|
+
|
|
557
648
|
eng_mgr = EngagementManager()
|
|
558
649
|
engagement = eng_mgr.get_by_id(engagement_id)
|
|
559
650
|
|
|
560
651
|
if not engagement:
|
|
561
|
-
click.echo(click.style("Engagement not found", fg=
|
|
652
|
+
click.echo(click.style("Engagement not found", fg="red"))
|
|
562
653
|
click.pause()
|
|
563
654
|
return
|
|
564
|
-
|
|
655
|
+
|
|
565
656
|
# Create reports directory in user home
|
|
566
657
|
reports_dir = Path.home() / ".souleyez" / "data" / "reports"
|
|
567
658
|
reports_dir.mkdir(parents=True, exist_ok=True)
|
|
568
|
-
|
|
659
|
+
|
|
569
660
|
# Generate filename
|
|
570
661
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
571
662
|
filename = f"gap_report_{engagement['name']}_{timestamp}.txt"
|
|
572
663
|
filepath = reports_dir / filename
|
|
573
|
-
|
|
664
|
+
|
|
574
665
|
# Write report
|
|
575
|
-
with open(filepath,
|
|
666
|
+
with open(filepath, "w") as f:
|
|
576
667
|
f.write("=" * 80 + "\n")
|
|
577
668
|
f.write(f"EXPLOITATION GAP REPORT\n")
|
|
578
669
|
f.write(f"Engagement: {engagement['name']}\n")
|
|
579
670
|
f.write(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
|
580
671
|
f.write("=" * 80 + "\n\n")
|
|
581
|
-
|
|
672
|
+
|
|
582
673
|
f.write(f"Total Gaps: {len(gaps)}\n\n")
|
|
583
|
-
|
|
674
|
+
|
|
584
675
|
for idx, gap in enumerate(gaps, 1):
|
|
585
676
|
f.write(f"GAP #{idx}\n")
|
|
586
677
|
f.write("-" * 80 + "\n")
|
|
587
678
|
f.write(f"Host: {gap['host']}\n")
|
|
588
|
-
if gap.get(
|
|
679
|
+
if gap.get("hostname"):
|
|
589
680
|
f.write(f"Hostname: {gap['hostname']}\n")
|
|
590
681
|
f.write(f"Port: {gap['port']}\n")
|
|
591
682
|
f.write(f"Service: {gap['service']}\n")
|
|
592
|
-
if gap.get(
|
|
683
|
+
if gap.get("version"):
|
|
593
684
|
f.write(f"Version: {gap['version']}\n")
|
|
594
685
|
f.write(f"Severity: {gap['severity'].upper()}\n")
|
|
595
686
|
f.write(f"Priority Score: {gap['priority_score']}/100\n")
|
|
596
687
|
f.write(f"Reason: {gap['reason']}\n")
|
|
597
688
|
f.write("\nSuggested Actions:\n")
|
|
598
|
-
for action in gap[
|
|
689
|
+
for action in gap["suggested_actions"]:
|
|
599
690
|
f.write(f" - {action}\n")
|
|
600
|
-
|
|
601
|
-
if gap.get(
|
|
691
|
+
|
|
692
|
+
if gap.get("msf_modules"):
|
|
602
693
|
f.write("\nMetasploit Modules:\n")
|
|
603
|
-
for module in gap[
|
|
694
|
+
for module in gap["msf_modules"]:
|
|
604
695
|
f.write(f" - {module}\n")
|
|
605
|
-
|
|
696
|
+
|
|
606
697
|
f.write("\n")
|
|
607
|
-
|
|
608
|
-
click.echo(click.style(f"✅ Gap report exported to: {filepath}", fg=
|
|
698
|
+
|
|
699
|
+
click.echo(click.style(f"✅ Gap report exported to: {filepath}", fg="green"))
|
|
609
700
|
click.pause()
|
|
610
701
|
|
|
611
702
|
|
|
612
703
|
def _get_access_color(access_level: str) -> str:
|
|
613
704
|
"""Get color for access level."""
|
|
614
|
-
colors = {
|
|
615
|
-
|
|
616
|
-
'admin': 'red',
|
|
617
|
-
'user': 'yellow',
|
|
618
|
-
'none': 'dim'
|
|
619
|
-
}
|
|
620
|
-
return colors.get(access_level, 'white')
|
|
705
|
+
colors = {"root": "red", "admin": "red", "user": "yellow", "none": "dim"}
|
|
706
|
+
return colors.get(access_level, "white")
|
|
621
707
|
|
|
622
708
|
|
|
623
709
|
def _get_status_color(status: str) -> str:
|
|
624
710
|
"""Get color for exploitation status."""
|
|
625
|
-
colors = {
|
|
626
|
-
|
|
627
|
-
'ATTEMPTED': 'yellow',
|
|
628
|
-
'NOT_ATTEMPTED': 'red'
|
|
629
|
-
}
|
|
630
|
-
return colors.get(status, 'white')
|
|
711
|
+
colors = {"EXPLOITED": "green", "ATTEMPTED": "yellow", "NOT_ATTEMPTED": "red"}
|
|
712
|
+
return colors.get(status, "white")
|