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.
Files changed (358) hide show
  1. souleyez/__init__.py +1 -2
  2. souleyez/ai/__init__.py +21 -15
  3. souleyez/ai/action_mapper.py +249 -150
  4. souleyez/ai/chain_advisor.py +116 -100
  5. souleyez/ai/claude_provider.py +29 -28
  6. souleyez/ai/context_builder.py +80 -62
  7. souleyez/ai/executor.py +158 -117
  8. souleyez/ai/feedback_handler.py +136 -121
  9. souleyez/ai/llm_factory.py +27 -20
  10. souleyez/ai/llm_provider.py +4 -2
  11. souleyez/ai/ollama_provider.py +6 -9
  12. souleyez/ai/ollama_service.py +44 -37
  13. souleyez/ai/path_scorer.py +91 -76
  14. souleyez/ai/recommender.py +176 -144
  15. souleyez/ai/report_context.py +74 -73
  16. souleyez/ai/report_service.py +84 -66
  17. souleyez/ai/result_parser.py +222 -229
  18. souleyez/ai/safety.py +67 -44
  19. souleyez/auth/__init__.py +23 -22
  20. souleyez/auth/audit.py +36 -26
  21. souleyez/auth/engagement_access.py +65 -48
  22. souleyez/auth/permissions.py +14 -3
  23. souleyez/auth/session_manager.py +54 -37
  24. souleyez/auth/user_manager.py +109 -64
  25. souleyez/commands/audit.py +40 -43
  26. souleyez/commands/auth.py +35 -15
  27. souleyez/commands/deliverables.py +55 -50
  28. souleyez/commands/engagement.py +47 -28
  29. souleyez/commands/license.py +32 -23
  30. souleyez/commands/screenshots.py +36 -32
  31. souleyez/commands/user.py +82 -36
  32. souleyez/config.py +52 -44
  33. souleyez/core/credential_tester.py +87 -81
  34. souleyez/core/cve_mappings.py +179 -192
  35. souleyez/core/cve_matcher.py +162 -148
  36. souleyez/core/msf_auto_mapper.py +100 -83
  37. souleyez/core/msf_chain_engine.py +294 -256
  38. souleyez/core/msf_database.py +153 -70
  39. souleyez/core/msf_integration.py +679 -673
  40. souleyez/core/msf_rpc_client.py +40 -42
  41. souleyez/core/msf_rpc_manager.py +77 -79
  42. souleyez/core/msf_sync_manager.py +241 -181
  43. souleyez/core/network_utils.py +22 -15
  44. souleyez/core/parser_handler.py +34 -25
  45. souleyez/core/pending_chains.py +114 -63
  46. souleyez/core/templates.py +158 -107
  47. souleyez/core/tool_chaining.py +9526 -2879
  48. souleyez/core/version_utils.py +79 -94
  49. souleyez/core/vuln_correlation.py +136 -89
  50. souleyez/core/web_utils.py +33 -32
  51. souleyez/data/wordlists/ad_users.txt +378 -0
  52. souleyez/data/wordlists/api_endpoints_large.txt +769 -0
  53. souleyez/data/wordlists/home_dir_sensitive.txt +39 -0
  54. souleyez/data/wordlists/lfi_payloads.txt +82 -0
  55. souleyez/data/wordlists/passwords_brute.txt +1548 -0
  56. souleyez/data/wordlists/passwords_crack.txt +2479 -0
  57. souleyez/data/wordlists/passwords_spray.txt +386 -0
  58. souleyez/data/wordlists/subdomains_large.txt +5057 -0
  59. souleyez/data/wordlists/usernames_common.txt +694 -0
  60. souleyez/data/wordlists/web_dirs_large.txt +4769 -0
  61. souleyez/detection/__init__.py +1 -1
  62. souleyez/detection/attack_signatures.py +12 -17
  63. souleyez/detection/mitre_mappings.py +61 -55
  64. souleyez/detection/validator.py +97 -86
  65. souleyez/devtools.py +23 -10
  66. souleyez/docs/README.md +4 -4
  67. souleyez/docs/api-reference/cli-commands.md +2 -2
  68. souleyez/docs/developer-guide/adding-new-tools.md +562 -0
  69. souleyez/docs/user-guide/auto-chaining.md +30 -8
  70. souleyez/docs/user-guide/getting-started.md +1 -1
  71. souleyez/docs/user-guide/installation.md +26 -3
  72. souleyez/docs/user-guide/metasploit-integration.md +2 -2
  73. souleyez/docs/user-guide/rbac.md +1 -1
  74. souleyez/docs/user-guide/scope-management.md +1 -1
  75. souleyez/docs/user-guide/siem-integration.md +1 -1
  76. souleyez/docs/user-guide/tools-reference.md +1 -8
  77. souleyez/docs/user-guide/worker-management.md +1 -1
  78. souleyez/engine/background.py +1239 -535
  79. souleyez/engine/base.py +4 -1
  80. souleyez/engine/job_status.py +17 -49
  81. souleyez/engine/log_sanitizer.py +103 -77
  82. souleyez/engine/manager.py +38 -7
  83. souleyez/engine/result_handler.py +2200 -1550
  84. souleyez/engine/worker_manager.py +50 -41
  85. souleyez/export/evidence_bundle.py +72 -62
  86. souleyez/feature_flags/features.py +16 -20
  87. souleyez/feature_flags.py +5 -9
  88. souleyez/handlers/__init__.py +11 -0
  89. souleyez/handlers/base.py +188 -0
  90. souleyez/handlers/bash_handler.py +277 -0
  91. souleyez/handlers/bloodhound_handler.py +243 -0
  92. souleyez/handlers/certipy_handler.py +311 -0
  93. souleyez/handlers/crackmapexec_handler.py +486 -0
  94. souleyez/handlers/dnsrecon_handler.py +344 -0
  95. souleyez/handlers/enum4linux_handler.py +400 -0
  96. souleyez/handlers/evil_winrm_handler.py +493 -0
  97. souleyez/handlers/ffuf_handler.py +815 -0
  98. souleyez/handlers/gobuster_handler.py +1114 -0
  99. souleyez/handlers/gpp_extract_handler.py +334 -0
  100. souleyez/handlers/hashcat_handler.py +444 -0
  101. souleyez/handlers/hydra_handler.py +563 -0
  102. souleyez/handlers/impacket_getuserspns_handler.py +343 -0
  103. souleyez/handlers/impacket_psexec_handler.py +222 -0
  104. souleyez/handlers/impacket_secretsdump_handler.py +426 -0
  105. souleyez/handlers/john_handler.py +286 -0
  106. souleyez/handlers/katana_handler.py +425 -0
  107. souleyez/handlers/kerbrute_handler.py +298 -0
  108. souleyez/handlers/ldapsearch_handler.py +636 -0
  109. souleyez/handlers/lfi_extract_handler.py +464 -0
  110. souleyez/handlers/msf_auxiliary_handler.py +408 -0
  111. souleyez/handlers/msf_exploit_handler.py +380 -0
  112. souleyez/handlers/nikto_handler.py +413 -0
  113. souleyez/handlers/nmap_handler.py +821 -0
  114. souleyez/handlers/nuclei_handler.py +359 -0
  115. souleyez/handlers/nxc_handler.py +371 -0
  116. souleyez/handlers/rdp_sec_check_handler.py +353 -0
  117. souleyez/handlers/registry.py +292 -0
  118. souleyez/handlers/responder_handler.py +232 -0
  119. souleyez/handlers/service_explorer_handler.py +434 -0
  120. souleyez/handlers/smbclient_handler.py +344 -0
  121. souleyez/handlers/smbmap_handler.py +510 -0
  122. souleyez/handlers/smbpasswd_handler.py +296 -0
  123. souleyez/handlers/sqlmap_handler.py +1116 -0
  124. souleyez/handlers/theharvester_handler.py +601 -0
  125. souleyez/handlers/web_login_test_handler.py +327 -0
  126. souleyez/handlers/whois_handler.py +277 -0
  127. souleyez/handlers/wpscan_handler.py +554 -0
  128. souleyez/history.py +32 -16
  129. souleyez/importers/msf_importer.py +106 -75
  130. souleyez/importers/smart_importer.py +208 -147
  131. souleyez/integrations/siem/__init__.py +10 -10
  132. souleyez/integrations/siem/base.py +17 -18
  133. souleyez/integrations/siem/elastic.py +108 -122
  134. souleyez/integrations/siem/factory.py +207 -80
  135. souleyez/integrations/siem/googlesecops.py +146 -154
  136. souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
  137. souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
  138. souleyez/integrations/siem/sentinel.py +107 -109
  139. souleyez/integrations/siem/splunk.py +246 -212
  140. souleyez/integrations/siem/wazuh.py +65 -71
  141. souleyez/integrations/wazuh/__init__.py +5 -5
  142. souleyez/integrations/wazuh/client.py +70 -93
  143. souleyez/integrations/wazuh/config.py +85 -57
  144. souleyez/integrations/wazuh/host_mapper.py +28 -36
  145. souleyez/integrations/wazuh/sync.py +78 -68
  146. souleyez/intelligence/__init__.py +4 -5
  147. souleyez/intelligence/correlation_analyzer.py +309 -295
  148. souleyez/intelligence/exploit_knowledge.py +661 -623
  149. souleyez/intelligence/exploit_suggestions.py +159 -139
  150. souleyez/intelligence/gap_analyzer.py +132 -97
  151. souleyez/intelligence/gap_detector.py +251 -214
  152. souleyez/intelligence/sensitive_tables.py +266 -129
  153. souleyez/intelligence/service_parser.py +137 -123
  154. souleyez/intelligence/surface_analyzer.py +407 -268
  155. souleyez/intelligence/target_parser.py +159 -162
  156. souleyez/licensing/__init__.py +6 -6
  157. souleyez/licensing/validator.py +17 -19
  158. souleyez/log_config.py +79 -54
  159. souleyez/main.py +1505 -687
  160. souleyez/migrations/fix_job_counter.py +16 -14
  161. souleyez/parsers/bloodhound_parser.py +41 -39
  162. souleyez/parsers/crackmapexec_parser.py +178 -111
  163. souleyez/parsers/dalfox_parser.py +72 -77
  164. souleyez/parsers/dnsrecon_parser.py +103 -91
  165. souleyez/parsers/enum4linux_parser.py +183 -153
  166. souleyez/parsers/ffuf_parser.py +29 -25
  167. souleyez/parsers/gobuster_parser.py +301 -41
  168. souleyez/parsers/hashcat_parser.py +324 -79
  169. souleyez/parsers/http_fingerprint_parser.py +350 -103
  170. souleyez/parsers/hydra_parser.py +131 -111
  171. souleyez/parsers/impacket_parser.py +231 -178
  172. souleyez/parsers/john_parser.py +98 -86
  173. souleyez/parsers/katana_parser.py +316 -0
  174. souleyez/parsers/msf_parser.py +943 -498
  175. souleyez/parsers/nikto_parser.py +346 -65
  176. souleyez/parsers/nmap_parser.py +262 -174
  177. souleyez/parsers/nuclei_parser.py +40 -44
  178. souleyez/parsers/responder_parser.py +26 -26
  179. souleyez/parsers/searchsploit_parser.py +74 -74
  180. souleyez/parsers/service_explorer_parser.py +279 -0
  181. souleyez/parsers/smbmap_parser.py +180 -124
  182. souleyez/parsers/sqlmap_parser.py +434 -308
  183. souleyez/parsers/theharvester_parser.py +75 -57
  184. souleyez/parsers/whois_parser.py +135 -94
  185. souleyez/parsers/wpscan_parser.py +278 -190
  186. souleyez/plugins/afp.py +44 -36
  187. souleyez/plugins/afp_brute.py +114 -46
  188. souleyez/plugins/ard.py +48 -37
  189. souleyez/plugins/bloodhound.py +95 -61
  190. souleyez/plugins/certipy.py +303 -0
  191. souleyez/plugins/crackmapexec.py +186 -85
  192. souleyez/plugins/dalfox.py +120 -59
  193. souleyez/plugins/dns_hijack.py +146 -41
  194. souleyez/plugins/dnsrecon.py +97 -61
  195. souleyez/plugins/enum4linux.py +91 -66
  196. souleyez/plugins/evil_winrm.py +291 -0
  197. souleyez/plugins/ffuf.py +166 -90
  198. souleyez/plugins/firmware_extract.py +133 -29
  199. souleyez/plugins/gobuster.py +387 -190
  200. souleyez/plugins/gpp_extract.py +393 -0
  201. souleyez/plugins/hashcat.py +100 -73
  202. souleyez/plugins/http_fingerprint.py +854 -267
  203. souleyez/plugins/hydra.py +566 -200
  204. souleyez/plugins/impacket_getnpusers.py +117 -69
  205. souleyez/plugins/impacket_psexec.py +84 -64
  206. souleyez/plugins/impacket_secretsdump.py +103 -69
  207. souleyez/plugins/impacket_smbclient.py +89 -75
  208. souleyez/plugins/john.py +86 -69
  209. souleyez/plugins/katana.py +313 -0
  210. souleyez/plugins/kerbrute.py +237 -0
  211. souleyez/plugins/lfi_extract.py +541 -0
  212. souleyez/plugins/macos_ssh.py +117 -48
  213. souleyez/plugins/mdns.py +35 -30
  214. souleyez/plugins/msf_auxiliary.py +253 -130
  215. souleyez/plugins/msf_exploit.py +239 -161
  216. souleyez/plugins/nikto.py +134 -78
  217. souleyez/plugins/nmap.py +275 -91
  218. souleyez/plugins/nuclei.py +180 -89
  219. souleyez/plugins/nxc.py +285 -0
  220. souleyez/plugins/plugin_base.py +35 -36
  221. souleyez/plugins/plugin_template.py +13 -5
  222. souleyez/plugins/rdp_sec_check.py +130 -0
  223. souleyez/plugins/responder.py +112 -71
  224. souleyez/plugins/router_http_brute.py +76 -65
  225. souleyez/plugins/router_ssh_brute.py +118 -41
  226. souleyez/plugins/router_telnet_brute.py +124 -42
  227. souleyez/plugins/routersploit.py +91 -59
  228. souleyez/plugins/routersploit_exploit.py +77 -55
  229. souleyez/plugins/searchsploit.py +91 -77
  230. souleyez/plugins/service_explorer.py +1160 -0
  231. souleyez/plugins/smbmap.py +122 -72
  232. souleyez/plugins/smbpasswd.py +215 -0
  233. souleyez/plugins/sqlmap.py +301 -113
  234. souleyez/plugins/theharvester.py +127 -75
  235. souleyez/plugins/tr069.py +79 -57
  236. souleyez/plugins/upnp.py +65 -47
  237. souleyez/plugins/upnp_abuse.py +73 -55
  238. souleyez/plugins/vnc_access.py +129 -42
  239. souleyez/plugins/vnc_brute.py +109 -38
  240. souleyez/plugins/web_login_test.py +417 -0
  241. souleyez/plugins/whois.py +77 -58
  242. souleyez/plugins/wpscan.py +173 -69
  243. souleyez/reporting/__init__.py +2 -1
  244. souleyez/reporting/attack_chain.py +411 -346
  245. souleyez/reporting/charts.py +436 -501
  246. souleyez/reporting/compliance_mappings.py +334 -201
  247. souleyez/reporting/detection_report.py +126 -125
  248. souleyez/reporting/formatters.py +828 -591
  249. souleyez/reporting/generator.py +386 -302
  250. souleyez/reporting/metrics.py +72 -75
  251. souleyez/scanner.py +35 -29
  252. souleyez/security/__init__.py +37 -11
  253. souleyez/security/scope_validator.py +175 -106
  254. souleyez/security/validation.py +223 -149
  255. souleyez/security.py +22 -6
  256. souleyez/storage/credentials.py +247 -186
  257. souleyez/storage/crypto.py +296 -129
  258. souleyez/storage/database.py +73 -50
  259. souleyez/storage/db.py +58 -36
  260. souleyez/storage/deliverable_evidence.py +177 -128
  261. souleyez/storage/deliverable_exporter.py +282 -246
  262. souleyez/storage/deliverable_templates.py +134 -116
  263. souleyez/storage/deliverables.py +135 -130
  264. souleyez/storage/engagements.py +109 -56
  265. souleyez/storage/evidence.py +181 -152
  266. souleyez/storage/execution_log.py +31 -17
  267. souleyez/storage/exploit_attempts.py +93 -57
  268. souleyez/storage/exploits.py +67 -36
  269. souleyez/storage/findings.py +48 -61
  270. souleyez/storage/hosts.py +176 -144
  271. souleyez/storage/migrate_to_engagements.py +43 -19
  272. souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
  273. souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
  274. souleyez/storage/migrations/_003_add_execution_log.py +14 -8
  275. souleyez/storage/migrations/_005_screenshots.py +13 -5
  276. souleyez/storage/migrations/_006_deliverables.py +13 -5
  277. souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
  278. souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
  279. souleyez/storage/migrations/_010_evidence_linking.py +17 -10
  280. souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
  281. souleyez/storage/migrations/_012_team_collaboration.py +34 -21
  282. souleyez/storage/migrations/_013_add_host_tags.py +12 -6
  283. souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
  284. souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
  285. souleyez/storage/migrations/_016_add_domain_field.py +10 -4
  286. souleyez/storage/migrations/_017_msf_sessions.py +16 -8
  287. souleyez/storage/migrations/_018_add_osint_target.py +10 -6
  288. souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
  289. souleyez/storage/migrations/_020_add_rbac.py +36 -15
  290. souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
  291. souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
  292. souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
  293. souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
  294. souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
  295. souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
  296. souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
  297. souleyez/storage/migrations/__init__.py +26 -26
  298. souleyez/storage/migrations/migration_manager.py +19 -19
  299. souleyez/storage/msf_sessions.py +100 -65
  300. souleyez/storage/osint.py +17 -24
  301. souleyez/storage/recommendation_engine.py +269 -235
  302. souleyez/storage/screenshots.py +33 -32
  303. souleyez/storage/smb_shares.py +136 -92
  304. souleyez/storage/sqlmap_data.py +183 -128
  305. souleyez/storage/team_collaboration.py +135 -141
  306. souleyez/storage/timeline_tracker.py +122 -94
  307. souleyez/storage/wazuh_vulns.py +64 -66
  308. souleyez/storage/web_paths.py +33 -37
  309. souleyez/testing/credential_tester.py +221 -205
  310. souleyez/ui/__init__.py +1 -1
  311. souleyez/ui/ai_quotes.py +12 -12
  312. souleyez/ui/attack_surface.py +2439 -1516
  313. souleyez/ui/chain_rules_view.py +914 -382
  314. souleyez/ui/correlation_view.py +312 -230
  315. souleyez/ui/dashboard.py +2382 -1130
  316. souleyez/ui/deliverables_view.py +148 -62
  317. souleyez/ui/design_system.py +13 -13
  318. souleyez/ui/errors.py +49 -49
  319. souleyez/ui/evidence_linking_view.py +284 -179
  320. souleyez/ui/evidence_vault.py +393 -285
  321. souleyez/ui/exploit_suggestions_view.py +555 -349
  322. souleyez/ui/export_view.py +100 -66
  323. souleyez/ui/gap_analysis_view.py +315 -171
  324. souleyez/ui/help_system.py +105 -97
  325. souleyez/ui/intelligence_view.py +436 -293
  326. souleyez/ui/interactive.py +22827 -10678
  327. souleyez/ui/interactive_selector.py +75 -68
  328. souleyez/ui/log_formatter.py +47 -39
  329. souleyez/ui/menu_components.py +22 -13
  330. souleyez/ui/msf_auxiliary_menu.py +184 -133
  331. souleyez/ui/pending_chains_view.py +336 -172
  332. souleyez/ui/progress_indicators.py +5 -3
  333. souleyez/ui/recommendations_view.py +195 -137
  334. souleyez/ui/rule_builder.py +343 -225
  335. souleyez/ui/setup_wizard.py +678 -284
  336. souleyez/ui/shortcuts.py +217 -165
  337. souleyez/ui/splunk_gap_analysis_view.py +452 -270
  338. souleyez/ui/splunk_vulns_view.py +139 -86
  339. souleyez/ui/team_dashboard.py +498 -335
  340. souleyez/ui/template_selector.py +196 -105
  341. souleyez/ui/terminal.py +6 -6
  342. souleyez/ui/timeline_view.py +198 -127
  343. souleyez/ui/tool_setup.py +264 -164
  344. souleyez/ui/tutorial.py +202 -72
  345. souleyez/ui/tutorial_state.py +40 -40
  346. souleyez/ui/wazuh_vulns_view.py +235 -141
  347. souleyez/ui/wordlist_browser.py +260 -107
  348. souleyez/ui.py +464 -312
  349. souleyez/utils/tool_checker.py +427 -367
  350. souleyez/utils.py +33 -29
  351. souleyez/wordlists.py +134 -167
  352. {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/METADATA +1 -1
  353. souleyez-2.43.34.dist-info/RECORD +443 -0
  354. {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/WHEEL +1 -1
  355. souleyez-2.43.29.dist-info/RECORD +0 -379
  356. {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/entry_points.txt +0 -0
  357. {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/licenses/LICENSE +0 -0
  358. {souleyez-2.43.29.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 = [h for h in all_hosts if h.get('status') == 'up' or self._has_activity(h, findings, credentials)]
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('engagement_id') == engagement_id]
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(host, findings, credentials, jobs, exploit_attempts, wazuh_vulns)
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['score'], reverse=True)
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
- 'overview': overview,
88
- 'hosts': host_surfaces,
89
- 'recommendations': recommendations
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(self, host: Dict, findings: List[Dict],
98
- credentials: List[Dict], jobs: List[Dict],
99
- exploit_attempts: List[Dict] = None,
100
- wazuh_vulns: List[Dict] = None) -> Dict:
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 = host.get('ip') or host.get('ip_address') or host.get('address', 'Unknown')
103
- host_id = host.get('id')
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('services', [])
112
-
126
+ services = host.get("services", [])
127
+
113
128
  # Get findings for this host
114
- host_findings = [f for f in findings if f.get('ip_address') == host_ip or f.get('url', '').startswith(f'http://{host_ip}') or f.get('url', '').startswith(f'https://{host_ip}')]
115
- critical_findings = [f for f in host_findings if f.get('severity') == 'critical']
116
- high_findings = [f for f in host_findings if f.get('severity') == 'high']
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 for v in wazuh_vulns
123
- if v.get('host_id') == host_id or v.get('agent_ip') == host_ip or v.get('host_ip') == host_ip
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 = [v for v in host_wazuh_vulns if v.get('severity') == 'Critical']
126
- wazuh_high = [v for v in host_wazuh_vulns if v.get('severity') == 'High']
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('port')
135
- path = finding.get('path') or finding.get('url', '')
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('https://'):
167
+ if path.startswith("https://"):
140
168
  port = 443
141
- elif path.startswith('http://'):
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 = 'http' if port in [80, 8080, 8000] else 'https' if port == 443 else 'unknown'
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
- 'port': port,
150
- 'service': service_name,
151
- 'service_name': service_name,
152
- 'protocol': 'tcp',
153
- 'state': 'open',
154
- 'version': None,
155
- 'synthetic': True, # Flag as synthetic for reference
156
- 'status': 'attempted', # Has findings, so mark as attempted
157
- 'credentials': 0,
158
- 'findings': 0,
159
- 'jobs_run': 0
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['findings'] = len([f for f in host_findings if f.get('port') == service['port'] or
166
- (f.get('path', '').startswith('https://') and service['port'] == 443) or
167
- (f.get('path', '').startswith('http://') and service['port'] == 80)])
168
- service['credentials'] = len([c for c in credentials if c.get('port') == service['port']])
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, services, host_findings, critical_findings, high_findings,
173
- host_wazuh_vulns, wazuh_critical, wazuh_high
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 for a in exploit_attempts
181
- if a.get('host_id') == host_id or a.get('ip_address') == host_ip
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, service, jobs, credentials, host_findings, host_exploit_attempts
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['status'] == 'exploited'])
194
- attempted = len([s for s in service_statuses if s['status'] == 'attempted'])
195
- not_tried = len([s for s in service_statuses if s['status'] == 'not_tried'])
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 = ", ".join(reasoning_parts) if reasoning_parts else "Open services discovered"
213
-
270
+ reasoning = (
271
+ ", ".join(reasoning_parts)
272
+ if reasoning_parts
273
+ else "Open services discovered"
274
+ )
275
+
214
276
  return {
215
- 'host': host_ip,
216
- 'hostname': host.get('hostname'),
217
- 'score': score,
218
- 'open_ports': len(services),
219
- 'service_count': len(services),
220
- 'services': service_statuses, # Full list for display
221
- 'findings': len(host_findings),
222
- 'critical_findings': len(critical_findings),
223
- 'high_findings': len(high_findings),
224
- 'wazuh_vulns': len(host_wazuh_vulns),
225
- 'wazuh_critical': len(wazuh_critical),
226
- 'wazuh_high': len(wazuh_high),
227
- 'reasoning': reasoning,
228
- 'exploitation_progress': {
229
- 'exploited': exploited,
230
- 'attempted': attempted,
231
- 'not_tried': not_tried,
232
- 'total': len(services)
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(self, host: Dict, services: List[Dict],
237
- findings: List[Dict], critical_findings: List[Dict],
238
- high_findings: List[Dict],
239
- wazuh_vulns: List[Dict] = None,
240
- wazuh_critical: List[Dict] = None,
241
- wazuh_high: List[Dict] = None) -> int:
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 + # Each port/service
260
- len(services) * 3 + # Service identification
261
- len(findings) * 5 + # Total findings
262
- len(high_findings) * 8 + # High severity findings
263
- len(critical_findings) * 15 + # Critical severity findings
264
- len(wazuh_vulns) * 4 + # Wazuh vulnerabilities (passive detection)
265
- len(wazuh_high) * 8 + # Wazuh high severity
266
- len(wazuh_critical) * 12 # Wazuh critical severity
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(self, host: str, service: Dict,
288
- jobs: List[Dict], credentials: List[Dict],
289
- findings: List[Dict],
290
- exploit_attempts: List[Dict] = None) -> Dict:
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('port')
293
- service_id = service.get('id')
294
- service_name = service.get('service_name') or 'unknown'
295
- version = service.get('service_version') or ''
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 for a in exploit_attempts
302
- if a.get('service_id') == service_id or a.get('port') == port
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 for c in credentials
308
- if c.get('ip_address') == host and c.get('port') == port
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 for f in findings
322
- if f.get('ip_address') == host and
323
- f.get('severity') in ['critical', 'high']
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
- exploit_tools = {'msfconsole', 'msf', 'hydra', 'medusa', 'sqlmap', 'crackmapexec'}
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('target') or ''):
414
+ if host not in (j.get("target") or ""):
333
415
  continue
334
- if j.get('status') not in ['done', 'error', 'no_results']:
416
+ if j.get("status") not in ["done", "error", "no_results"]:
335
417
  continue
336
- if j.get('tool', '').lower() not in exploit_tools:
418
+ if j.get("tool", "").lower() not in exploit_tools:
337
419
  continue
338
420
  # Extract port from job args
339
- args = j.get('args', [])
340
- args_str = ' '.join(args) if args else ''
341
- port_match = re.search(r'RPORT\s+(\d+)|:(\d+)|-p\s+(\d+)', args_str, re.IGNORECASE)
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(port_match.group(1) or port_match.group(2) or port_match.group(3))
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('status')
354
- if attempt_status == 'success':
439
+ attempt_status = attempt.get("status")
440
+ if attempt_status == "success":
355
441
  exploited = True
356
442
  break
357
- elif attempt_status in ('attempted', 'failed'):
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
- 'exploited', 'breach', 'dumped', 'exfiltrated',
368
- 'enumerated', 'extracted', 'compromised'
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('title', '').lower()
373
- severity = finding.get('severity', '')
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 == 'critical' and any(vuln in title for vuln in ['sql injection', 'rce', 'command injection', 'data breach']):
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 = 'exploited'
487
+ status = "exploited"
389
488
  elif manually_attempted or service_jobs:
390
- status = 'attempted'
489
+ status = "attempted"
391
490
  else:
392
- status = 'not_tried'
393
-
491
+ status = "not_tried"
492
+
394
493
  return {
395
- 'port': port,
396
- 'service': service_name,
397
- 'version': version,
398
- 'status': status,
399
- 'credentials': len(service_creds),
400
- 'findings': len(service_findings),
401
- 'jobs_run': len(service_jobs),
402
- 'suggested_actions': self._suggest_actions(service_name, version, status)
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 == 'not_tried':
507
+
508
+ if status == "not_tried":
410
509
  # Suggest initial enumeration
411
510
  service_lower = service.lower()
412
- if 'ssh' in service_lower or 'telnet' in service_lower:
413
- actions.append('Enumerate users')
414
- actions.append('Brute force')
415
- elif 'mysql' in service_lower or 'postgresql' in service_lower or 'mssql' in service_lower:
416
- actions.append('Try default creds')
417
- actions.append('Enumerate databases')
418
- elif 'http' in service_lower:
419
- actions.append('Scan vulnerabilities')
420
- actions.append('Directory brute force')
421
- elif 'smb' in service_lower or 'netbios' in service_lower:
422
- actions.append('Enumerate shares')
423
- actions.append('Enumerate users')
424
- elif 'ftp' in service_lower:
425
- actions.append('Anonymous login')
426
- actions.append('Brute force')
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('Enumerate service')
429
-
430
- elif status == 'attempted':
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 'ssh' in service_lower or 'telnet' in service_lower or 'login' in service_lower:
434
- actions.append('Try different wordlist')
435
- actions.append('Check for known exploits')
436
- elif 'mysql' in service_lower or 'postgresql' in service_lower or 'mssql' in service_lower:
437
- actions.append('Try SQL injection')
438
- actions.append('Check for CVEs')
439
- elif 'http' in service_lower or 'https' in service_lower:
440
- actions.append('Run Nuclei/Gobuster')
441
- actions.append('Check web app vulns')
442
- elif 'smb' in service_lower or 'netbios' in service_lower or 'microsoft-ds' in service_lower:
443
- actions.append('Try null session')
444
- actions.append('Check EternalBlue')
445
- elif 'ftp' in service_lower:
446
- actions.append('Check for backdoor')
447
- actions.append('Try credential stuffing')
448
- elif 'rpc' in service_lower or 'nfs' in service_lower:
449
- actions.append('Enumerate exports')
450
- actions.append('Check mount options')
451
- elif 'vnc' in service_lower:
452
- actions.append('Try known passwords')
453
- actions.append('Check for no-auth')
454
- elif 'domain' in service_lower or 'dns' in service_lower:
455
- actions.append('Zone transfer')
456
- actions.append('Subdomain enum')
457
- elif 'smtp' in service_lower or 'mail' in service_lower:
458
- actions.append('User enumeration')
459
- actions.append('Check open relay')
460
- elif 'irc' in service_lower:
461
- actions.append('Check for UnrealIRCd backdoor')
462
- actions.append('Connect and enum')
463
- elif 'java' in service_lower or 'rmi' in service_lower:
464
- actions.append('Check Java deserialization')
465
- actions.append('RMI registry enum')
466
- elif 'ajp' in service_lower or 'tomcat' in service_lower:
467
- actions.append('Ghostcat (CVE-2020-1938)')
468
- actions.append('Manager console access')
469
- elif 'exec' in service_lower or 'shell' in service_lower or 'bindshell' in service_lower:
470
- actions.append('Direct connection attempt')
471
- actions.append('Check authentication')
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('Research service vulns')
474
- actions.append('Manual enumeration')
475
-
476
- elif status == 'exploited':
477
- actions.append('Escalate privileges')
478
- actions.append('Dump credentials')
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(self, host: Dict, findings: List[Dict], credentials: List[Dict]) -> bool:
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('id')
485
- host_ip = host.get('ip') or host.get('ip_address')
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('ip_address') == host_ip]
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('ip_address') == host_ip]
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(self, host_surfaces: List[Dict], findings: List[Dict],
505
- credentials: List[Dict]) -> Dict:
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('services', [])) for h in host_surfaces)
629
+ total_services = sum(len(h.get("services", [])) for h in host_surfaces)
508
630
  exploited_services = sum(
509
- h.get('exploitation_progress', {}).get('exploited', 0)
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('severity') == 'critical']
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('wazuh_vulns', 0) for h in host_surfaces)
516
- wazuh_critical = sum(h.get('wazuh_critical', 0) for h in host_surfaces)
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
- 'total_hosts': len(host_surfaces),
520
- 'total_services': total_services,
521
- 'exploited_services': exploited_services,
522
- 'exploitation_percentage': round(
523
- (exploited_services / total_services * 100) if total_services > 0 else 0, 1
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
- 'credentials_found': len(credentials),
526
- 'critical_findings': len(critical_findings),
527
- 'wazuh_vulns': total_wazuh_vulns,
528
- 'wazuh_critical': wazuh_critical
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('services', []): # Changed from 'service_statuses'
537
- if service_status['status'] == 'not_tried' and service_status['suggested_actions']:
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
- 'priority': 'high',
541
- 'host': host_surface['host'],
542
- 'port': service_status['port'],
543
- 'service': service_status['service'],
544
- 'action': service_status['suggested_actions'][0],
545
- 'reason': f"Untried service on high-value target"
546
- })
547
-
548
- elif service_status['status'] == 'attempted' and len(recommendations) < 10:
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
- 'priority': 'medium',
552
- 'host': host_surface['host'],
553
- 'port': service_status['port'],
554
- 'service': service_status['service'],
555
- 'action': 'Retry exploitation',
556
- 'reason': f"Previous attempt incomplete"
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 = {'high': 0, 'medium': 1, 'low': 2}
561
- recommendations.sort(key=lambda x: priority_order[x['priority']])
699
+ priority_order = {"high": 0, "medium": 1, "low": 2}
700
+ recommendations.sort(key=lambda x: priority_order[x["priority"]])
562
701
  return recommendations[:5]