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
@@ -49,22 +49,26 @@ def parse_hydra_output(output: str, target: str = "") -> Dict[str, Any]:
49
49
  }
50
50
  """
51
51
  result = {
52
- 'target_host': target,
53
- 'service': None,
54
- 'port': None,
55
- 'credentials': [],
56
- 'usernames': [], # For username-only enumeration results
57
- 'attempts': 0,
58
- 'status': 'failed'
52
+ "target_host": target,
53
+ "service": None,
54
+ "port": None,
55
+ "credentials": [],
56
+ "usernames": [], # For username-only enumeration results
57
+ "attempts": 0,
58
+ "status": "failed",
59
59
  }
60
60
 
61
- lines = output.split('\n')
61
+ lines = output.split("\n")
62
62
 
63
63
  # Check for WordPress admin redirect (indicates successful login)
64
- has_wp_admin_redirect = bool(re.search(r'redirected to.*[:/]wp-admin', output, re.IGNORECASE))
64
+ has_wp_admin_redirect = bool(
65
+ re.search(r"redirected to.*[:/]wp-admin", output, re.IGNORECASE)
66
+ )
65
67
 
66
68
  # Detect if this is a WordPress/http-post-form attack
67
- is_wordpress_attack = 'http-post-form' in output.lower() and ('wp-login' in output.lower() or 'wordpress' in output.lower())
69
+ is_wordpress_attack = "http-post-form" in output.lower() and (
70
+ "wp-login" in output.lower() or "wordpress" in output.lower()
71
+ )
68
72
 
69
73
  # Track attempted credentials from [ATTEMPT] lines (for when Hydra doesn't report match lines)
70
74
  last_attempt = None
@@ -76,13 +80,13 @@ def parse_hydra_output(output: str, target: str = "") -> Dict[str, Any]:
76
80
  # Format: [ATTEMPT] target HOST - login "USER" - pass "PASS" - N of M [child X] (Y/Z)
77
81
  attempt_match = re.search(
78
82
  r'\[ATTEMPT\]\s+target\s+(\S+)\s+-\s+login\s+"([^"]+)"\s+-\s+pass\s+"([^"]+)"',
79
- line_stripped
83
+ line_stripped,
80
84
  )
81
85
  if attempt_match:
82
86
  last_attempt = {
83
- 'host': attempt_match.group(1),
84
- 'username': attempt_match.group(2),
85
- 'password': attempt_match.group(3)
87
+ "host": attempt_match.group(1),
88
+ "username": attempt_match.group(2),
89
+ "password": attempt_match.group(3),
86
90
  }
87
91
 
88
92
  # Parse successful login lines with multiple format support
@@ -100,8 +104,9 @@ def parse_hydra_output(output: str, target: str = "") -> Dict[str, Any]:
100
104
 
101
105
  # Try standard format: [PORT][SERVICE] host: HOST login: USER password: PASS
102
106
  login_match = re.search(
103
- r'\[(\d+)\]\[([\w-]+)\]\s+host:\s*(\S+)\s+login:\s*(\S+)\s+password:\s*(.+)',
104
- line_stripped, re.IGNORECASE
107
+ r"\[(\d+)\]\[([\w-]+)\]\s+host:\s*(\S+)\s+login:\s*(\S+)\s+password:\s*(.+)",
108
+ line_stripped,
109
+ re.IGNORECASE,
105
110
  )
106
111
  if login_match:
107
112
  port = int(login_match.group(1))
@@ -113,8 +118,9 @@ def parse_hydra_output(output: str, target: str = "") -> Dict[str, Any]:
113
118
  # Try swapped format: [SERVICE][PORT]
114
119
  if not login_match:
115
120
  login_match = re.search(
116
- r'\[([\w-]+)\]\[(\d+)\]\s+host:\s*(\S+)\s+login:\s*(\S+)\s+password:\s*(.+)',
117
- line_stripped, re.IGNORECASE
121
+ r"\[([\w-]+)\]\[(\d+)\]\s+host:\s*(\S+)\s+login:\s*(\S+)\s+password:\s*(.+)",
122
+ line_stripped,
123
+ re.IGNORECASE,
118
124
  )
119
125
  if login_match:
120
126
  service = login_match.group(1).lower()
@@ -126,8 +132,9 @@ def parse_hydra_output(output: str, target: str = "") -> Dict[str, Any]:
126
132
  # Try format without "host:" label
127
133
  if not login_match:
128
134
  login_match = re.search(
129
- r'\[(\d+)\]\[([\w-]+)\]\s+(\d+\.\d+\.\d+\.\d+|\S+)\s+login:\s*(\S+)\s+password:\s*(.+)',
130
- line_stripped, re.IGNORECASE
135
+ r"\[(\d+)\]\[([\w-]+)\]\s+(\d+\.\d+\.\d+\.\d+|\S+)\s+login:\s*(\S+)\s+password:\s*(.+)",
136
+ line_stripped,
137
+ re.IGNORECASE,
131
138
  )
132
139
  if login_match:
133
140
  port = int(login_match.group(1))
@@ -137,10 +144,13 @@ def parse_hydra_output(output: str, target: str = "") -> Dict[str, Any]:
137
144
  password = login_match.group(5).strip()
138
145
 
139
146
  # Try flexible format with any whitespace between fields
147
+ # Note: Use \s+login: to require whitespace before "login:" and require the colon
148
+ # This prevents matching "login" inside paths like "wp-login.php"
140
149
  if not login_match:
141
150
  login_match = re.search(
142
- r'\[(\d+)\]\[([\w-]+)\].*?(?:host:?\s*)?(\d+\.\d+\.\d+\.\d+|\S+\.\S+).*?login:?\s*(\S+).*?password:?\s*(.+)',
143
- line_stripped, re.IGNORECASE
151
+ r"\[(\d+)\]\[([\w-]+)\].*?(?:host:?\s*)?(\d+\.\d+\.\d+\.\d+|\S+\.\S+).*?\s+login:\s*(\S+).*?password:\s*(.+)",
152
+ line_stripped,
153
+ re.IGNORECASE,
144
154
  )
145
155
  if login_match:
146
156
  port = int(login_match.group(1))
@@ -151,76 +161,85 @@ def parse_hydra_output(output: str, target: str = "") -> Dict[str, Any]:
151
161
 
152
162
  if login_match and port and service and username:
153
163
  # Store service info if not already set
154
- if not result['service']:
155
- result['service'] = service
156
- if not result['port']:
157
- result['port'] = port
158
- if not result['target_host'] or result['target_host'] == '':
159
- result['target_host'] = host
164
+ if not result["service"]:
165
+ result["service"] = service
166
+ if not result["port"]:
167
+ result["port"] = port
168
+ if not result["target_host"] or result["target_host"] == "":
169
+ result["target_host"] = host
160
170
 
161
171
  # For WordPress attacks, check if this is a full credential or just username enumeration
162
172
  if is_wordpress_attack and not has_wp_admin_redirect:
163
173
  # No redirect to wp-admin means only username was validated
164
174
  # (password was wrong but username exists)
165
- if username not in result['usernames']:
166
- result['usernames'].append(username)
167
- result['status'] = 'partial' # Partial success - username found
175
+ if username not in result["usernames"]:
176
+ result["usernames"].append(username)
177
+ result["status"] = "partial" # Partial success - username found
168
178
  else:
169
179
  # Full credential validation (redirect found or non-WordPress service)
170
180
  credential = {
171
- 'username': username,
172
- 'password': password,
173
- 'service': service,
174
- 'port': port,
175
- 'host': host,
176
- 'username_only': False
181
+ "username": username,
182
+ "password": password,
183
+ "service": service,
184
+ "port": port,
185
+ "host": host,
186
+ "username_only": False,
177
187
  }
178
188
 
179
- result['credentials'].append(credential)
180
- result['status'] = 'success'
189
+ result["credentials"].append(credential)
190
+ result["status"] = "success"
181
191
 
182
192
  # Extract attacking target info
183
- attacking_match = re.search(r'attacking\s+([\w-]+)://([^:]+):(\d+)', line_stripped)
193
+ attacking_match = re.search(
194
+ r"attacking\s+([\w-]+)://([^:]+):(\d+)", line_stripped
195
+ )
184
196
  if attacking_match:
185
- result['service'] = attacking_match.group(1).lower()
186
- if not result['target_host'] or result['target_host'] == '':
187
- result['target_host'] = attacking_match.group(2)
188
- result['port'] = int(attacking_match.group(3))
197
+ result["service"] = attacking_match.group(1).lower()
198
+ if not result["target_host"] or result["target_host"] == "":
199
+ result["target_host"] = attacking_match.group(2)
200
+ result["port"] = int(attacking_match.group(3))
189
201
 
190
202
  # Extract attempt count
191
- attempts_match = re.search(r'(\d+)\s+tasks', line_stripped)
203
+ attempts_match = re.search(r"(\d+)\s+tasks", line_stripped)
192
204
  if attempts_match:
193
- result['attempts'] = int(attempts_match.group(1))
205
+ result["attempts"] = int(attempts_match.group(1))
194
206
 
195
207
  # For WordPress attacks: If we see wp-admin redirect but no match lines were found,
196
208
  # extract credentials from the last ATTEMPT line (handles F=loginform case where
197
209
  # Hydra doesn't produce match lines even for valid credentials)
198
- if is_wordpress_attack and has_wp_admin_redirect and not result['credentials'] and last_attempt:
210
+ if (
211
+ is_wordpress_attack
212
+ and has_wp_admin_redirect
213
+ and not result["credentials"]
214
+ and last_attempt
215
+ ):
199
216
  credential = {
200
- 'username': last_attempt['username'],
201
- 'password': last_attempt['password'],
202
- 'service': result.get('service', 'http-post-form'),
203
- 'port': result.get('port', 80),
204
- 'host': last_attempt['host'],
205
- 'username_only': False
217
+ "username": last_attempt["username"],
218
+ "password": last_attempt["password"],
219
+ "service": result.get("service", "http-post-form"),
220
+ "port": result.get("port", 80),
221
+ "host": last_attempt["host"],
222
+ "username_only": False,
206
223
  }
207
- result['credentials'].append(credential)
208
- result['status'] = 'success'
224
+ result["credentials"].append(credential)
225
+ result["status"] = "success"
209
226
 
210
227
  # Determine final status
211
- if result['credentials']:
212
- result['status'] = 'success'
213
- elif result['usernames']:
214
- result['status'] = 'partial'
215
- elif 'attack finished' in output.lower():
216
- result['status'] = 'failed'
228
+ if result["credentials"]:
229
+ result["status"] = "success"
230
+ elif result["usernames"]:
231
+ result["status"] = "partial"
232
+ elif "attack finished" in output.lower():
233
+ result["status"] = "failed"
217
234
  else:
218
- result['status'] = 'partial'
235
+ result["status"] = "partial"
219
236
 
220
237
  return result
221
238
 
222
239
 
223
- def map_to_credentials(parsed_data: Dict[str, Any], engagement_id: int, host_id: Optional[int] = None) -> List[Dict[str, Any]]:
240
+ def map_to_credentials(
241
+ parsed_data: Dict[str, Any], engagement_id: int, host_id: Optional[int] = None
242
+ ) -> List[Dict[str, Any]]:
224
243
  """
