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
|
@@ -15,7 +15,7 @@ _CACHE_TIMEOUT = 30
|
|
|
15
15
|
|
|
16
16
|
class AttackSurfaceAnalyzer:
|
|
17
17
|
"""Analyzes and scores attack surface for pentesting engagements."""
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
def __init__(self):
|
|
20
20
|
from souleyez.storage.hosts import HostManager
|
|
21
21
|
from souleyez.storage.findings import FindingsManager
|
|
@@ -26,12 +26,12 @@ class AttackSurfaceAnalyzer:
|
|
|
26
26
|
self.findings_mgr = FindingsManager()
|
|
27
27
|
self.creds_mgr = CredentialsManager()
|
|
28
28
|
self.wazuh_mgr = WazuhVulnsManager()
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
def analyze_engagement(self, engagement_id: int) -> Dict:
|
|
31
31
|
"""
|
|
32
32
|
Analyze complete attack surface for engagement.
|
|
33
33
|
Results cached for 30 seconds to improve dashboard performance.
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
Returns:
|
|
36
36
|
{
|
|
37
37
|
'overview': {...},
|
|
@@ -45,7 +45,7 @@ class AttackSurfaceAnalyzer:
|
|
|
45
45
|
cached_result, cached_time = _ANALYSIS_CACHE[cache_key]
|
|
46
46
|
if time.time() - cached_time < _CACHE_TIMEOUT:
|
|
47
47
|
return cached_result
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
# Cache miss or expired - do the analysis
|
|
50
50
|
all_hosts = self.hosts_mgr.list_hosts(engagement_id)
|
|
51
51
|
|
|
@@ -58,72 +58,100 @@ class AttackSurfaceAnalyzer:
|
|
|
58
58
|
|
|
59
59
|
# Fetch exploit attempts for this engagement
|
|
60
60
|
from souleyez.storage.exploit_attempts import get_attempts_by_engagement
|
|
61
|
+
|
|
61
62
|
exploit_attempts = get_attempts_by_engagement(engagement_id)
|
|
62
|
-
|
|
63
|
+
|
|
63
64
|
# Filter to active hosts only
|
|
64
|
-
hosts = [
|
|
65
|
-
|
|
65
|
+
hosts = [
|
|
66
|
+
h
|
|
67
|
+
for h in all_hosts
|
|
68
|
+
if h.get("status") == "up" or self._has_activity(h, findings, credentials)
|
|
69
|
+
]
|
|
70
|
+
|
|
66
71
|
# Get jobs
|
|
67
72
|
from souleyez.engine.background import list_jobs
|
|
73
|
+
|
|
68
74
|
all_jobs = list_jobs()
|
|
69
|
-
jobs = [j for j in all_jobs if j.get(
|
|
70
|
-
|
|
75
|
+
jobs = [j for j in all_jobs if j.get("engagement_id") == engagement_id]
|
|
76
|
+
|
|
71
77
|
# Calculate attack surface for each host
|
|
72
78
|
host_surfaces = []
|
|
73
79
|
for host in hosts:
|
|
74
|
-
surface = self._analyze_host(
|
|
80
|
+
surface = self._analyze_host(
|
|
81
|
+
host, findings, credentials, jobs, exploit_attempts, wazuh_vulns
|
|
82
|
+
)
|
|
75
83
|
host_surfaces.append(surface)
|
|
76
|
-
|
|
84
|
+
|
|
77
85
|
# Sort by attack surface score (highest first)
|
|
78
|
-
host_surfaces.sort(key=lambda x: x[
|
|
79
|
-
|
|
86
|
+
host_surfaces.sort(key=lambda x: x["score"], reverse=True)
|
|
87
|
+
|
|
80
88
|
# Generate overview
|
|
81
89
|
overview = self._generate_overview(host_surfaces, findings, credentials)
|
|
82
|
-
|
|
90
|
+
|
|
83
91
|
# Generate recommendations
|
|
84
92
|
recommendations = self._generate_recommendations(host_surfaces)
|
|
85
|
-
|
|
93
|
+
|
|
86
94
|
result = {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
95
|
+
"overview": overview,
|
|
96
|
+
"hosts": host_surfaces,
|
|
97
|
+
"recommendations": recommendations,
|
|
90
98
|
}
|
|
91
|
-
|
|
99
|
+
|
|
92
100
|
# Cache the result
|
|
93
101
|
_ANALYSIS_CACHE[cache_key] = (result, time.time())
|
|
94
|
-
|
|
102
|
+
|
|
95
103
|
return result
|
|
96
|
-
|
|
97
|
-
def _analyze_host(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
104
|
+
|
|
105
|
+
def _analyze_host(
|
|
106
|
+
self,
|
|
107
|
+
host: Dict,
|
|
108
|
+
findings: List[Dict],
|
|
109
|
+
credentials: List[Dict],
|
|
110
|
+
jobs: List[Dict],
|
|
111
|
+
exploit_attempts: List[Dict] = None,
|
|
112
|
+
wazuh_vulns: List[Dict] = None,
|
|
113
|
+
) -> Dict:
|
|
101
114
|
"""Analyze attack surface for a single host."""
|
|
102
|
-
host_ip =
|
|
103
|
-
|
|
104
|
-
|
|
115
|
+
host_ip = (
|
|
116
|
+
host.get("ip") or host.get("ip_address") or host.get("address", "Unknown")
|
|
117
|
+
)
|
|
118
|
+
host_id = host.get("id")
|
|
119
|
+
|
|
105
120
|
# Get services for this host from database
|
|
106
121
|
services = []
|
|
107
122
|
if host_id:
|
|
108
123
|
services = self.hosts_mgr.get_host_services(host_id)
|
|
109
124
|
else:
|
|
110
125
|
# Fallback: check if services embedded in host object
|
|
111
|
-
services = host.get(
|
|
112
|
-
|
|
126
|
+
services = host.get("services", [])
|
|
127
|
+
|
|
113
128
|
# Get findings for this host
|
|
114
|
-
host_findings = [
|
|
115
|
-
|
|
116
|
-
|
|
129
|
+
host_findings = [
|
|
130
|
+
f
|
|
131
|
+
for f in findings
|
|
132
|
+
if f.get("ip_address") == host_ip
|
|
133
|
+
or f.get("url", "").startswith(f"http://{host_ip}")
|
|
134
|
+
or f.get("url", "").startswith(f"https://{host_ip}")
|
|
135
|
+
]
|
|
136
|
+
critical_findings = [
|
|
137
|
+
f for f in host_findings if f.get("severity") == "critical"
|
|
138
|
+
]
|
|
139
|
+
high_findings = [f for f in host_findings if f.get("severity") == "high"]
|
|
117
140
|
|
|
118
141
|
# Get Wazuh vulnerabilities for this host (by mapped host_id or agent_ip)
|
|
119
142
|
host_wazuh_vulns = []
|
|
120
143
|
if wazuh_vulns:
|
|
121
144
|
host_wazuh_vulns = [
|
|
122
|
-
v
|
|
123
|
-
|
|
145
|
+
v
|
|
146
|
+
for v in wazuh_vulns
|
|
147
|
+
if v.get("host_id") == host_id
|
|
148
|
+
or v.get("agent_ip") == host_ip
|
|
149
|
+
or v.get("host_ip") == host_ip
|
|
124
150
|
]
|
|
125
|
-
wazuh_critical = [
|
|
126
|
-
|
|
151
|
+
wazuh_critical = [
|
|
152
|
+
v for v in host_wazuh_vulns if v.get("severity") == "Critical"
|
|
153
|
+
]
|
|
154
|
+
wazuh_high = [v for v in host_wazuh_vulns if v.get("severity") == "High"]
|
|
127
155
|
|
|
128
156
|
# Fallback: Create synthetic services from findings if no services exist
|
|
129
157
|
# This handles legacy data where web targets have findings but no service entries
|
|
@@ -131,69 +159,99 @@ class AttackSurfaceAnalyzer:
|
|
|
131
159
|
ports_found = {}
|
|
132
160
|
|
|
133
161
|
for finding in host_findings:
|
|
134
|
-
port = finding.get(
|
|
135
|
-
path = finding.get(
|
|
162
|
+
port = finding.get("port")
|
|
163
|
+
path = finding.get("path") or finding.get("url", "")
|
|
136
164
|
|
|
137
165
|
# Extract port from URL if not directly available
|
|
138
166
|
if not port and path:
|
|
139
|
-
if path.startswith(
|
|
167
|
+
if path.startswith("https://"):
|
|
140
168
|
port = 443
|
|
141
|
-
elif path.startswith(
|
|
169
|
+
elif path.startswith("http://"):
|
|
142
170
|
port = 80
|
|
143
171
|
|
|
144
172
|
if port:
|
|
145
173
|
# Determine service name from port
|
|
146
174
|
if port not in ports_found:
|
|
147
|
-
service_name =
|
|
175
|
+
service_name = (
|
|
176
|
+
"http"
|
|
177
|
+
if port in [80, 8080, 8000]
|
|
178
|
+
else "https" if port == 443 else "unknown"
|
|
179
|
+
)
|
|
148
180
|
ports_found[port] = {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
181
|
+
"port": port,
|
|
182
|
+
"service": service_name,
|
|
183
|
+
"service_name": service_name,
|
|
184
|
+
"protocol": "tcp",
|
|
185
|
+
"state": "open",
|
|
186
|
+
"version": None,
|
|
187
|
+
"synthetic": True, # Flag as synthetic for reference
|
|
188
|
+
"status": "attempted", # Has findings, so mark as attempted
|
|
189
|
+
"credentials": 0,
|
|
190
|
+
"findings": 0,
|
|
191
|
+
"jobs_run": 0,
|
|
160
192
|
}
|
|
161
193
|
|
|
162
194
|
# Convert to list and count findings per synthetic service
|
|
163
195
|
services = list(ports_found.values())
|
|
164
196
|
for service in services:
|
|
165
|
-
service[
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
197
|
+
service["findings"] = len(
|
|
198
|
+
[
|
|
199
|
+
f
|
|
200
|
+
for f in host_findings
|
|
201
|
+
if f.get("port") == service["port"]
|
|
202
|
+
or (
|
|
203
|
+
f.get("path", "").startswith("https://")
|
|
204
|
+
and service["port"] == 443
|
|
205
|
+
)
|
|
206
|
+
or (
|
|
207
|
+
f.get("path", "").startswith("http://")
|
|
208
|
+
and service["port"] == 80
|
|
209
|
+
)
|
|
210
|
+
]
|
|
211
|
+
)
|
|
212
|
+
service["credentials"] = len(
|
|
213
|
+
[c for c in credentials if c.get("port") == service["port"]]
|
|
214
|
+
)
|
|
215
|
+
|
|
170
216
|
# Calculate attack surface score (includes Wazuh vulnerabilities)
|
|
171
217
|
score = self._calculate_score(
|
|
172
|
-
host,
|
|
173
|
-
|
|
218
|
+
host,
|
|
219
|
+
services,
|
|
220
|
+
host_findings,
|
|
221
|
+
critical_findings,
|
|
222
|
+
high_findings,
|
|
223
|
+
host_wazuh_vulns,
|
|
224
|
+
wazuh_critical,
|
|
225
|
+
wazuh_high,
|
|
174
226
|
)
|
|
175
227
|
|
|
176
228
|
# Filter exploit attempts for this host
|
|
177
229
|
host_exploit_attempts = []
|
|
178
230
|
if exploit_attempts:
|
|
179
231
|
host_exploit_attempts = [
|
|
180
|
-
a
|
|
181
|
-
|
|
232
|
+
a
|
|
233
|
+
for a in exploit_attempts
|
|
234
|
+
if a.get("host_id") == host_id or a.get("ip_address") == host_ip
|
|
182
235
|
]
|
|
183
236
|
|
|
184
237
|
# Analyze exploitation status per service
|
|
185
238
|
service_statuses = []
|
|
186
239
|
for service in services:
|
|
187
240
|
status = self._analyze_service_exploitation(
|
|
188
|
-
host_ip,
|
|
241
|
+
host_ip,
|
|
242
|
+
service,
|
|
243
|
+
jobs,
|
|
244
|
+
credentials,
|
|
245
|
+
host_findings,
|
|
246
|
+
host_exploit_attempts,
|
|
189
247
|
)
|
|
190
248
|
service_statuses.append(status)
|
|
191
|
-
|
|
249
|
+
|
|
192
250
|
# Count exploitation progress
|
|
193
|
-
exploited = len([s for s in service_statuses if s[
|
|
194
|
-
attempted = len([s for s in service_statuses if s[
|
|
195
|
-
not_tried = len([s for s in service_statuses if s[
|
|
196
|
-
|
|
251
|
+
exploited = len([s for s in service_statuses if s["status"] == "exploited"])
|
|
252
|
+
attempted = len([s for s in service_statuses if s["status"] == "attempted"])
|
|
253
|
+
not_tried = len([s for s in service_statuses if s["status"] == "not_tried"])
|
|
254
|
+
|
|
197
255
|
# Generate reasoning for why this host is high priority
|
|
198
256
|
reasoning_parts = []
|
|
199
257
|
if critical_findings:
|
|
@@ -209,36 +267,45 @@ class AttackSurfaceAnalyzer:
|
|
|
209
267
|
if not_tried > 0:
|
|
210
268
|
reasoning_parts.append(f"{not_tried} untested service(s)")
|
|
211
269
|
|
|
212
|
-
reasoning =
|
|
213
|
-
|
|
270
|
+
reasoning = (
|
|
271
|
+
", ".join(reasoning_parts)
|
|
272
|
+
if reasoning_parts
|
|
273
|
+
else "Open services discovered"
|
|
274
|
+
)
|
|
275
|
+
|
|
214
276
|
return {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
277
|
+
"host": host_ip,
|
|
278
|
+
"hostname": host.get("hostname"),
|
|
279
|
+
"score": score,
|
|
280
|
+
"open_ports": len(services),
|
|
281
|
+
"service_count": len(services),
|
|
282
|
+
"services": service_statuses, # Full list for display
|
|
283
|
+
"findings": len(host_findings),
|
|
284
|
+
"critical_findings": len(critical_findings),
|
|
285
|
+
"high_findings": len(high_findings),
|
|
286
|
+
"wazuh_vulns": len(host_wazuh_vulns),
|
|
287
|
+
"wazuh_critical": len(wazuh_critical),
|
|
288
|
+
"wazuh_high": len(wazuh_high),
|
|
289
|
+
"reasoning": reasoning,
|
|
290
|
+
"exploitation_progress": {
|
|
291
|
+
"exploited": exploited,
|
|
292
|
+
"attempted": attempted,
|
|
293
|
+
"not_tried": not_tried,
|
|
294
|
+
"total": len(services),
|
|
295
|
+
},
|
|
234
296
|
}
|
|
235
|
-
|
|
236
|
-
def _calculate_score(
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
297
|
+
|
|
298
|
+
def _calculate_score(
|
|
299
|
+
self,
|
|
300
|
+
host: Dict,
|
|
301
|
+
services: List[Dict],
|
|
302
|
+
findings: List[Dict],
|
|
303
|
+
critical_findings: List[Dict],
|
|
304
|
+
high_findings: List[Dict],
|
|
305
|
+
wazuh_vulns: List[Dict] = None,
|
|
306
|
+
wazuh_critical: List[Dict] = None,
|
|
307
|
+
wazuh_high: List[Dict] = None,
|
|
308
|
+
) -> int:
|
|
242
309
|
"""
|
|
243
310
|
Calculate attack surface score (0-100 scale).
|
|
244
311
|
|
|
@@ -256,14 +323,14 @@ class AttackSurfaceAnalyzer:
|
|
|
256
323
|
|
|
257
324
|
# Calculate raw score using weighted factors
|
|
258
325
|
raw_score = (
|
|
259
|
-
len(services) * 2
|
|
260
|
-
len(services) * 3
|
|
261
|
-
len(findings) * 5
|
|
262
|
-
len(high_findings) * 8
|
|
263
|
-
len(critical_findings) * 15
|
|
264
|
-
len(wazuh_vulns) * 4
|
|
265
|
-
len(wazuh_high) * 8
|
|
266
|
-
len(wazuh_critical) * 12
|
|
326
|
+
len(services) * 2 # Each port/service
|
|
327
|
+
+ len(services) * 3 # Service identification
|
|
328
|
+
+ len(findings) * 5 # Total findings
|
|
329
|
+
+ len(high_findings) * 8 # High severity findings
|
|
330
|
+
+ len(critical_findings) * 15 # Critical severity findings
|
|
331
|
+
+ len(wazuh_vulns) * 4 # Wazuh vulnerabilities (passive detection)
|
|
332
|
+
+ len(wazuh_high) * 8 # Wazuh high severity
|
|
333
|
+
+ len(wazuh_critical) * 12 # Wazuh critical severity
|
|
267
334
|
)
|
|
268
335
|
|
|
269
336
|
# Normalize to 0-100 using logarithmic scale
|
|
@@ -283,64 +350,83 @@ class AttackSurfaceAnalyzer:
|
|
|
283
350
|
|
|
284
351
|
normalized_score = min(100, int(20 * math.log10(raw_score + 10)))
|
|
285
352
|
return normalized_score
|
|
286
|
-
|
|
287
|
-
def _analyze_service_exploitation(
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
353
|
+
|
|
354
|
+
def _analyze_service_exploitation(
|
|
355
|
+
self,
|
|
356
|
+
host: str,
|
|
357
|
+
service: Dict,
|
|
358
|
+
jobs: List[Dict],
|
|
359
|
+
credentials: List[Dict],
|
|
360
|
+
findings: List[Dict],
|
|
361
|
+
exploit_attempts: List[Dict] = None,
|
|
362
|
+
) -> Dict:
|
|
291
363
|
"""Determine exploitation status for a service."""
|
|
292
|
-
port = service.get(
|
|
293
|
-
service_id = service.get(
|
|
294
|
-
service_name = service.get(
|
|
295
|
-
version = service.get(
|
|
364
|
+
port = service.get("port")
|
|
365
|
+
service_id = service.get("id")
|
|
366
|
+
service_name = service.get("service_name") or "unknown"
|
|
367
|
+
version = service.get("service_version") or ""
|
|
296
368
|
|
|
297
369
|
# Check for manual exploit attempts on this service
|
|
298
370
|
service_exploit_attempts = []
|
|
299
371
|
if exploit_attempts:
|
|
300
372
|
service_exploit_attempts = [
|
|
301
|
-
a
|
|
302
|
-
|
|
373
|
+
a
|
|
374
|
+
for a in exploit_attempts
|
|
375
|
+
if a.get("service_id") == service_id or a.get("port") == port
|
|
303
376
|
]
|
|
304
377
|
|
|
305
378
|
# Check for credentials on this service
|
|
306
379
|
service_creds = [
|
|
307
|
-
c
|
|
308
|
-
|
|
380
|
+
c
|
|
381
|
+
for c in credentials
|
|
382
|
+
if c.get("ip_address") == host and c.get("port") == port
|
|
309
383
|
]
|
|
310
384
|
|
|
311
385
|
# Check for findings on this service - Multi-tier matching
|
|
312
386
|
# Tier 1: Exact match by port
|
|
313
387
|
service_findings = [
|
|
314
|
-
f for f in findings
|
|
315
|
-
if f.get('ip_address') == host and f.get('port') == port
|
|
388
|
+
f for f in findings if f.get("ip_address") == host and f.get("port") == port
|
|
316
389
|
]
|
|
317
390
|
|
|
318
391
|
# Tier 2: Fallback for web services - match by host only if port-based match failed
|
|
319
392
|
if not service_findings and port in [80, 443, 8080, 8000, 8443]:
|
|
320
393
|
service_findings = [
|
|
321
|
-
f
|
|
322
|
-
|
|
323
|
-
f.get(
|
|
394
|
+
f
|
|
395
|
+
for f in findings
|
|
396
|
+
if f.get("ip_address") == host
|
|
397
|
+
and f.get("severity") in ["critical", "high"]
|
|
324
398
|
]
|
|
325
399
|
|
|
326
400
|
# Check for EXPLOIT jobs targeting this specific service (host + port)
|
|
327
401
|
# Only count actual exploitation tools, not recon tools like nmap
|
|
328
402
|
import re
|
|
329
|
-
|
|
403
|
+
|
|
404
|
+
exploit_tools = {
|
|
405
|
+
"msfconsole",
|
|
406
|
+
"msf",
|
|
407
|
+
"hydra",
|
|
408
|
+
"medusa",
|
|
409
|
+
"sqlmap",
|
|
410
|
+
"crackmapexec",
|
|
411
|
+
}
|
|
330
412
|
service_jobs = []
|
|
331
413
|
for j in jobs:
|
|
332
|
-
if host not in (j.get(
|
|
414
|
+
if host not in (j.get("target") or ""):
|
|
333
415
|
continue
|
|
334
|
-
if j.get(
|
|
416
|
+
if j.get("status") not in ["done", "error", "no_results"]:
|
|
335
417
|
continue
|
|
336
|
-
if j.get(
|
|
418
|
+
if j.get("tool", "").lower() not in exploit_tools:
|
|
337
419
|
continue
|
|
338
420
|
# Extract port from job args
|
|
339
|
-
args = j.get(
|
|
340
|
-
args_str =
|
|
341
|
-
port_match = re.search(
|
|
421
|
+
args = j.get("args", [])
|
|
422
|
+
args_str = " ".join(args) if args else ""
|
|
423
|
+
port_match = re.search(
|
|
424
|
+
r"RPORT\s+(\d+)|:(\d+)|-p\s+(\d+)", args_str, re.IGNORECASE
|
|
425
|
+
)
|
|
342
426
|
if port_match:
|
|
343
|
-
job_port = int(
|
|
427
|
+
job_port = int(
|
|
428
|
+
port_match.group(1) or port_match.group(2) or port_match.group(3)
|
|
429
|
+
)
|
|
344
430
|
if job_port == port:
|
|
345
431
|
service_jobs.append(j)
|
|
346
432
|
|
|
@@ -350,11 +436,11 @@ class AttackSurfaceAnalyzer:
|
|
|
350
436
|
|
|
351
437
|
# Check manual exploit attempts first (from exploit_attempts table)
|
|
352
438
|
for attempt in service_exploit_attempts:
|
|
353
|
-
attempt_status = attempt.get(
|
|
354
|
-
if attempt_status ==
|
|
439
|
+
attempt_status = attempt.get("status")
|
|
440
|
+
if attempt_status == "success":
|
|
355
441
|
exploited = True
|
|
356
442
|
break
|
|
357
|
-
elif attempt_status in (
|
|
443
|
+
elif attempt_status in ("attempted", "failed"):
|
|
358
444
|
manually_attempted = True
|
|
359
445
|
|
|
360
446
|
# Check for credentials (definitive exploitation)
|
|
@@ -364,20 +450,33 @@ class AttackSurfaceAnalyzer:
|
|
|
364
450
|
elif not exploited and service_findings:
|
|
365
451
|
# Keywords that indicate successful exploitation
|
|
366
452
|
exploitation_keywords = [
|
|
367
|
-
|
|
368
|
-
|
|
453
|
+
"exploited",
|
|
454
|
+
"breach",
|
|
455
|
+
"dumped",
|
|
456
|
+
"exfiltrated",
|
|
457
|
+
"enumerated",
|
|
458
|
+
"extracted",
|
|
459
|
+
"compromised",
|
|
369
460
|
]
|
|
370
461
|
|
|
371
462
|
for finding in service_findings:
|
|
372
|
-
title = finding.get(
|
|
373
|
-
severity = finding.get(
|
|
463
|
+
title = finding.get("title", "").lower()
|
|
464
|
+
severity = finding.get("severity", "")
|
|
374
465
|
|
|
375
466
|
# Check for exploitation keywords in title
|
|
376
467
|
if any(keyword in title for keyword in exploitation_keywords):
|
|
377
468
|
exploited = True
|
|
378
469
|
break
|
|
379
470
|
# Critical SQLi, RCE, or data breach findings are exploitation
|
|
380
|
-
elif severity ==
|
|
471
|
+
elif severity == "critical" and any(
|
|
472
|
+
vuln in title
|
|
473
|
+
for vuln in [
|
|
474
|
+
"sql injection",
|
|
475
|
+
"rce",
|
|
476
|
+
"command injection",
|
|
477
|
+
"data breach",
|
|
478
|
+
]
|
|
479
|
+
):
|
|
381
480
|
exploited = True
|
|
382
481
|
break
|
|
383
482
|
|
|
@@ -385,178 +484,218 @@ class AttackSurfaceAnalyzer:
|
|
|
385
484
|
# Note: service_findings alone does NOT mean "attempted" - those are just detected vulns
|
|
386
485
|
# Only explicit exploit attempts or exploit tool jobs count as "attempted"
|
|
387
486
|
if exploited:
|
|
388
|
-
status =
|
|
487
|
+
status = "exploited"
|
|
389
488
|
elif manually_attempted or service_jobs:
|
|
390
|
-
status =
|
|
489
|
+
status = "attempted"
|
|
391
490
|
else:
|
|
392
|
-
status =
|
|
393
|
-
|
|
491
|
+
status = "not_tried"
|
|
492
|
+
|
|
394
493
|
return {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
494
|
+
"port": port,
|
|
495
|
+
"service": service_name,
|
|
496
|
+
"version": version,
|
|
497
|
+
"status": status,
|
|
498
|
+
"credentials": len(service_creds),
|
|
499
|
+
"findings": len(service_findings),
|
|
500
|
+
"jobs_run": len(service_jobs),
|
|
501
|
+
"suggested_actions": self._suggest_actions(service_name, version, status),
|
|
403
502
|
}
|
|
404
|
-
|
|
503
|
+
|
|
405
504
|
def _suggest_actions(self, service: str, version: str, status: str) -> List[str]:
|
|
406
505
|
"""Suggest next actions for a service."""
|
|
407
506
|
actions = []
|
|
408
|
-
|
|
409
|
-
if status ==
|
|
507
|
+
|
|
508
|
+
if status == "not_tried":
|
|
410
509
|
# Suggest initial enumeration
|
|
411
510
|
service_lower = service.lower()
|
|
412
|
-
if
|
|
413
|
-
actions.append(
|
|
414
|
-
actions.append(
|
|
415
|
-
elif
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
actions.append(
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
actions.append(
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
actions.append(
|
|
511
|
+
if "ssh" in service_lower or "telnet" in service_lower:
|
|
512
|
+
actions.append("Enumerate users")
|
|
513
|
+
actions.append("Brute force")
|
|
514
|
+
elif (
|
|
515
|
+
"mysql" in service_lower
|
|
516
|
+
or "postgresql" in service_lower
|
|
517
|
+
or "mssql" in service_lower
|
|
518
|
+
):
|
|
519
|
+
actions.append("Try default creds")
|
|
520
|
+
actions.append("Enumerate databases")
|
|
521
|
+
elif "http" in service_lower:
|
|
522
|
+
actions.append("Scan vulnerabilities")
|
|
523
|
+
actions.append("Directory brute force")
|
|
524
|
+
elif "smb" in service_lower or "netbios" in service_lower:
|
|
525
|
+
actions.append("Enumerate shares")
|
|
526
|
+
actions.append("Enumerate users")
|
|
527
|
+
elif "ftp" in service_lower:
|
|
528
|
+
actions.append("Anonymous login")
|
|
529
|
+
actions.append("Brute force")
|
|
427
530
|
else:
|
|
428
|
-
actions.append(
|
|
429
|
-
|
|
430
|
-
elif status ==
|
|
531
|
+
actions.append("Enumerate service")
|
|
532
|
+
|
|
533
|
+
elif status == "attempted":
|
|
431
534
|
# Suggest next-level actions based on service type
|
|
432
535
|
service_lower = service.lower()
|
|
433
|
-
if
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
actions.append(
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
actions.append(
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
actions.append(
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
actions.append(
|
|
456
|
-
actions.append(
|
|
457
|
-
elif
|
|
458
|
-
actions.append(
|
|
459
|
-
actions.append(
|
|
460
|
-
elif
|
|
461
|
-
actions.append(
|
|
462
|
-
actions.append(
|
|
463
|
-
elif
|
|
464
|
-
actions.append(
|
|
465
|
-
actions.append(
|
|
466
|
-
elif
|
|
467
|
-
actions.append(
|
|
468
|
-
actions.append(
|
|
469
|
-
elif
|
|
470
|
-
actions.append(
|
|
471
|
-
actions.append(
|
|
536
|
+
if (
|
|
537
|
+
"ssh" in service_lower
|
|
538
|
+
or "telnet" in service_lower
|
|
539
|
+
or "login" in service_lower
|
|
540
|
+
):
|
|
541
|
+
actions.append("Try different wordlist")
|
|
542
|
+
actions.append("Check for known exploits")
|
|
543
|
+
elif (
|
|
544
|
+
"mysql" in service_lower
|
|
545
|
+
or "postgresql" in service_lower
|
|
546
|
+
or "mssql" in service_lower
|
|
547
|
+
):
|
|
548
|
+
actions.append("Try SQL injection")
|
|
549
|
+
actions.append("Check for CVEs")
|
|
550
|
+
elif "http" in service_lower or "https" in service_lower:
|
|
551
|
+
actions.append("Run Nuclei/Gobuster")
|
|
552
|
+
actions.append("Check web app vulns")
|
|
553
|
+
elif (
|
|
554
|
+
"smb" in service_lower
|
|
555
|
+
or "netbios" in service_lower
|
|
556
|
+
or "microsoft-ds" in service_lower
|
|
557
|
+
):
|
|
558
|
+
actions.append("Try null session")
|
|
559
|
+
actions.append("Check EternalBlue")
|
|
560
|
+
elif "ftp" in service_lower:
|
|
561
|
+
actions.append("Check for backdoor")
|
|
562
|
+
actions.append("Try credential stuffing")
|
|
563
|
+
elif "rpc" in service_lower or "nfs" in service_lower:
|
|
564
|
+
actions.append("Enumerate exports")
|
|
565
|
+
actions.append("Check mount options")
|
|
566
|
+
elif "vnc" in service_lower:
|
|
567
|
+
actions.append("Try known passwords")
|
|
568
|
+
actions.append("Check for no-auth")
|
|
569
|
+
elif "domain" in service_lower or "dns" in service_lower:
|
|
570
|
+
actions.append("Zone transfer")
|
|
571
|
+
actions.append("Subdomain enum")
|
|
572
|
+
elif "smtp" in service_lower or "mail" in service_lower:
|
|
573
|
+
actions.append("User enumeration")
|
|
574
|
+
actions.append("Check open relay")
|
|
575
|
+
elif "irc" in service_lower:
|
|
576
|
+
actions.append("Check for UnrealIRCd backdoor")
|
|
577
|
+
actions.append("Connect and enum")
|
|
578
|
+
elif "java" in service_lower or "rmi" in service_lower:
|
|
579
|
+
actions.append("Check Java deserialization")
|
|
580
|
+
actions.append("RMI registry enum")
|
|
581
|
+
elif "ajp" in service_lower or "tomcat" in service_lower:
|
|
582
|
+
actions.append("Ghostcat (CVE-2020-1938)")
|
|
583
|
+
actions.append("Manager console access")
|
|
584
|
+
elif (
|
|
585
|
+
"exec" in service_lower
|
|
586
|
+
or "shell" in service_lower
|
|
587
|
+
or "bindshell" in service_lower
|
|
588
|
+
):
|
|
589
|
+
actions.append("Direct connection attempt")
|
|
590
|
+
actions.append("Check authentication")
|
|
472
591
|
else:
|
|
473
|
-
actions.append(
|
|
474
|
-
actions.append(
|
|
475
|
-
|
|
476
|
-
elif status ==
|
|
477
|
-
actions.append(
|
|
478
|
-
actions.append(
|
|
479
|
-
|
|
592
|
+
actions.append("Research service vulns")
|
|
593
|
+
actions.append("Manual enumeration")
|
|
594
|
+
|
|
595
|
+
elif status == "exploited":
|
|
596
|
+
actions.append("Escalate privileges")
|
|
597
|
+
actions.append("Dump credentials")
|
|
598
|
+
|
|
480
599
|
return actions
|
|
481
|
-
|
|
482
|
-
def _has_activity(
|
|
600
|
+
|
|
601
|
+
def _has_activity(
|
|
602
|
+
self, host: Dict, findings: List[Dict], credentials: List[Dict]
|
|
603
|
+
) -> bool:
|
|
483
604
|
"""Check if host has any activity (services, findings, credentials)."""
|
|
484
|
-
host_id = host.get(
|
|
485
|
-
host_ip = host.get(
|
|
486
|
-
|
|
605
|
+
host_id = host.get("id")
|
|
606
|
+
host_ip = host.get("ip") or host.get("ip_address")
|
|
607
|
+
|
|
487
608
|
# Check for services
|
|
488
609
|
services = self.hosts_mgr.get_host_services(host_id) if host_id else []
|
|
489
610
|
if services:
|
|
490
611
|
return True
|
|
491
|
-
|
|
612
|
+
|
|
492
613
|
# Check for findings (passed in, no DB query)
|
|
493
|
-
host_findings = [f for f in findings if f.get(
|
|
614
|
+
host_findings = [f for f in findings if f.get("ip_address") == host_ip]
|
|
494
615
|
if host_findings:
|
|
495
616
|
return True
|
|
496
617
|
|
|
497
618
|
# Check for credentials (passed in, no DB query)
|
|
498
|
-
host_creds = [c for c in credentials if c.get(
|
|
619
|
+
host_creds = [c for c in credentials if c.get("ip_address") == host_ip]
|
|
499
620
|
if host_creds:
|
|
500
621
|
return True
|
|
501
|
-
|
|
622
|
+
|
|
502
623
|
return False
|
|
503
|
-
|
|
504
|
-
def _generate_overview(
|
|
505
|
-
|
|
624
|
+
|
|
625
|
+
def _generate_overview(
|
|
626
|
+
self, host_surfaces: List[Dict], findings: List[Dict], credentials: List[Dict]
|
|
627
|
+
) -> Dict:
|
|
506
628
|
"""Generate overview statistics."""
|
|
507
|
-
total_services = sum(len(h.get(
|
|
629
|
+
total_services = sum(len(h.get("services", [])) for h in host_surfaces)
|
|
508
630
|
exploited_services = sum(
|
|
509
|
-
h.get(
|
|
631
|
+
h.get("exploitation_progress", {}).get("exploited", 0)
|
|
510
632
|
for h in host_surfaces
|
|
511
633
|
)
|
|
512
|
-
critical_findings = [f for f in findings if f.get(
|
|
634
|
+
critical_findings = [f for f in findings if f.get("severity") == "critical"]
|
|
513
635
|
|
|
514
636
|
# Wazuh vulnerability totals
|
|
515
|
-
total_wazuh_vulns = sum(h.get(
|
|
516
|
-
wazuh_critical = sum(h.get(
|
|
637
|
+
total_wazuh_vulns = sum(h.get("wazuh_vulns", 0) for h in host_surfaces)
|
|
638
|
+
wazuh_critical = sum(h.get("wazuh_critical", 0) for h in host_surfaces)
|
|
517
639
|
|
|
518
640
|
return {
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
(
|
|
641
|
+
"total_hosts": len(host_surfaces),
|
|
642
|
+
"total_services": total_services,
|
|
643
|
+
"exploited_services": exploited_services,
|
|
644
|
+
"exploitation_percentage": round(
|
|
645
|
+
(
|
|
646
|
+
(exploited_services / total_services * 100)
|
|
647
|
+
if total_services > 0
|
|
648
|
+
else 0
|
|
649
|
+
),
|
|
650
|
+
1,
|
|
524
651
|
),
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
652
|
+
"credentials_found": len(credentials),
|
|
653
|
+
"critical_findings": len(critical_findings),
|
|
654
|
+
"wazuh_vulns": total_wazuh_vulns,
|
|
655
|
+
"wazuh_critical": wazuh_critical,
|
|
529
656
|
}
|
|
530
|
-
|
|
657
|
+
|
|
531
658
|
def _generate_recommendations(self, host_surfaces: List[Dict]) -> List[Dict]:
|
|
532
659
|
"""Generate prioritized recommendations."""
|
|
533
660
|
recommendations = []
|
|
534
|
-
|
|
661
|
+
|
|
535
662
|
for host_surface in host_surfaces[:3]: # Top 3 hosts
|
|
536
|
-
for service_status in host_surface.get(
|
|
537
|
-
|
|
663
|
+
for service_status in host_surface.get(
|
|
664
|
+
"services", []
|
|
665
|
+
): # Changed from 'service_statuses'
|
|
666
|
+
if (
|
|
667
|
+
service_status["status"] == "not_tried"
|
|
668
|
+
and service_status["suggested_actions"]
|
|
669
|
+
):
|
|
538
670
|
# High priority: Untried services on top hosts
|
|
539
|
-
recommendations.append(
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
671
|
+
recommendations.append(
|
|
672
|
+
{
|
|
673
|
+
"priority": "high",
|
|
674
|
+
"host": host_surface["host"],
|
|
675
|
+
"port": service_status["port"],
|
|
676
|
+
"service": service_status["service"],
|
|
677
|
+
"action": service_status["suggested_actions"][0],
|
|
678
|
+
"reason": f"Untried service on high-value target",
|
|
679
|
+
}
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
elif (
|
|
683
|
+
service_status["status"] == "attempted"
|
|
684
|
+
and len(recommendations) < 10
|
|
685
|
+
):
|
|
549
686
|
# Medium priority: Retry failed attempts
|
|
550
|
-
recommendations.append(
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
687
|
+
recommendations.append(
|
|
688
|
+
{
|
|
689
|
+
"priority": "medium",
|
|
690
|
+
"host": host_surface["host"],
|
|
691
|
+
"port": service_status["port"],
|
|
692
|
+
"service": service_status["service"],
|
|
693
|
+
"action": "Retry exploitation",
|
|
694
|
+
"reason": f"Previous attempt incomplete",
|
|
695
|
+
}
|
|
696
|
+
)
|
|
697
|
+
|
|
559
698
|
# Sort by priority and limit to top 5
|
|
560
|
-
priority_order = {
|
|
561
|
-
recommendations.sort(key=lambda x: priority_order[x[
|
|
699
|
+
priority_order = {"high": 0, "medium": 1, "low": 2}
|
|
700
|
+
recommendations.sort(key=lambda x: priority_order[x["priority"]])
|
|
562
701
|
return recommendations[:5]
|