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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. souleyez/__init__.py +1 -2
  2. souleyez/ai/__init__.py +21 -15
  3. souleyez/ai/action_mapper.py +249 -150
  4. souleyez/ai/chain_advisor.py +116 -100
  5. souleyez/ai/claude_provider.py +29 -28
  6. souleyez/ai/context_builder.py +80 -62
  7. souleyez/ai/executor.py +158 -117
  8. souleyez/ai/feedback_handler.py +136 -121
  9. souleyez/ai/llm_factory.py +27 -20
  10. souleyez/ai/llm_provider.py +4 -2
  11. souleyez/ai/ollama_provider.py +6 -9
  12. souleyez/ai/ollama_service.py +44 -37
  13. souleyez/ai/path_scorer.py +91 -76
  14. souleyez/ai/recommender.py +176 -144
  15. souleyez/ai/report_context.py +74 -73
  16. souleyez/ai/report_service.py +84 -66
  17. souleyez/ai/result_parser.py +222 -229
  18. souleyez/ai/safety.py +67 -44
  19. souleyez/auth/__init__.py +23 -22
  20. souleyez/auth/audit.py +36 -26
  21. souleyez/auth/engagement_access.py +65 -48
  22. souleyez/auth/permissions.py +14 -3
  23. souleyez/auth/session_manager.py +54 -37
  24. souleyez/auth/user_manager.py +109 -64
  25. souleyez/commands/audit.py +40 -43
  26. souleyez/commands/auth.py +35 -15
  27. souleyez/commands/deliverables.py +55 -50
  28. souleyez/commands/engagement.py +47 -28
  29. souleyez/commands/license.py +32 -23
  30. souleyez/commands/screenshots.py +36 -32
  31. souleyez/commands/user.py +82 -36
  32. souleyez/config.py +52 -44
  33. souleyez/core/credential_tester.py +87 -81
  34. souleyez/core/cve_mappings.py +179 -192
  35. souleyez/core/cve_matcher.py +162 -148
  36. souleyez/core/msf_auto_mapper.py +100 -83
  37. souleyez/core/msf_chain_engine.py +294 -256
  38. souleyez/core/msf_database.py +153 -70
  39. souleyez/core/msf_integration.py +679 -673
  40. souleyez/core/msf_rpc_client.py +40 -42
  41. souleyez/core/msf_rpc_manager.py +77 -79
  42. souleyez/core/msf_sync_manager.py +241 -181
  43. souleyez/core/network_utils.py +22 -15
  44. souleyez/core/parser_handler.py +34 -25
  45. souleyez/core/pending_chains.py +114 -63
  46. souleyez/core/templates.py +158 -107
  47. souleyez/core/tool_chaining.py +9564 -2881
  48. souleyez/core/version_utils.py +79 -94
  49. souleyez/core/vuln_correlation.py +136 -89
  50. souleyez/core/web_utils.py +33 -32
  51. souleyez/data/wordlists/ad_users.txt +378 -0
  52. souleyez/data/wordlists/api_endpoints_large.txt +769 -0
  53. souleyez/data/wordlists/home_dir_sensitive.txt +39 -0
  54. souleyez/data/wordlists/lfi_payloads.txt +82 -0
  55. souleyez/data/wordlists/passwords_brute.txt +1548 -0
  56. souleyez/data/wordlists/passwords_crack.txt +2479 -0
  57. souleyez/data/wordlists/passwords_spray.txt +386 -0
  58. souleyez/data/wordlists/subdomains_large.txt +5057 -0
  59. souleyez/data/wordlists/usernames_common.txt +694 -0
  60. souleyez/data/wordlists/web_dirs_large.txt +4769 -0
  61. souleyez/detection/__init__.py +1 -1
  62. souleyez/detection/attack_signatures.py +12 -17
  63. souleyez/detection/mitre_mappings.py +61 -55
  64. souleyez/detection/validator.py +97 -86
  65. souleyez/devtools.py +23 -10
  66. souleyez/docs/README.md +4 -4
  67. souleyez/docs/api-reference/cli-commands.md +2 -2
  68. souleyez/docs/developer-guide/adding-new-tools.md +562 -0
  69. souleyez/docs/user-guide/auto-chaining.md +30 -8
  70. souleyez/docs/user-guide/getting-started.md +1 -1
  71. souleyez/docs/user-guide/installation.md +26 -3
  72. souleyez/docs/user-guide/metasploit-integration.md +2 -2
  73. souleyez/docs/user-guide/rbac.md +1 -1
  74. souleyez/docs/user-guide/scope-management.md +1 -1
  75. souleyez/docs/user-guide/siem-integration.md +1 -1
  76. souleyez/docs/user-guide/tools-reference.md +1 -8
  77. souleyez/docs/user-guide/worker-management.md +1 -1
  78. souleyez/engine/background.py +1239 -535
  79. souleyez/engine/base.py +4 -1
  80. souleyez/engine/job_status.py +17 -49
  81. souleyez/engine/log_sanitizer.py +103 -77
  82. souleyez/engine/manager.py +38 -7
  83. souleyez/engine/result_handler.py +2200 -1550
  84. souleyez/engine/worker_manager.py +50 -41
  85. souleyez/export/evidence_bundle.py +72 -62
  86. souleyez/feature_flags/features.py +16 -20
  87. souleyez/feature_flags.py +5 -9
  88. souleyez/handlers/__init__.py +11 -0
  89. souleyez/handlers/base.py +188 -0
  90. souleyez/handlers/bash_handler.py +277 -0
  91. souleyez/handlers/bloodhound_handler.py +243 -0
  92. souleyez/handlers/certipy_handler.py +311 -0
  93. souleyez/handlers/crackmapexec_handler.py +486 -0
  94. souleyez/handlers/dnsrecon_handler.py +344 -0
  95. souleyez/handlers/enum4linux_handler.py +400 -0
  96. souleyez/handlers/evil_winrm_handler.py +493 -0
  97. souleyez/handlers/ffuf_handler.py +815 -0
  98. souleyez/handlers/gobuster_handler.py +1114 -0
  99. souleyez/handlers/gpp_extract_handler.py +334 -0
  100. souleyez/handlers/hashcat_handler.py +444 -0
  101. souleyez/handlers/hydra_handler.py +564 -0
  102. souleyez/handlers/impacket_getuserspns_handler.py +343 -0
  103. souleyez/handlers/impacket_psexec_handler.py +222 -0
  104. souleyez/handlers/impacket_secretsdump_handler.py +426 -0
  105. souleyez/handlers/john_handler.py +286 -0
  106. souleyez/handlers/katana_handler.py +425 -0
  107. souleyez/handlers/kerbrute_handler.py +298 -0
  108. souleyez/handlers/ldapsearch_handler.py +636 -0
  109. souleyez/handlers/lfi_extract_handler.py +464 -0
  110. souleyez/handlers/msf_auxiliary_handler.py +409 -0
  111. souleyez/handlers/msf_exploit_handler.py +380 -0
  112. souleyez/handlers/nikto_handler.py +413 -0
  113. souleyez/handlers/nmap_handler.py +821 -0
  114. souleyez/handlers/nuclei_handler.py +359 -0
  115. souleyez/handlers/nxc_handler.py +417 -0
  116. souleyez/handlers/rdp_sec_check_handler.py +353 -0
  117. souleyez/handlers/registry.py +292 -0
  118. souleyez/handlers/responder_handler.py +232 -0
  119. souleyez/handlers/service_explorer_handler.py +434 -0
  120. souleyez/handlers/smbclient_handler.py +344 -0
  121. souleyez/handlers/smbmap_handler.py +510 -0
  122. souleyez/handlers/smbpasswd_handler.py +296 -0
  123. souleyez/handlers/sqlmap_handler.py +1116 -0
  124. souleyez/handlers/theharvester_handler.py +601 -0
  125. souleyez/handlers/web_login_test_handler.py +327 -0
  126. souleyez/handlers/whois_handler.py +277 -0
  127. souleyez/handlers/wpscan_handler.py +554 -0
  128. souleyez/history.py +32 -16
  129. souleyez/importers/msf_importer.py +106 -75
  130. souleyez/importers/smart_importer.py +208 -147
  131. souleyez/integrations/siem/__init__.py +10 -10
  132. souleyez/integrations/siem/base.py +17 -18
  133. souleyez/integrations/siem/elastic.py +108 -122
  134. souleyez/integrations/siem/factory.py +207 -80
  135. souleyez/integrations/siem/googlesecops.py +146 -154
  136. souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
  137. souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
  138. souleyez/integrations/siem/sentinel.py +107 -109
  139. souleyez/integrations/siem/splunk.py +246 -212
  140. souleyez/integrations/siem/wazuh.py +65 -71
  141. souleyez/integrations/wazuh/__init__.py +5 -5
  142. souleyez/integrations/wazuh/client.py +70 -93
  143. souleyez/integrations/wazuh/config.py +85 -57
  144. souleyez/integrations/wazuh/host_mapper.py +28 -36
  145. souleyez/integrations/wazuh/sync.py +78 -68
  146. souleyez/intelligence/__init__.py +4 -5
  147. souleyez/intelligence/correlation_analyzer.py +309 -295
  148. souleyez/intelligence/exploit_knowledge.py +661 -623
  149. souleyez/intelligence/exploit_suggestions.py +159 -139
  150. souleyez/intelligence/gap_analyzer.py +132 -97
  151. souleyez/intelligence/gap_detector.py +251 -214
  152. souleyez/intelligence/sensitive_tables.py +266 -129
  153. souleyez/intelligence/service_parser.py +137 -123
  154. souleyez/intelligence/surface_analyzer.py +407 -268
  155. souleyez/intelligence/target_parser.py +159 -162
  156. souleyez/licensing/__init__.py +6 -6
  157. souleyez/licensing/validator.py +17 -19
  158. souleyez/log_config.py +79 -54
  159. souleyez/main.py +1505 -687
  160. souleyez/migrations/fix_job_counter.py +16 -14
  161. souleyez/parsers/bloodhound_parser.py +41 -39
  162. souleyez/parsers/crackmapexec_parser.py +178 -111
  163. souleyez/parsers/dalfox_parser.py +72 -77
  164. souleyez/parsers/dnsrecon_parser.py +103 -91
  165. souleyez/parsers/enum4linux_parser.py +183 -153
  166. souleyez/parsers/ffuf_parser.py +29 -25
  167. souleyez/parsers/gobuster_parser.py +301 -41
  168. souleyez/parsers/hashcat_parser.py +324 -79
  169. souleyez/parsers/http_fingerprint_parser.py +350 -103
  170. souleyez/parsers/hydra_parser.py +131 -111
  171. souleyez/parsers/impacket_parser.py +231 -178
  172. souleyez/parsers/john_parser.py +98 -86
  173. souleyez/parsers/katana_parser.py +316 -0
  174. souleyez/parsers/msf_parser.py +943 -498
  175. souleyez/parsers/nikto_parser.py +346 -65
  176. souleyez/parsers/nmap_parser.py +262 -174
  177. souleyez/parsers/nuclei_parser.py +40 -44
  178. souleyez/parsers/responder_parser.py +26 -26
  179. souleyez/parsers/searchsploit_parser.py +74 -74
  180. souleyez/parsers/service_explorer_parser.py +279 -0
  181. souleyez/parsers/smbmap_parser.py +180 -124
  182. souleyez/parsers/sqlmap_parser.py +434 -308
  183. souleyez/parsers/theharvester_parser.py +75 -57
  184. souleyez/parsers/whois_parser.py +135 -94
  185. souleyez/parsers/wpscan_parser.py +278 -190
  186. souleyez/plugins/afp.py +44 -36
  187. souleyez/plugins/afp_brute.py +114 -46
  188. souleyez/plugins/ard.py +48 -37
  189. souleyez/plugins/bloodhound.py +95 -61
  190. souleyez/plugins/certipy.py +303 -0
  191. souleyez/plugins/crackmapexec.py +186 -85
  192. souleyez/plugins/dalfox.py +120 -59
  193. souleyez/plugins/dns_hijack.py +146 -41
  194. souleyez/plugins/dnsrecon.py +97 -61
  195. souleyez/plugins/enum4linux.py +91 -66
  196. souleyez/plugins/evil_winrm.py +291 -0
  197. souleyez/plugins/ffuf.py +166 -90
  198. souleyez/plugins/firmware_extract.py +133 -29
  199. souleyez/plugins/gobuster.py +387 -190
  200. souleyez/plugins/gpp_extract.py +393 -0
  201. souleyez/plugins/hashcat.py +100 -73
  202. souleyez/plugins/http_fingerprint.py +913 -267
  203. souleyez/plugins/hydra.py +566 -200
  204. souleyez/plugins/impacket_getnpusers.py +117 -69
  205. souleyez/plugins/impacket_psexec.py +84 -64
  206. souleyez/plugins/impacket_secretsdump.py +103 -69
  207. souleyez/plugins/impacket_smbclient.py +89 -75
  208. souleyez/plugins/john.py +86 -69
  209. souleyez/plugins/katana.py +313 -0
  210. souleyez/plugins/kerbrute.py +237 -0
  211. souleyez/plugins/lfi_extract.py +541 -0
  212. souleyez/plugins/macos_ssh.py +117 -48
  213. souleyez/plugins/mdns.py +35 -30
  214. souleyez/plugins/msf_auxiliary.py +253 -130
  215. souleyez/plugins/msf_exploit.py +239 -161
  216. souleyez/plugins/nikto.py +134 -78
  217. souleyez/plugins/nmap.py +275 -91
  218. souleyez/plugins/nuclei.py +180 -89
  219. souleyez/plugins/nxc.py +285 -0
  220. souleyez/plugins/plugin_base.py +35 -36
  221. souleyez/plugins/plugin_template.py +13 -5
  222. souleyez/plugins/rdp_sec_check.py +130 -0
  223. souleyez/plugins/responder.py +112 -71
  224. souleyez/plugins/router_http_brute.py +76 -65
  225. souleyez/plugins/router_ssh_brute.py +118 -41
  226. souleyez/plugins/router_telnet_brute.py +124 -42
  227. souleyez/plugins/routersploit.py +91 -59
  228. souleyez/plugins/routersploit_exploit.py +77 -55
  229. souleyez/plugins/searchsploit.py +91 -77
  230. souleyez/plugins/service_explorer.py +1160 -0
  231. souleyez/plugins/smbmap.py +122 -72
  232. souleyez/plugins/smbpasswd.py +215 -0
  233. souleyez/plugins/sqlmap.py +301 -113
  234. souleyez/plugins/theharvester.py +127 -75
  235. souleyez/plugins/tr069.py +79 -57
  236. souleyez/plugins/upnp.py +65 -47
  237. souleyez/plugins/upnp_abuse.py +73 -55
  238. souleyez/plugins/vnc_access.py +129 -42
  239. souleyez/plugins/vnc_brute.py +109 -38
  240. souleyez/plugins/web_login_test.py +417 -0
  241. souleyez/plugins/whois.py +77 -58
  242. souleyez/plugins/wpscan.py +219 -69
  243. souleyez/reporting/__init__.py +2 -1
  244. souleyez/reporting/attack_chain.py +411 -346
  245. souleyez/reporting/charts.py +436 -501
  246. souleyez/reporting/compliance_mappings.py +334 -201
  247. souleyez/reporting/detection_report.py +126 -125
  248. souleyez/reporting/formatters.py +828 -591
  249. souleyez/reporting/generator.py +386 -302
  250. souleyez/reporting/metrics.py +72 -75
  251. souleyez/scanner.py +35 -29
  252. souleyez/security/__init__.py +37 -11
  253. souleyez/security/scope_validator.py +175 -106
  254. souleyez/security/validation.py +237 -149
  255. souleyez/security.py +22 -6
  256. souleyez/storage/credentials.py +247 -186
  257. souleyez/storage/crypto.py +296 -129
  258. souleyez/storage/database.py +73 -50
  259. souleyez/storage/db.py +58 -36
  260. souleyez/storage/deliverable_evidence.py +177 -128
  261. souleyez/storage/deliverable_exporter.py +282 -246
  262. souleyez/storage/deliverable_templates.py +134 -116
  263. souleyez/storage/deliverables.py +135 -130
  264. souleyez/storage/engagements.py +109 -56
  265. souleyez/storage/evidence.py +181 -152
  266. souleyez/storage/execution_log.py +31 -17
  267. souleyez/storage/exploit_attempts.py +93 -57
  268. souleyez/storage/exploits.py +67 -36
  269. souleyez/storage/findings.py +48 -61
  270. souleyez/storage/hosts.py +176 -144
  271. souleyez/storage/migrate_to_engagements.py +43 -19
  272. souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
  273. souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
  274. souleyez/storage/migrations/_003_add_execution_log.py +14 -8
  275. souleyez/storage/migrations/_005_screenshots.py +13 -5
  276. souleyez/storage/migrations/_006_deliverables.py +13 -5
  277. souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
  278. souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
  279. souleyez/storage/migrations/_010_evidence_linking.py +17 -10
  280. souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
  281. souleyez/storage/migrations/_012_team_collaboration.py +34 -21
  282. souleyez/storage/migrations/_013_add_host_tags.py +12 -6
  283. souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
  284. souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
  285. souleyez/storage/migrations/_016_add_domain_field.py +10 -4
  286. souleyez/storage/migrations/_017_msf_sessions.py +16 -8
  287. souleyez/storage/migrations/_018_add_osint_target.py +10 -6
  288. souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
  289. souleyez/storage/migrations/_020_add_rbac.py +36 -15
  290. souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
  291. souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
  292. souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
  293. souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
  294. souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
  295. souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
  296. souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
  297. souleyez/storage/migrations/__init__.py +26 -26
  298. souleyez/storage/migrations/migration_manager.py +19 -19
  299. souleyez/storage/msf_sessions.py +100 -65
  300. souleyez/storage/osint.py +17 -24
  301. souleyez/storage/recommendation_engine.py +269 -235
  302. souleyez/storage/screenshots.py +33 -32
  303. souleyez/storage/smb_shares.py +136 -92
  304. souleyez/storage/sqlmap_data.py +183 -128
  305. souleyez/storage/team_collaboration.py +135 -141
  306. souleyez/storage/timeline_tracker.py +122 -94
  307. souleyez/storage/wazuh_vulns.py +64 -66
  308. souleyez/storage/web_paths.py +33 -37
  309. souleyez/testing/credential_tester.py +221 -205
  310. souleyez/ui/__init__.py +1 -1
  311. souleyez/ui/ai_quotes.py +12 -12
  312. souleyez/ui/attack_surface.py +2439 -1516
  313. souleyez/ui/chain_rules_view.py +914 -382
  314. souleyez/ui/correlation_view.py +312 -230
  315. souleyez/ui/dashboard.py +2382 -1130
  316. souleyez/ui/deliverables_view.py +148 -62
  317. souleyez/ui/design_system.py +13 -13
  318. souleyez/ui/errors.py +49 -49
  319. souleyez/ui/evidence_linking_view.py +284 -179
  320. souleyez/ui/evidence_vault.py +393 -285
  321. souleyez/ui/exploit_suggestions_view.py +555 -349
  322. souleyez/ui/export_view.py +100 -66
  323. souleyez/ui/gap_analysis_view.py +315 -171
  324. souleyez/ui/help_system.py +105 -97
  325. souleyez/ui/intelligence_view.py +436 -293
  326. souleyez/ui/interactive.py +23034 -10679
  327. souleyez/ui/interactive_selector.py +75 -68
  328. souleyez/ui/log_formatter.py +47 -39
  329. souleyez/ui/menu_components.py +22 -13
  330. souleyez/ui/msf_auxiliary_menu.py +184 -133
  331. souleyez/ui/pending_chains_view.py +336 -172
  332. souleyez/ui/progress_indicators.py +5 -3
  333. souleyez/ui/recommendations_view.py +195 -137
  334. souleyez/ui/rule_builder.py +343 -225
  335. souleyez/ui/setup_wizard.py +678 -284
  336. souleyez/ui/shortcuts.py +217 -165
  337. souleyez/ui/splunk_gap_analysis_view.py +452 -270
  338. souleyez/ui/splunk_vulns_view.py +139 -86
  339. souleyez/ui/team_dashboard.py +498 -335
  340. souleyez/ui/template_selector.py +196 -105
  341. souleyez/ui/terminal.py +6 -6
  342. souleyez/ui/timeline_view.py +198 -127
  343. souleyez/ui/tool_setup.py +264 -164
  344. souleyez/ui/tutorial.py +202 -72
  345. souleyez/ui/tutorial_state.py +40 -40
  346. souleyez/ui/wazuh_vulns_view.py +235 -141
  347. souleyez/ui/wordlist_browser.py +260 -107
  348. souleyez/ui.py +464 -312
  349. souleyez/utils/tool_checker.py +427 -367
  350. souleyez/utils.py +33 -29
  351. souleyez/wordlists.py +134 -167
  352. {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/METADATA +2 -2
  353. souleyez-3.0.0.dist-info/RECORD +443 -0
  354. {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/WHEEL +1 -1
  355. souleyez-2.43.29.dist-info/RECORD +0 -379
  356. {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/entry_points.txt +0 -0
  357. {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/licenses/LICENSE +0 -0
  358. {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/top_level.txt +0 -0
@@ -11,7 +11,7 @@ from typing import Dict, Any
11
11
  def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
12
12
  """
13
13
  Parse SQLMap output and extract SQL injection vulnerabilities.
14
-
14
+
15
15
  VERSION: 2.0 - Fixed injectable_url for auto-chaining
16
16
 
17
17
  SQLMap output contains:
@@ -44,40 +44,37 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
44
44
  }
45
45
  """
46
46
  result = {
47
- 'target_url': target,
48
- 'urls_tested': [],
49
- 'vulnerabilities': [],
50
- 'databases': [],
51
- 'tables': {}, # {database: [table1, table2, ...]}
52
- 'columns': {}, # {database.table: [col1, col2, ...]}
53
- 'dumped_data': {}, # {db.table: {'rows': [...], 'csv_path': '...', 'columns': [...]}}
54
- 'dbms': None, # Database type (MySQL, PostgreSQL, etc.)
55
-
47
+ "target_url": target,
48
+ "urls_tested": [],
49
+ "vulnerabilities": [],
50
+ "databases": [],
51
+ "tables": {}, # {database: [table1, table2, ...]}
52
+ "columns": {}, # {database.table: [col1, col2, ...]}
53
+ "dumped_data": {}, # {db.table: {'rows': [...], 'csv_path': '...', 'columns': [...]}}
54
+ "dbms": None, # Database type (MySQL, PostgreSQL, etc.)
56
55
  # NEW: Add SQL injection confirmation flags
57
- 'sql_injection_confirmed': False,
58
- 'injectable_parameter': '',
59
- 'injectable_url': target,
60
- 'injectable_post_data': None, # POST data for POST injections
61
- 'injectable_method': 'GET', # GET or POST
62
- 'all_injection_points': [], # ALL injection points found (for fallback)
63
-
56
+ "sql_injection_confirmed": False,
57
+ "injectable_parameter": "",
58
+ "injectable_url": target,
59
+ "injectable_post_data": None, # POST data for POST injections
60
+ "injectable_method": "GET", # GET or POST
61
+ "all_injection_points": [], # ALL injection points found (for fallback)
64
62
  # NEW: Web stack information
65
- 'web_server_os': None,
66
- 'web_app_technology': [],
67
- 'injection_techniques': [],
68
-
63
+ "web_server_os": None,
64
+ "web_app_technology": [],
65
+ "injection_techniques": [],
69
66
  # NEW: Post-exploitation flags for auto-chaining
70
- 'is_dba': False,
71
- 'privileges': [],
72
- 'current_user': None,
73
- 'file_read_success': False,
74
- 'os_command_success': False,
67
+ "is_dba": False,
68
+ "privileges": [],
69
+ "current_user": None,
70
+ "file_read_success": False,
71
+ "os_command_success": False,
75
72
  }
76
73
 
77
- lines = output.split('\n')
74
+ lines = output.split("\n")
78
75
  current_url = None
79
76
  current_param = None
80
- current_method = 'GET'
77
+ current_method = "GET"
81
78
  current_post_data = None
82
79
 
83
80
  # Track POST form URLs separately to prevent GET URL testing from overwriting them
@@ -90,31 +87,33 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
90
87
 
91
88
  # Extract URL being tested (GET requests typically)
92
89
  # Format variations: "testing URL 'http://...'" or 'testing URL "http://..."' or testing URL http://...
93
- if 'testing URL' in line or 'testing url' in line.lower():
90
+ if "testing URL" in line or "testing url" in line.lower():
94
91
  # Try single quotes first
95
- url_match = re.search(r"testing URL ['\"]?([^'\"]+)['\"]?", line, re.IGNORECASE)
92
+ url_match = re.search(
93
+ r"testing URL ['\"]?([^'\"]+)['\"]?", line, re.IGNORECASE
94
+ )
96
95
  if url_match:
97
96
  current_url = url_match.group(1).strip()
98
- if current_url and current_url not in result['urls_tested']:
99
- result['urls_tested'].append(current_url)
97
+ if current_url and current_url not in result["urls_tested"]:
98
+ result["urls_tested"].append(current_url)
100
99
 
101
100
  # Extract POST/GET URLs from form testing (crawl mode)
102
101
  # Format: "POST http://testphp.vulnweb.com/search.php?test=query"
103
- if re.match(r'^(POST|GET)\s+http', line):
104
- url_match = re.search(r'^(POST|GET)\s+(https?://[^\s]+)', line)
102
+ if re.match(r"^(POST|GET)\s+http", line):
103
+ url_match = re.search(r"^(POST|GET)\s+(https?://[^\s]+)", line)
105
104
  if url_match:
106
105
  current_method = url_match.group(1)
107
106
  current_url = url_match.group(2)
108
- if current_url not in result['urls_tested']:
109
- result['urls_tested'].append(current_url)
107
+ if current_url not in result["urls_tested"]:
108
+ result["urls_tested"].append(current_url)
110
109
  # Save POST form URL separately for later use
111
- if current_method == 'POST':
110
+ if current_method == "POST":
112
111
  last_post_form_url = current_url
113
112
 
114
113
  # Extract POST data (appears after "POST http://..." line)
115
114
  # Format: "POST data: username=&password=&submit=Login"
116
- if line.startswith('POST data:'):
117
- post_data_match = re.search(r'^POST data:\s*(.+)$', line)
115
+ if line.startswith("POST data:"):
116
+ post_data_match = re.search(r"^POST data:\s*(.+)$", line)
118
117
  if post_data_match:
119
118
  current_post_data = post_data_match.group(1).strip()
120
119
  # Associate POST data with the POST form URL
@@ -123,67 +122,96 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
123
122
  # Handle resumed injection points from stored session
124
123
  # Pattern: "sqlmap resumed the following injection point(s) from stored session:"
125
124
  # Followed by: "Parameter: User-Agent (User-Agent)" or similar
126
- if 'resumed the following injection point' in line:
125
+ if "resumed the following injection point" in line:
127
126
  # Look ahead for Parameter line
128
127
  for j in range(i + 1, min(i + 10, len(lines))):
129
128
  next_line = lines[j].strip()
130
129
  # Pattern: "Parameter: username (POST)" or "Parameter: User-Agent (User-Agent)"
131
130
  param_match = re.search(
132
- r'^Parameter:\s*([^\s(]+)\s*\(([^)]+)\)', next_line
131
+ r"^Parameter:\s*([^\s(]+)\s*\(([^)]+)\)", next_line
133
132
  )
134
133
  if param_match:
135
134
  param = param_match.group(1)
136
135
  param_location = param_match.group(2)
137
136
 
138
137
  # Determine method from parameter location
139
- if param_location in ('POST', 'GET'):
138
+ if param_location in ("POST", "GET"):
140
139
  method = param_location
141
140
  else:
142
141
  method = current_method # Use current context
143
142
 
144
143
  # For POST parameters, use the saved POST form URL instead of current_url
145
- if method == 'POST' and last_post_form_url:
144
+ if method == "POST" and last_post_form_url:
146
145
  effective_url = last_post_form_url
147
146
  effective_post_data = last_post_form_data or current_post_data
148
147
  else:
149
148
  effective_url = current_url or target
150
- effective_post_data = current_post_data if method == 'POST' else None
149
+ effective_post_data = (
150
+ current_post_data if method == "POST" else None
151
+ )
151
152
 
152
153
  # Mark as confirmed injection
153
- result['sql_injection_confirmed'] = True
154
- result['injectable_parameter'] = param
155
- result['injectable_url'] = effective_url
156
- result['injectable_method'] = method
154
+ result["sql_injection_confirmed"] = True
155
+ result["injectable_parameter"] = param
156
+ result["injectable_url"] = effective_url
157
+ result["injectable_method"] = method
158
+
159
+ # For POST injections, extract POST data from Payload line if not already set
160
+ if method == "POST" and not effective_post_data:
161
+ # Look ahead for Payload line that contains POST data
162
+ for k in range(j + 1, min(j + 15, len(lines))):
163
+ payload_line = lines[k].strip()
164
+ if payload_line.startswith("Payload:"):
165
+ # Extract the POST data from payload
166
+ # Format: "Payload: csrf-token=...&param=value&..."
167
+ payload_match = re.search(
168
+ r"^Payload:\s*(.+)$", payload_line
169
+ )
170
+ if payload_match:
171
+ payload_data = payload_match.group(1).strip()
172
+ # Remove the injection payload part to get clean POST data
173
+ # The payload contains the base POST data with injection appended
174
+ effective_post_data = payload_data
175
+ result["injectable_post_data"] = effective_post_data
176
+ break
177
+ if payload_line.startswith(
178
+ "---"
179
+ ) or payload_line.startswith("["):
180
+ break
181
+ elif method == "POST" and effective_post_data:
182
+ result["injectable_post_data"] = effective_post_data
157
183
 
158
184
  # Add vulnerability entry
159
- result['vulnerabilities'].append({
160
- 'url': effective_url,
161
- 'parameter': param,
162
- 'vuln_type': 'sqli',
163
- 'injectable': True,
164
- 'severity': 'critical',
165
- 'description': f"Parameter '{param}' is vulnerable to SQL injection (resumed from session)"
166
- })
185
+ result["vulnerabilities"].append(
186
+ {
187
+ "url": effective_url,
188
+ "parameter": param,
189
+ "vuln_type": "sqli",
190
+ "injectable": True,
191
+ "severity": "critical",
192
+ "description": f"Parameter '{param}' is vulnerable to SQL injection (resumed from session)",
193
+ }
194
+ )
167
195
 
168
196
  # Collect injection point
169
197
  injection_point = {
170
- 'url': effective_url,
171
- 'parameter': param,
172
- 'method': method,
173
- 'post_data': effective_post_data,
174
- 'techniques': []
198
+ "url": effective_url,
199
+ "parameter": param,
200
+ "method": method,
201
+ "post_data": effective_post_data,
202
+ "techniques": [],
175
203
  }
176
204
  if not any(
177
- ip['url'] == injection_point['url'] and
178
- ip['parameter'] == injection_point['parameter']
179
- for ip in result['all_injection_points']
205
+ ip["url"] == injection_point["url"]
206
+ and ip["parameter"] == injection_point["parameter"]
207
+ for ip in result["all_injection_points"]
180
208
  ):
181
- result['all_injection_points'].append(injection_point)
209
+ result["all_injection_points"].append(injection_point)
182
210
  break
183
211
  # Stop if we hit a delimiter
184
- if next_line == '---':
212
+ if next_line == "---":
185
213
  continue
186
- if next_line.startswith('[') or next_line.startswith('back-end'):
214
+ if next_line.startswith("[") or next_line.startswith("back-end"):
187
215
  break
188
216
 
189
217
  # Extract DBMS type with full version info
@@ -191,120 +219,150 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
191
219
  # "back-end DBMS: MySQL >= 5.0.12"
192
220
  # "back-end DBMS: Microsoft SQL Server 2019"
193
221
  # "back-end DBMS: PostgreSQL"
194
- if 'back-end DBMS:' in line or 'back-end dbms:' in line.lower():
222
+ if "back-end DBMS:" in line or "back-end dbms:" in line.lower():
195
223
  dbms_match = re.search(r"back-end DBMS:\s*(.+)", line, re.IGNORECASE)
196
- if dbms_match and not result['dbms']:
224
+ if dbms_match and not result["dbms"]:
197
225
  dbms_full = dbms_match.group(1).strip()
198
226
  # Extract just the DBMS name for the main field (first word)
199
227
  # but store full version in a separate field
200
- result['dbms'] = dbms_full.split()[0] if dbms_full else None
201
- result['dbms_full'] = dbms_full # Keep full string
228
+ result["dbms"] = dbms_full.split()[0] if dbms_full else None
229
+ result["dbms_full"] = dbms_full # Keep full string
202
230
 
203
231
  # Extract web server OS
204
- if 'web server operating system:' in line.lower():
205
- os_match = re.search(r"web server operating system:\s*(.+)", line, re.IGNORECASE)
232
+ if "web server operating system:" in line.lower():
233
+ os_match = re.search(
234
+ r"web server operating system:\s*(.+)", line, re.IGNORECASE
235
+ )
206
236
  if os_match:
207
- result['web_server_os'] = os_match.group(1).strip()
237
+ result["web_server_os"] = os_match.group(1).strip()
208
238
 
209
239
  # Extract web application technology
210
- if 'web application technology:' in line.lower():
211
- tech_match = re.search(r"web application technology:\s*(.+)", line, re.IGNORECASE)
240
+ if "web application technology:" in line.lower():
241
+ tech_match = re.search(
242
+ r"web application technology:\s*(.+)", line, re.IGNORECASE
243
+ )
212
244
  if tech_match:
213
245
  # Parse comma-separated technologies (e.g., "PHP 5.6.40, Nginx 1.19.0")
214
246
  tech_str = tech_match.group(1).strip()
215
- result['web_app_technology'] = [t.strip() for t in tech_str.split(',')]
247
+ result["web_app_technology"] = [t.strip() for t in tech_str.split(",")]
216
248
 
217
249
  # === POST-EXPLOITATION FLAGS ===
218
250
 
219
251
  # Detect DBA status: "current user is DBA: True" or "current user is DBA: False"
220
- if 'current user is dba:' in line.lower():
221
- if 'true' in line.lower():
222
- result['is_dba'] = True
252
+ if "current user is dba:" in line.lower():
253
+ if "true" in line.lower():
254
+ result["is_dba"] = True
223
255
  # False is default, no need to set
224
256
 
225
257
  # Detect current user: "current user: 'root@localhost'"
226
- if 'current user:' in line.lower() and 'is dba' not in line.lower():
258
+ if "current user:" in line.lower() and "is dba" not in line.lower():
227
259
  user_match = re.search(r"current user:\s*'?([^']+)'?", line, re.IGNORECASE)
228
260
  if user_match:
229
- result['current_user'] = user_match.group(1).strip()
261
+ result["current_user"] = user_match.group(1).strip()
230
262
 
231
263
  # Detect privileges: Look for privilege enumeration output
232
264
  # SQLMap shows: "database management system users privileges:"
233
265
  # followed by "[*] 'user'@'host' [1]:" and privilege lists
234
- if 'database management system users privileges' in line.lower():
266
+ if "database management system users privileges" in line.lower():
235
267
  # Parse privileges from following lines
236
268
  j = i + 1
237
269
  while j < len(lines):
238
270
  priv_line = lines[j].strip()
239
271
  # Look for privilege entries like "privilege: FILE" or just "FILE"
240
- if priv_line.startswith('[*]') and '@' in priv_line:
272
+ if priv_line.startswith("[*]") and "@" in priv_line:
241
273
  # User entry like "[*] 'root'@'localhost' [1]:"
242
274
  j += 1
243
275
  continue
244
- elif priv_line.startswith('privilege:'):
245
- priv = priv_line.replace('privilege:', '').strip()
246
- if priv and priv not in result['privileges']:
247
- result['privileges'].append(priv)
248
- elif priv_line and not priv_line.startswith('[') and not priv_line.startswith('-'):
276
+ elif priv_line.startswith("privilege:"):
277
+ priv = priv_line.replace("privilege:", "").strip()
278
+ if priv and priv not in result["privileges"]:
279
+ result["privileges"].append(priv)
280
+ elif (
281
+ priv_line
282
+ and not priv_line.startswith("[")
283
+ and not priv_line.startswith("-")
284
+ ):
249
285
  # Direct privilege name
250
- if priv_line.isupper() or priv_line in ['FILE', 'SUPER', 'PROCESS', 'SHUTDOWN']:
251
- if priv_line not in result['privileges']:
252
- result['privileges'].append(priv_line)
253
- elif not priv_line or priv_line.startswith('[INFO]') or priv_line.startswith('---'):
286
+ if priv_line.isupper() or priv_line in [
287
+ "FILE",
288
+ "SUPER",
289
+ "PROCESS",
290
+ "SHUTDOWN",
291
+ ]:
292
+ if priv_line not in result["privileges"]:
293
+ result["privileges"].append(priv_line)
294
+ elif (
295
+ not priv_line
296
+ or priv_line.startswith("[INFO]")
297
+ or priv_line.startswith("---")
298
+ ):
254
299
  break
255
300
  j += 1
256
301
 
257
302
  # Detect file read success: "do you want to retrieve the content" followed by actual content
258
303
  # or "the file has been saved to:" indicating successful read
259
- if 'the file has been saved to:' in line.lower() or 'retrieved' in line.lower():
260
- if '/etc/passwd' in output or '/etc/shadow' in output or 'win.ini' in output.lower():
261
- result['file_read_success'] = True
304
+ if "the file has been saved to:" in line.lower() or "retrieved" in line.lower():
305
+ if (
306
+ "/etc/passwd" in output
307
+ or "/etc/shadow" in output
308
+ or "win.ini" in output.lower()
309
+ ):
310
+ result["file_read_success"] = True
262
311
 
263
312
  # Detect OS command success: Look for command output
264
313
  # SQLMap shows: "command standard output:" followed by output
265
- if 'command standard output:' in line.lower():
266
- result['os_command_success'] = True
314
+ if "command standard output:" in line.lower():
315
+ result["os_command_success"] = True
267
316
 
268
317
  # === END POST-EXPLOITATION FLAGS ===
269
318
 
270
319
  # Extract parameter being tested
271
- if 'testing if' in line and 'parameter' in line:
272
- param_match = re.search(r"(?:GET|POST|Cookie|User-Agent|Referer) parameter '([^']+)'", line)
320
+ if "testing if" in line and "parameter" in line:
321
+ param_match = re.search(
322
+ r"(?:GET|POST|Cookie|User-Agent|Referer) parameter '([^']+)'", line
323
+ )
273
324
  if param_match:
274
325
  current_param = param_match.group(1)
275
326
 
276
327
  # Detect XSS vulnerability hint
277
- if 'might be vulnerable to cross-site scripting (XSS)' in line:
328
+ if "might be vulnerable to cross-site scripting (XSS)" in line:
278
329
  param_match = re.search(r"parameter '([^']+)'", line)
279
330
  if param_match or current_param:
280
331
  param = param_match.group(1) if param_match else current_param
281
- result['vulnerabilities'].append({
282
- 'url': current_url or target,
283
- 'parameter': param,
284
- 'vuln_type': 'xss',
285
- 'injectable': False,
286
- 'severity': 'medium',
287
- 'description': f"Parameter '{param}' might be vulnerable to XSS"
288
- })
332
+ result["vulnerabilities"].append(
333
+ {
334
+ "url": current_url or target,
335
+ "parameter": param,
336
+ "vuln_type": "xss",
337
+ "injectable": False,
338
+ "severity": "medium",
339
+ "description": f"Parameter '{param}' might be vulnerable to XSS",
340
+ }
341
+ )
289
342
 
290
343
  # Detect File Inclusion vulnerability hint
291
- if 'might be vulnerable to file inclusion (FI)' in line:
344
+ if "might be vulnerable to file inclusion (FI)" in line:
292
345
  param_match = re.search(r"parameter '([^']+)'", line)
293
346
  if param_match or current_param:
294
347
  param = param_match.group(1) if param_match else current_param
295
- result['vulnerabilities'].append({
296
- 'url': current_url or target,
297
- 'parameter': param,
298
- 'vuln_type': 'file_inclusion',
299
- 'injectable': False,
300
- 'severity': 'high',
301
- 'description': f"Parameter '{param}' might be vulnerable to File Inclusion"
302
- })
348
+ result["vulnerabilities"].append(
349
+ {
350
+ "url": current_url or target,
351
+ "parameter": param,
352
+ "vuln_type": "file_inclusion",
353
+ "injectable": False,
354
+ "severity": "high",
355
+ "description": f"Parameter '{param}' might be vulnerable to File Inclusion",
356
+ }
357
+ )
303
358
 
304
359
  # NEW: Detect SQL injection from resumed session (Parameter: X (GET/POST))
305
360
  # This catches cases where SQLMap resumes and shows injection point without saying "is vulnerable"
306
- if re.match(r'^Parameter:\s+[\w\-]+\s+\((GET|POST|COOKIE|URI|User-Agent|Referer|Host)\)', line):
307
- param_match = re.search(r'^Parameter:\s+([\w\-]+)\s+\(([\w\-]+)\)', line)
361
+ if re.match(
362
+ r"^Parameter:\s+[\w\-]+\s+\((GET|POST|COOKIE|URI|User-Agent|Referer|Host)\)",
363
+ line,
364
+ ):
365
+ param_match = re.search(r"^Parameter:\s+([\w\-]+)\s+\(([\w\-]+)\)", line)
308
366
  if param_match:
309
367
  param = param_match.group(1)
310
368
  param_method = param_match.group(2)
@@ -316,22 +374,28 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
316
374
  next_line = lines[j].strip()
317
375
 
318
376
  # Check for Type: line
319
- if next_line.startswith('Type:'):
320
- technique = {'type': '', 'title': '', 'payload': ''}
321
- technique['type'] = next_line.replace('Type:', '').strip()
377
+ if next_line.startswith("Type:"):
378
+ technique = {"type": "", "title": "", "payload": ""}
379
+ technique["type"] = next_line.replace("Type:", "").strip()
322
380
 
323
381
  # Look for Title: on next lines
324
- if j + 1 < len(lines) and 'Title:' in lines[j + 1]:
325
- technique['title'] = lines[j + 1].replace('Title:', '').strip()
382
+ if j + 1 < len(lines) and "Title:" in lines[j + 1]:
383
+ technique["title"] = (
384
+ lines[j + 1].replace("Title:", "").strip()
385
+ )
326
386
 
327
387
  # Look for Payload: on next lines
328
- if j + 2 < len(lines) and 'Payload:' in lines[j + 2]:
329
- technique['payload'] = lines[j + 2].replace('Payload:', '').strip()
388
+ if j + 2 < len(lines) and "Payload:" in lines[j + 2]:
389
+ technique["payload"] = (
390
+ lines[j + 2].replace("Payload:", "").strip()
391
+ )
330
392
 
331
393
  techniques.append(technique)
332
394
  j += 3
333
395
  # Stop when we hit next major section
334
- elif next_line.startswith('---') or next_line.startswith('do you want'):
396
+ elif next_line.startswith("---") or next_line.startswith(
397
+ "do you want"
398
+ ):
335
399
  break
336
400
  else:
337
401
  j += 1
@@ -340,131 +404,154 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
340
404
  if techniques:
341
405
  # Check if we already added this parameter (avoid duplicates)
342
406
  already_added = any(
343
- v['parameter'] == param and v['vuln_type'] == 'sqli'
344
- for v in result['vulnerabilities']
407
+ v["parameter"] == param and v["vuln_type"] == "sqli"
408
+ for v in result["vulnerabilities"]
345
409
  )
346
410
 
347
411
  if not already_added:
348
412
  # For POST parameters, use the saved POST form URL instead of current_url
349
413
  # This prevents bug where GET URL testing overwrites the correct POST form URL
350
- if param_method == 'POST' and last_post_form_url:
414
+ if param_method == "POST" and last_post_form_url:
351
415
  effective_url = last_post_form_url
352
- effective_post_data = last_post_form_data or current_post_data
416
+ effective_post_data = (
417
+ last_post_form_data or current_post_data
418
+ )
353
419
  else:
354
420
  effective_url = current_url or target
355
- effective_post_data = current_post_data if param_method == 'POST' else None
356
-
357
- result['vulnerabilities'].append({
358
- 'url': effective_url,
359
- 'parameter': param,
360
- 'vuln_type': 'sqli',
361
- 'injectable': True,
362
- 'severity': 'critical',
363
- 'description': f"Parameter '{param}' is vulnerable to SQL injection",
364
- 'technique': 'multiple', # SQLMap found multiple techniques
365
- 'dbms': result.get('dbms', 'Unknown')
366
- })
421
+ effective_post_data = (
422
+ current_post_data if param_method == "POST" else None
423
+ )
424
+
425
+ result["vulnerabilities"].append(
426
+ {
427
+ "url": effective_url,
428
+ "parameter": param,
429
+ "vuln_type": "sqli",
430
+ "injectable": True,
431
+ "severity": "critical",
432
+ "description": f"Parameter '{param}' is vulnerable to SQL injection",
433
+ "technique": "multiple", # SQLMap found multiple techniques
434
+ "dbms": result.get("dbms", "Unknown"),
435
+ }
436
+ )
367
437
 
368
438
  # Set confirmation flags
369
- result['sql_injection_confirmed'] = True
370
- result['injectable_parameter'] = param
371
- result['injectable_url'] = effective_url
372
- result['injectable_method'] = param_method # GET, POST, etc.
373
- if param_method == 'POST' and effective_post_data:
374
- result['injectable_post_data'] = effective_post_data
439
+ result["sql_injection_confirmed"] = True
440
+ result["injectable_parameter"] = param
441
+ result["injectable_url"] = effective_url
442
+ result["injectable_method"] = param_method # GET, POST, etc.
443
+ if param_method == "POST" and effective_post_data:
444
+ result["injectable_post_data"] = effective_post_data
375
445
 
376
446
  # Collect ALL injection points for fallback
377
447
  injection_point = {
378
- 'url': effective_url,
379
- 'parameter': param,
380
- 'method': param_method,
381
- 'post_data': effective_post_data,
382
- 'techniques': techniques
448
+ "url": effective_url,
449
+ "parameter": param,
450
+ "method": param_method,
451
+ "post_data": effective_post_data,
452
+ "techniques": techniques,
383
453
  }
384
454
  # Avoid duplicates
385
- if not any(ip['url'] == injection_point['url'] and ip['parameter'] == injection_point['parameter']
386
- for ip in result['all_injection_points']):
387
- result['all_injection_points'].append(injection_point)
455
+ if not any(
456
+ ip["url"] == injection_point["url"]
457
+ and ip["parameter"] == injection_point["parameter"]
458
+ for ip in result["all_injection_points"]
459
+ ):
460
+ result["all_injection_points"].append(injection_point)
388
461
 
389
462
  # Add detailed injection techniques
390
- result['injection_techniques'].append({
391
- 'parameter': param,
392
- 'method': param_method,
393
- 'techniques': techniques
394
- })
463
+ result["injection_techniques"].append(
464
+ {
465
+ "parameter": param,
466
+ "method": param_method,
467
+ "techniques": techniques,
468
+ }
469
+ )
395
470
 
396
471
  # Detect SQL injection vulnerability
397
472
  # Pattern: "POST parameter 'username' is vulnerable" or "GET parameter 'id' is vulnerable"
398
- if 'parameter' in line and 'is vulnerable' in line:
399
- param_match = re.search(r"(GET|POST|Cookie|User-Agent|Referer|Host)?\s*parameter '([^']+)' is vulnerable", line)
473
+ if "parameter" in line and "is vulnerable" in line:
474
+ param_match = re.search(
475
+ r"(GET|POST|Cookie|User-Agent|Referer|Host)?\s*parameter '([^']+)' is vulnerable",
476
+ line,
477
+ )
400
478
  if param_match:
401
479
  method = param_match.group(1) or current_method
402
480
  param = param_match.group(2)
403
481
 
404
482
  # For POST parameters, use the saved POST form URL instead of current_url
405
483
  # This prevents bug where GET URL testing overwrites the correct POST form URL
406
- if method == 'POST' and last_post_form_url:
484
+ if method == "POST" and last_post_form_url:
407
485
  effective_url = last_post_form_url
408
486
  effective_post_data = last_post_form_data or current_post_data
409
487
  else:
410
488
  effective_url = current_url or target
411
- effective_post_data = current_post_data if method == 'POST' else None
489
+ effective_post_data = (
490
+ current_post_data if method == "POST" else None
491
+ )
412
492
 
413
- result['vulnerabilities'].append({
414
- 'url': effective_url,
415
- 'parameter': param,
416
- 'vuln_type': 'sqli',
417
- 'injectable': True,
418
- 'severity': 'critical',
419
- 'description': f"Parameter '{param}' is vulnerable to SQL injection"
420
- })
493
+ result["vulnerabilities"].append(
494
+ {
495
+ "url": effective_url,
496
+ "parameter": param,
497
+ "vuln_type": "sqli",
498
+ "injectable": True,
499
+ "severity": "critical",
500
+ "description": f"Parameter '{param}' is vulnerable to SQL injection",
501
+ }
502
+ )
421
503
 
422
504
  # Set confirmation flags
423
- result['sql_injection_confirmed'] = True
424
- result['injectable_parameter'] = param
425
- result['injectable_url'] = effective_url
426
- result['injectable_method'] = method
427
- if method == 'POST' and effective_post_data:
428
- result['injectable_post_data'] = effective_post_data
505
+ result["sql_injection_confirmed"] = True
506
+ result["injectable_parameter"] = param
507
+ result["injectable_url"] = effective_url
508
+ result["injectable_method"] = method
509
+ if method == "POST" and effective_post_data:
510
+ result["injectable_post_data"] = effective_post_data
429
511
 
430
512
  # Collect ALL injection points for fallback
431
513
  injection_point = {
432
- 'url': effective_url,
433
- 'parameter': param,
434
- 'method': method,
435
- 'post_data': effective_post_data,
436
- 'techniques': [] # Technique details not available at this detection point
514
+ "url": effective_url,
515
+ "parameter": param,
516
+ "method": method,
517
+ "post_data": effective_post_data,
518
+ "techniques": [], # Technique details not available at this detection point
437
519
  }
438
520
  # Avoid duplicates
439
- if not any(ip['url'] == injection_point['url'] and ip['parameter'] == injection_point['parameter']
440
- for ip in result['all_injection_points']):
441
- result['all_injection_points'].append(injection_point)
521
+ if not any(
522
+ ip["url"] == injection_point["url"]
523
+ and ip["parameter"] == injection_point["parameter"]
524
+ for ip in result["all_injection_points"]
525
+ ):
526
+ result["all_injection_points"].append(injection_point)
442
527
 
443
528
  # Detect not injectable result
444
- if 'does not seem to be injectable' in line:
445
- param_match = re.search(r"parameter '([^']+)' does not seem to be injectable", line)
529
+ if "does not seem to be injectable" in line:
530
+ param_match = re.search(
531
+ r"parameter '([^']+)' does not seem to be injectable", line
532
+ )
446
533
  # We skip these - only store actual vulnerabilities
447
534
 
448
535
  # Extract databases (if enumerated)
449
- if 'available databases' in line.lower():
536
+ if "available databases" in line.lower():
450
537
  # Next few lines will contain database names
451
538
  j = i + 1
452
- while j < len(lines) and lines[j].strip().startswith('[*]'):
539
+ while j < len(lines) and lines[j].strip().startswith("[*]"):
453
540
  db_line = lines[j].strip()
454
- if db_line.startswith('[*]'):
455
- db_name = db_line.replace('[*]', '').strip()
456
- if db_name and not db_name.startswith('INFO'):
457
- result['databases'].append(db_name)
541
+ if db_line.startswith("[*]"):
542
+ db_name = db_line.replace("[*]", "").strip()
543
+ if db_name and not db_name.startswith("INFO"):
544
+ result["databases"].append(db_name)
458
545
  j += 1
459
546
 
460
547
  # Extract tables for a database (from "Database: dbname" followed by table list)
461
- if re.match(r'^Database:\s*\w+', line):
548
+ if re.match(r"^Database:\s*\w+", line):
462
549
  # Pattern: "Database: dbname"
463
550
  db_match = re.search(r"Database:\s*(\w+)", line)
464
551
  if db_match:
465
552
  current_db = db_match.group(1)
466
553
  # Check if next line says "[X table]" or "[X tables]"
467
- if i + 1 < len(lines) and 'table' in lines[i + 1]:
554
+ if i + 1 < len(lines) and "table" in lines[i + 1]:
468
555
  # Look for ASCII table with | borders
469
556
  j = i + 2
470
557
  in_table = False
@@ -472,34 +559,42 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
472
559
  table_line = lines[j].strip()
473
560
 
474
561
  # Start of table (first +---+ border)
475
- if table_line.startswith('+') and '-' in table_line:
562
+ if table_line.startswith("+") and "-" in table_line:
476
563
  in_table = True
477
564
  j += 1
478
565
  continue
479
566
 
480
567
  # End of table
481
- if in_table and (not table_line or table_line.startswith('[') or table_line.startswith('SQL')):
568
+ if in_table and (
569
+ not table_line
570
+ or table_line.startswith("[")
571
+ or table_line.startswith("SQL")
572
+ ):
482
573
  break
483
574
 
484
575
  # Extract table name from | table_name |
485
- if in_table and table_line.startswith('|') and table_line != '|':
576
+ if (
577
+ in_table
578
+ and table_line.startswith("|")
579
+ and table_line != "|"
580
+ ):
486
581
  # Skip header rows and separator rows
487
- if not all(c in '|+- ' for c in table_line):
582
+ if not all(c in "|+- " for c in table_line):
488
583
  # Extract content between pipes
489
- parts = [p.strip() for p in table_line.split('|')]
584
+ parts = [p.strip() for p in table_line.split("|")]
490
585
  for part in parts:
491
- if part and part not in ['', 'table', 'tables']:
492
- if current_db not in result['tables']:
493
- result['tables'][current_db] = []
494
- if part not in result['tables'][current_db]:
495
- result['tables'][current_db].append(part)
586
+ if part and part not in ["", "table", "tables"]:
587
+ if current_db not in result["tables"]:
588
+ result["tables"][current_db] = []
589
+ if part not in result["tables"][current_db]:
590
+ result["tables"][current_db].append(part)
496
591
  j += 1
497
592
 
498
593
  # Extract tables for SQLite (no database prefix, just "[X tables]" followed by table list)
499
594
  # SQLite format: "[21 tables]" then "+---+" then "| TableName |" rows
500
- if re.match(r'^\[\d+\s+tables?\]$', line):
595
+ if re.match(r"^\[\d+\s+tables?\]$", line):
501
596
  # SQLite uses implicit database name
502
- sqlite_db = 'SQLite_masterdb'
597
+ sqlite_db = "SQLite_masterdb"
503
598
  # Look for ASCII table with | borders starting after this line
504
599
  j = i + 1
505
600
  in_table = False
@@ -507,31 +602,35 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
507
602
  table_line = lines[j].strip()
508
603
 
509
604
  # Start of table (first +---+ border)
510
- if table_line.startswith('+') and '-' in table_line:
605
+ if table_line.startswith("+") and "-" in table_line:
511
606
  in_table = True
512
607
  j += 1
513
608
  continue
514
609
 
515
610
  # End of table
516
- if in_table and (not table_line or table_line.startswith('[') or table_line.startswith('SQL')):
611
+ if in_table and (
612
+ not table_line
613
+ or table_line.startswith("[")
614
+ or table_line.startswith("SQL")
615
+ ):
517
616
  break
518
617
 
519
618
  # Extract table name from | table_name |
520
- if in_table and table_line.startswith('|') and table_line != '|':
619
+ if in_table and table_line.startswith("|") and table_line != "|":
521
620
  # Skip separator rows
522
- if not all(c in '|+- ' for c in table_line):
621
+ if not all(c in "|+- " for c in table_line):
523
622
  # Extract content between pipes
524
- parts = [p.strip() for p in table_line.split('|')]
623
+ parts = [p.strip() for p in table_line.split("|")]
525
624
  for part in parts:
526
- if part and part not in ['', 'table', 'tables']:
527
- if sqlite_db not in result['tables']:
528
- result['tables'][sqlite_db] = []
529
- if part not in result['tables'][sqlite_db]:
530
- result['tables'][sqlite_db].append(part)
625
+ if part and part not in ["", "table", "tables"]:
626
+ if sqlite_db not in result["tables"]:
627
+ result["tables"][sqlite_db] = []
628
+ if part not in result["tables"][sqlite_db]:
629
+ result["tables"][sqlite_db].append(part)
531
630
  j += 1
532
631
 
533
632
  # Extract columns for a table (from "Table: tablename" with column list)
534
- if re.match(r'^(Database:.*)?Table:\s*\w+', line):
633
+ if re.match(r"^(Database:.*)?Table:\s*\w+", line):
535
634
  # Pattern: "Table: tablename" or "Database: db\nTable: table"
536
635
  table_match = re.search(r"Table:\s*(\w+)", line)
537
636
  db_match = re.search(r"Database:\s*(\w+)", line)
@@ -546,7 +645,7 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
546
645
  db_name = db_match.group(1) if db_match else None
547
646
 
548
647
  # Check if next line says "[X columns]"
549
- if i + 1 < len(lines) and 'column' in lines[i + 1]:
648
+ if i + 1 < len(lines) and "column" in lines[i + 1]:
550
649
  # Look for ASCII table with | Column | Type | borders
551
650
  j = i + 2
552
651
  in_table = False
@@ -556,22 +655,26 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
556
655
  col_line = lines[j].strip()
557
656
 
558
657
  # Start of table (first +---+ border)
559
- if col_line.startswith('+') and '-' in col_line:
658
+ if col_line.startswith("+") and "-" in col_line:
560
659
  in_table = True
561
660
  j += 1
562
661
  continue
563
662
 
564
663
  # End of table
565
- if in_table and (not col_line or col_line.startswith('[') or col_line.startswith('SQL')):
664
+ if in_table and (
665
+ not col_line
666
+ or col_line.startswith("[")
667
+ or col_line.startswith("SQL")
668
+ ):
566
669
  break
567
670
 
568
671
  # Extract column name from | column_name | type |
569
- if in_table and col_line.startswith('|') and col_line != '|':
672
+ if in_table and col_line.startswith("|") and col_line != "|":
570
673
  # Skip header rows "| Column | Type |" and separator rows
571
- if 'Column' not in col_line and 'Type' not in col_line:
572
- if not all(c in '|+- ' for c in col_line):
674
+ if "Column" not in col_line and "Type" not in col_line:
675
+ if not all(c in "|+- " for c in col_line):
573
676
  # Extract first column (column name)
574
- parts = [p.strip() for p in col_line.split('|')]
677
+ parts = [p.strip() for p in col_line.split("|")]
575
678
  if len(parts) >= 2 and parts[1]:
576
679
  col_name = parts[1]
577
680
  if col_name not in columns:
@@ -581,138 +684,150 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
581
684
  if columns:
582
685
  # Store with db.table key if we have db, otherwise just table
583
686
  key = f"{db_name}.{table_name}" if db_name else table_name
584
- result['columns'][key] = columns
585
-
687
+ result["columns"][key] = columns
688
+
586
689
  # Extract dumped data (e.g., "Database: owasp10\nTable: credit_cards\n[5 entries]")
587
- if ('entries]' in line or 'entry]' in line) and i > 0:
690
+ if ("entries]" in line or "entry]" in line) and i > 0:
588
691
  # Check for "Table: tablename" in previous lines
589
692
  db_name = None
590
693
  table_name = None
591
-
592
- for k in range(max(0, i-3), i):
694
+
695
+ for k in range(max(0, i - 3), i):
593
696
  prev_line = lines[k].strip()
594
- if prev_line.startswith('Database:'):
595
- db_name = prev_line.split('Database:')[1].strip()
596
- elif prev_line.startswith('Table:'):
597
- table_name = prev_line.split('Table:')[1].strip()
598
-
697
+ if prev_line.startswith("Database:"):
698
+ db_name = prev_line.split("Database:")[1].strip()
699
+ elif prev_line.startswith("Table:"):
700
+ table_name = prev_line.split("Table:")[1].strip()
701
+
599
702
  if table_name:
600
703
  # Parse the data table that follows
601
704
  j = i + 1
602
705
  in_data_table = False
603
706
  table_columns = []
604
707
  table_rows = []
605
-
708
+
606
709
  while j < len(lines):
607
710
  data_line = lines[j].strip()
608
-
711
+
609
712
  # Start of table (first +---+ border)
610
- if data_line.startswith('+') and '-' in data_line:
713
+ if data_line.startswith("+") and "-" in data_line:
611
714
  if not in_data_table:
612
715
  in_data_table = True
613
716
  j += 1
614
717
  continue
615
-
718
+
616
719
  # End of table
617
- if in_data_table and (not data_line or data_line.startswith('[') or
618
- 'dumped to CSV' in data_line):
720
+ if in_data_table and (
721
+ not data_line
722
+ or data_line.startswith("[")
723
+ or "dumped to CSV" in data_line
724
+ ):
619
725
  break
620
-
726
+
621
727
  # Extract column headers from first | ... | ... | row
622
- if in_data_table and data_line.startswith('|') and not table_columns:
623
- parts = [p.strip() for p in data_line.split('|')]
728
+ if (
729
+ in_data_table
730
+ and data_line.startswith("|")
731
+ and not table_columns
732
+ ):
733
+ parts = [p.strip() for p in data_line.split("|")]
624
734
  table_columns = [p for p in parts if p]
625
735
  j += 1
626
736
  continue
627
-
737
+
628
738
  # Extract data rows
629
- if in_data_table and table_columns and data_line.startswith('|'):
630
- parts = [p.strip() for p in data_line.split('|')]
631
- values = [p for p in parts if p != '']
739
+ if in_data_table and table_columns and data_line.startswith("|"):
740
+ parts = [p.strip() for p in data_line.split("|")]
741
+ values = [p for p in parts if p != ""]
632
742
  if len(values) == len(table_columns):
633
743
  row_dict = dict(zip(table_columns, values))
634
744
  table_rows.append(row_dict)
635
-
745
+
636
746
  j += 1
637
-
747
+
638
748
  # Look for CSV file path
639
749
  csv_path = None
640
- for k in range(j, min(j+5, len(lines))):
641
- if 'dumped to CSV file' in lines[k]:
750
+ for k in range(j, min(j + 5, len(lines))):
751
+ if "dumped to CSV file" in lines[k]:
642
752
  csv_match = re.search(r"'([^']+\.csv)'", lines[k])
643
753
  if csv_match:
644
754
  csv_path = csv_match.group(1)
645
755
  break
646
-
756
+
647
757
  if table_rows:
648
758
  key = f"{db_name}.{table_name}" if db_name else table_name
649
- result['dumped_data'][key] = {
650
- 'rows': table_rows,
651
- 'csv_path': csv_path,
652
- 'columns': table_columns,
653
- 'row_count': len(table_rows)
759
+ result["dumped_data"][key] = {
760
+ "rows": table_rows,
761
+ "csv_path": csv_path,
762
+ "columns": table_columns,
763
+ "row_count": len(table_rows),
654
764
  }
655
765
 
656
766
  # NEW: Detect SQL injection confirmation
657
767
  # Look for "Parameter: X (GET)" and "Type: boolean-based|error-based|time-based|UNION"
658
- injection_param_pattern = r'Parameter:\s+([\w\-]+)\s+\((GET|POST|COOKIE|URI|User-Agent|Referer|Host)\)'
768
+ injection_param_pattern = (
769
+ r"Parameter:\s+([\w\-]+)\s+\((GET|POST|COOKIE|URI|User-Agent|Referer|Host)\)"
770
+ )
659
771
  injection_type_patterns = [
660
- r'Type:\s+boolean-based blind',
661
- r'Type:\s+error-based',
662
- r'Type:\s+time-based blind',
663
- r'Type:\s+UNION query',
664
- r'Type:\s+stacked queries'
772
+ r"Type:\s+boolean-based blind",
773
+ r"Type:\s+error-based",
774
+ r"Type:\s+time-based blind",
775
+ r"Type:\s+UNION query",
776
+ r"Type:\s+stacked queries",
665
777
  ]
666
778
 
667
779
  # Search for injection parameter
668
780
  param_match = re.search(injection_param_pattern, output)
669
781
  if param_match:
670
- result['injectable_parameter'] = param_match.group(1)
671
-
782
+ result["injectable_parameter"] = param_match.group(1)
783
+
672
784
  # Check if any injection type is confirmed
673
785
  for pattern in injection_type_patterns:
674
786
  if re.search(pattern, output, re.IGNORECASE):
675
- result['sql_injection_confirmed'] = True
787
+ result["sql_injection_confirmed"] = True
676
788
  break
677
789
 
678
790
  # Add enumeration flags for tool chaining
679
- if result['databases']:
680
- result['databases_enumerated'] = True
681
- if result['tables']:
682
- result['tables_enumerated'] = True
683
- if result['columns']:
684
- result['columns_enumerated'] = True
791
+ if result["databases"]:
792
+ result["databases_enumerated"] = True
793
+ if result["tables"]:
794
+ result["tables_enumerated"] = True
795
+ if result["columns"]:
796
+ result["columns_enumerated"] = True
685
797
 
686
798
  # POST-PROCESSING: Select the BEST injection point
687
799
  # Prefer error-based over UNION (error-based is more reliable with redirects)
688
- if result['all_injection_points'] and len(result['all_injection_points']) > 1:
800
+ if result["all_injection_points"] and len(result["all_injection_points"]) > 1:
801
+
689
802
  def score_injection_point(ip):
690
803
  """Score injection points - higher is better."""
691
804
  score = 0
692
- techniques = ip.get('techniques', [])
805
+ techniques = ip.get("techniques", [])
693
806
  for tech in techniques:
694
- if 'error-based' in tech:
807
+ if "error-based" in tech:
695
808
  score += 100 # Best - works despite redirects
696
- elif 'boolean-based' in tech:
809
+ elif "boolean-based" in tech:
697
810
  score += 50
698
- elif 'stacked' in tech:
811
+ elif "stacked" in tech:
699
812
  score += 40
700
- elif 'time-based' in tech:
813
+ elif "time-based" in tech:
701
814
  score += 20
702
- elif 'UNION' in tech:
815
+ elif "UNION" in tech:
703
816
  score += 10 # Least reliable with redirects
704
817
  return score
705
818
 
706
819
  # Sort by score (highest first) and pick the best
707
- sorted_points = sorted(result['all_injection_points'], key=score_injection_point, reverse=True)
820
+ sorted_points = sorted(
821
+ result["all_injection_points"], key=score_injection_point, reverse=True
822
+ )
708
823
  best = sorted_points[0]
709
824
 
710
825
  # Update the primary injection point to the best one
711
- result['injectable_url'] = best['url']
712
- result['injectable_parameter'] = best['parameter']
713
- result['injectable_method'] = best['method']
714
- if best['post_data']:
715
- result['injectable_post_data'] = best['post_data']
826
+ result["injectable_url"] = best["url"]
827
+ result["injectable_parameter"] = best["parameter"]
828
+ result["injectable_method"] = best["method"]
829
+ if best["post_data"]:
830
+ result["injectable_post_data"] = best["post_data"]
716
831
 
717
832
  return result
718
833
 
@@ -724,24 +839,35 @@ def get_sqli_stats(parsed: Dict[str, Any]) -> Dict[str, Any]:
724
839
  Returns:
725
840
  Dict with counts and summary info
726
841
  """
727
- sqli_count = sum(1 for v in parsed.get('vulnerabilities', [])
728
- if v.get('vuln_type') == 'sqli' and v.get('injectable'))
729
-
730
- xss_count = sum(1 for v in parsed.get('vulnerabilities', [])
731
- if v.get('vuln_type') == 'xss')
732
-
733
- fi_count = sum(1 for v in parsed.get('vulnerabilities', [])
734
- if v.get('vuln_type') == 'file_inclusion')
842
+ sqli_count = sum(
843
+ 1
844
+ for v in parsed.get("vulnerabilities", [])
845
+ if v.get("vuln_type") == "sqli" and v.get("injectable")
846
+ )
847
+
848
+ xss_count = sum(
849
+ 1 for v in parsed.get("vulnerabilities", []) if v.get("vuln_type") == "xss"
850
+ )
851
+
852
+ fi_count = sum(
853
+ 1
854
+ for v in parsed.get("vulnerabilities", [])
855
+ if v.get("vuln_type") == "file_inclusion"
856
+ )
735
857
 
736
858
  return {
737
- 'total_vulns': len(parsed.get('vulnerabilities', [])),
738
- 'sqli_confirmed': sqli_count,
739
- 'xss_possible': xss_count,
740
- 'fi_possible': fi_count,
741
- 'urls_tested': len(parsed.get('urls_tested', [])),
742
- 'databases_found': len(parsed.get('databases', [])),
743
- 'tables_found': sum(len(tables) for tables in parsed.get('tables', {}).values()),
744
- 'columns_found': sum(len(cols) for cols in parsed.get('columns', {}).values()),
745
- 'dumped_tables': len(parsed.get('dumped_data', {})),
746
- 'dumped_rows': sum(data.get('row_count', 0) for data in parsed.get('dumped_data', {}).values())
859
+ "total_vulns": len(parsed.get("vulnerabilities", [])),
860
+ "sqli_confirmed": sqli_count,
861
+ "xss_possible": xss_count,
862
+ "fi_possible": fi_count,
863
+ "urls_tested": len(parsed.get("urls_tested", [])),
864
+ "databases_found": len(parsed.get("databases", [])),
865
+ "tables_found": sum(
866
+ len(tables) for tables in parsed.get("tables", {}).values()
867
+ ),
868
+ "columns_found": sum(len(cols) for cols in parsed.get("columns", {}).values()),
869
+ "dumped_tables": len(parsed.get("dumped_data", {})),
870
+ "dumped_rows": sum(
871
+ data.get("row_count", 0) for data in parsed.get("dumped_data", {}).values()
872
+ ),
747
873
  }