souleyez 2.43.29__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 +22783 -10678
  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.29.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.29.dist-info → souleyez-2.43.32.dist-info}/WHEEL +1 -1
  353. souleyez-2.43.29.dist-info/RECORD +0 -379
  354. {souleyez-2.43.29.dist-info → souleyez-2.43.32.dist-info}/entry_points.txt +0 -0
  355. {souleyez-2.43.29.dist-info → souleyez-2.43.32.dist-info}/licenses/LICENSE +0 -0
  356. {souleyez-2.43.29.dist-info → souleyez-2.43.32.dist-info}/top_level.txt +0 -0
@@ -11,14 +11,14 @@ from typing import Dict, Any, List
11
11
  def parse_hashcat_output(output: str, hash_file: str = "") -> Dict[str, Any]:
12
12
  """
13
13
  Parse hashcat output and extract cracked passwords.
14
-
14
+
15
15
  Hashcat output format:
16
16
  <hash>:<password>
17
-
17
+
18
18
  Args:
19
19
  output: Raw hashcat output text
20
- hash_file: Path to hash file (for reference)
21
-
20
+ hash_file: Path to hash file (for reference, also used to map hashes to usernames)
21
+
22
22
  Returns:
23
23
  Dict with structure:
24
24
  {
@@ -27,7 +27,8 @@ def parse_hashcat_output(output: str, hash_file: str = "") -> Dict[str, Any]:
27
27
  {
28
28
  'hash': str,
29
29
  'password': str,
30
- 'hash_type': str # if identifiable
30
+ 'hash_type': str, # if identifiable
31
+ 'username': str # if available from hash file
31
32
  }
32
33
  ],
33
34
  'stats': {
@@ -37,123 +38,367 @@ def parse_hashcat_output(output: str, hash_file: str = "") -> Dict[str, Any]:
37
38
  }
38
39
  """
39
40
  result = {
40
- 'hash_file': hash_file,
41
- 'cracked': [],
42
- 'stats': {
43
- 'cracked_count': 0,
44
- 'status': 'unknown'
45
- }
41
+ "hash_file": hash_file,
42
+ "cracked": [],
43
+ "stats": {
44
+ "cracked_count": 0,
45
+ "total_count": 0,
46
+ "potfile_hits": 0, # Hashes already cracked in potfile
47
+ "status": "unknown",
48
+ },
46
49
  }
47
-
48
- lines = output.split('\n')
49
-
50
+
51
+ # Build hash → username mapping from the original hash file
52
+ # This handles --username format where input is username:hash
53
+ hash_to_username = {}
54
+ if hash_file:
55
+ try:
56
+ import os
57
+
58
+ if os.path.exists(hash_file):
59
+ with open(hash_file, "r", encoding="utf-8", errors="replace") as f:
60
+ for line in f:
61
+ line = line.strip()
62
+ if ":" in line and not line.startswith("#"):
63
+ parts = line.split(":")
64
+ if len(parts) >= 2:
65
+ # Format: username:hash or username:hash:... (for complex hashes)
66
+ potential_user = parts[0].strip()
67
+ potential_hash = parts[1].strip()
68
+ # Hash should be hex (MD5, SHA, etc.) or start with $
69
+ if re.match(
70
+ r"^[a-fA-F0-9]+$", potential_hash
71
+ ) or potential_hash.startswith("$"):
72
+ hash_to_username[potential_hash.lower()] = (
73
+ potential_user
74
+ )
75
+ except Exception:
76
+ pass # Continue without username mapping
77
+
78
+ lines = output.split("\n")
79
+
80
+ # Check for potfile hits (already cracked)
81
+ for line in lines:
82
+ if "Removed hash found as potfile entry" in line:
83
+ result["stats"]["potfile_hits"] += 1
84
+ # Also parse "INFO: Removed 3 hashes found as potfile entries"
85
+ potfile_match = re.search(
86
+ r"Removed (\d+) hash(?:es)? found as potfile entr", line
87
+ )
88
+ if potfile_match:
89
+ result["stats"]["potfile_hits"] = int(potfile_match.group(1))
90
+
50
91
  for line in lines:
51
92
  line_stripped = line.strip()
