souleyez 2.43.28__py3-none-any.whl → 2.43.32__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 (356) 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 +9592 -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 +1238 -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 +2198 -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 +288 -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/whois_handler.py +277 -0
  126. souleyez/handlers/wpscan_handler.py +554 -0
  127. souleyez/history.py +32 -16
  128. souleyez/importers/msf_importer.py +106 -75
  129. souleyez/importers/smart_importer.py +208 -147
  130. souleyez/integrations/siem/__init__.py +10 -10
  131. souleyez/integrations/siem/base.py +17 -18
  132. souleyez/integrations/siem/elastic.py +108 -122
  133. souleyez/integrations/siem/factory.py +207 -80
  134. souleyez/integrations/siem/googlesecops.py +146 -154
  135. souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
  136. souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
  137. souleyez/integrations/siem/sentinel.py +107 -109
  138. souleyez/integrations/siem/splunk.py +246 -212
  139. souleyez/integrations/siem/wazuh.py +65 -71
  140. souleyez/integrations/wazuh/__init__.py +5 -5
  141. souleyez/integrations/wazuh/client.py +70 -93
  142. souleyez/integrations/wazuh/config.py +85 -57
  143. souleyez/integrations/wazuh/host_mapper.py +28 -36
  144. souleyez/integrations/wazuh/sync.py +78 -68
  145. souleyez/intelligence/__init__.py +4 -5
  146. souleyez/intelligence/correlation_analyzer.py +309 -295
  147. souleyez/intelligence/exploit_knowledge.py +661 -623
  148. souleyez/intelligence/exploit_suggestions.py +159 -139
  149. souleyez/intelligence/gap_analyzer.py +132 -97
  150. souleyez/intelligence/gap_detector.py +251 -214
  151. souleyez/intelligence/sensitive_tables.py +266 -129
  152. souleyez/intelligence/service_parser.py +137 -123
  153. souleyez/intelligence/surface_analyzer.py +407 -268
  154. souleyez/intelligence/target_parser.py +159 -162
  155. souleyez/licensing/__init__.py +6 -6
  156. souleyez/licensing/validator.py +17 -19
  157. souleyez/log_config.py +79 -54
  158. souleyez/main.py +1505 -687
  159. souleyez/migrations/fix_job_counter.py +16 -14
  160. souleyez/parsers/bloodhound_parser.py +41 -39
  161. souleyez/parsers/crackmapexec_parser.py +178 -111
  162. souleyez/parsers/dalfox_parser.py +72 -77
  163. souleyez/parsers/dnsrecon_parser.py +103 -91
  164. souleyez/parsers/enum4linux_parser.py +183 -153
  165. souleyez/parsers/ffuf_parser.py +29 -25
  166. souleyez/parsers/gobuster_parser.py +301 -41
  167. souleyez/parsers/hashcat_parser.py +324 -79
  168. souleyez/parsers/http_fingerprint_parser.py +350 -103
  169. souleyez/parsers/hydra_parser.py +131 -111
  170. souleyez/parsers/impacket_parser.py +231 -178
  171. souleyez/parsers/john_parser.py +98 -86
  172. souleyez/parsers/katana_parser.py +316 -0
  173. souleyez/parsers/msf_parser.py +943 -498
  174. souleyez/parsers/nikto_parser.py +346 -65
  175. souleyez/parsers/nmap_parser.py +262 -174
  176. souleyez/parsers/nuclei_parser.py +40 -44
  177. souleyez/parsers/responder_parser.py +26 -26
  178. souleyez/parsers/searchsploit_parser.py +74 -74
  179. souleyez/parsers/service_explorer_parser.py +279 -0
  180. souleyez/parsers/smbmap_parser.py +180 -124
  181. souleyez/parsers/sqlmap_parser.py +434 -308
  182. souleyez/parsers/theharvester_parser.py +75 -57
  183. souleyez/parsers/whois_parser.py +135 -94
  184. souleyez/parsers/wpscan_parser.py +278 -190
  185. souleyez/plugins/afp.py +44 -36
  186. souleyez/plugins/afp_brute.py +114 -46
  187. souleyez/plugins/ard.py +48 -37
  188. souleyez/plugins/bloodhound.py +95 -61
  189. souleyez/plugins/certipy.py +303 -0
  190. souleyez/plugins/crackmapexec.py +186 -85
  191. souleyez/plugins/dalfox.py +120 -59
  192. souleyez/plugins/dns_hijack.py +146 -41
  193. souleyez/plugins/dnsrecon.py +97 -61
  194. souleyez/plugins/enum4linux.py +91 -66
  195. souleyez/plugins/evil_winrm.py +291 -0
  196. souleyez/plugins/ffuf.py +166 -90
  197. souleyez/plugins/firmware_extract.py +133 -29
  198. souleyez/plugins/gobuster.py +387 -190
  199. souleyez/plugins/gpp_extract.py +393 -0
  200. souleyez/plugins/hashcat.py +100 -73
  201. souleyez/plugins/http_fingerprint.py +854 -267
  202. souleyez/plugins/hydra.py +566 -200
  203. souleyez/plugins/impacket_getnpusers.py +117 -69
  204. souleyez/plugins/impacket_psexec.py +84 -64
  205. souleyez/plugins/impacket_secretsdump.py +103 -69
  206. souleyez/plugins/impacket_smbclient.py +89 -75
  207. souleyez/plugins/john.py +86 -69
  208. souleyez/plugins/katana.py +313 -0
  209. souleyez/plugins/kerbrute.py +237 -0
  210. souleyez/plugins/lfi_extract.py +541 -0
  211. souleyez/plugins/macos_ssh.py +117 -48
  212. souleyez/plugins/mdns.py +35 -30
  213. souleyez/plugins/msf_auxiliary.py +253 -130
  214. souleyez/plugins/msf_exploit.py +239 -161
  215. souleyez/plugins/nikto.py +134 -78
  216. souleyez/plugins/nmap.py +275 -91
  217. souleyez/plugins/nuclei.py +180 -89
  218. souleyez/plugins/nxc.py +285 -0
  219. souleyez/plugins/plugin_base.py +35 -36
  220. souleyez/plugins/plugin_template.py +13 -5
  221. souleyez/plugins/rdp_sec_check.py +130 -0
  222. souleyez/plugins/responder.py +112 -71
  223. souleyez/plugins/router_http_brute.py +76 -65
  224. souleyez/plugins/router_ssh_brute.py +118 -41
  225. souleyez/plugins/router_telnet_brute.py +124 -42
  226. souleyez/plugins/routersploit.py +91 -59
  227. souleyez/plugins/routersploit_exploit.py +77 -55
  228. souleyez/plugins/searchsploit.py +91 -77
  229. souleyez/plugins/service_explorer.py +1160 -0
  230. souleyez/plugins/smbmap.py +122 -72
  231. souleyez/plugins/smbpasswd.py +215 -0
  232. souleyez/plugins/sqlmap.py +301 -113
  233. souleyez/plugins/theharvester.py +127 -75
  234. souleyez/plugins/tr069.py +79 -57
  235. souleyez/plugins/upnp.py +65 -47
  236. souleyez/plugins/upnp_abuse.py +73 -55
  237. souleyez/plugins/vnc_access.py +129 -42
  238. souleyez/plugins/vnc_brute.py +109 -38
  239. souleyez/plugins/whois.py +77 -58
  240. souleyez/plugins/wpscan.py +173 -69
  241. souleyez/reporting/__init__.py +2 -1
  242. souleyez/reporting/attack_chain.py +411 -346
  243. souleyez/reporting/charts.py +436 -501
  244. souleyez/reporting/compliance_mappings.py +334 -201
  245. souleyez/reporting/detection_report.py +126 -125
  246. souleyez/reporting/formatters.py +828 -591
  247. souleyez/reporting/generator.py +386 -302
  248. souleyez/reporting/metrics.py +72 -75
  249. souleyez/scanner.py +35 -29
  250. souleyez/security/__init__.py +37 -11
  251. souleyez/security/scope_validator.py +175 -106
  252. souleyez/security/validation.py +223 -149
  253. souleyez/security.py +22 -6
  254. souleyez/storage/credentials.py +247 -186
  255. souleyez/storage/crypto.py +296 -129
  256. souleyez/storage/database.py +73 -50
  257. souleyez/storage/db.py +58 -36
  258. souleyez/storage/deliverable_evidence.py +177 -128
  259. souleyez/storage/deliverable_exporter.py +282 -246
  260. souleyez/storage/deliverable_templates.py +134 -116
  261. souleyez/storage/deliverables.py +135 -130
  262. souleyez/storage/engagements.py +109 -56
  263. souleyez/storage/evidence.py +181 -152
  264. souleyez/storage/execution_log.py +31 -17
  265. souleyez/storage/exploit_attempts.py +93 -57
  266. souleyez/storage/exploits.py +67 -36
  267. souleyez/storage/findings.py +48 -61
  268. souleyez/storage/hosts.py +176 -144
  269. souleyez/storage/migrate_to_engagements.py +43 -19
  270. souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
  271. souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
  272. souleyez/storage/migrations/_003_add_execution_log.py +14 -8
  273. souleyez/storage/migrations/_005_screenshots.py +13 -5
  274. souleyez/storage/migrations/_006_deliverables.py +13 -5
  275. souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
  276. souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
  277. souleyez/storage/migrations/_010_evidence_linking.py +17 -10
  278. souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
  279. souleyez/storage/migrations/_012_team_collaboration.py +34 -21
  280. souleyez/storage/migrations/_013_add_host_tags.py +12 -6
  281. souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
  282. souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
  283. souleyez/storage/migrations/_016_add_domain_field.py +10 -4
  284. souleyez/storage/migrations/_017_msf_sessions.py +16 -8
  285. souleyez/storage/migrations/_018_add_osint_target.py +10 -6
  286. souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
  287. souleyez/storage/migrations/_020_add_rbac.py +36 -15
  288. souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
  289. souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
  290. souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
  291. souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
  292. souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
  293. souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
  294. souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
  295. souleyez/storage/migrations/__init__.py +26 -26
  296. souleyez/storage/migrations/migration_manager.py +19 -19
  297. souleyez/storage/msf_sessions.py +100 -65
  298. souleyez/storage/osint.py +17 -24
  299. souleyez/storage/recommendation_engine.py +269 -235
  300. souleyez/storage/screenshots.py +33 -32
  301. souleyez/storage/smb_shares.py +136 -92
  302. souleyez/storage/sqlmap_data.py +183 -128
  303. souleyez/storage/team_collaboration.py +135 -141
  304. souleyez/storage/timeline_tracker.py +122 -94
  305. souleyez/storage/wazuh_vulns.py +64 -66
  306. souleyez/storage/web_paths.py +33 -37
  307. souleyez/testing/credential_tester.py +221 -205
  308. souleyez/ui/__init__.py +1 -1
  309. souleyez/ui/ai_quotes.py +12 -12
  310. souleyez/ui/attack_surface.py +2439 -1516
  311. souleyez/ui/chain_rules_view.py +914 -382
  312. souleyez/ui/correlation_view.py +312 -230
  313. souleyez/ui/dashboard.py +2382 -1130
  314. souleyez/ui/deliverables_view.py +148 -62
  315. souleyez/ui/design_system.py +13 -13
  316. souleyez/ui/errors.py +49 -49
  317. souleyez/ui/evidence_linking_view.py +284 -179
  318. souleyez/ui/evidence_vault.py +393 -285
  319. souleyez/ui/exploit_suggestions_view.py +555 -349
  320. souleyez/ui/export_view.py +100 -66
  321. souleyez/ui/gap_analysis_view.py +315 -171
  322. souleyez/ui/help_system.py +105 -97
  323. souleyez/ui/intelligence_view.py +436 -293
  324. souleyez/ui/interactive.py +23142 -10430
  325. souleyez/ui/interactive_selector.py +75 -68
  326. souleyez/ui/log_formatter.py +47 -39
  327. souleyez/ui/menu_components.py +22 -13
  328. souleyez/ui/msf_auxiliary_menu.py +184 -133
  329. souleyez/ui/pending_chains_view.py +336 -172
  330. souleyez/ui/progress_indicators.py +5 -3
  331. souleyez/ui/recommendations_view.py +195 -137
  332. souleyez/ui/rule_builder.py +343 -225
  333. souleyez/ui/setup_wizard.py +678 -284
  334. souleyez/ui/shortcuts.py +217 -165
  335. souleyez/ui/splunk_gap_analysis_view.py +452 -270
  336. souleyez/ui/splunk_vulns_view.py +139 -86
  337. souleyez/ui/team_dashboard.py +498 -335
  338. souleyez/ui/template_selector.py +196 -105
  339. souleyez/ui/terminal.py +6 -6
  340. souleyez/ui/timeline_view.py +198 -127
  341. souleyez/ui/tool_setup.py +264 -164
  342. souleyez/ui/tutorial.py +202 -72
  343. souleyez/ui/tutorial_state.py +40 -40
  344. souleyez/ui/wazuh_vulns_view.py +235 -141
  345. souleyez/ui/wordlist_browser.py +260 -107
  346. souleyez/ui.py +464 -312
  347. souleyez/utils/tool_checker.py +427 -367
  348. souleyez/utils.py +33 -29
  349. souleyez/wordlists.py +134 -167
  350. {souleyez-2.43.28.dist-info → souleyez-2.43.32.dist-info}/METADATA +1 -1
  351. souleyez-2.43.32.dist-info/RECORD +441 -0
  352. {souleyez-2.43.28.dist-info → souleyez-2.43.32.dist-info}/WHEEL +1 -1
  353. souleyez-2.43.28.dist-info/RECORD +0 -379
  354. {souleyez-2.43.28.dist-info → souleyez-2.43.32.dist-info}/entry_points.txt +0 -0
  355. {souleyez-2.43.28.dist-info → souleyez-2.43.32.dist-info}/licenses/LICENSE +0 -0
  356. {souleyez-2.43.28.dist-info → souleyez-2.43.32.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,243 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Handler for BloodHound - Active Directory attack path mapping.
4
+ Parses bloodhound-python collection results.
5
+ """
6
+ import logging
7
+ import os
8
+ import re
9
+ from typing import Any, Dict, List, Optional
10
+
11
+ import click
12
+
13
+ from souleyez.handlers.base import BaseToolHandler
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ STATUS_DONE = "done"
18
+ STATUS_ERROR = "error"
19
+ STATUS_WARNING = "warning"
20
+ STATUS_NO_RESULTS = "no_results"
21
+
22
+
23
+ class BloodhoundHandler(BaseToolHandler):
24
+ """Handler for BloodHound AD collection."""
25
+
26
+ tool_name = "bloodhound"
27
+ display_name = "BloodHound"
28
+
29
+ has_error_handler = True
30
+ has_warning_handler = True
31
+ has_no_results_handler = True
32
+ has_done_handler = True
33
+
34
+ def parse_job(
35
+ self,
36
+ engagement_id: int,
37
+ log_path: str,
38
+ job: Dict[str, Any],
39
+ host_manager: Optional[Any] = None,
40
+ findings_manager: Optional[Any] = None,
41
+ credentials_manager: Optional[Any] = None,
42
+ ) -> Dict[str, Any]:
43
+ """Parse bloodhound-python results."""
44
+ try:
45
+ target = job.get("target", "")
46
+
47
+ if not log_path or not os.path.exists(log_path):
48
+ return {
49
+ "tool": "bloodhound",
50
+ "status": STATUS_ERROR,
51
+ "error": "Log file not found",
52
+ }
53
+
54
+ with open(log_path, "r", encoding="utf-8", errors="replace") as f:
55
+ log_content = f.read()
56
+
57
+ # Check for errors
58
+ if "ERROR" in log_content and "bloodhound-python not found" in log_content:
59
+ return {
60
+ "tool": "bloodhound",
61
+ "status": STATUS_ERROR,
62
+ "error": "bloodhound-python not installed",
63
+ }
64
+
65
+ if "ERROR: Missing required arguments" in log_content:
66
+ return {
67
+ "tool": "bloodhound",
68
+ "status": STATUS_ERROR,
69
+ "error": "Missing credentials",
70
+ }
71
+
72
+ # Check for authentication errors
73
+ auth_errors = [
74
+ "Authentication failed",
75
+ "Invalid credentials",
76
+ "Logon failure",
77
+ "KDC_ERR_PREAUTH_FAILED",
78
+ "KDC_ERR_C_PRINCIPAL_UNKNOWN",
79
+ ]
80
+ for err in auth_errors:
81
+ if err.lower() in log_content.lower():
82
+ return {
83
+ "tool": "bloodhound",
84
+ "status": STATUS_ERROR,
85
+ "error": f"Authentication failed: {err}",
86
+ }
87
+
88
+ # Parse collection statistics
89
+ stats = {
90
+ "users": 0,
91
+ "groups": 0,
92
+ "computers": 0,
93
+ "domains": 0,
94
+ "gpos": 0,
95
+ "ous": 0,
96
+ "containers": 0,
97
+ }
98
+
99
+ # Pattern: "Done in 00m 05s" or object counts
100
+ patterns = [
101
+ (r"(\d+)\s+user", "users"),
102
+ (r"(\d+)\s+group", "groups"),
103
+ (r"(\d+)\s+computer", "computers"),
104
+ (r"(\d+)\s+domain", "domains"),
105
+ (r"(\d+)\s+gpo", "gpos"),
106
+ (r"(\d+)\s+ou", "ous"),
107
+ (r"(\d+)\s+container", "containers"),
108
+ ]
109
+
110
+ for pattern, key in patterns:
111
+ match = re.search(pattern, log_content, re.IGNORECASE)
112
+ if match:
113
+ stats[key] = int(match.group(1))
114
+
115
+ # Check for output files
116
+ output_path = ""
117
+ output_match = re.search(r"Output saved to:\s*(.+)", log_content)
118
+ if output_match:
119
+ output_path = output_match.group(1).strip()
120
+
121
+ # Check for success indicators
122
+ success = (
123
+ "Data collection complete" in log_content
124
+ or "Done in" in log_content
125
+ or any(v > 0 for v in stats.values())
126
+ )
127
+
128
+ if success:
129
+ status = STATUS_DONE
130
+ total_objects = sum(stats.values())
131
+ if total_objects == 0:
132
+ # Collected but no stats parsed - still success
133
+ status = STATUS_DONE
134
+ elif "timeout" in log_content.lower():
135
+ status = STATUS_ERROR
136
+ else:
137
+ status = STATUS_NO_RESULTS
138
+
139
+ result = {
140
+ "tool": "bloodhound",
141
+ "status": status,
142
+ "target": target,
143
+ "stats": stats,
144
+ "total_objects": sum(stats.values()),
145
+ "output_path": output_path,
146
+ }
147
+
148
+ if sum(stats.values()) > 0:
149
+ logger.info(f"bloodhound: Collected {sum(stats.values())} AD objects")
150
+
151
+ return result
152
+
153
+ except Exception as e:
154
+ logger.error(f"Error parsing bloodhound job: {e}")
155
+ return {"tool": "bloodhound", "status": STATUS_ERROR, "error": str(e)}
156
+
157
+ def display_done(
158
+ self,
159
+ job: Dict[str, Any],
160
+ log_path: str,
161
+ show_all: bool = False,
162
+ show_passwords: bool = False,
163
+ ) -> None:
164
+ """Display successful bloodhound results."""
165
+ click.echo()
166
+ click.echo(click.style("=" * 70, fg="green"))
167
+ click.echo(click.style("BLOODHOUND AD COLLECTION", fg="green", bold=True))
168
+ click.echo(click.style("=" * 70, fg="green"))
169
+ click.echo()
170
+
171
+ parse_result = job.get("parse_result", {})
172
+ stats = parse_result.get("stats", {})
173
+ output_path = parse_result.get("output_path", "")
174
+ total = parse_result.get("total_objects", 0)
175
+
176
+ if total > 0:
177
+ click.echo(click.style(" Objects Collected:", bold=True))
178
+ for key, value in stats.items():
179
+ if value > 0:
180
+ click.echo(f" {key.capitalize()}: {value}")
181
+ click.echo()
182
+
183
+ if output_path:
184
+ click.echo(f" Output: {output_path}")
185
+ click.echo()
186
+
187
+ click.echo(click.style(" Next Steps:", fg="cyan"))
188
+ click.echo(" 1. Start BloodHound GUI: bloodhound")
189
+ click.echo(" 2. Import the ZIP file(s)")
190
+ click.echo(" 3. Run query: 'Shortest Path to Domain Admins'")
191
+ click.echo()
192
+
193
+ def display_error(
194
+ self,
195
+ job: Dict[str, Any],
196
+ log_path: str,
197
+ show_all: bool = False,
198
+ ) -> None:
199
+ """Display bloodhound error."""
200
+ click.echo()
201
+ click.echo(click.style("=" * 70, fg="red"))
202
+ click.echo(click.style("BLOODHOUND COLLECTION FAILED", fg="red", bold=True))
203
+ click.echo(click.style("=" * 70, fg="red"))
204
+ click.echo()
205
+
206
+ error = job.get("parse_result", {}).get("error") or job.get("error")
207
+ if error:
208
+ click.echo(f" Error: {error}")
209
+ else:
210
+ click.echo(" Check log for details")
211
+ click.echo()
212
+
213
+ def display_warning(
214
+ self,
215
+ job: Dict[str, Any],
216
+ log_path: str,
217
+ show_all: bool = False,
218
+ ) -> None:
219
+ """Display bloodhound warning."""
220
+ self.display_done(job, log_path, show_all, False)
221
+
222
+ def display_no_results(
223
+ self,
224
+ job: Dict[str, Any],
225
+ log_path: str,
226
+ show_all: bool = False,
227
+ ) -> None:
228
+ """Display bloodhound no results."""
229
+ click.echo()
230
+ click.echo(click.style("=" * 70, fg="yellow"))
231
+ click.echo(click.style("BLOODHOUND NO DATA COLLECTED", fg="yellow", bold=True))
232
+ click.echo(click.style("=" * 70, fg="yellow"))
233
+ click.echo()
234
+ click.echo(" No AD objects were collected.")
235
+ click.echo(" Possible causes:")
236
+ click.echo(" - Invalid credentials")
237
+ click.echo(" - Network connectivity issues")
238
+ click.echo(" - Domain controller not reachable")
239
+ click.echo()
240
+
241
+
242
+ # Register handler
243
+ handler = BloodhoundHandler()
@@ -0,0 +1,311 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Handler for Certipy - Active Directory Certificate Services (ADCS) enumeration.
4
+ Parses vulnerable certificate templates (ESC1-ESC8).
5
+ """
6
+ import logging
7
+ import os
8
+ import re
9
+ from typing import Any, Dict, List, Optional
10
+
11
+ from souleyez.handlers.base import BaseToolHandler
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ STATUS_DONE = "done"
16
+ STATUS_ERROR = "error"
17
+ STATUS_WARNING = "warning"
18
+ STATUS_NO_RESULTS = "no_results"
19
+
20
+
21
+ class CertipyHandler(BaseToolHandler):
22
+ """Handler for Certipy ADCS enumeration."""
23
+
24
+ tool_name = "certipy"
25
+ display_name = "Certipy"
26
+
27
+ has_error_handler = True
28
+ has_warning_handler = True
29
+ has_no_results_handler = True
30
+ has_done_handler = True
31
+
32
+ # ESC vulnerability patterns
33
+ ESC_PATTERNS = {
34
+ "ESC1": r"ESC1\s*:.*Enrollee Supplies Subject",
35
+ "ESC2": r"ESC2\s*:.*Any Purpose",
36
+ "ESC3": r"ESC3\s*:.*Certificate Request Agent",
37
+ "ESC4": r"ESC4\s*:.*Vulnerable Access Control",
38
+ "ESC5": r"ESC5\s*:.*PKI Object Access Control",
39
+ "ESC6": r"ESC6\s*:.*EDITF_ATTRIBUTESUBJECTALTNAME2",
40
+ "ESC7": r"ESC7\s*:.*CA Access Control",
41
+ "ESC8": r"ESC8\s*:.*NTLM Relay.*AD CS",
42
+ "ESC9": r"ESC9\s*:.*No Security Extension",
43
+ "ESC10": r"ESC10\s*:.*Weak Certificate Mappings",
44
+ }
45
+
46
+ def parse_job(
47
+ self,
48
+ engagement_id: int,
49
+ log_path: str,
50
+ job: Dict[str, Any],
51
+ host_manager: Optional[Any] = None,
52
+ findings_manager: Optional[Any] = None,
53
+ credentials_manager: Optional[Any] = None,
54
+ ) -> Dict[str, Any]:
55
+ """Parse certipy results."""
56
+ try:
57
+ target = job.get("target", "")
58
+ if not log_path or not os.path.exists(log_path):
59
+ return {
60
+ "tool": "certipy",
61
+ "status": STATUS_ERROR,
62
+ "error": "Log file not found",
63
+ }
64
+
65
+ with open(log_path, "r", encoding="utf-8", errors="replace") as f:
66
+ log_content = f.read()
67
+
68
+ # Strip ANSI codes
69
+ log_content = re.sub(r"\x1b\[[0-9;]*m", "", log_content)
70
+
71
+ vulnerabilities = []
72
+ templates = []
73
+ cas = [] # Certificate Authorities
74
+ domain = ""
75
+
76
+ # Parse domain
77
+ domain_match = re.search(r"Domain\s*:\s*(\S+)", log_content, re.IGNORECASE)
78
+ if domain_match:
79
+ domain = domain_match.group(1)
80
+
81
+ # Track counts from summary output (certipy v5.x stdout format)
82
+ template_count = 0
83
+ ca_count = 0
84
+ enabled_count = 0
85
+
86
+ # Parse counts from stdout summary lines
87
+ template_count_match = re.search(
88
+ r"Found (\d+) certificate templates", log_content
89
+ )
90
+ if template_count_match:
91
+ template_count = int(template_count_match.group(1))
92
+
93
+ ca_count_match = re.search(r"Found (\d+) certificate authorit", log_content)
94
+ if ca_count_match:
95
+ ca_count = int(ca_count_match.group(1))
96
+
97
+ enabled_count_match = re.search(
98
+ r"Found (\d+) enabled certificate templates", log_content
99
+ )
100
+ if enabled_count_match:
101
+ enabled_count = int(enabled_count_match.group(1))
102
+
103
+ # Parse Certificate Authorities (from detailed output if available)
104
+ ca_pattern = r"CA Name\s*:\s*(.+?)(?:\n|$)"
105
+ for match in re.finditer(ca_pattern, log_content):
106
+ ca_name = match.group(1).strip()
107
+ if ca_name and ca_name not in cas:
108
+ cas.append(ca_name)
109
+
110
+ # Also extract CA name from "CA configuration for 'name'" pattern
111
+ ca_config_match = re.search(r"CA configuration for '([^']+)'", log_content)
112
+ if ca_config_match:
113
+ ca_name = ca_config_match.group(1)
114
+ if ca_name and ca_name not in cas:
115
+ cas.append(ca_name)
116
+
117
+ # Parse vulnerable templates (from detailed output if available)
118
+ template_pattern = r"Template Name\s*:\s*(.+?)(?:\n|$)"
119
+ for match in re.finditer(template_pattern, log_content):
120
+ template_name = match.group(1).strip()
121
+ if template_name and template_name not in templates:
122
+ templates.append(template_name)
123
+
124
+ # Check for ESC vulnerabilities
125
+ for esc_name, pattern in self.ESC_PATTERNS.items():
126
+ if re.search(pattern, log_content, re.IGNORECASE):
127
+ # Extract the template name associated with this vulnerability
128
+ # Look for template name before the ESC finding
129
+ vuln = {
130
+ "type": esc_name,
131
+ "severity": (
132
+ "high"
133
+ if esc_name in ["ESC1", "ESC4", "ESC7", "ESC8"]
134
+ else "medium"
135
+ ),
136
+ "description": self._get_esc_description(esc_name),
137
+ }
138
+ vulnerabilities.append(vuln)
139
+
140
+ # Store as finding
141
+ if findings_manager and host_manager:
142
+ try:
143
+ host = host_manager.get_host_by_ip(engagement_id, target)
144
+ if host:
145
+ findings_manager.add_finding(
146
+ host_id=host["id"],
147
+ title=f"ADCS {esc_name} Vulnerability",
148
+ severity=vuln["severity"],
149
+ description=vuln["description"],
150
+ tool="certipy",
151
+ port=0,
152
+ service="adcs",
153
+ )
154
+ except Exception as e:
155
+ logger.debug(f"Could not store finding: {e}")
156
+
157
+ # Check for "[!] Vulnerabilities" section
158
+ vuln_section = re.search(
159
+ r"\[\!\]\s*Vulnerabilities", log_content, re.IGNORECASE
160
+ )
161
+
162
+ # Check for real errors (not just timeout warnings)
163
+ has_real_error = False
164
+ if "error" in log_content.lower() or "failed" in log_content.lower():
165
+ # Exclude known non-fatal warnings
166
+ non_fatal_patterns = [
167
+ "error checking web enrollment: timed out",
168
+ "error checking web enrollment",
169
+ "timed out",
170
+ ]
171
+ # Check if there are errors OTHER than non-fatal ones
172
+ error_lines = [
173
+ line
174
+ for line in log_content.lower().split("\n")
175
+ if "error" in line or "failed" in line
176
+ ]
177
+ for line in error_lines:
178
+ is_non_fatal = any(
179
+ pattern in line for pattern in non_fatal_patterns
180
+ )
181
+ if not is_non_fatal:
182
+ has_real_error = True
183
+ break
184
+
185
+ # Determine status - use counts if detailed lists are empty
186
+ has_results = templates or cas or template_count > 0 or ca_count > 0
187
+
188
+ if vulnerabilities:
189
+ status = (
190
+ STATUS_WARNING
191
+ if any(v["severity"] == "high" for v in vulnerabilities)
192
+ else STATUS_DONE
193
+ )
194
+ elif has_results:
195
+ status = STATUS_DONE
196
+ elif has_real_error:
197
+ status = STATUS_ERROR
198
+ else:
199
+ status = STATUS_NO_RESULTS
200
+
201
+ result = {
202
+ "tool": "certipy",
203
+ "status": status,
204
+ "target": target,
205
+ "domain": domain,
206
+ "certificate_authorities": cas,
207
+ "templates": templates,
208
+ "template_count": template_count,
209
+ "ca_count": ca_count,
210
+ "enabled_template_count": enabled_count,
211
+ "vulnerabilities": vulnerabilities,
212
+ "findings": vulnerabilities, # For chaining compatibility
213
+ }
214
+
215
+ if vulnerabilities:
216
+ logger.warning(
217
+ f"certipy: Found {len(vulnerabilities)} ADCS vulnerability(ies)!"
218
+ )
219
+ if template_count > 0 or templates:
220
+ count = template_count if template_count > 0 else len(templates)
221
+ logger.info(f"certipy: Found {count} certificate template(s)")
222
+
223
+ return result
224
+
225
+ except Exception as e:
226
+ logger.error(f"Error parsing certipy job: {e}")
227
+ return {"tool": "certipy", "status": STATUS_ERROR, "error": str(e)}
228
+
229
+ def _get_esc_description(self, esc_type: str) -> str:
230
+ """Get description for ESC vulnerability type."""
231
+ descriptions = {
232
+ "ESC1": "Template allows requestor to specify Subject Alternative Name (SAN). Attacker can request cert as any user.",
233
+ "ESC2": "Template allows Any Purpose or no EKU. Can be used for any authentication.",
234
+ "ESC3": "Template allows Certificate Request Agent enrollment. Can enroll on behalf of others.",
235
+ "ESC4": "Template has vulnerable access control. Low-privileged users can modify template.",
236
+ "ESC5": "PKI object has vulnerable access control. Can modify CA or template objects.",
237
+ "ESC6": "CA has EDITF_ATTRIBUTESUBJECTALTNAME2 enabled. Requestor can specify SAN in any request.",
238
+ "ESC7": "CA has vulnerable access control. Low-privileged users can manage CA.",
239
+ "ESC8": "CA Web Enrollment or CEP/CES endpoints vulnerable to NTLM relay.",
240
+ "ESC9": "Certificate has no security extension (szOID_NTDS_CA_SECURITY_EXT).",
241
+ "ESC10": "Weak certificate mapping allows impersonation.",
242
+ }
243
+ return descriptions.get(esc_type, f"{esc_type} vulnerability detected")
244
+
245
+ def display_done(
246
+ self,
247
+ job: Dict[str, Any],
248
+ log_path: str,
249
+ show_all: bool = False,
250
+ show_passwords: bool = False,
251
+ ) -> None:
252
+ """Display successful certipy results."""
253
+ import click
254
+
255
+ parse_result = job.get("parse_result", {})
256
+
257
+ domain = parse_result.get("domain", "")
258
+ cas = parse_result.get("certificate_authorities", [])
259
+ templates = parse_result.get("templates", [])
260
+ vulnerabilities = parse_result.get("vulnerabilities", [])
261
+ template_count = parse_result.get("template_count", 0)
262
+ ca_count = parse_result.get("ca_count", 0)
263
+ enabled_count = parse_result.get("enabled_template_count", 0)
264
+
265
+ if domain:
266
+ click.echo(f" Domain: {domain}")
267
+
268
+ # Show CA info
269
+ if cas:
270
+ click.secho(
271
+ f"\n Certificate Authorities ({len(cas)}):", fg="cyan", bold=True
272
+ )
273
+ for ca in cas:
274
+ click.echo(f" - {ca}")
275
+ elif ca_count > 0:
276
+ click.secho(
277
+ f"\n Certificate Authorities: {ca_count}", fg="cyan", bold=True
278
+ )
279
+
280
+ # Show template summary
281
+ if template_count > 0 or enabled_count > 0:
282
+ click.echo(
283
+ f"\n Templates: {template_count} total, {enabled_count} enabled"
284
+ )
285
+
286
+ if vulnerabilities:
287
+ click.secho(
288
+ f"\n ADCS Vulnerabilities ({len(vulnerabilities)}):",
289
+ fg="red",
290
+ bold=True,
291
+ )
292
+ for vuln in vulnerabilities:
293
+ esc_type = vuln.get("type", "")
294
+ severity = vuln.get("severity", "medium")
295
+ desc = vuln.get("description", "")
296
+
297
+ color = "red" if severity == "high" else "yellow"
298
+ click.secho(f" [{esc_type}] {desc}", fg=color)
299
+ elif template_count > 0:
300
+ click.secho("\n No vulnerable templates found (ESC1-ESC10)", fg="green")
301
+
302
+ if templates and show_all:
303
+ click.secho(f"\n Certificate Templates ({len(templates)}):", fg="cyan")
304
+ for template in templates[:10]: # Limit display
305
+ click.echo(f" - {template}")
306
+ if len(templates) > 10:
307
+ click.echo(f" ... and {len(templates) - 10} more")
308
+
309
+
310
+ # Register handler
311
+ handler = CertipyHandler()