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.

Potentially problematic release.


This version of souleyez might be problematic. Click here for more details.

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