52
-
53
- # Parse cracked hash:password pairs
54
- # Format varies but typically: hash:password
55
- if ':' in line_stripped and not line_stripped.startswith('#'):
56
- # Skip status lines
57
- if any(x in line_stripped.lower() for x in ['status', 'progress', 'speed', 'recovered']):
58
- continue
59
-
60
- parts = line_stripped.split(':', 1)
93
+
94
+ # Skip empty lines and comments
95
+ if not line_stripped or line_stripped.startswith("#"):
96
+ continue
97
+
98
+ # === PRIORITY 1: Check for cracked Kerberos hashes ===
99
+ # Format: $krb5tgs$23$*user$realm$spn*$...:password
100
+ # Format: $krb5asrep$23$user@realm:hash:password
101
+ if "$krb5" in line_stripped:
102
+ krb_start = line_stripped.find("$krb5")
103
+ if krb_start >= 0:
104
+ krb_portion = line_stripped[krb_start:]
105
+ # Password is after the last colon
106
+ last_colon = krb_portion.rfind(":")
107
+ if last_colon > 0:
108
+ hash_value = krb_portion[:last_colon]
109
+ password = krb_portion[last_colon + 1 :]
110
+ # Validate: hash must be long, password must exist and not look like hash data
111
+ if (
112
+ password
113
+ and len(hash_value) > 50
114
+ and not password.startswith("$")
115
+ ):
116
+ # Extract username from TGS hash: $krb5tgs$23$*user$realm$spn*$...
117
+ username = None
118
+ user_match = re.search(
119
+ r"\$krb5(?:tgs|asrep)\$\d+\$\*([^$*]+)", hash_value
120
+ )
121
+ if user_match:
122
+ username = user_match.group(1)
123
+ result["cracked"].append(
124
+ {
125
+ "hash": hash_value,
126
+ "password": password,
127
+ "hash_type": "kerberos",
128
+ "username": username,
129
+ }
130
+ )
131
+ continue
132
+
133
+ # === PRIORITY 2: Parse status line ===
134
+ # Multiple formats: "Status...........: Cracked" or "Status........: Cracked"
135
+ # Use flexible pattern that handles varying dots/spaces
136
+ status_match = re.search(
137
+ r"Status[.\s]+:\s*(Cracked|Exhausted|Running|Quit)", line_stripped
138
+ )
139
+ if status_match:
140
+ status_value = status_match.group(1).lower()
141
+ result["stats"]["status"] = status_value
142
+ continue
143
+
144
+ # === PRIORITY 3: Parse recovered count ===
145
+ # Format: "Recovered........: 1/1 (100.00%)" or "Recovered: 1/1"
146
+ recovered_match = re.search(r"Recovered[.\s]*:\s*(\d+)/(\d+)", line_stripped)
147
+ if recovered_match:
148
+ cracked_count = int(recovered_match.group(1))
149
+ total_count = int(recovered_match.group(2))
150
+ result["stats"]["cracked_count"] = cracked_count
151
+ result["stats"]["total_count"] = total_count
152
+ # If recovered > 0, mark as cracked
153
+ if cracked_count > 0 and result["stats"]["status"] == "unknown":
154
+ result["stats"]["status"] = "cracked"
155
+ continue
156
+
157
+ # === Skip known status/progress lines ===
158
+ line_lower = line_stripped.lower()
159
+ if any(
160
+ x in line_lower
161
+ for x in [
162
+ "progress",
163
+ "speed",
164
+ "session",
165
+ "time.",
166
+ "[s]tatus",
167
+ "hash.mode",
168
+ "hash.target",
169
+ "kernel",
170
+ "guess",
171
+ "restore",
172
+ "candidate",
173
+ "hardware",
174
+ "started",
175
+ "stopped",
176
+ "watchdog",
177
+ "attention",
178
+ "pure kernels",
179
+ "optimizers",
180
+ "bitmaps",
181
+ "hashes:",
182
+ "initializ",
183
+ "dictionary cache",
184
+ "approaching",
185
+ "workload",
186
+ "keyspace",
187
+ "rejected",
188
+ "accel",
189
+ "loops",
190
+ "thr",
191
+ "vec",
192
+ ]
193
+ ):
194
+ continue
195
+
196
+ # === PRIORITY 4: Check for NTLM/other hashes ===
197
+ # Format: hash:password (simple colon-separated)
198
+ # Format with --username: username:hash:password
199
+ if ":" in line_stripped:
200
+ parts = line_stripped.split(":")
201
+ username = None
202
+ hash_value = None
203
+ password = None
204
+
61
205
  if len(parts) == 2:
206
+ # Simple hash:password format
62
207
  hash_value = parts[0].strip()
63
208
  password = parts[1].strip()
