souleyez 2.43.29__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 +22827 -10678
- 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-2.43.34.dist-info}/METADATA +1 -1
- souleyez-2.43.34.dist-info/RECORD +443 -0
- {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/WHEEL +1 -1
- souleyez-2.43.29.dist-info/RECORD +0 -379
- {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/entry_points.txt +0 -0
- {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/licenses/LICENSE +0 -0
- {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/top_level.txt +0 -0
|
@@ -17,18 +17,19 @@ from souleyez.intelligence.target_parser import TargetParser
|
|
|
17
17
|
|
|
18
18
|
class CorrelationAnalyzer:
|
|
19
19
|
"""Analyze relationships between services, findings, jobs, and credentials."""
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
def __init__(self):
|
|
22
22
|
self.hosts_mgr = HostManager()
|
|
23
23
|
self.findings_mgr = FindingsManager()
|
|
24
24
|
self.creds_mgr = CredentialsManager()
|
|
25
25
|
self.target_parser = TargetParser()
|
|
26
|
-
|
|
27
|
-
def analyze_service(
|
|
28
|
-
|
|
26
|
+
|
|
27
|
+
def analyze_service(
|
|
28
|
+
self, engagement_id: int, host_id: int, port: int, protocol: str = "tcp"
|
|
29
|
+
) -> Dict:
|
|
29
30
|
"""
|
|
30
31
|
Analyze exploitation status for a single service.
|
|
31
|
-
|
|
32
|
+
|
|
32
33
|
Returns:
|
|
33
34
|
{
|
|
34
35
|
'service': {
|
|
@@ -54,115 +55,107 @@ class CorrelationAnalyzer:
|
|
|
54
55
|
host = self.hosts_mgr.get_host(host_id)
|
|
55
56
|
if not host:
|
|
56
57
|
return {}
|
|
57
|
-
|
|
58
|
+
|
|
58
59
|
# Get service info
|
|
59
60
|
services = self.hosts_mgr.get_host_services(host_id)
|
|
60
61
|
service_info = None
|
|
61
62
|
for svc in services:
|
|
62
|
-
if svc[
|
|
63
|
+
if svc["port"] == port and svc["protocol"] == protocol:
|
|
63
64
|
service_info = svc
|
|
64
65
|
break
|
|
65
|
-
|
|
66
|
+
|
|
66
67
|
if not service_info:
|
|
67
68
|
# Service not in database - create minimal info
|
|
68
69
|
service_info = {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
"host_id": host_id,
|
|
71
|
+
"port": port,
|
|
72
|
+
"protocol": protocol,
|
|
73
|
+
"service_name": self.target_parser.infer_service_from_port(port)
|
|
74
|
+
or "unknown",
|
|
75
|
+
"version": None,
|
|
74
76
|
}
|
|
75
|
-
|
|
77
|
+
|
|
76
78
|
result = {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
"service": {
|
|
80
|
+
"host_id": host_id,
|
|
81
|
+
"host_ip": host["ip_address"],
|
|
82
|
+
"hostname": host.get("hostname"),
|
|
83
|
+
"port": port,
|
|
84
|
+
"protocol": protocol,
|
|
85
|
+
"service_name": service_info.get("service_name", "unknown"),
|
|
86
|
+
"version": service_info.get("version"),
|
|
87
|
+
"state": service_info.get("state", "open"),
|
|
86
88
|
}
|
|
87
89
|
}
|
|
88
|
-
|
|
90
|
+
|
|
89
91
|
# Find related findings
|
|
90
|
-
findings = self._find_findings_for_service(
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
findings = self._find_findings_for_service(
|
|
93
|
+
engagement_id, host["ip_address"], port
|
|
94
|
+
)
|
|
95
|
+
result["findings"] = findings
|
|
96
|
+
|
|
93
97
|
# Find related jobs
|
|
94
98
|
jobs = self._link_jobs_to_service(
|
|
95
|
-
engagement_id,
|
|
96
|
-
host['ip_address'],
|
|
97
|
-
port,
|
|
98
|
-
service_info.get('service_name')
|
|
99
|
+
engagement_id, host["ip_address"], port, service_info.get("service_name")
|
|
99
100
|
)
|
|
100
|
-
result[
|
|
101
|
-
|
|
101
|
+
result["jobs"] = jobs
|
|
102
|
+
|
|
102
103
|
# Find related credentials
|
|
103
104
|
credentials = self._find_credentials_for_service(
|
|
104
|
-
engagement_id,
|
|
105
|
-
host['ip_address'],
|
|
106
|
-
port,
|
|
107
|
-
service_info.get('service_name')
|
|
105
|
+
engagement_id, host["ip_address"], port, service_info.get("service_name")
|
|
108
106
|
)
|
|
109
|
-
result[
|
|
110
|
-
|
|
107
|
+
result["credentials"] = credentials
|
|
108
|
+
|
|
111
109
|
# Collect evidence paths
|
|
112
110
|
evidence = []
|
|
113
111
|
for job in jobs:
|
|
114
112
|
# Log file
|
|
115
113
|
log_path = Path.home() / ".souleyez" / "data" / "jobs" / f"{job['id']}.log"
|
|
116
114
|
if log_path.exists():
|
|
117
|
-
evidence.append(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
'job_id': job['id']
|
|
121
|
-
})
|
|
115
|
+
evidence.append(
|
|
116
|
+
{"type": "log", "path": str(log_path), "job_id": job["id"]}
|
|
117
|
+
)
|
|
122
118
|
|
|
123
119
|
# Output file (if exists)
|
|
124
|
-
output_path =
|
|
120
|
+
output_path = (
|
|
121
|
+
Path.home() / ".souleyez" / "data" / "jobs" / f"{job['id']}_output.txt"
|
|
122
|
+
)
|
|
125
123
|
if output_path.exists():
|
|
126
|
-
evidence.append(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
result['evidence'] = evidence
|
|
133
|
-
|
|
124
|
+
evidence.append(
|
|
125
|
+
{"type": "output", "path": str(output_path), "job_id": job["id"]}
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
result["evidence"] = evidence
|
|
129
|
+
|
|
134
130
|
# Determine exploitation status
|
|
135
|
-
result[
|
|
136
|
-
jobs, credentials, host.get(
|
|
131
|
+
result["exploitation_status"] = self._determine_exploitation_status(
|
|
132
|
+
jobs, credentials, host.get("access_level", "none")
|
|
137
133
|
)
|
|
138
|
-
|
|
134
|
+
|
|
139
135
|
# Access level from host
|
|
140
|
-
result[
|
|
141
|
-
|
|
136
|
+
result["access_level"] = host.get("access_level", "none")
|
|
137
|
+
|
|
142
138
|
# Calculate last attempt time
|
|
143
139
|
if jobs:
|
|
144
|
-
last_job = max(jobs, key=lambda j: j.get(
|
|
145
|
-
result[
|
|
140
|
+
last_job = max(jobs, key=lambda j: j.get("created_at", ""))
|
|
141
|
+
result["last_attempt"] = last_job.get("created_at")
|
|
146
142
|
else:
|
|
147
|
-
result[
|
|
148
|
-
|
|
143
|
+
result["last_attempt"] = None
|
|
144
|
+
|
|
149
145
|
# Calculate success rate
|
|
150
|
-
result[
|
|
151
|
-
|
|
146
|
+
result["success_rate"] = self._calculate_success_rate(jobs, credentials)
|
|
147
|
+
|
|
152
148
|
# Generate recommendations
|
|
153
|
-
result[
|
|
154
|
-
result[
|
|
155
|
-
result['exploitation_status'],
|
|
156
|
-
credentials,
|
|
157
|
-
findings
|
|
149
|
+
result["recommendations"] = self._generate_recommendations(
|
|
150
|
+
result["service"], result["exploitation_status"], credentials, findings
|
|
158
151
|
)
|
|
159
|
-
|
|
152
|
+
|
|
160
153
|
return result
|
|
161
|
-
|
|
154
|
+
|
|
162
155
|
def analyze_host(self, engagement_id: int, host_id: int) -> Dict:
|
|
163
156
|
"""
|
|
164
157
|
Analyze all services for a host.
|
|
165
|
-
|
|
158
|
+
|
|
166
159
|
Returns:
|
|
167
160
|
{
|
|
168
161
|
'host': {...},
|
|
@@ -180,41 +173,42 @@ class CorrelationAnalyzer:
|
|
|
180
173
|
host = self.hosts_mgr.get_host(host_id)
|
|
181
174
|
if not host:
|
|
182
175
|
return {}
|
|
183
|
-
|
|
176
|
+
|
|
184
177
|
# Get all services
|
|
185
178
|
services = self.hosts_mgr.get_host_services(host_id)
|
|
186
|
-
|
|
179
|
+
|
|
187
180
|
# Analyze each service
|
|
188
181
|
service_analyses = []
|
|
189
182
|
for svc in services:
|
|
190
183
|
analysis = self.analyze_service(
|
|
191
|
-
engagement_id,
|
|
192
|
-
host_id,
|
|
193
|
-
svc['port'],
|
|
194
|
-
svc.get('protocol', 'tcp')
|
|
184
|
+
engagement_id, host_id, svc["port"], svc.get("protocol", "tcp")
|
|
195
185
|
)
|
|
196
186
|
service_analyses.append(analysis)
|
|
197
|
-
|
|
187
|
+
|
|
198
188
|
# Calculate summary stats
|
|
199
189
|
summary = {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
190
|
+
"total_services": len(service_analyses),
|
|
191
|
+
"exploited": sum(
|
|
192
|
+
1 for s in service_analyses if s["exploitation_status"] == "EXPLOITED"
|
|
193
|
+
),
|
|
194
|
+
"attempted": sum(
|
|
195
|
+
1 for s in service_analyses if s["exploitation_status"] == "ATTEMPTED"
|
|
196
|
+
),
|
|
197
|
+
"not_attempted": sum(
|
|
198
|
+
1
|
|
199
|
+
for s in service_analyses
|
|
200
|
+
if s["exploitation_status"] == "NOT_ATTEMPTED"
|
|
201
|
+
),
|
|
202
|
+
"credentials_found": sum(len(s["credentials"]) for s in service_analyses),
|
|
203
|
+
"access_level": host.get("access_level", "none"),
|
|
212
204
|
}
|
|
213
|
-
|
|
205
|
+
|
|
206
|
+
return {"host": host, "services": service_analyses, "summary": summary}
|
|
207
|
+
|
|
214
208
|
def analyze_engagement(self, engagement_id: int) -> Dict:
|
|
215
209
|
"""
|
|
216
210
|
Analyze complete engagement.
|
|
217
|
-
|
|
211
|
+
|
|
218
212
|
Returns:
|
|
219
213
|
{
|
|
220
214
|
'hosts': [<analyze_host results>],
|
|
@@ -232,368 +226,388 @@ class CorrelationAnalyzer:
|
|
|
232
226
|
"""
|
|
233
227
|
# Get all hosts for engagement
|
|
234
228
|
hosts = self.hosts_mgr.list_hosts(engagement_id)
|
|
235
|
-
|
|
229
|
+
|
|
236
230
|
# Analyze each host
|
|
237
231
|
host_analyses = []
|
|
238
232
|
for host in hosts:
|
|
239
|
-
analysis = self.analyze_host(engagement_id, host[
|
|
233
|
+
analysis = self.analyze_host(engagement_id, host["id"])
|
|
240
234
|
if analysis:
|
|
241
235
|
host_analyses.append(analysis)
|
|
242
|
-
|
|
236
|
+
|
|
243
237
|
# Calculate engagement-wide summary
|
|
244
238
|
summary = {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
239
|
+
"total_hosts": len(host_analyses),
|
|
240
|
+
"total_services": sum(
|
|
241
|
+
h["summary"]["total_services"] for h in host_analyses
|
|
242
|
+
),
|
|
243
|
+
"exploited_services": sum(h["summary"]["exploited"] for h in host_analyses),
|
|
244
|
+
"attempted_services": sum(h["summary"]["attempted"] for h in host_analyses),
|
|
245
|
+
"not_attempted_services": sum(
|
|
246
|
+
h["summary"]["not_attempted"] for h in host_analyses
|
|
247
|
+
),
|
|
248
|
+
"total_credentials": sum(
|
|
249
|
+
h["summary"]["credentials_found"] for h in host_analyses
|
|
250
|
+
),
|
|
251
|
+
"compromised_hosts": sum(
|
|
252
|
+
1 for h in host_analyses if h["summary"]["access_level"] != "none"
|
|
253
|
+
),
|
|
252
254
|
}
|
|
253
|
-
|
|
255
|
+
|
|
254
256
|
# Identify gaps
|
|
255
257
|
gaps = self._identify_gaps(host_analyses)
|
|
256
|
-
|
|
257
|
-
return {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
def _link_jobs_to_service(self, engagement_id: int, host_ip: str,
|
|
264
|
-
port: int, service_name: str = None) -> List[Dict]:
|
|
258
|
+
|
|
259
|
+
return {"hosts": host_analyses, "summary": summary, "gaps": gaps}
|
|
260
|
+
|
|
261
|
+
def _link_jobs_to_service(
|
|
262
|
+
self, engagement_id: int, host_ip: str, port: int, service_name: str = None
|
|
263
|
+
) -> List[Dict]:
|
|
265
264
|
"""
|
|
266
265
|
Find all jobs that targeted a specific service.
|
|
267
|
-
|
|
266
|
+
|
|
268
267
|
Logic:
|
|
269
268
|
1. Get all jobs for engagement
|
|
270
269
|
2. For each job, parse target
|
|
271
270
|
3. Match: host IP + port OR host IP + service name
|
|
272
271
|
4. Return matching jobs with enriched info
|
|
273
272
|
"""
|
|
274
|
-
all_jobs = [j for j in list_jobs() if j.get(
|
|
273
|
+
all_jobs = [j for j in list_jobs() if j.get("engagement_id") == engagement_id]
|
|
275
274
|
matching_jobs = []
|
|
276
|
-
|
|
275
|
+
|
|
277
276
|
for job in all_jobs:
|
|
278
277
|
try:
|
|
279
278
|
# Parse job args (handle both string and already-parsed list)
|
|
280
|
-
args = job.get(
|
|
279
|
+
args = job.get("args")
|
|
281
280
|
if args:
|
|
282
281
|
if isinstance(args, str):
|
|
283
282
|
args = json.loads(args)
|
|
284
283
|
else:
|
|
285
284
|
args = None
|
|
286
|
-
|
|
285
|
+
|
|
287
286
|
# Parse job target
|
|
288
287
|
target_info = self.target_parser.parse_target(
|
|
289
|
-
job[
|
|
290
|
-
job['target'],
|
|
291
|
-
args
|
|
288
|
+
job["tool"], job["target"], args
|
|
292
289
|
)
|
|
293
|
-
|
|
290
|
+
|
|
294
291
|
# Check if this job targets our service
|
|
295
|
-
if target_info.get(
|
|
292
|
+
if target_info.get("host") != host_ip:
|
|
296
293
|
continue
|
|
297
|
-
|
|
294
|
+
|
|
298
295
|
# Match by port
|
|
299
|
-
if target_info.get(
|
|
296
|
+
if target_info.get("port") == port:
|
|
300
297
|
matching_jobs.append(self._enrich_job_info(job))
|
|
301
298
|
continue
|
|
302
|
-
|
|
299
|
+
|
|
303
300
|
# Match by ports list (e.g., Nmap scan)
|
|
304
|
-
if port in target_info.get(
|
|
301
|
+
if port in target_info.get("ports", []):
|
|
305
302
|
matching_jobs.append(self._enrich_job_info(job))
|
|
306
303
|
continue
|
|
307
|
-
|
|
304
|
+
|
|
308
305
|
# Match by service name
|
|
309
|
-
if service_name and target_info.get(
|
|
306
|
+
if service_name and target_info.get("service") == service_name:
|
|
310
307
|
matching_jobs.append(self._enrich_job_info(job))
|
|
311
308
|
continue
|
|
312
|
-
|
|
309
|
+
|
|
313
310
|
except Exception:
|
|
314
311
|
# Skip jobs we can't parse
|
|
315
312
|
continue
|
|
316
|
-
|
|
313
|
+
|
|
317
314
|
# Sort by creation time
|
|
318
|
-
matching_jobs.sort(key=lambda j: j.get(
|
|
319
|
-
|
|
315
|
+
matching_jobs.sort(key=lambda j: j.get("created_at", ""))
|
|
316
|
+
|
|
320
317
|
return matching_jobs
|
|
321
|
-
|
|
318
|
+
|
|
322
319
|
def _enrich_job_info(self, job: Dict) -> Dict:
|
|
323
320
|
"""Add enriched information to job dict."""
|
|
324
321
|
enriched = job.copy()
|
|
325
|
-
|
|
322
|
+
|
|
326
323
|
# Determine if job was successful
|
|
327
|
-
enriched[
|
|
328
|
-
|
|
324
|
+
enriched["success"] = self._is_job_successful(job)
|
|
325
|
+
|
|
329
326
|
# Parse args for display
|
|
330
|
-
if job.get(
|
|
327
|
+
if job.get("args"):
|
|
331
328
|
try:
|
|
332
|
-
enriched[
|
|
329
|
+
enriched["parsed_args"] = json.loads(job["args"])
|
|
333
330
|
except:
|
|
334
|
-
enriched[
|
|
335
|
-
|
|
331
|
+
enriched["parsed_args"] = job["args"]
|
|
332
|
+
|
|
336
333
|
return enriched
|
|
337
|
-
|
|
334
|
+
|
|
338
335
|
def _is_job_successful(self, job: Dict) -> bool:
|
|
339
336
|
"""
|
|
340
337
|
Determine if a job was successful.
|
|
341
|
-
|
|
338
|
+
|
|
342
339
|
Heuristics:
|
|
343
340
|
- Status = 'done' (baseline)
|
|
344
341
|
- Has parse_result with valuable data
|
|
345
342
|
- Credentials found for this job
|
|
346
343
|
"""
|
|
347
|
-
if job[
|
|
344
|
+
if job["status"] != "done":
|
|
348
345
|
return False
|
|
349
|
-
|
|
346
|
+
|
|
350
347
|
# Check parse_result
|
|
351
|
-
if job.get(
|
|
348
|
+
if job.get("parse_result"):
|
|
352
349
|
try:
|
|
353
|
-
result = json.loads(job[
|
|
354
|
-
|
|
350
|
+
result = json.loads(job["parse_result"])
|
|
351
|
+
|
|
355
352
|
# Check for credentials
|
|
356
|
-
if result.get(
|
|
353
|
+
if result.get("credentials"):
|
|
357
354
|
return True
|
|
358
|
-
|
|
355
|
+
|
|
359
356
|
# Check for vulnerabilities
|
|
360
|
-
if result.get(
|
|
357
|
+
if result.get("vulnerabilities"):
|
|
361
358
|
return True
|
|
362
|
-
|
|
359
|
+
|
|
363
360
|
# Check for exploits
|
|
364
|
-
if result.get(
|
|
361
|
+
if result.get("exploits"):
|
|
365
362
|
return True
|
|
366
|
-
|
|
363
|
+
|
|
367
364
|
except:
|
|
368
365
|
pass
|
|
369
|
-
|
|
366
|
+
|
|
370
367
|
# Check if credentials exist for this job
|
|
371
|
-
creds = self.creds_mgr.list_credentials_for_engagement(job[
|
|
368
|
+
creds = self.creds_mgr.list_credentials_for_engagement(job["engagement_id"])
|
|
372
369
|
for cred in creds:
|
|
373
|
-
if cred.get(
|
|
370
|
+
if cred.get("source_job_id") == job["id"]:
|
|
374
371
|
return True
|
|
375
|
-
|
|
372
|
+
|
|
376
373
|
return False
|
|
377
|
-
|
|
378
|
-
def _find_findings_for_service(
|
|
374
|
+
|
|
375
|
+
def _find_findings_for_service(
|
|
376
|
+
self, engagement_id: int, host_ip: str, port: int
|
|
377
|
+
) -> List[Dict]:
|
|
379
378
|
"""Find all findings related to a service."""
|
|
380
379
|
all_findings = self.findings_mgr.list_findings(engagement_id)
|
|
381
|
-
|
|
380
|
+
|
|
382
381
|
matching = []
|
|
383
382
|
for finding in all_findings:
|
|
384
383
|
# Match by ip_address from JOIN with hosts table
|
|
385
|
-
if finding.get(
|
|
384
|
+
if finding.get("ip_address") != host_ip:
|
|
386
385
|
continue
|
|
387
|
-
|
|
386
|
+
|
|
388
387
|
# Match by port field or check if port mentioned in affected_service
|
|
389
|
-
if finding.get(
|
|
388
|
+
if finding.get("port") == port:
|
|
390
389
|
matching.append(finding)
|
|
391
390
|
continue
|
|
392
|
-
|
|
391
|
+
|
|
393
392
|
# Also check affected_service text field for legacy findings
|
|
394
|
-
affected_svc = finding.get(
|
|
395
|
-
if affected_svc and (
|
|
393
|
+
affected_svc = finding.get("affected_service", "")
|
|
394
|
+
if affected_svc and (
|
|
395
|
+
str(port) in affected_svc or f":{port}" in affected_svc
|
|
396
|
+
):
|
|
396
397
|
matching.append(finding)
|
|
397
|
-
|
|
398
|
+
|
|
398
399
|
return matching
|
|
399
|
-
|
|
400
|
-
def _find_credentials_for_service(
|
|
401
|
-
|
|
400
|
+
|
|
401
|
+
def _find_credentials_for_service(
|
|
402
|
+
self, engagement_id: int, host_ip: str, port: int, service_name: str = None
|
|
403
|
+
) -> List[Dict]:
|
|
402
404
|
"""Find credentials related to a service."""
|
|
403
405
|
all_creds = self.creds_mgr.list_credentials_for_engagement(engagement_id)
|
|
404
|
-
|
|
406
|
+
|
|
405
407
|
matching = []
|
|
406
408
|
for cred in all_creds:
|
|
407
409
|
# Match by host (use ip_address from JOIN, not 'host')
|
|
408
|
-
if cred.get(
|
|
410
|
+
if cred.get("ip_address") != host_ip:
|
|
409
411
|
continue
|
|
410
|
-
|
|
412
|
+
|
|
411
413
|
# Match by port (if port is set on credential)
|
|
412
|
-
if cred.get(
|
|
414
|
+
if cred.get("port") == port:
|
|
413
415
|
matching.append(cred)
|
|
414
416
|
continue
|
|
415
|
-
|
|
417
|
+
|
|
416
418
|
# Match by service name
|
|
417
|
-
if service_name and cred.get(
|
|
419
|
+
if service_name and cred.get("service") == service_name:
|
|
418
420
|
matching.append(cred)
|
|
419
421
|
continue
|
|
420
|
-
|
|
422
|
+
|
|
421
423
|
# For web services, match credentials without specific port set
|
|
422
424
|
# (e.g., SQLMap credentials for 'web' service)
|
|
423
|
-
cred_service = cred.get(
|
|
424
|
-
if cred_service ==
|
|
425
|
+
cred_service = cred.get("service", "").lower()
|
|
426
|
+
if cred_service == "web":
|
|
425
427
|
# Match if service_name is a web service OR if port is 80/443/8080/8443
|
|
426
|
-
is_web_service =
|
|
428
|
+
is_web_service = service_name and service_name.lower() in [
|
|
429
|
+
"http",
|
|
430
|
+
"https",
|
|
431
|
+
"web",
|
|
432
|
+
]
|
|
427
433
|
is_web_port = port in [80, 443, 8080, 8443, 8000, 8888]
|
|
428
|
-
|
|
429
|
-
if (is_web_service or is_web_port) and cred.get(
|
|
434
|
+
|
|
435
|
+
if (is_web_service or is_web_port) and cred.get("port") is None:
|
|
430
436
|
matching.append(cred)
|
|
431
437
|
continue
|
|
432
|
-
|
|
438
|
+
|
|
433
439
|
return matching
|
|
434
|
-
|
|
435
|
-
def _determine_exploitation_status(
|
|
436
|
-
|
|
440
|
+
|
|
441
|
+
def _determine_exploitation_status(
|
|
442
|
+
self, jobs: List, credentials: List, access_level: str
|
|
443
|
+
) -> str:
|
|
437
444
|
"""
|
|
438
445
|
Determine exploitation status based on evidence.
|
|
439
|
-
|
|
446
|
+
|
|
440
447
|
Logic:
|
|
441
448
|
- If no jobs: 'NOT_ATTEMPTED'
|
|
442
449
|
- If jobs but no creds and access_level='none': 'ATTEMPTED'
|
|
443
450
|
- If credentials found OR access_level != 'none': 'EXPLOITED'
|
|
444
451
|
"""
|
|
445
452
|
if not jobs:
|
|
446
|
-
return
|
|
447
|
-
|
|
453
|
+
return "NOT_ATTEMPTED"
|
|
454
|
+
|
|
448
455
|
# Check for successful exploitation
|
|
449
|
-
if credentials or access_level !=
|
|
450
|
-
return
|
|
451
|
-
|
|
456
|
+
if credentials or access_level != "none":
|
|
457
|
+
return "EXPLOITED"
|
|
458
|
+
|
|
452
459
|
# Jobs exist but no success
|
|
453
|
-
return
|
|
454
|
-
|
|
460
|
+
return "ATTEMPTED"
|
|
461
|
+
|
|
455
462
|
def _calculate_success_rate(self, jobs: List, credentials: List) -> float:
|
|
456
463
|
"""Calculate success rate of exploitation attempts."""
|
|
457
464
|
if not jobs:
|
|
458
465
|
return 0.0
|
|
459
|
-
|
|
460
|
-
successful_jobs = sum(1 for j in jobs if j.get(
|
|
461
|
-
|
|
466
|
+
|
|
467
|
+
successful_jobs = sum(1 for j in jobs if j.get("success", False))
|
|
468
|
+
|
|
462
469
|
return successful_jobs / len(jobs)
|
|
463
|
-
|
|
464
|
-
def _generate_recommendations(
|
|
465
|
-
|
|
470
|
+
|
|
471
|
+
def _generate_recommendations(
|
|
472
|
+
self, service: Dict, status: str, credentials: List, findings: List
|
|
473
|
+
) -> List[str]:
|
|
466
474
|
"""Generate actionable recommendations."""
|
|
467
475
|
recommendations = []
|
|
468
|
-
|
|
469
|
-
service_name = (service.get(
|
|
470
|
-
port = service.get(
|
|
471
|
-
|
|
472
|
-
if status ==
|
|
476
|
+
|
|
477
|
+
service_name = (service.get("service_name") or "").lower()
|
|
478
|
+
port = service.get("port")
|
|
479
|
+
|
|
480
|
+
if status == "NOT_ATTEMPTED":
|
|
473
481
|
# Suggest initial attack vectors
|
|
474
|
-
if service_name ==
|
|
475
|
-
recommendations.append(
|
|
476
|
-
recommendations.append(
|
|
477
|
-
|
|
478
|
-
elif service_name ==
|
|
479
|
-
recommendations.append(
|
|
480
|
-
recommendations.append(
|
|
481
|
-
|
|
482
|
-
elif service_name in [
|
|
483
|
-
recommendations.append(
|
|
484
|
-
recommendations.append(
|
|
485
|
-
|
|
486
|
-
elif service_name ==
|
|
487
|
-
recommendations.append(
|
|
488
|
-
recommendations.append(
|
|
489
|
-
|
|
490
|
-
elif service_name ==
|
|
491
|
-
recommendations.append(
|
|
492
|
-
recommendations.append(
|
|
493
|
-
|
|
482
|
+
if service_name == "ssh" or port == 22:
|
|
483
|
+
recommendations.append("Try ssh_login with common credentials")
|
|
484
|
+
recommendations.append("Check for weak SSH configurations")
|
|
485
|
+
|
|
486
|
+
elif service_name == "ftp" or port == 21:
|
|
487
|
+
recommendations.append("Try anonymous FTP login")
|
|
488
|
+
recommendations.append("Check for vsftpd backdoor (version 2.3.4)")
|
|
489
|
+
|
|
490
|
+
elif service_name in ["mysql", "mariadb"] or port == 3306:
|
|
491
|
+
recommendations.append("Try mysql_login brute force")
|
|
492
|
+
recommendations.append("Check for CVE-2009-2446 (yaSSL overflow)")
|
|
493
|
+
|
|
494
|
+
elif service_name == "smb" or port in [139, 445]:
|
|
495
|
+
recommendations.append("Try SMB null session enumeration")
|
|
496
|
+
recommendations.append("Check for EternalBlue vulnerability")
|
|
497
|
+
|
|
498
|
+
elif service_name == "http" or port in [80, 443, 8080, 8443]:
|
|
499
|
+
recommendations.append("Run Nuclei web vulnerability scan")
|
|
500
|
+
recommendations.append("Try directory brute-forcing with Gobuster")
|
|
501
|
+
|
|
494
502
|
else:
|
|
495
|
-
recommendations.append(f
|
|
496
|
-
recommendations.append(f
|
|
497
|
-
|
|
498
|
-
elif status ==
|
|
503
|
+
recommendations.append(f"Research common exploits for {service_name}")
|
|
504
|
+
recommendations.append(f"Try brute-forcing port {port}")
|
|
505
|
+
|
|
506
|
+
elif status == "ATTEMPTED":
|
|
499
507
|
# Suggest retrying with different tactics
|
|
500
|
-
recommendations.append(
|
|
501
|
-
recommendations.append(
|
|
502
|
-
|
|
503
|
-
if service_name in [
|
|
504
|
-
recommendations.append(
|
|
505
|
-
|
|
506
|
-
elif status ==
|
|
508
|
+
recommendations.append("Retry with expanded wordlist")
|
|
509
|
+
recommendations.append("Try different exploitation modules")
|
|
510
|
+
|
|
511
|
+
if service_name in ["ssh", "ftp", "mysql", "smb"]:
|
|
512
|
+
recommendations.append("Attempt password spraying")
|
|
513
|
+
|
|
514
|
+
elif status == "EXPLOITED":
|
|
507
515
|
# Suggest post-exploitation
|
|
508
516
|
if credentials:
|
|
509
|
-
recommendations.append(
|
|
510
|
-
recommendations.append(
|
|
511
|
-
|
|
512
|
-
recommendations.append(
|
|
513
|
-
|
|
517
|
+
recommendations.append("Attempt privilege escalation")
|
|
518
|
+
recommendations.append("Enumerate for additional access")
|
|
519
|
+
|
|
520
|
+
recommendations.append("Collect evidence and document access")
|
|
521
|
+
|
|
514
522
|
return recommendations
|
|
515
|
-
|
|
523
|
+
|
|
516
524
|
def _identify_gaps(self, host_analyses: List[Dict]) -> List[Dict]:
|
|
517
525
|
"""
|
|
518
526
|
Identify services that haven't been exploited.
|
|
519
|
-
|
|
527
|
+
|
|
520
528
|
Returns list of gaps with suggested actions.
|
|
521
529
|
"""
|
|
522
530
|
gaps = []
|
|
523
|
-
|
|
531
|
+
|
|
524
532
|
for host_analysis in host_analyses:
|
|
525
|
-
host = host_analysis[
|
|
526
|
-
|
|
527
|
-
for service_analysis in host_analysis[
|
|
528
|
-
if service_analysis[
|
|
529
|
-
service = service_analysis[
|
|
533
|
+
host = host_analysis["host"]
|
|
534
|
+
|
|
535
|
+
for service_analysis in host_analysis["services"]:
|
|
536
|
+
if service_analysis["exploitation_status"] == "NOT_ATTEMPTED":
|
|
537
|
+
service = service_analysis["service"]
|
|
530
538
|
severity = self._assess_gap_severity(service)
|
|
531
|
-
|
|
539
|
+
|
|
532
540
|
gap = {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
541
|
+
"host": service["host_ip"],
|
|
542
|
+
"hostname": service.get("hostname"),
|
|
543
|
+
"port": service["port"],
|
|
544
|
+
"service": service["service_name"],
|
|
545
|
+
"version": service.get("version"),
|
|
546
|
+
"reason": "Service discovered but no exploitation attempts",
|
|
547
|
+
"severity": severity,
|
|
548
|
+
"priority_score": self._calculate_priority_score(
|
|
549
|
+
service, severity
|
|
550
|
+
),
|
|
551
|
+
"suggested_actions": service_analysis["recommendations"],
|
|
542
552
|
}
|
|
543
|
-
|
|
553
|
+
|
|
544
554
|
gaps.append(gap)
|
|
545
|
-
|
|
555
|
+
|
|
546
556
|
return gaps
|
|
547
|
-
|
|
557
|
+
|
|
548
558
|
def _assess_gap_severity(self, service: Dict) -> str:
|
|
549
559
|
"""Assess severity of an exploitation gap."""
|
|
550
|
-
service_name = (service.get(
|
|
551
|
-
port = service.get(
|
|
552
|
-
|
|
560
|
+
service_name = (service.get("service_name") or "").lower()
|
|
561
|
+
port = service.get("port")
|
|
562
|
+
|
|
553
563
|
# Critical services
|
|
554
|
-
if service_name in [
|
|
555
|
-
return
|
|
556
|
-
|
|
564
|
+
if service_name in ["mysql", "postgres", "mssql", "mongodb", "redis"]:
|
|
565
|
+
return "critical"
|
|
566
|
+
|
|
557
567
|
if port in [3306, 5432, 1433, 27017, 6379]:
|
|
558
|
-
return
|
|
559
|
-
|
|
568
|
+
return "critical"
|
|
569
|
+
|
|
560
570
|
# High-value services
|
|
561
|
-
if service_name in [
|
|
562
|
-
return
|
|
563
|
-
|
|
571
|
+
if service_name in ["ssh", "rdp", "smb", "ftp"]:
|
|
572
|
+
return "high"
|
|
573
|
+
|
|
564
574
|
if port in [21, 22, 139, 445, 3389]:
|
|
565
|
-
return
|
|
566
|
-
|
|
575
|
+
return "high"
|
|
576
|
+
|
|
567
577
|
# Medium-value services
|
|
568
|
-
if service_name in [
|
|
569
|
-
return
|
|
570
|
-
|
|
578
|
+
if service_name in ["http", "https", "smtp", "imap", "pop3"]:
|
|
579
|
+
return "medium"
|
|
580
|
+
|
|
571
581
|
# Low-value
|
|
572
|
-
return
|
|
573
|
-
|
|
582
|
+
return "low"
|
|
583
|
+
|
|
574
584
|
def _calculate_priority_score(self, service: Dict, severity: str) -> int:
|
|
575
585
|
"""
|
|
576
586
|
Calculate priority score for a service (0-100).
|
|
577
587
|
Higher scores = higher priority to exploit.
|
|
578
588
|
"""
|
|
579
589
|
# Base scores by severity
|
|
580
|
-
severity_scores = {
|
|
581
|
-
|
|
582
|
-
'high': 70,
|
|
583
|
-
'medium': 50,
|
|
584
|
-
'low': 30
|
|
585
|
-
}
|
|
586
|
-
|
|
590
|
+
severity_scores = {"critical": 90, "high": 70, "medium": 50, "low": 30}
|
|
591
|
+
|
|
587
592
|
score = severity_scores.get(severity, 30)
|
|
588
|
-
|
|
593
|
+
|
|
589
594
|
# Bonus for known vulnerable versions
|
|
590
|
-
version = service.get(
|
|
591
|
-
if version and any(
|
|
595
|
+
version = service.get("version") or ""
|
|
596
|
+
if version and any(
|
|
597
|
+
vuln in version.lower()
|
|
598
|
+
for vuln in ["vsftpd 2.3.4", "proftpd 1.3.3", "unrealircd"]
|
|
599
|
+
):
|
|
592
600
|
score += 10
|
|
593
|
-
|
|
601
|
+
|
|
594
602
|
# Bonus for services commonly with weak auth
|
|
595
|
-
service_name = service.get(
|
|
596
|
-
if service_name and service_name.lower() in [
|
|
603
|
+
service_name = service.get("service_name") or ""
|
|
604
|
+
if service_name and service_name.lower() in [
|
|
605
|
+
"ftp",
|
|
606
|
+
"telnet",
|
|
607
|
+
"mysql",
|
|
608
|
+
"postgres",
|
|
609
|
+
"smb",
|
|
610
|
+
]:
|
|
597
611
|
score += 5
|
|
598
|
-
|
|
612
|
+
|
|
599
613
|
return min(score, 100)
|