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
@@ -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"]