64
-
65
- # Basic validation - hashes are typically hex or base64
66
- if len(hash_value) >= 16 and password: # Minimum hash length
67
- result['cracked'].append({
68
- 'hash': hash_value,
69
- 'password': password,
70
- 'hash_type': 'unknown' # Could be determined from -m flag
71
- })
72
-
73
- # Parse status information
74
- if 'Status.........:' in line_stripped:
75
- if 'Cracked' in line_stripped:
76
- result['stats']['status'] = 'cracked'
77
- elif 'Exhausted' in line_stripped:
78
- result['stats']['status'] = 'exhausted'
79
- elif 'Running' in line_stripped:
80
- result['stats']['status'] = 'running'
81
-
82
- # Parse recovered count
83
- recovered_match = re.search(r'Recovered[.\s]+:\s+(\d+)/(\d+)', line_stripped)
84
- if recovered_match:
85
- result['stats']['cracked_count'] = int(recovered_match.group(1))
86
- result['stats']['total_count'] = int(recovered_match.group(2))
87
-
88
- # Update count from actual cracked list if not found in stats
89
- if result['stats']['cracked_count'] == 0 and result['cracked']:
90
- result['stats']['cracked_count'] = len(result['cracked'])
91
-
209
+ elif len(parts) == 3:
210
+ # username:hash:password format (--username flag)
211
+ username = parts[0].strip()
212
+ hash_value = parts[1].strip()
213
+ password = parts[2].strip()
214
+ elif len(parts) > 3:
215
+ # Could be username:hash:password where password contains ':'
216
+ # Try: first part is username, second is hash, rest is password
217
+ username = parts[0].strip()
218
+ hash_value = parts[1].strip()
219
+ password = ":".join(parts[2:]).strip()
220
+
221
+ # Validate: hash looks like hex, password exists
222
+ if hash_value and password and len(hash_value) >= 16:
223
+ if re.match(r"^[a-fA-F0-9]+$", hash_value) or hash_value.startswith(
224
+ "$"
225
+ ):
226
+ cracked_entry = {
227
+ "hash": hash_value,
228
+ "password": password,
229
+ "hash_type": "unknown",
230
+ }
231
+ # Get username from parsing or from hash file mapping
232
+ if username:
233
+ cracked_entry["username"] = username
234
+ elif hash_to_username:
235
+ # Look up username from hash file mapping
236
+ mapped_user = hash_to_username.get(hash_value.lower())
237
+ if mapped_user:
238
+ cracked_entry["username"] = mapped_user
239
+ result["cracked"].append(cracked_entry)
240
+
241
+ # === Post-processing ===
242
+ # Update count from cracked list if not found in stats
243
+ if result["stats"]["cracked_count"] == 0 and result["cracked"]:
244
+ result["stats"]["cracked_count"] = len(result["cracked"])
245
+
246
+ # If we found cracked hashes, ensure status reflects that
247
+ if result["cracked"] and result["stats"]["status"] == "unknown":
248
+ result["stats"]["status"] = "cracked"
249
+
250
+ # If no new cracks but potfile hits, retrieve from potfile using --show
251
+ if not result["cracked"] and result["stats"]["potfile_hits"] > 0:
252
+ if result["stats"]["status"] in ["unknown", "exhausted"]:
253
+ result["stats"]["status"] = "already_cracked"
254
+
255
+ # Try to retrieve already cracked passwords using hashcat --show
256
+ if hash_file:
257
+ potfile_cracked = _get_potfile_cracked(hash_file, hash_to_username)
258
+ if potfile_cracked:
259
+ result["cracked"] = potfile_cracked
260
+ result["stats"]["cracked_count"] = len(potfile_cracked)
261
+
92
262
  return result
93
263
 
94
264
 
