souleyez 2.43.29__py3-none-any.whl → 3.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of souleyez might be problematic. Click here for more details.

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 +9564 -2881
  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 +564 -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 +409 -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 +417 -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 +913 -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 +219 -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 +237 -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 +23034 -10679
  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-3.0.0.dist-info}/METADATA +2 -2
  353. souleyez-3.0.0.dist-info/RECORD +443 -0
  354. {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/WHEEL +1 -1
  355. souleyez-2.43.29.dist-info/RECORD +0 -379
  356. {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/entry_points.txt +0 -0
  357. {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/licenses/LICENSE +0 -0
  358. {souleyez-2.43.29.dist-info → souleyez-3.0.0.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(self, engagement_id: int, host_id: int,
28
- port: int, protocol: str = 'tcp') -> Dict:
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['port'] == port and svc['protocol'] == protocol:
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
- 'host_id': host_id,
70
- 'port': port,
71
- 'protocol': protocol,
72
- 'service_name': self.target_parser.infer_service_from_port(port) or 'unknown',
73
- 'version': None
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
- 'service': {
78
- 'host_id': host_id,
79
- 'host_ip': host['ip_address'],
80
- 'hostname': host.get('hostname'),
81
- 'port': port,
82
- 'protocol': protocol,
83
- 'service_name': service_info.get('service_name', 'unknown'),
84
- 'version': service_info.get('version'),
85
- 'state': service_info.get('state', 'open')
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(engagement_id, host['ip_address'], port)
91
- result['findings'] = findings
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['jobs'] = jobs
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['credentials'] = credentials
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
- 'type': 'log',
119
- 'path': str(log_path),
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 = Path.home() / ".souleyez" / "data" / "jobs" / f"{job['id']}_output.txt"
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
- 'type': 'output',
128
- 'path': str(output_path),
129
- 'job_id': job['id']
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['exploitation_status'] = self._determine_exploitation_status(
136
- jobs, credentials, host.get('access_level', 'none')
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['access_level'] = host.get('access_level', 'none')
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('created_at', ''))
145
- result['last_attempt'] = last_job.get('created_at')
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['last_attempt'] = None
148
-
143
+ result["last_attempt"] = None
144
+
149
145
  # Calculate success rate
150
- result['success_rate'] = self._calculate_success_rate(jobs, credentials)
151
-
146
+ result["success_rate"] = self._calculate_success_rate(jobs, credentials)
147
+
152
148
  # Generate recommendations
153
- result['recommendations'] = self._generate_recommendations(
154
- result['service'],
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
- 'total_services': len(service_analyses),
201
- 'exploited': sum(1 for s in service_analyses if s['exploitation_status'] == 'EXPLOITED'),
202
- 'attempted': sum(1 for s in service_analyses if s['exploitation_status'] == 'ATTEMPTED'),
203
- 'not_attempted': sum(1 for s in service_analyses if s['exploitation_status'] == 'NOT_ATTEMPTED'),
204
- 'credentials_found': sum(len(s['credentials']) for s in service_analyses),
205
- 'access_level': host.get('access_level', 'none')
206
- }
207
-
208
- return {
209
- 'host': host,
210
- 'services': service_analyses,
211
- 'summary': summary
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['id'])
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
- 'total_hosts': len(host_analyses),
246
- 'total_services': sum(h['summary']['total_services'] for h in host_analyses),
247
- 'exploited_services': sum(h['summary']['exploited'] for h in host_analyses),
248
- 'attempted_services': sum(h['summary']['attempted'] for h in host_analyses),
249
- 'not_attempted_services': sum(h['summary']['not_attempted'] for h in host_analyses),
250
- 'total_credentials': sum(h['summary']['credentials_found'] for h in host_analyses),
251
- 'compromised_hosts': sum(1 for h in host_analyses if h['summary']['access_level'] != 'none')
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
- 'hosts': host_analyses,
259
- 'summary': summary,
260
- 'gaps': gaps
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('engagement_id') == engagement_id]
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('args')
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['tool'],
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('host') != host_ip:
292
+ if target_info.get("host") != host_ip:
296
293
  continue
297
-
294
+
298
295
  # Match by port
299
- if target_info.get('port') == port:
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('ports', []):
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('service') == service_name:
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('created_at', ''))
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['success'] = self._is_job_successful(job)
328
-
324
+ enriched["success"] = self._is_job_successful(job)
325
+
329
326
  # Parse args for display
330
- if job.get('args'):
327
+ if job.get("args"):
331
328
  try:
332
- enriched['parsed_args'] = json.loads(job['args'])
329
+ enriched["parsed_args"] = json.loads(job["args"])
333
330
  except:
334
- enriched['parsed_args'] = job['args']
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['status'] != 'done':
344
+ if job["status"] != "done":
348
345
  return False
349
-
346
+
350
347
  # Check parse_result
351
- if job.get('parse_result'):
348
+ if job.get("parse_result"):
352
349
  try:
353
- result = json.loads(job['parse_result'])
354
-
350
+ result = json.loads(job["parse_result"])
351
+
355
352
  # Check for credentials
356
- if result.get('credentials'):
353
+ if result.get("credentials"):
357
354
  return True
358
-
355
+
359
356
  # Check for vulnerabilities
360
- if result.get('vulnerabilities'):
357
+ if result.get("vulnerabilities"):
361
358
  return True
362
-
359
+
363
360
  # Check for exploits
364
- if result.get('exploits'):
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['engagement_id'])
368
+ creds = self.creds_mgr.list_credentials_for_engagement(job["engagement_id"])
372
369
  for cred in creds:
373
- if cred.get('source_job_id') == job['id']:
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(self, engagement_id: int, host_ip: str, port: int) -> List[Dict]:
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('ip_address') != host_ip:
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('port') == port:
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('affected_service', '')
395
- if affected_svc and (str(port) in affected_svc or f':{port}' in affected_svc):
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(self, engagement_id: int, host_ip: str,
401
- port: int, service_name: str = None) -> List[Dict]:
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('ip_address') != host_ip:
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('port') == port:
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('service') == service_name:
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('service', '').lower()
424
- if cred_service == 'web':
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 = (service_name and service_name.lower() in ['http', 'https', 'web'])
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('port') is None:
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(self, jobs: List, credentials: List,
436
- access_level: str) -> str:
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 'NOT_ATTEMPTED'
447
-
453
+ return "NOT_ATTEMPTED"
454
+
448
455
  # Check for successful exploitation
449
- if credentials or access_level != 'none':
450
- return 'EXPLOITED'
451
-
456
+ if credentials or access_level != "none":
457
+ return "EXPLOITED"
458
+
452
459
  # Jobs exist but no success
453
- return 'ATTEMPTED'
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('success', False))
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(self, service: Dict, status: str,
465
- credentials: List, findings: List) -> List[str]:
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('service_name') or '').lower()
470
- port = service.get('port')
471
-
472
- if status == 'NOT_ATTEMPTED':
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 == 'ssh' or port == 22:
475
- recommendations.append('Try ssh_login with common credentials')
476
- recommendations.append('Check for weak SSH configurations')
477
-
478
- elif service_name == 'ftp' or port == 21:
479
- recommendations.append('Try anonymous FTP login')
480
- recommendations.append('Check for vsftpd backdoor (version 2.3.4)')
481
-
482
- elif service_name in ['mysql', 'mariadb'] or port == 3306:
483
- recommendations.append('Try mysql_login brute force')
484
- recommendations.append('Check for CVE-2009-2446 (yaSSL overflow)')
485
-
486
- elif service_name == 'smb' or port in [139, 445]:
487
- recommendations.append('Try SMB null session enumeration')
488
- recommendations.append('Check for EternalBlue vulnerability')
489
-
490
- elif service_name == 'http' or port in [80, 443, 8080, 8443]:
491
- recommendations.append('Run Nuclei web vulnerability scan')
492
- recommendations.append('Try directory brute-forcing with Gobuster')
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'Research common exploits for {service_name}')
496
- recommendations.append(f'Try brute-forcing port {port}')
497
-
498
- elif status == 'ATTEMPTED':
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('Retry with expanded wordlist')
501
- recommendations.append('Try different exploitation modules')
502
-
503
- if service_name in ['ssh', 'ftp', 'mysql', 'smb']:
504
- recommendations.append('Attempt password spraying')
505
-
506
- elif status == 'EXPLOITED':
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('Attempt privilege escalation')
510
- recommendations.append('Enumerate for additional access')
511
-
512
- recommendations.append('Collect evidence and document access')
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['host']
526
-
527
- for service_analysis in host_analysis['services']:
528
- if service_analysis['exploitation_status'] == 'NOT_ATTEMPTED':
529
- service = service_analysis['service']
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
- 'host': service['host_ip'],
534
- 'hostname': service.get('hostname'),
535
- 'port': service['port'],
536
- 'service': service['service_name'],
537
- 'version': service.get('version'),
538
- 'reason': 'Service discovered but no exploitation attempts',
539
- 'severity': severity,
540
- 'priority_score': self._calculate_priority_score(service, severity),
541
- 'suggested_actions': service_analysis['recommendations']
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('service_name') or '').lower()
551
- port = service.get('port')
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 ['mysql', 'postgres', 'mssql', 'mongodb', 'redis']:
555
- return 'critical'
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 'critical'
559
-
568
+ return "critical"
569
+
560
570
  # High-value services
561
- if service_name in ['ssh', 'rdp', 'smb', 'ftp']:
562
- return 'high'
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 'high'
566
-
575
+ return "high"
576
+
567
577
  # Medium-value services
568
- if service_name in ['http', 'https', 'smtp', 'imap', 'pop3']:
569
- return 'medium'
570
-
578
+ if service_name in ["http", "https", "smtp", "imap", "pop3"]:
579
+ return "medium"
580
+
571
581
  # Low-value
572
- return 'low'
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
- 'critical': 90,
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('version') or ''
591
- if version and any(vuln in version.lower() for vuln in ['vsftpd 2.3.4', 'proftpd 1.3.3', 'unrealircd']):
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('service_name') or ''
596
- if service_name and service_name.lower() in ['ftp', 'telnet', 'mysql', 'postgres', 'smb']:
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)