souleyez 2.43.29__py3-none-any.whl → 3.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. souleyez/__init__.py +1 -2
  2. souleyez/ai/__init__.py +21 -15
  3. souleyez/ai/action_mapper.py +249 -150
  4. souleyez/ai/chain_advisor.py +116 -100
  5. souleyez/ai/claude_provider.py +29 -28
  6. souleyez/ai/context_builder.py +80 -62
  7. souleyez/ai/executor.py +158 -117
  8. souleyez/ai/feedback_handler.py +136 -121
  9. souleyez/ai/llm_factory.py +27 -20
  10. souleyez/ai/llm_provider.py +4 -2
  11. souleyez/ai/ollama_provider.py +6 -9
  12. souleyez/ai/ollama_service.py +44 -37
  13. souleyez/ai/path_scorer.py +91 -76
  14. souleyez/ai/recommender.py +176 -144
  15. souleyez/ai/report_context.py +74 -73
  16. souleyez/ai/report_service.py +84 -66
  17. souleyez/ai/result_parser.py +222 -229
  18. souleyez/ai/safety.py +67 -44
  19. souleyez/auth/__init__.py +23 -22
  20. souleyez/auth/audit.py +36 -26
  21. souleyez/auth/engagement_access.py +65 -48
  22. souleyez/auth/permissions.py +14 -3
  23. souleyez/auth/session_manager.py +54 -37
  24. souleyez/auth/user_manager.py +109 -64
  25. souleyez/commands/audit.py +40 -43
  26. souleyez/commands/auth.py +35 -15
  27. souleyez/commands/deliverables.py +55 -50
  28. souleyez/commands/engagement.py +47 -28
  29. souleyez/commands/license.py +32 -23
  30. souleyez/commands/screenshots.py +36 -32
  31. souleyez/commands/user.py +82 -36
  32. souleyez/config.py +52 -44
  33. souleyez/core/credential_tester.py +87 -81
  34. souleyez/core/cve_mappings.py +179 -192
  35. souleyez/core/cve_matcher.py +162 -148
  36. souleyez/core/msf_auto_mapper.py +100 -83
  37. souleyez/core/msf_chain_engine.py +294 -256
  38. souleyez/core/msf_database.py +153 -70
  39. souleyez/core/msf_integration.py +679 -673
  40. souleyez/core/msf_rpc_client.py +40 -42
  41. souleyez/core/msf_rpc_manager.py +77 -79
  42. souleyez/core/msf_sync_manager.py +241 -181
  43. souleyez/core/network_utils.py +22 -15
  44. souleyez/core/parser_handler.py +34 -25
  45. souleyez/core/pending_chains.py +114 -63
  46. souleyez/core/templates.py +158 -107
  47. souleyez/core/tool_chaining.py +9564 -2881
  48. souleyez/core/version_utils.py +79 -94
  49. souleyez/core/vuln_correlation.py +136 -89
  50. souleyez/core/web_utils.py +33 -32
  51. souleyez/data/wordlists/ad_users.txt +378 -0
  52. souleyez/data/wordlists/api_endpoints_large.txt +769 -0
  53. souleyez/data/wordlists/home_dir_sensitive.txt +39 -0
  54. souleyez/data/wordlists/lfi_payloads.txt +82 -0
  55. souleyez/data/wordlists/passwords_brute.txt +1548 -0
  56. souleyez/data/wordlists/passwords_crack.txt +2479 -0
  57. souleyez/data/wordlists/passwords_spray.txt +386 -0
  58. souleyez/data/wordlists/subdomains_large.txt +5057 -0
  59. souleyez/data/wordlists/usernames_common.txt +694 -0
  60. souleyez/data/wordlists/web_dirs_large.txt +4769 -0
  61. souleyez/detection/__init__.py +1 -1
  62. souleyez/detection/attack_signatures.py +12 -17
  63. souleyez/detection/mitre_mappings.py +61 -55
  64. souleyez/detection/validator.py +97 -86
  65. souleyez/devtools.py +23 -10
  66. souleyez/docs/README.md +4 -4
  67. souleyez/docs/api-reference/cli-commands.md +2 -2
  68. souleyez/docs/developer-guide/adding-new-tools.md +562 -0
  69. souleyez/docs/user-guide/auto-chaining.md +30 -8
  70. souleyez/docs/user-guide/getting-started.md +1 -1
  71. souleyez/docs/user-guide/installation.md +26 -3
  72. souleyez/docs/user-guide/metasploit-integration.md +2 -2
  73. souleyez/docs/user-guide/rbac.md +1 -1
  74. souleyez/docs/user-guide/scope-management.md +1 -1
  75. souleyez/docs/user-guide/siem-integration.md +1 -1
  76. souleyez/docs/user-guide/tools-reference.md +1 -8
  77. souleyez/docs/user-guide/worker-management.md +1 -1
  78. souleyez/engine/background.py +1239 -535
  79. souleyez/engine/base.py +4 -1
  80. souleyez/engine/job_status.py +17 -49
  81. souleyez/engine/log_sanitizer.py +103 -77
  82. souleyez/engine/manager.py +38 -7
  83. souleyez/engine/result_handler.py +2200 -1550
  84. souleyez/engine/worker_manager.py +50 -41
  85. souleyez/export/evidence_bundle.py +72 -62
  86. souleyez/feature_flags/features.py +16 -20
  87. souleyez/feature_flags.py +5 -9
  88. souleyez/handlers/__init__.py +11 -0
  89. souleyez/handlers/base.py +188 -0
  90. souleyez/handlers/bash_handler.py +277 -0
  91. souleyez/handlers/bloodhound_handler.py +243 -0
  92. souleyez/handlers/certipy_handler.py +311 -0
  93. souleyez/handlers/crackmapexec_handler.py +486 -0
  94. souleyez/handlers/dnsrecon_handler.py +344 -0
  95. souleyez/handlers/enum4linux_handler.py +400 -0
  96. souleyez/handlers/evil_winrm_handler.py +493 -0
  97. souleyez/handlers/ffuf_handler.py +815 -0
  98. souleyez/handlers/gobuster_handler.py +1114 -0
  99. souleyez/handlers/gpp_extract_handler.py +334 -0
  100. souleyez/handlers/hashcat_handler.py +444 -0
  101. souleyez/handlers/hydra_handler.py +564 -0
  102. souleyez/handlers/impacket_getuserspns_handler.py +343 -0
  103. souleyez/handlers/impacket_psexec_handler.py +222 -0
  104. souleyez/handlers/impacket_secretsdump_handler.py +426 -0
  105. souleyez/handlers/john_handler.py +286 -0
  106. souleyez/handlers/katana_handler.py +425 -0
  107. souleyez/handlers/kerbrute_handler.py +298 -0
  108. souleyez/handlers/ldapsearch_handler.py +636 -0
  109. souleyez/handlers/lfi_extract_handler.py +464 -0
  110. souleyez/handlers/msf_auxiliary_handler.py +409 -0
  111. souleyez/handlers/msf_exploit_handler.py +380 -0
  112. souleyez/handlers/nikto_handler.py +413 -0
  113. souleyez/handlers/nmap_handler.py +821 -0
  114. souleyez/handlers/nuclei_handler.py +359 -0
  115. souleyez/handlers/nxc_handler.py +417 -0
  116. souleyez/handlers/rdp_sec_check_handler.py +353 -0
  117. souleyez/handlers/registry.py +292 -0
  118. souleyez/handlers/responder_handler.py +232 -0
  119. souleyez/handlers/service_explorer_handler.py +434 -0
  120. souleyez/handlers/smbclient_handler.py +344 -0
  121. souleyez/handlers/smbmap_handler.py +510 -0
  122. souleyez/handlers/smbpasswd_handler.py +296 -0
  123. souleyez/handlers/sqlmap_handler.py +1116 -0
  124. souleyez/handlers/theharvester_handler.py +601 -0
  125. souleyez/handlers/web_login_test_handler.py +327 -0
  126. souleyez/handlers/whois_handler.py +277 -0
  127. souleyez/handlers/wpscan_handler.py +554 -0
  128. souleyez/history.py +32 -16
  129. souleyez/importers/msf_importer.py +106 -75
  130. souleyez/importers/smart_importer.py +208 -147
  131. souleyez/integrations/siem/__init__.py +10 -10
  132. souleyez/integrations/siem/base.py +17 -18
  133. souleyez/integrations/siem/elastic.py +108 -122
  134. souleyez/integrations/siem/factory.py +207 -80
  135. souleyez/integrations/siem/googlesecops.py +146 -154
  136. souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
  137. souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
  138. souleyez/integrations/siem/sentinel.py +107 -109
  139. souleyez/integrations/siem/splunk.py +246 -212
  140. souleyez/integrations/siem/wazuh.py +65 -71
  141. souleyez/integrations/wazuh/__init__.py +5 -5
  142. souleyez/integrations/wazuh/client.py +70 -93
  143. souleyez/integrations/wazuh/config.py +85 -57
  144. souleyez/integrations/wazuh/host_mapper.py +28 -36
  145. souleyez/integrations/wazuh/sync.py +78 -68
  146. souleyez/intelligence/__init__.py +4 -5
  147. souleyez/intelligence/correlation_analyzer.py +309 -295
  148. souleyez/intelligence/exploit_knowledge.py +661 -623
  149. souleyez/intelligence/exploit_suggestions.py +159 -139
  150. souleyez/intelligence/gap_analyzer.py +132 -97
  151. souleyez/intelligence/gap_detector.py +251 -214
  152. souleyez/intelligence/sensitive_tables.py +266 -129
  153. souleyez/intelligence/service_parser.py +137 -123
  154. souleyez/intelligence/surface_analyzer.py +407 -268
  155. souleyez/intelligence/target_parser.py +159 -162
  156. souleyez/licensing/__init__.py +6 -6
  157. souleyez/licensing/validator.py +17 -19
  158. souleyez/log_config.py +79 -54
  159. souleyez/main.py +1505 -687
  160. souleyez/migrations/fix_job_counter.py +16 -14
  161. souleyez/parsers/bloodhound_parser.py +41 -39
  162. souleyez/parsers/crackmapexec_parser.py +178 -111
  163. souleyez/parsers/dalfox_parser.py +72 -77
  164. souleyez/parsers/dnsrecon_parser.py +103 -91
  165. souleyez/parsers/enum4linux_parser.py +183 -153
  166. souleyez/parsers/ffuf_parser.py +29 -25
  167. souleyez/parsers/gobuster_parser.py +301 -41
  168. souleyez/parsers/hashcat_parser.py +324 -79
  169. souleyez/parsers/http_fingerprint_parser.py +350 -103
  170. souleyez/parsers/hydra_parser.py +131 -111
  171. souleyez/parsers/impacket_parser.py +231 -178
  172. souleyez/parsers/john_parser.py +98 -86
  173. souleyez/parsers/katana_parser.py +316 -0
  174. souleyez/parsers/msf_parser.py +943 -498
  175. souleyez/parsers/nikto_parser.py +346 -65
  176. souleyez/parsers/nmap_parser.py +262 -174
  177. souleyez/parsers/nuclei_parser.py +40 -44
  178. souleyez/parsers/responder_parser.py +26 -26
  179. souleyez/parsers/searchsploit_parser.py +74 -74
  180. souleyez/parsers/service_explorer_parser.py +279 -0
  181. souleyez/parsers/smbmap_parser.py +180 -124
  182. souleyez/parsers/sqlmap_parser.py +434 -308
  183. souleyez/parsers/theharvester_parser.py +75 -57
  184. souleyez/parsers/whois_parser.py +135 -94
  185. souleyez/parsers/wpscan_parser.py +278 -190
  186. souleyez/plugins/afp.py +44 -36
  187. souleyez/plugins/afp_brute.py +114 -46
  188. souleyez/plugins/ard.py +48 -37
  189. souleyez/plugins/bloodhound.py +95 -61
  190. souleyez/plugins/certipy.py +303 -0
  191. souleyez/plugins/crackmapexec.py +186 -85
  192. souleyez/plugins/dalfox.py +120 -59
  193. souleyez/plugins/dns_hijack.py +146 -41
  194. souleyez/plugins/dnsrecon.py +97 -61
  195. souleyez/plugins/enum4linux.py +91 -66
  196. souleyez/plugins/evil_winrm.py +291 -0
  197. souleyez/plugins/ffuf.py +166 -90
  198. souleyez/plugins/firmware_extract.py +133 -29
  199. souleyez/plugins/gobuster.py +387 -190
  200. souleyez/plugins/gpp_extract.py +393 -0
  201. souleyez/plugins/hashcat.py +100 -73
  202. souleyez/plugins/http_fingerprint.py +913 -267
  203. souleyez/plugins/hydra.py +566 -200
  204. souleyez/plugins/impacket_getnpusers.py +117 -69
  205. souleyez/plugins/impacket_psexec.py +84 -64
  206. souleyez/plugins/impacket_secretsdump.py +103 -69
  207. souleyez/plugins/impacket_smbclient.py +89 -75
  208. souleyez/plugins/john.py +86 -69
  209. souleyez/plugins/katana.py +313 -0
  210. souleyez/plugins/kerbrute.py +237 -0
  211. souleyez/plugins/lfi_extract.py +541 -0
  212. souleyez/plugins/macos_ssh.py +117 -48
  213. souleyez/plugins/mdns.py +35 -30
  214. souleyez/plugins/msf_auxiliary.py +253 -130
  215. souleyez/plugins/msf_exploit.py +239 -161
  216. souleyez/plugins/nikto.py +134 -78
  217. souleyez/plugins/nmap.py +275 -91
  218. souleyez/plugins/nuclei.py +180 -89
  219. souleyez/plugins/nxc.py +285 -0
  220. souleyez/plugins/plugin_base.py +35 -36
  221. souleyez/plugins/plugin_template.py +13 -5
  222. souleyez/plugins/rdp_sec_check.py +130 -0
  223. souleyez/plugins/responder.py +112 -71
  224. souleyez/plugins/router_http_brute.py +76 -65
  225. souleyez/plugins/router_ssh_brute.py +118 -41
  226. souleyez/plugins/router_telnet_brute.py +124 -42
  227. souleyez/plugins/routersploit.py +91 -59
  228. souleyez/plugins/routersploit_exploit.py +77 -55
  229. souleyez/plugins/searchsploit.py +91 -77
  230. souleyez/plugins/service_explorer.py +1160 -0
  231. souleyez/plugins/smbmap.py +122 -72
  232. souleyez/plugins/smbpasswd.py +215 -0
  233. souleyez/plugins/sqlmap.py +301 -113
  234. souleyez/plugins/theharvester.py +127 -75
  235. souleyez/plugins/tr069.py +79 -57
  236. souleyez/plugins/upnp.py +65 -47
  237. souleyez/plugins/upnp_abuse.py +73 -55
  238. souleyez/plugins/vnc_access.py +129 -42
  239. souleyez/plugins/vnc_brute.py +109 -38
  240. souleyez/plugins/web_login_test.py +417 -0
  241. souleyez/plugins/whois.py +77 -58
  242. souleyez/plugins/wpscan.py +219 -69
  243. souleyez/reporting/__init__.py +2 -1
  244. souleyez/reporting/attack_chain.py +411 -346
  245. souleyez/reporting/charts.py +436 -501
  246. souleyez/reporting/compliance_mappings.py +334 -201
  247. souleyez/reporting/detection_report.py +126 -125
  248. souleyez/reporting/formatters.py +828 -591
  249. souleyez/reporting/generator.py +386 -302
  250. souleyez/reporting/metrics.py +72 -75
  251. souleyez/scanner.py +35 -29
  252. souleyez/security/__init__.py +37 -11
  253. souleyez/security/scope_validator.py +175 -106
  254. souleyez/security/validation.py +237 -149
  255. souleyez/security.py +22 -6
  256. souleyez/storage/credentials.py +247 -186
  257. souleyez/storage/crypto.py +296 -129
  258. souleyez/storage/database.py +73 -50
  259. souleyez/storage/db.py +58 -36
  260. souleyez/storage/deliverable_evidence.py +177 -128
  261. souleyez/storage/deliverable_exporter.py +282 -246
  262. souleyez/storage/deliverable_templates.py +134 -116
  263. souleyez/storage/deliverables.py +135 -130
  264. souleyez/storage/engagements.py +109 -56
  265. souleyez/storage/evidence.py +181 -152
  266. souleyez/storage/execution_log.py +31 -17
  267. souleyez/storage/exploit_attempts.py +93 -57
  268. souleyez/storage/exploits.py +67 -36
  269. souleyez/storage/findings.py +48 -61
  270. souleyez/storage/hosts.py +176 -144
  271. souleyez/storage/migrate_to_engagements.py +43 -19
  272. souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
  273. souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
  274. souleyez/storage/migrations/_003_add_execution_log.py +14 -8
  275. souleyez/storage/migrations/_005_screenshots.py +13 -5
  276. souleyez/storage/migrations/_006_deliverables.py +13 -5
  277. souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
  278. souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
  279. souleyez/storage/migrations/_010_evidence_linking.py +17 -10
  280. souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
  281. souleyez/storage/migrations/_012_team_collaboration.py +34 -21
  282. souleyez/storage/migrations/_013_add_host_tags.py +12 -6
  283. souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
  284. souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
  285. souleyez/storage/migrations/_016_add_domain_field.py +10 -4
  286. souleyez/storage/migrations/_017_msf_sessions.py +16 -8
  287. souleyez/storage/migrations/_018_add_osint_target.py +10 -6
  288. souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
  289. souleyez/storage/migrations/_020_add_rbac.py +36 -15
  290. souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
  291. souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
  292. souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
  293. souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
  294. souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
  295. souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
  296. souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
  297. souleyez/storage/migrations/__init__.py +26 -26
  298. souleyez/storage/migrations/migration_manager.py +19 -19
  299. souleyez/storage/msf_sessions.py +100 -65
  300. souleyez/storage/osint.py +17 -24
  301. souleyez/storage/recommendation_engine.py +269 -235
  302. souleyez/storage/screenshots.py +33 -32
  303. souleyez/storage/smb_shares.py +136 -92
  304. souleyez/storage/sqlmap_data.py +183 -128
  305. souleyez/storage/team_collaboration.py +135 -141
  306. souleyez/storage/timeline_tracker.py +122 -94
  307. souleyez/storage/wazuh_vulns.py +64 -66
  308. souleyez/storage/web_paths.py +33 -37
  309. souleyez/testing/credential_tester.py +221 -205
  310. souleyez/ui/__init__.py +1 -1
  311. souleyez/ui/ai_quotes.py +12 -12
  312. souleyez/ui/attack_surface.py +2439 -1516
  313. souleyez/ui/chain_rules_view.py +914 -382
  314. souleyez/ui/correlation_view.py +312 -230
  315. souleyez/ui/dashboard.py +2382 -1130
  316. souleyez/ui/deliverables_view.py +148 -62
  317. souleyez/ui/design_system.py +13 -13
  318. souleyez/ui/errors.py +49 -49
  319. souleyez/ui/evidence_linking_view.py +284 -179
  320. souleyez/ui/evidence_vault.py +393 -285
  321. souleyez/ui/exploit_suggestions_view.py +555 -349
  322. souleyez/ui/export_view.py +100 -66
  323. souleyez/ui/gap_analysis_view.py +315 -171
  324. souleyez/ui/help_system.py +105 -97
  325. souleyez/ui/intelligence_view.py +436 -293
  326. souleyez/ui/interactive.py +23034 -10679
  327. souleyez/ui/interactive_selector.py +75 -68
  328. souleyez/ui/log_formatter.py +47 -39
  329. souleyez/ui/menu_components.py +22 -13
  330. souleyez/ui/msf_auxiliary_menu.py +184 -133
  331. souleyez/ui/pending_chains_view.py +336 -172
  332. souleyez/ui/progress_indicators.py +5 -3
  333. souleyez/ui/recommendations_view.py +195 -137
  334. souleyez/ui/rule_builder.py +343 -225
  335. souleyez/ui/setup_wizard.py +678 -284
  336. souleyez/ui/shortcuts.py +217 -165
  337. souleyez/ui/splunk_gap_analysis_view.py +452 -270
  338. souleyez/ui/splunk_vulns_view.py +139 -86
  339. souleyez/ui/team_dashboard.py +498 -335
  340. souleyez/ui/template_selector.py +196 -105
  341. souleyez/ui/terminal.py +6 -6
  342. souleyez/ui/timeline_view.py +198 -127
  343. souleyez/ui/tool_setup.py +264 -164
  344. souleyez/ui/tutorial.py +202 -72
  345. souleyez/ui/tutorial_state.py +40 -40
  346. souleyez/ui/wazuh_vulns_view.py +235 -141
  347. souleyez/ui/wordlist_browser.py +260 -107
  348. souleyez/ui.py +464 -312
  349. souleyez/utils/tool_checker.py +427 -367
  350. souleyez/utils.py +33 -29
  351. souleyez/wordlists.py +134 -167
  352. {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/METADATA +2 -2
  353. souleyez-3.0.0.dist-info/RECORD +443 -0
  354. {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/WHEEL +1 -1
  355. souleyez-2.43.29.dist-info/RECORD +0 -379
  356. {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/entry_points.txt +0 -0
  357. {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/licenses/LICENSE +0 -0
  358. {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,327 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Web Login Test handler.
4
+
5
+ Parses and displays results from web login credential tests.
6
+ """
7
+ import json
8
+ import logging
9
+ import os
10
+ import re
11
+ from typing import Any, Dict, Optional
12
+
13
+ import click
14
+
15
+ from souleyez.engine.job_status import STATUS_DONE, STATUS_ERROR, STATUS_NO_RESULTS
16
+ from souleyez.handlers.base import BaseToolHandler
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class WebLoginTestHandler(BaseToolHandler):
22
+ """Handler for web login credential test jobs."""
23
+
24
+ tool_name = "web_login_test"
25
+ display_name = "Web Login Test"
26
+
27
+ has_error_handler = True
28
+ has_warning_handler = False
29
+ has_no_results_handler = True
30
+ has_done_handler = True
31
+
32
+ def parse_job(
33
+ self,
34
+ engagement_id: int,
35
+ log_path: str,
36
+ job: Dict[str, Any],
37
+ host_manager: Optional[Any] = None,
38
+ findings_manager: Optional[Any] = None,
39
+ credentials_manager: Optional[Any] = None,
40
+ ) -> Dict[str, Any]:
41
+ """
42
+ Parse web login test results.
43
+
44
+ Returns parsed result including:
45
+ - login_success: bool
46
+ - username: str
47
+ - http_code: int
48
+ - reason: str
49
+ """
50
+ try:
51
+ if not log_path or not os.path.exists(log_path):
52
+ return {
53
+ "tool": self.tool_name,
54
+ "status": STATUS_ERROR,
55
+ "error": "Log file not found",
56
+ }
57
+
58
+ with open(log_path, "r", encoding="utf-8", errors="replace") as f:
59
+ log_content = f.read()
60
+
61
+ # Try to parse JSON result
62
+ json_match = re.search(
63
+ r"=== JSON_RESULT ===\s*(.*?)\s*=== END_JSON_RESULT ===",
64
+ log_content,
65
+ re.DOTALL,
66
+ )
67
+
68
+ result = {
69
+ "tool": self.tool_name,
70
+ "status": STATUS_NO_RESULTS,
71
+ "login_success": False,
72
+ "username": None,
73
+ "http_code": None,
74
+ "reason": None,
75
+ "response": None,
76
+ }
77
+
78
+ # Extract username from log
79
+ username_match = re.search(r"Username: (\S+)", log_content)
80
+ if username_match:
81
+ result["username"] = username_match.group(1)
82
+
83
+ if json_match:
84
+ try:
85
+ json_data = json.loads(json_match.group(1))
86
+
87
+ if json_data.get("error"):
88
+ result["status"] = STATUS_ERROR
89
+ result["error"] = json_data["error"]
90
+ result["summary"] = f"Error: {json_data['error'][:50]}"
91
+ return result
92
+
93
+ result["login_success"] = json_data.get("success", False)
94
+ result["http_code"] = json_data.get("http_code")
95
+ result["reason"] = json_data.get("reason")
96
+ result["response"] = json_data.get("response", "")[:100]
97
+
98
+ except json.JSONDecodeError:
99
+ pass
100
+
101
+ # Fallback: parse from log text
102
+ if "[+] LOGIN SUCCESS" in log_content:
103
+ result["login_success"] = True
104
+ result["status"] = STATUS_DONE
105
+ elif "[-] LOGIN FAILED" in log_content:
106
+ result["login_success"] = False
107
+ result["status"] = STATUS_NO_RESULTS
108
+
109
+ # Extract HTTP code if not already found
110
+ if not result["http_code"]:
111
+ http_match = re.search(r"HTTP Code: (\d+)", log_content)
112
+ if http_match:
113
+ result["http_code"] = int(http_match.group(1))
114
+
115
+ # Extract reason if not already found
116
+ if not result["reason"]:
117
+ reason_match = re.search(r"Reason: (.+?)(?:\n|$)", log_content)
118
+ if reason_match:
119
+ result["reason"] = reason_match.group(1).strip()
120
+
121
+ # Build summary
122
+ if result["login_success"]:
123
+ result["status"] = STATUS_DONE
124
+ result["summary"] = f"Login successful: {result['username']}"
125
+ # Store validated credential
126
+ if credentials_manager and result["username"]:
127
+ target = job.get("target", "")
128
+ # Extract password from args
129
+ password = self._extract_password_from_args(job.get("args", []))
130
+ if password:
131
+ try:
132
+ credentials_manager.add_credential(
133
+ engagement_id=engagement_id,
134
+ host_id=None,
135
+ username=result["username"],
136
+ password=password,
137
+ service="web",
138
+ credential_type="password",
139
+ tool="web_login_test",
140
+ status="validated",
141
+ notes=f"Validated against {target}",
142
+ )
143
+ result["credentials_validated"] = 1
144
+ logger.info(
145
+ f"Web login test: Validated credential {result['username']}"
146
+ )
147
+ except Exception as e:
148
+ logger.debug(f"Error storing validated credential: {e}")
149
+ else:
150
+ result["status"] = STATUS_NO_RESULTS
151
+ # Build informative failure summary
152
+ if result["reason"]:
153
+ # Truncate long reasons
154
+ reason = result["reason"]
155
+ if len(reason) > 50:
156
+ reason = reason[:47] + "..."
157
+ result["summary"] = f"Login failed: {reason}"
158
+ elif result["http_code"]:
159
+ result["summary"] = f"Login failed (HTTP {result['http_code']})"
160
+ else:
161
+ result["summary"] = "Login failed"
162
+
163
+ return result
164
+
165
+ except Exception as e:
166
+ logger.error(f"Error parsing web_login_test job: {e}")
167
+ return {"tool": self.tool_name, "status": STATUS_ERROR, "error": str(e)}
168
+
169
+ def _extract_password_from_args(self, args: list) -> Optional[str]:
170
+ """Extract password from job args."""
171
+ for i, arg in enumerate(args):
172
+ if arg == "--password" and i + 1 < len(args):
173
+ return args[i + 1]
174
+ return None
175
+
176
+ def display_done(
177
+ self,
178
+ job: Dict[str, Any],
179
+ log_path: str,
180
+ show_all: bool = False,
181
+ show_passwords: bool = False,
182
+ ) -> None:
183
+ """Display successful login test results."""
184
+ result = self._parse_log(log_path)
185
+
186
+ click.echo()
187
+ click.echo(click.style("=" * 60, fg="green"))
188
+ click.echo(click.style("WEB LOGIN TEST - SUCCESS", bold=True, fg="green"))
189
+ click.echo(click.style("=" * 60, fg="green"))
190
+ click.echo()
191
+
192
+ target = job.get("target", "unknown")
193
+ click.echo(f" Target: {target}")
194
+
195
+ username = result.get("username", "unknown")
196
+ if show_passwords:
197
+ password = self._extract_password_from_args(job.get("args", []))
198
+ click.echo(click.style(f" Credential: {username}:{password}", fg="green"))
199
+ else:
200
+ click.echo(click.style(f" Credential: {username}:***", fg="green"))
201
+
202
+ if result.get("http_code"):
203
+ click.echo(f" HTTP Code: {result['http_code']}")
204
+
205
+ if result.get("reason"):
206
+ click.echo(f" Reason: {result['reason']}")
207
+
208
+ click.echo()
209
+ click.echo(click.style("=" * 60, fg="green"))
210
+ click.echo()
211
+
212
+ def display_no_results(
213
+ self,
214
+ job: Dict[str, Any],
215
+ log_path: str,
216
+ ) -> None:
217
+ """Display failed login test results."""
218
+ result = self._parse_log(log_path)
219
+
220
+ click.echo()
221
+ click.echo(click.style("=" * 60, fg="yellow"))
222
+ click.echo(click.style("WEB LOGIN TEST - FAILED", bold=True, fg="yellow"))
223
+ click.echo(click.style("=" * 60, fg="yellow"))
224
+ click.echo()
225
+
226
+ target = job.get("target", "unknown")
227
+ click.echo(f" Target: {target}")
228
+
229
+ username = result.get("username", "unknown")
230
+ click.echo(f" Credential: {username}:***")
231
+
232
+ if result.get("http_code"):
233
+ click.echo(f" HTTP Code: {result['http_code']}")
234
+
235
+ if result.get("reason"):
236
+ click.echo(f" Reason: {result['reason']}")
237
+
238
+ click.echo()
239
+ click.echo(click.style(" Result: Invalid credentials", fg="yellow"))
240
+ click.echo()
241
+ click.echo(click.style("=" * 60, fg="yellow"))
242
+ click.echo()
243
+
244
+ def display_error(
245
+ self,
246
+ job: Dict[str, Any],
247
+ log_path: str,
248
+ log_content: Optional[str] = None,
249
+ ) -> None:
250
+ """Display error status."""
251
+ click.echo()
252
+ click.echo(click.style("=" * 60, fg="red"))
253
+ click.echo(click.style("[ERROR] WEB LOGIN TEST FAILED", bold=True, fg="red"))
254
+ click.echo(click.style("=" * 60, fg="red"))
255
+ click.echo()
256
+
257
+ target = job.get("target", "unknown")
258
+ click.echo(f" Target: {target}")
259
+
260
+ # Try to get error details
261
+ if log_path and os.path.exists(log_path):
262
+ try:
263
+ with open(log_path, "r", encoding="utf-8", errors="replace") as f:
264
+ content = f.read()
265
+ error_match = re.search(r"ERROR: (.+?)(?:\n|$)", content)
266
+ if error_match:
267
+ click.echo(f" Error: {error_match.group(1)}")
268
+ except Exception:
269
+ pass
270
+
271
+ click.echo()
272
+ click.echo(" Check raw logs for details (press 'r').")
273
+ click.echo()
274
+ click.echo(click.style("=" * 60, fg="red"))
275
+ click.echo()
276
+
277
+ def _parse_log(self, log_path: str) -> Dict[str, Any]:
278
+ """Parse log file and return result dict."""
279
+ result = {
280
+ "username": None,
281
+ "http_code": None,
282
+ "reason": None,
283
+ "response": None,
284
+ }
285
+
286
+ if not log_path or not os.path.exists(log_path):
287
+ return result
288
+
289
+ try:
290
+ with open(log_path, "r", encoding="utf-8", errors="replace") as f:
291
+ log_content = f.read()
292
+
293
+ # Extract username
294
+ username_match = re.search(r"Username: (\S+)", log_content)
295
+ if username_match:
296
+ result["username"] = username_match.group(1)
297
+
298
+ # Try JSON result
299
+ json_match = re.search(
300
+ r"=== JSON_RESULT ===\s*(.*?)\s*=== END_JSON_RESULT ===",
301
+ log_content,
302
+ re.DOTALL,
303
+ )
304
+ if json_match:
305
+ try:
306
+ json_data = json.loads(json_match.group(1))
307
+ result["http_code"] = json_data.get("http_code")
308
+ result["reason"] = json_data.get("reason")
309
+ result["response"] = json_data.get("response", "")[:100]
310
+ except json.JSONDecodeError:
311
+ pass
312
+
313
+ # Fallback parsing
314
+ if not result["http_code"]:
315
+ http_match = re.search(r"HTTP Code: (\d+)", log_content)
316
+ if http_match:
317
+ result["http_code"] = int(http_match.group(1))
318
+
319
+ if not result["reason"]:
320
+ reason_match = re.search(r"Reason: (.+?)(?:\n|$)", log_content)
321
+ if reason_match:
322
+ result["reason"] = reason_match.group(1).strip()
323
+
324
+ except Exception:
325
+ pass
326
+
327
+ return result
@@ -0,0 +1,277 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ WHOIS handler.
4
+
5
+ Consolidates parsing and display logic for WHOIS domain lookup jobs.
6
+ """
7
+ import logging
8
+ import os
9
+ from typing import Any, Dict, Optional
10
+
11
+ import click
12
+
13
+ from souleyez.engine.job_status import STATUS_DONE, STATUS_NO_RESULTS
14
+ from souleyez.handlers.base import BaseToolHandler
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class WhoisHandler(BaseToolHandler):
20
+ """Handler for WHOIS domain lookup jobs."""
21
+
22
+ tool_name = "whois"
23
+ display_name = "WHOIS"
24
+
25
+ # All handlers enabled
26
+ has_error_handler = True
27
+ has_warning_handler = True
28
+ has_no_results_handler = True
29
+ has_done_handler = True
30
+
31
+ def parse_job(
32
+ self,
33
+ engagement_id: int,
34
+ log_path: str,
35
+ job: Dict[str, Any],
36
+ host_manager: Optional[Any] = None,
37
+ findings_manager: Optional[Any] = None,
38
+ credentials_manager: Optional[Any] = None,
39
+ ) -> Dict[str, Any]:
40
+ """
41
+ Parse WHOIS job results.
42
+
43
+ Extracts domain registration information and stores as OSINT data.
44
+ """
45
+ try:
46
+ from souleyez.parsers.whois_parser import (
47
+ parse_whois_output,
48
+ map_to_osint_data,
49
+ extract_emails,
50
+ )
51
+ from souleyez.storage.osint import OsintManager
52
+
53
+ # Read the log file
54
+ with open(log_path, "r", encoding="utf-8", errors="replace") as f:
55
+ log_content = f.read()
56
+
57
+ # Parse WHOIS output
58
+ target = job.get("target", "")
59
+ parsed = parse_whois_output(log_content, target)
60
+
61
+ # Store OSINT data
62
+ om = OsintManager()
63
+ osint_record = map_to_osint_data(parsed, engagement_id)
64
+ om.add_osint_data(
65
+ engagement_id,
66
+ osint_record["data_type"],
67
+ osint_record["target"],
68
+ source=osint_record["source"],
69
+ target=target,
70
+ summary=osint_record["summary"],
71
+ content=osint_record["content"],
72
+ metadata=osint_record["metadata"],
73
+ )
74
+
75
+ # Extract emails and add separately for better querying
76
+ emails = extract_emails(parsed)
77
+ emails_added = 0
78
+ if emails:
79
+ emails_added = om.bulk_add_osint_data(
80
+ engagement_id, "email", emails, "whois", target
81
+ )
82
+
83
+ return {
84
+ "tool": "whois",
85
+ "status": (
86
+ STATUS_DONE
87
+ if (parsed.get("registrar") or parsed.get("nameservers"))
88
+ else STATUS_NO_RESULTS
89
+ ),
90
+ "domain": parsed.get("domain", target),
91
+ "registrar": parsed.get("registrar"),
92
+ "created": parsed.get("dates", {}).get("created"),
93
+ "expires": parsed.get("dates", {}).get("expires"),
94
+ "emails_found": len(emails),
95
+ "nameservers": len(parsed.get("nameservers", [])),
96
+ "osint_records_added": 1,
97
+ "emails_added": emails_added,
98
+ }
99
+
100
+ except Exception as e:
101
+ logger.error(f"Error parsing whois job: {e}")
102
+ return {"error": str(e)}
103
+
104
+ def display_done(
105
+ self,
106
+ job: Dict[str, Any],
107
+ log_path: str,
108
+ show_all: bool = False,
109
+ show_passwords: bool = False,
110
+ ) -> None:
111
+ """Display successful WHOIS results."""
112
+ try:
113
+ from souleyez.parsers.whois_parser import parse_whois_output
114
+
115
+ if not log_path or not os.path.exists(log_path):
116
+ return
117
+
118
+ with open(log_path, "r", encoding="utf-8", errors="replace") as f:
119
+ log_content = f.read()
120
+
121
+ parsed = parse_whois_output(log_content, job.get("target", ""))
122
+
123
+ click.echo(click.style("=" * 70, fg="cyan"))
124
+ click.echo(click.style("WHOIS DOMAIN INFORMATION", bold=True, fg="cyan"))
125
+ click.echo(click.style("=" * 70, fg="cyan"))
126
+ click.echo()
127
+
128
+ # Check if we have any data
129
+ has_data = (
130
+ parsed.get("domain")
131
+ or parsed.get("registrar")
132
+ or parsed.get("dates")
133
+ or parsed.get("nameservers")
134
+ or parsed.get("status")
135
+ or parsed.get("dnssec")
136
+ )
137
+
138
+ if has_data:
139
+ # Domain and registrar
140
+ if parsed.get("domain"):
141
+ click.echo(click.style(f"Domain: {parsed['domain']}", bold=True))
142
+ elif job.get("target"):
143
+ click.echo(click.style(f"Target: {job.get('target')}", bold=True))
144
+ if parsed.get("registrar"):
145
+ click.echo(f"Registrar: {parsed['registrar']}")
146
+ click.echo()
147
+
148
+ # Registration dates
149
+ dates = parsed.get("dates", {})
150
+ if dates:
151
+ click.echo(click.style("Registration Information:", bold=True))
152
+ if dates.get("created"):
153
+ click.echo(f" Created: {dates['created']}")
154
+ if dates.get("updated"):
155
+ click.echo(f" Updated: {dates['updated']}")
156
+ if dates.get("expires"):
157
+ click.echo(f" Expires: {dates['expires']}")
158
+ click.echo()
159
+
160
+ # Nameservers
161
+ ns = parsed.get("nameservers", [])
162
+ if ns:
163
+ click.echo(click.style(f"Nameservers: {len(ns)}", bold=True))
164
+ for server in ns:
165
+ click.echo(f" - {server}")
166
+ click.echo()
167
+
168
+ # Status
169
+ status_list = parsed.get("status", [])
170
+ if status_list:
171
+ click.echo(click.style("Domain Status:", bold=True))
172
+ for status in status_list:
173
+ click.echo(f" - {status}")
174
+ click.echo()
175
+
176
+ # DNSSEC
177
+ if parsed.get("dnssec"):
178
+ click.echo(f"DNSSEC: {parsed['dnssec']}")
179
+ click.echo()
180
+ else:
181
+ self.display_no_results(job, log_path)
182
+ return
183
+
184
+ click.echo(click.style("=" * 70, fg="cyan"))
185
+ click.echo()
186
+
187
+ except Exception as e:
188
+ logger.debug(f"Error in display_done: {e}")
189
+
190
+ def display_warning(
191
+ self,
192
+ job: Dict[str, Any],
193
+ log_path: str,
194
+ log_content: Optional[str] = None,
195
+ ) -> None:
196
+ """Display warning status for WHOIS."""
197
+ click.echo(click.style("=" * 70, fg="yellow"))
198
+ click.echo(click.style("[WARNING] WHOIS LOOKUP", bold=True, fg="yellow"))
199
+ click.echo(click.style("=" * 70, fg="yellow"))
200
+ click.echo()
201
+ click.echo(" WHOIS lookup completed with warnings.")
202
+ click.echo(" Check raw logs for details (press 'r').")
203
+ click.echo()
204
+ click.echo(click.style("=" * 70, fg="yellow"))
205
+ click.echo()
206
+
207
+ def display_error(
208
+ self,
209
+ job: Dict[str, Any],
210
+ log_path: str,
211
+ log_content: Optional[str] = None,
212
+ ) -> None:
213
+ """Display error status for WHOIS."""
214
+ # Read log if not provided
215
+ if log_content is None and log_path and os.path.exists(log_path):
216
+ try:
217
+ with open(log_path, "r", encoding="utf-8", errors="replace") as f:
218
+ log_content = f.read()
219
+ except Exception:
220
+ log_content = ""
221
+
222
+ click.echo(click.style("=" * 70, fg="red"))
223
+ click.echo(click.style("[ERROR] WHOIS LOOKUP FAILED", bold=True, fg="red"))
224
+ click.echo(click.style("=" * 70, fg="red"))
225
+ click.echo()
226
+
227
+ # Check for common whois errors
228
+ error_msg = None
229
+ if log_content:
230
+ if "No match for" in log_content or "NOT FOUND" in log_content.upper():
231
+ error_msg = "Domain not found in WHOIS database"
232
+ elif "timed out" in log_content.lower() or "timeout" in log_content.lower():
233
+ error_msg = "WHOIS query timed out - server may be slow"
234
+ elif "Connection refused" in log_content:
235
+ error_msg = "Connection refused - WHOIS server may be down"
236
+ elif (
237
+ "rate limit" in log_content.lower() or "too many" in log_content.lower()
238
+ ):
239
+ error_msg = "Rate limited - too many WHOIS queries"
240
+
241
+ if error_msg:
242
+ click.echo(f" {error_msg}")
243
+ else:
244
+ click.echo(" Lookup failed - see raw logs for details (press 'r')")
245
+
246
+ click.echo()
247
+ click.echo(click.style("=" * 70, fg="red"))
248
+ click.echo()
249
+
250
+ def display_no_results(
251
+ self,
252
+ job: Dict[str, Any],
253
+ log_path: str,
254
+ ) -> None:
255
+ """Display no_results status for WHOIS."""
256
+ click.echo(click.style("=" * 70, fg="cyan"))
257
+ click.echo(click.style("WHOIS DOMAIN INFORMATION", bold=True, fg="cyan"))
258
+ click.echo(click.style("=" * 70, fg="cyan"))
259
+ click.echo()
260
+
261
+ if job.get("target"):
262
+ click.echo(click.style(f"Target: {job.get('target')}", bold=True))
263
+ click.echo()
264
+
265
+ click.echo(
266
+ click.style("Result: No WHOIS information found", fg="yellow", bold=True)
267
+ )
268
+ click.echo()
269
+ click.echo(" The WHOIS lookup did not return any information.")
270
+ click.echo()
271
+ click.echo(click.style("Tips:", dim=True))
272
+ click.echo(" - Verify the domain name is correct")
273
+ click.echo(" - Some domains have private WHOIS")
274
+ click.echo(" - Try a different WHOIS server")
275
+ click.echo()
276
+ click.echo(click.style("=" * 70, fg="cyan"))
277
+ click.echo()