souleyez 2.43.26__py3-none-any.whl → 2.43.34__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 +9526 -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 +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 +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 +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 +854 -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 +173 -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 +223 -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 +23434 -10286
  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.26.dist-info → souleyez-2.43.34.dist-info}/METADATA +1 -1
  353. souleyez-2.43.34.dist-info/RECORD +443 -0
  354. {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/WHEEL +1 -1
  355. souleyez-2.43.26.dist-info/RECORD +0 -379
  356. {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/entry_points.txt +0 -0
  357. {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/licenses/LICENSE +0 -0
  358. {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1116 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ SQLMap handler.
4
+
5
+ Consolidates parsing and display logic for SQLMap SQL injection scanner jobs.
6
+ """
7
+ import logging
8
+ import os
9
+ import re
10
+ from typing import Any, Dict, Optional
11
+ from urllib.parse import urlparse
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 SQLMapHandler(BaseToolHandler):
22
+ """Handler for SQLMap SQL injection scanner jobs."""
23
+
24
+ tool_name = "sqlmap"
25
+ display_name = "SQLMap"
26
+
27
+ # All handlers enabled
28
+ has_error_handler = True
29
+ has_warning_handler = True
30
+ has_no_results_handler = True
31
+ has_done_handler = True
32
+
33
+ def parse_job(
34
+ self,
35
+ engagement_id: int,
36
+ log_path: str,
37
+ job: Dict[str, Any],
38
+ host_manager: Optional[Any] = None,
39
+ findings_manager: Optional[Any] = None,
40
+ credentials_manager: Optional[Any] = None,
41
+ ) -> Dict[str, Any]:
42
+ """
43
+ Parse SQLMap job results.
44
+
45
+ Extracts SQL injection vulnerabilities, databases, tables, and dumped data.
46
+ """
47
+ try:
48
+ from souleyez.parsers.sqlmap_parser import (
49
+ parse_sqlmap_output,
50
+ get_sqli_stats,
51
+ )
52
+ from souleyez.storage.sqlmap_data import SQLMapDataManager
53
+ from souleyez.engine.result_handler import detect_tool_error
54
+ import socket
55
+
56
+ # Import managers if not provided
57
+ if host_manager is None:
58
+ from souleyez.storage.hosts import HostManager
59
+
60
+ host_manager = HostManager()
61
+ if findings_manager is None:
62
+ from souleyez.storage.findings import FindingsManager
63
+
64
+ findings_manager = FindingsManager()
65
+ if credentials_manager is None:
66
+ from souleyez.storage.credentials import CredentialsManager
67
+
68
+ credentials_manager = CredentialsManager()
69
+
70
+ target = job.get("target", "")
71
+
72
+ # Read log file
73
+ with open(log_path, "r", encoding="utf-8", errors="replace") as f:
74
+ output = f.read()
75
+
76
+ parsed = parse_sqlmap_output(output, target)
77
+ stats = get_sqli_stats(parsed)
78
+
79
+ # Get or create host from target URL
80
+ host_id = None
81
+ target_port = None
82
+
83
+ if parsed.get("target_url"):
84
+ parsed_url = urlparse(parsed["target_url"])
85
+ hostname = parsed_url.hostname
86
+ target_port = parsed_url.port or (
87
+ 443 if parsed_url.scheme == "https" else 80
88
+ )
89
+
90
+ if hostname:
91
+ is_ip = re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", hostname)
92
+
93
+ if is_ip:
94
+ host = host_manager.get_host_by_ip(engagement_id, hostname)
95
+ if host:
96
+ host_id = host["id"]
97
+ else:
98
+ host_id = host_manager.add_or_update_host(
99
+ engagement_id, {"ip": hostname, "status": "up"}
100
+ )
101
+ else:
102
+ # Try to match by hostname
103
+ hosts = host_manager.list_hosts(engagement_id)
104
+ for h in hosts:
105
+ if (
106
+ h.get("hostname") == hostname
107
+ or h.get("ip") == hostname
108
+ or h.get("ip_address") == hostname
109
+ ):
110
+ host_id = h["id"]
111
+ break
112
+
113
+ if not host_id:
114
+ try:
115
+ ip_address = socket.gethostbyname(hostname)
116
+ host_id = host_manager.add_or_update_host(
117
+ engagement_id,
118
+ {
119
+ "ip": ip_address,
120
+ "hostname": hostname,
121
+ "status": "up",
122
+ },
123
+ )
124
+ except (socket.gaierror, socket.herror):
125
+ pass
126
+
127
+ # Store vulnerabilities as findings
128
+ findings_added = 0
129
+
130
+ for vuln in parsed.get("vulnerabilities", []):
131
+ vuln_type = vuln.get("vuln_type", "unknown")
132
+ if vuln_type == "sqli" and vuln.get("injectable"):
133
+ severity = "critical"
134
+ finding_type = "sql_injection"
135
+ title = f"SQL Injection in parameter '{vuln['parameter']}'"
136
+ elif vuln_type == "xss":
137
+ severity = vuln.get("severity", "medium")
138
+ finding_type = "xss"
139
+ title = f"Possible XSS in parameter '{vuln['parameter']}'"
140
+ elif vuln_type == "file_inclusion":
141
+ severity = vuln.get("severity", "high")
142
+ finding_type = "file_inclusion"
143
+ title = (
144
+ f"Possible File Inclusion in parameter '{vuln['parameter']}'"
145
+ )
146
+ else:
147
+ severity = "medium"
148
+ finding_type = "web_vulnerability"
149
+ title = f"Vulnerability in parameter '{vuln['parameter']}'"
150
+
151
+ description = vuln.get("description", "")
152
+ if vuln.get("technique"):
153
+ description += f"\nTechnique: {vuln['technique']}"
154
+ if vuln.get("dbms"):
155
+ description += f"\nDBMS: {vuln['dbms']}"
156
+
157
+ findings_manager.add_finding(
158
+ engagement_id=engagement_id,
159
+ host_id=host_id,
160
+ port=target_port,
161
+ title=title,
162
+ finding_type=finding_type,
163
+ severity=severity,
164
+ description=description,
165
+ tool="sqlmap",
166
+ path=vuln.get("url"),
167
+ )
168
+ findings_added += 1
169
+
170
+ # Exploitation findings - Database Enumeration
171
+ databases = parsed.get("databases", [])
172
+ if databases and host_id:
173
+ dbms_info = parsed.get("dbms", "Unknown")
174
+ db_count = len(databases)
175
+ db_list = databases[:10]
176
+ db_list_str = ", ".join(db_list)
177
+ if len(databases) > 10:
178
+ db_list_str += f" ... and {len(databases) - 10} more"
179
+
180
+ description = f"SQL injection was successfully exploited to enumerate {db_count} database(s).\n\n"
181
+ description += f"DBMS: {dbms_info}\n"
182
+ description += f"Databases: {db_list_str}"
183
+
184
+ findings_manager.add_finding(
185
+ engagement_id=engagement_id,
186
+ host_id=host_id,
187
+ port=target_port,
188
+ title=f"SQL Injection Exploited - {db_count} Database(s) Enumerated",
189
+ finding_type="sql_injection_exploitation",
190
+ severity="critical",
191
+ description=description,
192
+ tool="sqlmap",
193
+ path=parsed.get("target_url"),
194
+ )
195
+ findings_added += 1
196
+
197
+ # Exploitation findings - Table Enumeration
198
+ tables = parsed.get("tables", {})
199
+ if tables and host_id:
200
+ total_tables = sum(len(table_list) for table_list in tables.values())
201
+ if total_tables > 0:
202
+ description = f"SQL injection was exploited to enumerate {total_tables} table(s) across {len(tables)} database(s)."
203
+
204
+ findings_manager.add_finding(
205
+ engagement_id=engagement_id,
206
+ host_id=host_id,
207
+ port=target_port,
208
+ title=f"SQL Injection Exploited - {total_tables} Table(s) Enumerated",
209
+ finding_type="sql_injection_exploitation",
210
+ severity="high",
211
+ description=description,
212
+ tool="sqlmap",
213
+ path=parsed.get("target_url"),
214
+ )
215
+ findings_added += 1
216
+
217
+ # Exploitation findings - Data Dump
218
+ dumped_data = parsed.get("dumped_data", {})
219
+ credentials_added = 0
220
+ extracted_credentials = [] # Default empty list for chaining
221
+ total_users_found = 0 # Total users in credential tables (including those without passwords)
222
+ all_users = (
223
+ []
224
+ ) # All users including those without passwords (for expanded view)
225
+ if dumped_data and host_id:
226
+ total_rows = sum(
227
+ d.get("row_count", len(d.get("rows", [])))
228
+ for d in dumped_data.values()
229
+ )
230
+ table_names = list(dumped_data.keys())
231
+
232
+ if total_rows > 0:
233
+ description = f"SQL injection was exploited to dump {total_rows} row(s) from {len(table_names)} table(s).\n\n"
234
+ description += f"Tables dumped: {', '.join(table_names[:5])}"
235
+ if len(table_names) > 5:
236
+ description += f" ... and {len(table_names) - 5} more"
237
+
238
+ findings_manager.add_finding(
239
+ engagement_id=engagement_id,
240
+ host_id=host_id,
241
+ port=target_port,
242
+ title=f"SQL Injection Exploited - {total_rows} Row(s) Dumped",
243
+ finding_type="sql_injection_exploitation",
244
+ severity="critical",
245
+ description=description,
246
+ tool="sqlmap",
247
+ path=parsed.get("target_url"),
248
+ )
249
+ findings_added += 1
250
+
251
+ # Extract credentials from dumped data
252
+ (
253
+ credentials_added,
254
+ extracted_credentials,
255
+ total_users_found,
256
+ all_users,
257
+ ) = self._extract_credentials_from_dump(
258
+ dumped_data=dumped_data,
259
+ engagement_id=engagement_id,
260
+ host_id=host_id,
261
+ credentials_manager=credentials_manager,
262
+ )
263
+
264
+ if credentials_added > 0:
265
+ logger.info(
266
+ f"SQLMap: Extracted {credentials_added} credentials from dumped tables"
267
+ )
268
+
269
+ # Store SQLMap database discoveries to SQLMapDataManager
270
+ has_data_to_store = (
271
+ parsed.get("databases")
272
+ or parsed.get("tables")
273
+ or parsed.get("dumped_data")
274
+ )
275
+
276
+ if host_id and has_data_to_store:
277
+ sdm = SQLMapDataManager()
278
+ dbms_type = parsed.get("dbms", "Unknown")
279
+
280
+ # Store databases
281
+ db_ids = {}
282
+ for db_name in parsed.get("databases", []):
283
+ db_id = sdm.add_database(engagement_id, host_id, db_name, dbms_type)
284
+ if db_id:
285
+ db_ids[db_name] = db_id
286
+
287
+ # Store tables
288
+ table_ids = {}
289
+ for db_table_key, table_list in parsed.get("tables", {}).items():
290
+ for table_name in table_list:
291
+ db_id = db_ids.get(db_table_key)
292
+ if not db_id and db_ids:
293
+ db_id = list(db_ids.values())[0]
294
+
295
+ if db_id:
296
+ table_id = sdm.add_table(db_id, table_name)
297
+ if table_id:
298
+ full_key = f"{db_table_key}.{table_name}"
299
+ table_ids[full_key] = table_id
300
+
301
+ # Store columns
302
+ for table_key, column_list in parsed.get("columns", {}).items():
303
+ table_id = table_ids.get(table_key)
304
+ if table_id:
305
+ columns = [{"name": col} for col in column_list]
306
+ sdm.add_columns(table_id, columns)
307
+
308
+ # Store dumped data
309
+ for data_key, dump_info in parsed.get("dumped_data", {}).items():
310
+ table_id = table_ids.get(data_key)
311
+
312
+ if not table_id and "." in data_key:
313
+ db_name, table_name = data_key.rsplit(".", 1)
314
+
315
+ db_id = db_ids.get(db_name)
316
+ if not db_id:
317
+ db_id = sdm.add_database(
318
+ engagement_id, host_id, db_name, dbms_type
319
+ )
320
+ if db_id:
321
+ db_ids[db_name] = db_id
322
+
323
+ if db_id:
324
+ row_count = dump_info.get(
325
+ "row_count", len(dump_info.get("rows", []))
326
+ )
327
+ table_id = sdm.add_table(db_id, table_name, row_count)
328
+ if table_id:
329
+ table_ids[data_key] = table_id
330
+
331
+ if dump_info.get("columns"):
332
+ columns = [
333
+ {"name": col} for col in dump_info["columns"]
334
+ ]
335
+ sdm.add_columns(table_id, columns)
336
+
337
+ if table_id:
338
+ sdm.add_dumped_data(
339
+ table_id,
340
+ dump_info.get("rows", []),
341
+ dump_info.get("csv_path"),
342
+ )
343
+
344
+ # Check for sqlmap errors
345
+ sqlmap_error = detect_tool_error(output, "sqlmap")
346
+
347
+ # Determine status
348
+ if sqlmap_error:
349
+ status = STATUS_ERROR
350
+ elif stats["total_vulns"] > 0 or stats["databases_found"] > 0:
351
+ status = STATUS_DONE
352
+ else:
353
+ status = STATUS_NO_RESULTS
354
+
355
+ # Build summary for job queue display
356
+ summary_parts = []
357
+ if stats["total_vulns"] > 0:
358
+ summary_parts.append(f"{stats['total_vulns']} SQLi vuln(s)")
359
+ if stats["databases_found"] > 0:
360
+ summary_parts.append(f"{stats['databases_found']} DB(s)")
361
+ tables_count = sum(len(t) for t in tables.values()) if tables else 0
362
+ if tables_count > 0:
363
+ summary_parts.append(f"{tables_count} table(s)")
364
+ dumped_tables = stats.get("dumped_tables", 0)
365
+ dumped_rows = stats.get("dumped_rows", 0)
366
+ if dumped_rows > 0:
367
+ summary_parts.append(f"{dumped_rows} row(s) dumped")
368
+ if credentials_added > 0:
369
+ if total_users_found > 0 and total_users_found != credentials_added:
370
+ summary_parts.append(
371
+ f"{credentials_added}/{total_users_found} creds"
372
+ )
373
+ else:
374
+ summary_parts.append(f"{credentials_added} credential(s)")
375
+ summary = " | ".join(summary_parts) if summary_parts else "No findings"
376
+
377
+ return {
378
+ "tool": "sqlmap",
379
+ "status": status,
380
+ "summary": summary,
381
+ "target": target,
382
+ "target_url": parsed.get("target_url"),
383
+ "dbms": parsed.get("dbms"),
384
+ "total_vulns": stats["total_vulns"],
385
+ "sqli_confirmed": stats["sqli_confirmed"],
386
+ "databases_found": stats["databases_found"],
387
+ "tables_found": sum(len(t) for t in tables.values()) if tables else 0,
388
+ "findings_added": findings_added,
389
+ # Additional stats from parser
390
+ "xss_possible": stats.get("xss_possible", 0),
391
+ "fi_possible": stats.get("fi_possible", 0),
392
+ "urls_tested": stats.get("urls_tested", 0),
393
+ "databases": parsed.get("databases", []),
394
+ "tables": parsed.get("tables", {}),
395
+ "columns": parsed.get("columns", {}),
396
+ "dumped_tables": stats.get("dumped_tables", 0),
397
+ "dumped_rows": stats.get("dumped_rows", 0),
398
+ "dumped_data": parsed.get("dumped_data", {}),
399
+ # CRITICAL: Chaining flags for auto-chain rules
400
+ "sql_injection_confirmed": parsed.get("sql_injection_confirmed", False),
401
+ "injectable_parameter": parsed.get("injectable_parameter", ""),
402
+ "injectable_url": parsed.get("injectable_url", target),
403
+ "injectable_post_data": parsed.get("injectable_post_data", ""),
404
+ "injectable_method": parsed.get("injectable_method", "GET"),
405
+ "all_injection_points": parsed.get("all_injection_points", []),
406
+ "databases_enumerated": len(parsed.get("databases", [])) > 0,
407
+ "tables_enumerated": len(parsed.get("tables", {})) > 0,
408
+ "columns_enumerated": len(parsed.get("columns", {})) > 0,
409
+ # Post-exploitation flags for advanced chaining
410
+ "is_dba": parsed.get("is_dba", False),
411
+ "privileges": parsed.get("privileges", []),
412
+ "current_user": parsed.get("current_user"),
413
+ "file_read_success": parsed.get("file_read_success", False),
414
+ "os_command_success": parsed.get("os_command_success", False),
415
+ # Credentials flag for cross-tool chaining
416
+ "credentials_dumped": credentials_added > 0,
417
+ "credentials_count": credentials_added,
418
+ "total_users_count": total_users_found, # All users found (including those without passwords)
419
+ "credentials": extracted_credentials, # For direct chaining without DB lookup
420
+ "all_users": all_users, # All users including those without passwords (for expanded view)
421
+ }
422
+
423
+ except Exception as e:
424
+ logger.error(f"Error parsing sqlmap job: {e}")
425
+ return {"error": str(e)}
426
+
427
+ def _extract_credentials_from_dump(
428
+ self,
429
+ dumped_data: Dict[str, Any],
430
+ engagement_id: int,
431
+ host_id: int,
432
+ credentials_manager: Any,
433
+ ) -> tuple:
434
+ """
435
+ Extract credentials from SQLMap dumped data.
436
+
437
+ Args:
438
+ dumped_data: Dict of {table_key: {rows, columns, row_count, csv_path}}
439
+ engagement_id: Engagement ID
440
+ host_id: Host ID
441
+ credentials_manager: CredentialsManager instance
442
+
443
+ Returns:
444
+ tuple: (count of credentials added, list of credential dicts, total users count)
445
+ """
446
+ from souleyez.intelligence.sensitive_tables import is_sensitive_table
447
+
448
+ credentials_added = 0
449
+ credentials_list = []
450
+ all_users_list = [] # All users including those without passwords
451
+ total_users = 0 # Track all users, not just those with passwords
452
+
453
+ for table_key, data_info in dumped_data.items():
454
+ # Parse table name
455
+ parts = table_key.split(".")
456
+ table_name = parts[-1]
457
+
458
+ # Check if this is a credentials table
459
+ is_sensitive, category, priority = is_sensitive_table(table_name)
460
+
461
+ if category != "credentials":
462
+ continue # Only extract from credential tables
463
+
464
+ rows = data_info.get("rows", [])
465
+ columns = data_info.get("columns", [])
466
+
467
+ if not rows or not columns:
468
+ continue
469
+
470
+ # Find username and password columns
471
+ username_col = None
472
+ password_col = None
473
+
474
+ username_patterns = [
475
+ "username",
476
+ "uname",
477
+ "user_name",
478
+ "login",
479
+ "account",
480
+ "email",
481
+ "user",
482
+ ]
483
+ password_patterns = [
484
+ "password",
485
+ "passwd",
486
+ "pass",
487
+ "pwd",
488
+ "hash",
489
+ "pwd_hash",
490
+ "password_hash",
491
+ ]
492
+ id_columns = ["id", "user_id", "userid", "account_id", "user_fk"]
493
+
494
+ # Try EXACT matches first for username
495
+ for pattern in username_patterns:
496
+ for col in columns:
497
+ if col.lower() == pattern and col.lower() not in id_columns:
498
+ username_col = col
499
+ break
500
+ if username_col:
501
+ break
502
+
503
+ # Fall back to SUBSTRING matches for username
504
+ if not username_col:
505
+ for pattern in username_patterns:
506
+ for col in columns:
507
+ if pattern in col.lower() and col.lower() not in id_columns:
508
+ username_col = col
509
+ break
510
+ if username_col:
511
+ break
512
+
513
+ # Try EXACT matches first for password
514
+ for pattern in password_patterns:
515
+ for col in columns:
516
+ if col.lower() == pattern and col.lower() not in id_columns:
517
+ password_col = col
518
+ break
519
+ if password_col:
520
+ break
521
+
522
+ # Fall back to SUBSTRING matches for password
523
+ if not password_col:
524
+ for pattern in password_patterns:
525
+ for col in columns:
526
+ if pattern in col.lower() and col.lower() not in id_columns:
527
+ password_col = col
528
+ break
529
+ if password_col:
530
+ break
531
+
532
+ if not username_col or not password_col:
533
+ logger.debug(f"Skipping {table_key}: missing username/password columns")
534
+ continue
535
+
536
+ # Extract credentials from rows
537
+ for row in rows:
538
+ username = str(row.get(username_col, "")).strip()
539
+ password = str(row.get(password_col, "")).strip()
540
+
541
+ # Handle SQLMap's <blank> placeholder for NULL/empty values
542
+ if username in ["<blank>", "NULL", "None", ""]:
543
+ email = str(row.get("email", "")).strip()
544
+ if email and email not in ["<blank>", "NULL", "None", ""]:
545
+ username = email
546
+ else:
547
+ continue
548
+
549
+ # Validate username is not scanner garbage / injection payload
550
+ if not self._is_valid_username(username):
551
+ logger.debug(
552
+ f"Skipping invalid username (scanner artifact): {username[:50]}..."
553
+ )
554
+ continue
555
+
556
+ # Count this as a valid user (has username/email)
557
+ total_users += 1
558
+
559
+ # Check if user has a password
560
+ has_password = password and password not in [
561
+ "NULL",
562
+ "None",
563
+ "",
564
+ "<blank>",
565
+ ]
566
+
567
+ # Add to all users list (for expanded view)
568
+ all_users_list.append(
569
+ {
570
+ "username": username,
571
+ "password": password if has_password else None,
572
+ "has_password": has_password,
573
+ }
574
+ )
575
+
576
+ # Skip users without passwords for credential cracking
577
+ if not has_password:
578
+ continue
579
+
580
+ # Detect hash type
581
+ credential_type = self._detect_hash_type(password)
582
+
583
+ # Add to credentials database
584
+ try:
585
+ credentials_manager.add_credential(
586
+ engagement_id=engagement_id,
587
+ host_id=host_id,
588
+ username=username,
589
+ password=password,
590
+ credential_type=credential_type,
591
+ tool="sqlmap",
592
+ service="web",
593
+ )
594
+ credentials_added += 1
595
+ # Add to list for chaining and 🔓 indicator
596
+ credentials_list.append(
597
+ {
598
+ "username": username,
599
+ "password": password,
600
+ "credential_type": credential_type,
601
+ }
602
+ )
603
+ except Exception as e:
604
+ logger.warning(f"Failed to add credential {username}: {e}")
605
+
606
+ return credentials_added, credentials_list, total_users, all_users_list
607
+
608
+ def _is_valid_username(self, username: str) -> bool:
609
+ """
610
+ Validate that a username is legitimate and not scanner garbage.
611
+
612
+ Rejects:
613
+ - Injection payloads (netsparker, burp, etc.)
614
+ - Scanner artifacts ({{, ${, %27, etc.)
615
+ - Path patterns (/etc/passwd, .asp, .axd, etc.)
616
+ - Command injection attempts (ping, whoami, etc.)
617
+ - Overly long values (>100 chars)
618
+
619
+ Args:
620
+ username: Username string to validate
621
+
622
+ Returns:
623
+ bool: True if username appears legitimate
624
+ """
625
+ if not username or len(username) > 100:
626
+ return False
627
+
628
+ username_lower = username.lower()
629
+
630
+ # Injection tool signatures
631
+ scanner_patterns = [
632
+ "netsparker",
633
+ "burpsuite",
634
+ "burp",
635
+ "acunetix",
636
+ "nikto",
637
+ "sqlmap",
638
+ "havij",
639
+ "w3af",
640
+ "owasp",
641
+ "zap",
642
+ "wvs",
643
+ ]
644
+ for pattern in scanner_patterns:
645
+ if pattern in username_lower:
646
+ return False
647
+
648
+ # Template injection / expression patterns
649
+ injection_patterns = [
650
+ "{{",
651
+ "}}",
652
+ "${",
653
+ "}$",
654
+ "<%",
655
+ "%>",
656
+ "{%",
657
+ "%}",
658
+ "${7*7}",
659
+ "{{7*7}}",
660
+ "sleep(",
661
+ "benchmark(",
662
+ "waitfor delay",
663
+ "pg_sleep",
664
+ ]
665
+ for pattern in injection_patterns:
666
+ if pattern in username_lower:
667
+ return False
668
+
669
+ # Path traversal / file patterns
670
+ path_patterns = [
671
+ "/etc/",
672
+ "\\etc\\",
673
+ "/passwd",
674
+ "/shadow",
675
+ "/windows/",
676
+ "c:\\",
677
+ ".asp",
678
+ ".aspx",
679
+ ".axd",
680
+ ".php",
681
+ ".jsp",
682
+ ".pl",
683
+ "../",
684
+ "..\\",
685
+ "file://",
686
+ "php://",
687
+ "data://",
688
+ "::1/",
689
+ "[::1]",
690
+ "/elmah",
691
+ "/trace",
692
+ "127.0.0.1/",
693
+ ]
694
+ for pattern in path_patterns:
695
+ if pattern in username_lower:
696
+ return False
697
+
698
+ # Command injection patterns
699
+ cmd_patterns = [
700
+ "& ping ",
701
+ "| ping ",
702
+ "; ping ",
703
+ "ping -",
704
+ "& whoami",
705
+ "| whoami",
706
+ "; whoami",
707
+ "`whoami`",
708
+ "$(whoami)",
709
+ "cmd.exe",
710
+ "/bin/sh",
711
+ "& dir",
712
+ "| dir",
713
+ "; dir",
714
+ "& ls",
715
+ "| ls",
716
+ "; ls",
717
+ "nc -",
718
+ "ncat ",
719
+ "netcat ",
720
+ ]
721
+ for pattern in cmd_patterns:
722
+ if pattern in username_lower:
723
+ return False
724
+
725
+ # SQL injection patterns
726
+ sql_patterns = [
727
+ "' or ",
728
+ "' and ",
729
+ "1=1",
730
+ "1'='1",
731
+ "' union ",
732
+ "select ",
733
+ "insert ",
734
+ "update ",
735
+ "delete ",
736
+ "drop ",
737
+ "concat(",
738
+ "char(",
739
+ "chr(",
740
+ "0x00",
741
+ "@@version",
742
+ ]
743
+ for pattern in sql_patterns:
744
+ if pattern in username_lower:
745
+ return False
746
+
747
+ # URL encoding patterns that indicate payloads
748
+ if "%27" in username or "%22" in username or "%3c" in username_lower:
749
+ return False
750
+
751
+ # Hex patterns (0x...) that indicate payloads
752
+ if re.search(r"0x[0-9a-f]{4,}", username_lower):
753
+ return False
754
+
755
+ # If it starts/ends with special injection chars, reject
756
+ injection_chars = ['"', "'", ";", "|", "&", "`", "(", ")", "<", ">"]
757
+ if username[0] in injection_chars or username[-1] in injection_chars:
758
+ return False
759
+
760
+ # Too many special characters (normal usernames don't have many)
761
+ special_count = sum(1 for c in username if c in "{}[]()$%^&*|\\/<>\"`'")
762
+ if special_count > 3:
763
+ return False
764
+
765
+ # If mostly digits/hex and long, likely a payload
766
+ if len(username) > 20:
767
+ alnum_only = re.sub(r"[^a-zA-Z0-9]", "", username)
768
+ if len(alnum_only) > 0:
769
+ digit_ratio = sum(1 for c in alnum_only if c.isdigit()) / len(
770
+ alnum_only
771
+ )
772
+ if digit_ratio > 0.7:
773
+ return False
774
+
775
+ return True
776
+
777
+ def _detect_hash_type(self, password: str) -> str:
778
+ """
779
+ Detect hash type from password string.
780
+
781
+ Args:
782
+ password: Password or hash string
783
+
784
+ Returns:
785
+ str: Credential type ('password', 'hash:bcrypt', 'hash:md5', etc.)
786
+ """
787
+ # Check for hash prefixes
788
+ if (
789
+ password.startswith("$2y$")
790
+ or password.startswith("$2a$")
791
+ or password.startswith("$2b$")
792
+ ):
793
+ return "hash:bcrypt"
794
+ elif password.startswith("$1$"):
795
+ return "hash:md5crypt"
796
+ elif password.startswith("$5$"):
797
+ return "hash:sha256crypt"
798
+ elif password.startswith("$6$"):
799
+ return "hash:sha512crypt"
800
+ elif password.startswith("$apr1$"):
801
+ return "hash:apr1"
802
+ elif password.startswith("{SHA}") or password.startswith("{SSHA}"):
803
+ return "hash:ldap"
804
+
805
+ # Check hex patterns
806
+ if re.match(r"^[a-fA-F0-9]{32}$", password):
807
+ return "hash:md5"
808
+ elif re.match(r"^[a-fA-F0-9]{40}$", password):
809
+ return "hash:sha1"
810
+ elif re.match(r"^[a-fA-F0-9]{64}$", password):
811
+ return "hash:sha256"
812
+ elif re.match(r"^[a-fA-F0-9]{128}$", password):
813
+ return "hash:sha512"
814
+
815
+ # Check for NTLM hash
816
+ if re.match(r"^[a-fA-F0-9]{32}:[a-fA-F0-9]{32}$", password):
817
+ return "hash:ntlm"
818
+
819
+ # Likely plaintext if short and no special patterns
820
+ if len(password) < 20 and not re.search(r"[\$\{\}]", password):
821
+ return "password"
822
+
823
+ # Default to generic hash
824
+ return "hash"
825
+
826
+ def display_done(
827
+ self,
828
+ job: Dict[str, Any],
829
+ log_path: str,
830
+ show_all: bool = False,
831
+ show_passwords: bool = False,
832
+ ) -> None:
833
+ """Display successful SQLMap results."""
834
+ try:
835
+ from souleyez.parsers.sqlmap_parser import (
836
+ parse_sqlmap_output,
837
+ get_sqli_stats,
838
+ )
839
+
840
+ if not log_path or not os.path.exists(log_path):
841
+ return
842
+
843
+ with open(log_path, "r", encoding="utf-8", errors="replace") as f:
844
+ log_content = f.read()
845
+ parsed = parse_sqlmap_output(log_content, job.get("target", ""))
846
+ stats = get_sqli_stats(parsed)
847
+
848
+ # Header
849
+ click.echo(click.style("=" * 70, fg="cyan"))
850
+ click.echo(click.style("SQL INJECTION SCAN", bold=True, fg="cyan"))
851
+ click.echo(click.style("=" * 70, fg="cyan"))
852
+ click.echo()
853
+
854
+ click.echo(
855
+ click.style(f"Target: {job.get('target', 'unknown')}", bold=True)
856
+ )
857
+
858
+ if stats["total_vulns"] == 0 and stats["databases_found"] == 0:
859
+ self.display_no_results(job, log_path)
860
+ return
861
+
862
+ click.echo(
863
+ click.style(
864
+ f"Result: {stats['total_vulns']} vulnerability(ies) found",
865
+ fg="red",
866
+ bold=True,
867
+ )
868
+ )
869
+ click.echo()
870
+
871
+ # Injection details with techniques and payloads
872
+ if parsed.get("injection_techniques"):
873
+ for inj in parsed["injection_techniques"]:
874
+ click.echo(
875
+ click.style(
876
+ f"[+] SQL Injection: {inj['parameter']} ({inj['method']})",
877
+ fg="red",
878
+ bold=True,
879
+ )
880
+ )
881
+ click.echo()
882
+
883
+ tech_limit = len(inj["techniques"]) if show_all else 4
884
+ click.echo(
885
+ click.style(
886
+ f" Injection Techniques Found: {len(inj['techniques'])}",
887
+ bold=True,
888
+ )
889
+ )
890
+ for tech in inj["techniques"][:tech_limit]:
891
+ click.echo(click.style(f" - {tech['type']}", fg="yellow"))
892
+ if tech.get("title"):
893
+ click.echo(f" Title: {tech['title']}")
894
+ if tech.get("payload"):
895
+ payload = tech["payload"]
896
+ if not show_all and len(payload) > 80:
897
+ payload = payload[:77] + "..."
898
+ click.echo(f" Payload: {payload}")
899
+ click.echo()
900
+
901
+ if not show_all and len(inj["techniques"]) > 4:
902
+ click.echo(
903
+ f" ... and {len(inj['techniques']) - 4} more techniques"
904
+ )
905
+ click.echo()
906
+ elif stats["sqli_confirmed"] > 0:
907
+ click.echo(
908
+ click.style(
909
+ f"[+] SQL Injection Found: {stats['sqli_confirmed']} parameter(s)",
910
+ fg="red",
911
+ bold=True,
912
+ )
913
+ )
914
+ click.echo()
915
+
916
+ # XSS and File Inclusion warnings
917
+ if stats.get("xss_possible", 0) > 0:
918
+ click.echo(
919
+ click.style(
920
+ f"[!] Possible XSS: {stats['xss_possible']} parameter(s)",
921
+ fg="yellow",
922
+ )
923
+ )
924
+ if stats.get("fi_possible", 0) > 0:
925
+ click.echo(
926
+ click.style(
927
+ f"[!] Possible File Inclusion: {stats['fi_possible']} parameter(s)",
928
+ fg="yellow",
929
+ )
930
+ )
931
+
932
+ if stats.get("xss_possible", 0) > 0 or stats.get("fi_possible", 0) > 0:
933
+ click.echo()
934
+
935
+ # Web stack information
936
+ if parsed.get("web_server_os"):
937
+ click.echo(
938
+ click.style("Web Server OS: ", bold=True) + parsed["web_server_os"]
939
+ )
940
+
941
+ if parsed.get("web_app_technology"):
942
+ click.echo(
943
+ click.style("Web Technology: ", bold=True)
944
+ + ", ".join(parsed["web_app_technology"])
945
+ )
946
+
947
+ if parsed.get("dbms"):
948
+ click.echo(click.style("Database: ", bold=True) + parsed["dbms"])
949
+
950
+ if (
951
+ parsed.get("web_server_os")
952
+ or parsed.get("web_app_technology")
953
+ or parsed.get("dbms")
954
+ ):
955
+ click.echo()
956
+
957
+ # Databases enumerated
958
+ if parsed.get("databases"):
959
+ click.echo(
960
+ click.style(
961
+ f"Databases Enumerated ({len(parsed['databases'])}):",
962
+ bold=True,
963
+ fg="green",
964
+ )
965
+ )
966
+ max_dbs = None if show_all else 10
967
+ display_dbs = (
968
+ parsed["databases"]
969
+ if max_dbs is None
970
+ else parsed["databases"][:max_dbs]
971
+ )
972
+ for db in display_dbs:
973
+ click.echo(f" - {db}")
974
+ if max_dbs and len(parsed["databases"]) > max_dbs:
975
+ click.echo(f" ... and {len(parsed['databases']) - max_dbs} more")
976
+ click.echo()
977
+
978
+ # Tables enumerated
979
+ if parsed.get("tables"):
980
+ total_tables = sum(len(tables) for tables in parsed["tables"].values())
981
+ click.echo(
982
+ click.style(
983
+ f"Tables Enumerated ({total_tables}):", bold=True, fg="green"
984
+ )
985
+ )
986
+ max_tables = None if show_all else 15
987
+ for db_name, tables in parsed["tables"].items():
988
+ if len(parsed["tables"]) > 1:
989
+ click.echo(f" [{db_name}]")
990
+ display_tables = tables if show_all else tables[:15]
991
+ for table in display_tables:
992
+ click.echo(f" - {table}")
993
+ if not show_all and len(tables) > 15:
994
+ click.echo(f" ... and {len(tables) - 15} more")
995
+ click.echo()
996
+
997
+ # Dumped data
998
+ if parsed.get("dumped_data"):
999
+ click.echo(click.style("Data Dumped:", bold=True, fg="red"))
1000
+ for table_key, data in parsed["dumped_data"].items():
1001
+ row_count = data.get("row_count", len(data.get("rows", [])))
1002
+ columns = data.get("columns", [])
1003
+ click.echo(f" - {table_key}: {row_count} row(s)")
1004
+ if columns:
1005
+ col_limit = None if show_all else 8
1006
+ display_cols = columns if show_all else columns[:col_limit]
1007
+ click.echo(f" Columns: {', '.join(display_cols)}")
1008
+ if not show_all and len(columns) > 8:
1009
+ click.echo(f" ... and {len(columns) - 8} more")
1010
+ # Skip raw data rows - shown cleaner in PARSED RESULTS
1011
+ click.echo()
1012
+
1013
+ click.echo(click.style("=" * 70, fg="cyan"))
1014
+ click.echo()
1015
+
1016
+ except Exception as e:
1017
+ logger.debug(f"Error in display_done: {e}")
1018
+
1019
+ def display_warning(
1020
+ self,
1021
+ job: Dict[str, Any],
1022
+ log_path: str,
1023
+ log_content: Optional[str] = None,
1024
+ ) -> None:
1025
+ """Display warning status for SQLMap."""
1026
+ click.echo(click.style("=" * 70, fg="yellow"))
1027
+ click.echo(click.style("[WARNING] SQLMAP SCAN", bold=True, fg="yellow"))
1028
+ click.echo(click.style("=" * 70, fg="yellow"))
1029
+ click.echo()
1030
+ click.echo(" Scan completed with warnings. Check raw logs for details.")
1031
+ click.echo(" Press [r] to view raw logs.")
1032
+ click.echo()
1033
+ click.echo(click.style("=" * 70, fg="yellow"))
1034
+ click.echo()
1035
+
1036
+ def display_error(
1037
+ self,
1038
+ job: Dict[str, Any],
1039
+ log_path: str,
1040
+ log_content: Optional[str] = None,
1041
+ ) -> None:
1042
+ """Display error status for SQLMap."""
1043
+ # Read log if not provided
1044
+ if log_content is None and log_path and os.path.exists(log_path):
1045
+ try:
1046
+ with open(log_path, "r", encoding="utf-8", errors="replace") as f:
1047
+ log_content = f.read()
1048
+ except Exception:
1049
+ log_content = ""
1050
+
1051
+ click.echo(click.style("=" * 70, fg="red"))
1052
+ click.echo(click.style("[ERROR] SQLMAP SCAN FAILED", bold=True, fg="red"))
1053
+ click.echo(click.style("=" * 70, fg="red"))
1054
+ click.echo()
1055
+
1056
+ # Check for common sqlmap errors
1057
+ error_msg = None
1058
+ if log_content:
1059
+ if "connection timed out" in log_content.lower():
1060
+ error_msg = "Connection timed out - target may be slow or filtering"
1061
+ elif "unable to connect" in log_content.lower():
1062
+ error_msg = "Unable to connect to target URL"
1063
+ elif "page not found" in log_content.lower() or "404" in log_content:
1064
+ error_msg = "Target page not found (404)"
1065
+ elif "invalid target url" in log_content.lower():
1066
+ error_msg = "Invalid target URL - check the URL format"
1067
+ elif (
1068
+ "blocked by WAF/IPS" in log_content
1069
+ or "rejected by the target" in log_content.lower()
1070
+ ):
1071
+ # Only flag actual WAF blocks, not heuristic "might be protected" checks
1072
+ error_msg = "WAF/IPS detected - try --tamper scripts"
1073
+ elif "all tested parameters do not appear" in log_content.lower():
1074
+ error_msg = "No injectable parameters found"
1075
+ elif "[CRITICAL]" in log_content:
1076
+ match = re.search(r"\[CRITICAL\]\s*(.+?)(?:\n|$)", log_content)
1077
+ if match:
1078
+ error_msg = match.group(1).strip()[:100]
1079
+
1080
+ if error_msg:
1081
+ click.echo(f" {error_msg}")
1082
+ else:
1083
+ click.echo(" Scan failed - see raw logs for details (press 'r')")
1084
+
1085
+ click.echo()
1086
+ click.echo(click.style("=" * 70, fg="red"))
1087
+ click.echo()
1088
+
1089
+ def display_no_results(
1090
+ self,
1091
+ job: Dict[str, Any],
1092
+ log_path: str,
1093
+ ) -> None:
1094
+ """Display no_results status for SQLMap."""
1095
+ click.echo(click.style("=" * 70, fg="cyan"))
1096
+ click.echo(click.style("SQL INJECTION SCAN", bold=True, fg="cyan"))
1097
+ click.echo(click.style("=" * 70, fg="cyan"))
1098
+ click.echo()
1099
+
1100
+ if job.get("target"):
1101
+ click.echo(click.style(f"Target: {job.get('target')}", bold=True))
1102
+ click.echo()
1103
+
1104
+ click.echo(
1105
+ click.style("Result: No SQL injection vulnerabilities found", fg="yellow")
1106
+ )
1107
+ click.echo()
1108
+ click.echo(" The target was tested but no injectable parameters were found.")
1109
+ click.echo()
1110
+ click.echo(click.style("Tips:", dim=True))
1111
+ click.echo(" - Try increasing --level and --risk for deeper testing")
1112
+ click.echo(" - Test with authenticated session cookies")
1113
+ click.echo(" - Try different injection techniques (--technique=BEUST)")
1114
+ click.echo()
1115
+ click.echo(click.style("=" * 70, fg="cyan"))
1116
+ click.echo()