225
244
  Convert parsed Hydra data into credential records for database storage.
226
245
 
@@ -234,21 +253,21 @@ def map_to_credentials(parsed_data: Dict[str, Any], engagement_id: int, host_id:
234
253
  """
235
254
  credentials = []
236
255
 
237
- for cred in parsed_data.get('credentials', []):
256
+ for cred in parsed_data.get("credentials", []):
238
257
  credential = {
239
- 'username': cred['username'],
240
- 'password': cred['password'],
241
- 'credential_type': _service_to_credential_type(cred['service']),
242
- 'source': 'hydra',
243
- 'validation_status': 'confirmed', # Hydra only reports successful logins
244
- 'notes': f"Brute-forced via Hydra on {cred['service']}:{cred['port']}",
245
- 'target': f"{parsed_data.get('target_host')}:{cred['port']}",
246
- 'service': cred['service'],
247
- 'port': cred['port']
258
+ "username": cred["username"],
259
+ "password": cred["password"],
260
+ "credential_type": _service_to_credential_type(cred["service"]),
261
+ "source": "hydra",
262
+ "validation_status": "confirmed", # Hydra only reports successful logins
263
+ "notes": f"Brute-forced via Hydra on {cred['service']}:{cred['port']}",
264
+ "target": f"{parsed_data.get('target_host')}:{cred['port']}",
265
+ "service": cred["service"],
266
+ "port": cred["port"],
248
267
  }
249
268
 
250
269
  if host_id:
251
- credential['host_id'] = host_id
270
+ credential["host_id"] = host_id
252
271
 
253
272
  credentials.append(credential)
254
273
 
@@ -268,29 +287,29 @@ def _service_to_credential_type(service: str) -> str:
268
287
  service_lower = service.lower()
269
288
 
270
289
  type_map = {
271
- 'ssh': 'ssh',
272
- 'ftp': 'ftp',
273
- 'smb': 'windows',
274
- 'rdp': 'windows',
275
- 'mysql': 'database',
276
- 'postgres': 'database',
277
- 'postgresql': 'database',
278
- 'mssql': 'database',
279
- 'oracle': 'database',
280
- 'http': 'web',
281
- 'https': 'web',
282
- 'http-get': 'web',
283
- 'http-post': 'web',
284
- 'http-get-form': 'web',
285
- 'http-post-form': 'web',
286
- 'https-get-form': 'web',
287
- 'https-post-form': 'web',
288
- 'telnet': 'telnet',
289
- 'vnc': 'vnc',
290
- 'ldap': 'ldap'
290
+ "ssh": "ssh",
291
+ "ftp": "ftp",
292
+ "smb": "windows",
293
+ "rdp": "windows",
294
+ "mysql": "database",
295
+ "postgres": "database",
296
+ "postgresql": "database",
297
+ "mssql": "database",
298
+ "oracle": "database",
299
+ "http": "web",
300
+ "https": "web",
301
+ "http-get": "web",
302
+ "http-post": "web",
303
+ "http-get-form": "web",
304
+ "http-post-form": "web",
305
+ "https-get-form": "web",
306
+ "https-post-form": "web",
307
+ "telnet": "telnet",
308
+ "vnc": "vnc",
309
+ "ldap": "ldap",
291
310
  }
292
311
 
293
- return type_map.get(service_lower, 'other')
312
+ return type_map.get(service_lower, "other")
294
313
 
295
314
 
296
315
  def generate_summary(parsed_data: Dict[str, Any]) -> str:
@@ -303,12 +322,12 @@ def generate_summary(parsed_data: Dict[str, Any]) -> str:
303
322
  Returns:
304
323
  Formatted summary string
305
324
  """
306
- target = parsed_data.get('target_host', 'unknown')
307
- service = parsed_data.get('service', 'unknown')
308
- port = parsed_data.get('port', 'unknown')
309
- creds = parsed_data.get('credentials', [])
310
- usernames = parsed_data.get('usernames', [])
311
- status = parsed_data.get('status', 'unknown')
325
+ target = parsed_data.get("target_host", "unknown")
326
+ service = parsed_data.get("service", "unknown")
327
+ port = parsed_data.get("port", "unknown")
328
+ creds = parsed_data.get("credentials", [])
329
+ usernames = parsed_data.get("usernames", [])
330
+ status = parsed_data.get("status", "unknown")
312
331
 
313
332
  summary = "Hydra Attack Summary\n"
314
333
  summary += f"{'=' * 50}\n"
@@ -348,23 +367,24 @@ def extract_failed_attempts(output: str) -> Dict[str, int]:
348
367
  Returns:
349
368
  Dict with failure statistics
350
369
  """
351
- stats = {
352
- 'total_attempts': 0,
353
- 'successful': 0,
354
- 'failed': 0
355
- }
370
+ stats = {"total_attempts": 0, "successful": 0, "failed": 0}
356
371
 
357
372
  # Count login attempts (successful ones)
358
- successful_count = len(re.findall(r'\[\d+\]\[\w+\]\s+host:', output))
359
- stats['successful'] = successful_count
373
+ successful_count = len(re.findall(r"\[\d+\]\[\w+\]\s+host:", output))
374
+ stats["successful"] = successful_count
360
375
 
361
376
  # Try to extract total from status messages
362
- status_match = re.search(r'(\d+)\s+valid passwords? found', output, re.IGNORECASE)
377
+ status_match = re.search(r"(\d+)\s+valid passwords? found", output, re.IGNORECASE)
363
378
  if status_match:
364
- stats['successful'] = int(status_match.group(1))
379
+ stats["successful"] = int(status_match.group(1))
365
380
 
366
381
  return stats
367
382
 
368
383
 
369
384
  # Export the main functions
370
- __all__ = ['parse_hydra_output', 'map_to_credentials', 'generate_summary', 'extract_failed_attempts']
385
+ __all__ = [
386
+ "parse_hydra_output",
387
+ "map_to_credentials",
388
+ "generate_summary",
389
+ "extract_failed_attempts",
390
+ ]