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.
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
@@ -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()