265
+ def _get_potfile_cracked(
266
+ hash_file: str, hash_to_username: Dict[str, str] = None
267
+ ) -> List[Dict[str, Any]]:
268
+ """
269
+ Retrieve already cracked passwords from hashcat potfile using --show.
270
+
271
+ Args:
272
+ hash_file: Path to the hash file
273
+ hash_to_username: Mapping of hash -> username from the hash file
274
+
275
+ Returns:
276
+ List of cracked password dicts with username, hash, password
277
+ """
278
+ import subprocess
279
+ import os
280
+
281
+ cracked = []
282
+
283
+ if not hash_file or not os.path.exists(hash_file):
284
+ return cracked
285
+
286
+ try:
287
+ # Run hashcat --show to get cracked passwords from potfile
288
+ # Use -m 0 for MD5 (most common from SQLi dumps)
289
+ # --username flag tells hashcat the input format is username:hash
290
+ cmd = ["hashcat", "--show", "-m", "0", "--username", hash_file]
291
+ proc = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
292
+
293
+ # Parse output: username:hash:password
294
+ for line in proc.stdout.strip().split("\n"):
295
+ line = line.strip()
296
+ if not line:
297
+ continue
298
+
299
+ parts = line.split(":")
300
+ if len(parts) >= 3:
301
+ username = parts[0].strip()
302
+ hash_value = parts[1].strip()
303
+ password = ":".join(parts[2:]).strip() # Password may contain ':'
304
+
305
+ if username and password:
306
+ cracked.append(
307
+ {
308
+ "hash": hash_value,
309
+ "password": password,
310
+ "hash_type": "md5",
311
+ "username": username,
312
+ }
313
+ )
314
+ elif len(parts) == 2:
315
+ # hash:password format (no username in output)
316
+ hash_value = parts[0].strip()
317
+ password = parts[1].strip()
318
+
319
+ # Try to get username from mapping
320
+ username = None
321
+ if hash_to_username:
322
+ username = hash_to_username.get(hash_value.lower())
323
+
324
+ if password:
325
+ cracked.append(
326
+ {
327
+ "hash": hash_value,
328
+ "password": password,
329
+ "hash_type": "md5",
330
+ "username": username,
331
+ }
332
+ )
333
+ except subprocess.TimeoutExpired:
334
+ pass
335
+ except Exception:
336
+ pass
337
+
338
+ return cracked
339
+
340
+
95
341
  def parse_hashcat_potfile(potfile_path: str) -> List[Dict[str, str]]:
96
342
  """
97
343
  Parse hashcat potfile (hashcat.potfile) for cracked passwords.
98
-
344
+
99
345
  The potfile contains all previously cracked hashes in format:
100
346
  hash:password
101
-
347
+
102
348
  Args:
103
349
  potfile_path: Path to hashcat.potfile
104
-
350
+
105
351
  Returns:
106
352
  List of dicts with hash and password
107
353
  """
108
354
  cracked = []
109
-
355
+
110
356
  try:
111
- with open(potfile_path, 'r', encoding='utf-8', errors='replace') as f:
357
+ with open(potfile_path, "r", encoding="utf-8", errors="replace") as f:
112
358
  for line in f:
113
359
  line = line.strip()
114
- if ':' in line:
115
- parts = line.split(':', 1)
360
+ if ":" in line:
361
+ parts = line.split(":", 1)
116
362
  if len(parts) == 2:
117
- cracked.append({
118
- 'hash': parts[0],
119
- 'password': parts[1]
120
- })
363
+ cracked.append({"hash": parts[0], "password": parts[1]})
121
364
  except Exception:
122
365
  pass
123
-
366
+
124
367
  return cracked
125
368
 
126
369
 
127
- def map_to_credentials(parsed_data: Dict[str, Any], engagement_id: int, host_id: int = None) -> List[Dict[str, Any]]:
370
+ def map_to_credentials(
371
+ parsed_data: Dict[str, Any], engagement_id: int, host_id: int = None
372
+ ) -> List[Dict[str, Any]]:
128
373
  """
129
374
  Convert parsed hashcat data into credential records for database storage.
130
-
375
+
131
376
  Args:
132
377
  parsed_data: Output from parse_hashcat_output()
133
378
  engagement_id: Current engagement ID
134
379
  host_id: Optional host ID if known
135
-
380
+
136
381
  Returns:
137
382
  List of credential dicts ready for CredentialManager.add()
138
383
  """
139
384
  credentials = []
140
-
141
- for cracked in parsed_data.get('cracked', []):
385
+
386
+ for cracked in parsed_data.get("cracked", []):
142
387
  credential = {
143
- 'password': cracked['password'],
144
- 'credential_type': 'password',
145
- 'source': 'hashcat',
146
- 'validation_status': 'cracked',
147
- 'notes': f"Cracked from hash: {cracked['hash'][:32]}...",
148
- 'hash_original': cracked['hash']
388
+ "password": cracked["password"],
389
+ "credential_type": "password",
390
+ "source": "hashcat",
391
+ "validation_status": "cracked",
392
+ "notes": f"Cracked from hash: {cracked['hash'][:32]}...",
393
+ "hash_original": cracked["hash"],
149
394
  }
150
-
395
+
151
396
  if host_id:
152
- credential['host_id'] = host_id
153
-
397
+ credential["host_id"] = host_id
398
+
154
399
  credentials.append(credential)
155
-
400
+
156
401
  return credentials
157
402
 
158
403
 
159
- __all__ = ['parse_hashcat_output', 'parse_hashcat_potfile', 'map_to_credentials']
404
+ __all__ = ["parse_hashcat_output", "parse_hashcat_potfile", "map_to_credentials"]