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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (356) hide show
  1. souleyez/__init__.py +1 -2
  2. souleyez/ai/__init__.py +21 -15
  3. souleyez/ai/action_mapper.py +249 -150
  4. souleyez/ai/chain_advisor.py +116 -100
  5. souleyez/ai/claude_provider.py +29 -28
  6. souleyez/ai/context_builder.py +80 -62
  7. souleyez/ai/executor.py +158 -117
  8. souleyez/ai/feedback_handler.py +136 -121
  9. souleyez/ai/llm_factory.py +27 -20
  10. souleyez/ai/llm_provider.py +4 -2
  11. souleyez/ai/ollama_provider.py +6 -9
  12. souleyez/ai/ollama_service.py +44 -37
  13. souleyez/ai/path_scorer.py +91 -76
  14. souleyez/ai/recommender.py +176 -144
  15. souleyez/ai/report_context.py +74 -73
  16. souleyez/ai/report_service.py +84 -66
  17. souleyez/ai/result_parser.py +222 -229
  18. souleyez/ai/safety.py +67 -44
  19. souleyez/auth/__init__.py +23 -22
  20. souleyez/auth/audit.py +36 -26
  21. souleyez/auth/engagement_access.py +65 -48
  22. souleyez/auth/permissions.py +14 -3
  23. souleyez/auth/session_manager.py +54 -37
  24. souleyez/auth/user_manager.py +109 -64
  25. souleyez/commands/audit.py +40 -43
  26. souleyez/commands/auth.py +35 -15
  27. souleyez/commands/deliverables.py +55 -50
  28. souleyez/commands/engagement.py +47 -28
  29. souleyez/commands/license.py +32 -23
  30. souleyez/commands/screenshots.py +36 -32
  31. souleyez/commands/user.py +82 -36
  32. souleyez/config.py +52 -44
  33. souleyez/core/credential_tester.py +87 -81
  34. souleyez/core/cve_mappings.py +179 -192
  35. souleyez/core/cve_matcher.py +162 -148
  36. souleyez/core/msf_auto_mapper.py +100 -83
  37. souleyez/core/msf_chain_engine.py +294 -256
  38. souleyez/core/msf_database.py +153 -70
  39. souleyez/core/msf_integration.py +679 -673
  40. souleyez/core/msf_rpc_client.py +40 -42
  41. souleyez/core/msf_rpc_manager.py +77 -79
  42. souleyez/core/msf_sync_manager.py +241 -181
  43. souleyez/core/network_utils.py +22 -15
  44. souleyez/core/parser_handler.py +34 -25
  45. souleyez/core/pending_chains.py +114 -63
  46. souleyez/core/templates.py +158 -107
  47. souleyez/core/tool_chaining.py +9592 -2879
  48. souleyez/core/version_utils.py +79 -94
  49. souleyez/core/vuln_correlation.py +136 -89
  50. souleyez/core/web_utils.py +33 -32
  51. souleyez/data/wordlists/ad_users.txt +378 -0
  52. souleyez/data/wordlists/api_endpoints_large.txt +769 -0
  53. souleyez/data/wordlists/home_dir_sensitive.txt +39 -0
  54. souleyez/data/wordlists/lfi_payloads.txt +82 -0
  55. souleyez/data/wordlists/passwords_brute.txt +1548 -0
  56. souleyez/data/wordlists/passwords_crack.txt +2479 -0
  57. souleyez/data/wordlists/passwords_spray.txt +386 -0
  58. souleyez/data/wordlists/subdomains_large.txt +5057 -0
  59. souleyez/data/wordlists/usernames_common.txt +694 -0
  60. souleyez/data/wordlists/web_dirs_large.txt +4769 -0
  61. souleyez/detection/__init__.py +1 -1
  62. souleyez/detection/attack_signatures.py +12 -17
  63. souleyez/detection/mitre_mappings.py +61 -55
  64. souleyez/detection/validator.py +97 -86
  65. souleyez/devtools.py +23 -10
  66. souleyez/docs/README.md +4 -4
  67. souleyez/docs/api-reference/cli-commands.md +2 -2
  68. souleyez/docs/developer-guide/adding-new-tools.md +562 -0
  69. souleyez/docs/user-guide/auto-chaining.md +30 -8
  70. souleyez/docs/user-guide/getting-started.md +1 -1
  71. souleyez/docs/user-guide/installation.md +26 -3
  72. souleyez/docs/user-guide/metasploit-integration.md +2 -2
  73. souleyez/docs/user-guide/rbac.md +1 -1
  74. souleyez/docs/user-guide/scope-management.md +1 -1
  75. souleyez/docs/user-guide/siem-integration.md +1 -1
  76. souleyez/docs/user-guide/tools-reference.md +1 -8
  77. souleyez/docs/user-guide/worker-management.md +1 -1
  78. souleyez/engine/background.py +1238 -535
  79. souleyez/engine/base.py +4 -1
  80. souleyez/engine/job_status.py +17 -49
  81. souleyez/engine/log_sanitizer.py +103 -77
  82. souleyez/engine/manager.py +38 -7
  83. souleyez/engine/result_handler.py +2198 -1550
  84. souleyez/engine/worker_manager.py +50 -41
  85. souleyez/export/evidence_bundle.py +72 -62
  86. souleyez/feature_flags/features.py +16 -20
  87. souleyez/feature_flags.py +5 -9
  88. souleyez/handlers/__init__.py +11 -0
  89. souleyez/handlers/base.py +188 -0
  90. souleyez/handlers/bash_handler.py +277 -0
  91. souleyez/handlers/bloodhound_handler.py +243 -0
  92. souleyez/handlers/certipy_handler.py +311 -0
  93. souleyez/handlers/crackmapexec_handler.py +486 -0
  94. souleyez/handlers/dnsrecon_handler.py +344 -0
  95. souleyez/handlers/enum4linux_handler.py +400 -0
  96. souleyez/handlers/evil_winrm_handler.py +493 -0
  97. souleyez/handlers/ffuf_handler.py +815 -0
  98. souleyez/handlers/gobuster_handler.py +1114 -0
  99. souleyez/handlers/gpp_extract_handler.py +334 -0
  100. souleyez/handlers/hashcat_handler.py +444 -0
  101. souleyez/handlers/hydra_handler.py +563 -0
  102. souleyez/handlers/impacket_getuserspns_handler.py +343 -0
  103. souleyez/handlers/impacket_psexec_handler.py +222 -0
  104. souleyez/handlers/impacket_secretsdump_handler.py +426 -0
  105. souleyez/handlers/john_handler.py +286 -0
  106. souleyez/handlers/katana_handler.py +425 -0
  107. souleyez/handlers/kerbrute_handler.py +298 -0
  108. souleyez/handlers/ldapsearch_handler.py +636 -0
  109. souleyez/handlers/lfi_extract_handler.py +464 -0
  110. souleyez/handlers/msf_auxiliary_handler.py +408 -0
  111. souleyez/handlers/msf_exploit_handler.py +380 -0
  112. souleyez/handlers/nikto_handler.py +413 -0
  113. souleyez/handlers/nmap_handler.py +821 -0
  114. souleyez/handlers/nuclei_handler.py +359 -0
  115. souleyez/handlers/nxc_handler.py +371 -0
  116. souleyez/handlers/rdp_sec_check_handler.py +353 -0
  117. souleyez/handlers/registry.py +288 -0
  118. souleyez/handlers/responder_handler.py +232 -0
  119. souleyez/handlers/service_explorer_handler.py +434 -0
  120. souleyez/handlers/smbclient_handler.py +344 -0
  121. souleyez/handlers/smbmap_handler.py +510 -0
  122. souleyez/handlers/smbpasswd_handler.py +296 -0
  123. souleyez/handlers/sqlmap_handler.py +1116 -0
  124. souleyez/handlers/theharvester_handler.py +601 -0
  125. souleyez/handlers/whois_handler.py +277 -0
  126. souleyez/handlers/wpscan_handler.py +554 -0
  127. souleyez/history.py +32 -16
  128. souleyez/importers/msf_importer.py +106 -75
  129. souleyez/importers/smart_importer.py +208 -147
  130. souleyez/integrations/siem/__init__.py +10 -10
  131. souleyez/integrations/siem/base.py +17 -18
  132. souleyez/integrations/siem/elastic.py +108 -122
  133. souleyez/integrations/siem/factory.py +207 -80
  134. souleyez/integrations/siem/googlesecops.py +146 -154
  135. souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
  136. souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
  137. souleyez/integrations/siem/sentinel.py +107 -109
  138. souleyez/integrations/siem/splunk.py +246 -212
  139. souleyez/integrations/siem/wazuh.py +65 -71
  140. souleyez/integrations/wazuh/__init__.py +5 -5
  141. souleyez/integrations/wazuh/client.py +70 -93
  142. souleyez/integrations/wazuh/config.py +85 -57
  143. souleyez/integrations/wazuh/host_mapper.py +28 -36
  144. souleyez/integrations/wazuh/sync.py +78 -68
  145. souleyez/intelligence/__init__.py +4 -5
  146. souleyez/intelligence/correlation_analyzer.py +309 -295
  147. souleyez/intelligence/exploit_knowledge.py +661 -623
  148. souleyez/intelligence/exploit_suggestions.py +159 -139
  149. souleyez/intelligence/gap_analyzer.py +132 -97
  150. souleyez/intelligence/gap_detector.py +251 -214
  151. souleyez/intelligence/sensitive_tables.py +266 -129
  152. souleyez/intelligence/service_parser.py +137 -123
  153. souleyez/intelligence/surface_analyzer.py +407 -268
  154. souleyez/intelligence/target_parser.py +159 -162
  155. souleyez/licensing/__init__.py +6 -6
  156. souleyez/licensing/validator.py +17 -19
  157. souleyez/log_config.py +79 -54
  158. souleyez/main.py +1505 -687
  159. souleyez/migrations/fix_job_counter.py +16 -14
  160. souleyez/parsers/bloodhound_parser.py +41 -39
  161. souleyez/parsers/crackmapexec_parser.py +178 -111
  162. souleyez/parsers/dalfox_parser.py +72 -77
  163. souleyez/parsers/dnsrecon_parser.py +103 -91
  164. souleyez/parsers/enum4linux_parser.py +183 -153
  165. souleyez/parsers/ffuf_parser.py +29 -25
  166. souleyez/parsers/gobuster_parser.py +301 -41
  167. souleyez/parsers/hashcat_parser.py +324 -79
  168. souleyez/parsers/http_fingerprint_parser.py +350 -103
  169. souleyez/parsers/hydra_parser.py +131 -111
  170. souleyez/parsers/impacket_parser.py +231 -178
  171. souleyez/parsers/john_parser.py +98 -86
  172. souleyez/parsers/katana_parser.py +316 -0
  173. souleyez/parsers/msf_parser.py +943 -498
  174. souleyez/parsers/nikto_parser.py +346 -65
  175. souleyez/parsers/nmap_parser.py +262 -174
  176. souleyez/parsers/nuclei_parser.py +40 -44
  177. souleyez/parsers/responder_parser.py +26 -26
  178. souleyez/parsers/searchsploit_parser.py +74 -74
  179. souleyez/parsers/service_explorer_parser.py +279 -0
  180. souleyez/parsers/smbmap_parser.py +180 -124
  181. souleyez/parsers/sqlmap_parser.py +434 -308
  182. souleyez/parsers/theharvester_parser.py +75 -57
  183. souleyez/parsers/whois_parser.py +135 -94
  184. souleyez/parsers/wpscan_parser.py +278 -190
  185. souleyez/plugins/afp.py +44 -36
  186. souleyez/plugins/afp_brute.py +114 -46
  187. souleyez/plugins/ard.py +48 -37
  188. souleyez/plugins/bloodhound.py +95 -61
  189. souleyez/plugins/certipy.py +303 -0
  190. souleyez/plugins/crackmapexec.py +186 -85
  191. souleyez/plugins/dalfox.py +120 -59
  192. souleyez/plugins/dns_hijack.py +146 -41
  193. souleyez/plugins/dnsrecon.py +97 -61
  194. souleyez/plugins/enum4linux.py +91 -66
  195. souleyez/plugins/evil_winrm.py +291 -0
  196. souleyez/plugins/ffuf.py +166 -90
  197. souleyez/plugins/firmware_extract.py +133 -29
  198. souleyez/plugins/gobuster.py +387 -190
  199. souleyez/plugins/gpp_extract.py +393 -0
  200. souleyez/plugins/hashcat.py +100 -73
  201. souleyez/plugins/http_fingerprint.py +854 -267
  202. souleyez/plugins/hydra.py +566 -200
  203. souleyez/plugins/impacket_getnpusers.py +117 -69
  204. souleyez/plugins/impacket_psexec.py +84 -64
  205. souleyez/plugins/impacket_secretsdump.py +103 -69
  206. souleyez/plugins/impacket_smbclient.py +89 -75
  207. souleyez/plugins/john.py +86 -69
  208. souleyez/plugins/katana.py +313 -0
  209. souleyez/plugins/kerbrute.py +237 -0
  210. souleyez/plugins/lfi_extract.py +541 -0
  211. souleyez/plugins/macos_ssh.py +117 -48
  212. souleyez/plugins/mdns.py +35 -30
  213. souleyez/plugins/msf_auxiliary.py +253 -130
  214. souleyez/plugins/msf_exploit.py +239 -161
  215. souleyez/plugins/nikto.py +134 -78
  216. souleyez/plugins/nmap.py +275 -91
  217. souleyez/plugins/nuclei.py +180 -89
  218. souleyez/plugins/nxc.py +285 -0
  219. souleyez/plugins/plugin_base.py +35 -36
  220. souleyez/plugins/plugin_template.py +13 -5
  221. souleyez/plugins/rdp_sec_check.py +130 -0
  222. souleyez/plugins/responder.py +112 -71
  223. souleyez/plugins/router_http_brute.py +76 -65
  224. souleyez/plugins/router_ssh_brute.py +118 -41
  225. souleyez/plugins/router_telnet_brute.py +124 -42
  226. souleyez/plugins/routersploit.py +91 -59
  227. souleyez/plugins/routersploit_exploit.py +77 -55
  228. souleyez/plugins/searchsploit.py +91 -77
  229. souleyez/plugins/service_explorer.py +1160 -0
  230. souleyez/plugins/smbmap.py +122 -72
  231. souleyez/plugins/smbpasswd.py +215 -0
  232. souleyez/plugins/sqlmap.py +301 -113
  233. souleyez/plugins/theharvester.py +127 -75
  234. souleyez/plugins/tr069.py +79 -57
  235. souleyez/plugins/upnp.py +65 -47
  236. souleyez/plugins/upnp_abuse.py +73 -55
  237. souleyez/plugins/vnc_access.py +129 -42
  238. souleyez/plugins/vnc_brute.py +109 -38
  239. souleyez/plugins/whois.py +77 -58
  240. souleyez/plugins/wpscan.py +173 -69
  241. souleyez/reporting/__init__.py +2 -1
  242. souleyez/reporting/attack_chain.py +411 -346
  243. souleyez/reporting/charts.py +436 -501
  244. souleyez/reporting/compliance_mappings.py +334 -201
  245. souleyez/reporting/detection_report.py +126 -125
  246. souleyez/reporting/formatters.py +828 -591
  247. souleyez/reporting/generator.py +386 -302
  248. souleyez/reporting/metrics.py +72 -75
  249. souleyez/scanner.py +35 -29
  250. souleyez/security/__init__.py +37 -11
  251. souleyez/security/scope_validator.py +175 -106
  252. souleyez/security/validation.py +223 -149
  253. souleyez/security.py +22 -6
  254. souleyez/storage/credentials.py +247 -186
  255. souleyez/storage/crypto.py +296 -129
  256. souleyez/storage/database.py +73 -50
  257. souleyez/storage/db.py +58 -36
  258. souleyez/storage/deliverable_evidence.py +177 -128
  259. souleyez/storage/deliverable_exporter.py +282 -246
  260. souleyez/storage/deliverable_templates.py +134 -116
  261. souleyez/storage/deliverables.py +135 -130
  262. souleyez/storage/engagements.py +109 -56
  263. souleyez/storage/evidence.py +181 -152
  264. souleyez/storage/execution_log.py +31 -17
  265. souleyez/storage/exploit_attempts.py +93 -57
  266. souleyez/storage/exploits.py +67 -36
  267. souleyez/storage/findings.py +48 -61
  268. souleyez/storage/hosts.py +176 -144
  269. souleyez/storage/migrate_to_engagements.py +43 -19
  270. souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
  271. souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
  272. souleyez/storage/migrations/_003_add_execution_log.py +14 -8
  273. souleyez/storage/migrations/_005_screenshots.py +13 -5
  274. souleyez/storage/migrations/_006_deliverables.py +13 -5
  275. souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
  276. souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
  277. souleyez/storage/migrations/_010_evidence_linking.py +17 -10
  278. souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
  279. souleyez/storage/migrations/_012_team_collaboration.py +34 -21
  280. souleyez/storage/migrations/_013_add_host_tags.py +12 -6
  281. souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
  282. souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
  283. souleyez/storage/migrations/_016_add_domain_field.py +10 -4
  284. souleyez/storage/migrations/_017_msf_sessions.py +16 -8
  285. souleyez/storage/migrations/_018_add_osint_target.py +10 -6
  286. souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
  287. souleyez/storage/migrations/_020_add_rbac.py +36 -15
  288. souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
  289. souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
  290. souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
  291. souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
  292. souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
  293. souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
  294. souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
  295. souleyez/storage/migrations/__init__.py +26 -26
  296. souleyez/storage/migrations/migration_manager.py +19 -19
  297. souleyez/storage/msf_sessions.py +100 -65
  298. souleyez/storage/osint.py +17 -24
  299. souleyez/storage/recommendation_engine.py +269 -235
  300. souleyez/storage/screenshots.py +33 -32
  301. souleyez/storage/smb_shares.py +136 -92
  302. souleyez/storage/sqlmap_data.py +183 -128
  303. souleyez/storage/team_collaboration.py +135 -141
  304. souleyez/storage/timeline_tracker.py +122 -94
  305. souleyez/storage/wazuh_vulns.py +64 -66
  306. souleyez/storage/web_paths.py +33 -37
  307. souleyez/testing/credential_tester.py +221 -205
  308. souleyez/ui/__init__.py +1 -1
  309. souleyez/ui/ai_quotes.py +12 -12
  310. souleyez/ui/attack_surface.py +2439 -1516
  311. souleyez/ui/chain_rules_view.py +914 -382
  312. souleyez/ui/correlation_view.py +312 -230
  313. souleyez/ui/dashboard.py +2382 -1130
  314. souleyez/ui/deliverables_view.py +148 -62
  315. souleyez/ui/design_system.py +13 -13
  316. souleyez/ui/errors.py +49 -49
  317. souleyez/ui/evidence_linking_view.py +284 -179
  318. souleyez/ui/evidence_vault.py +393 -285
  319. souleyez/ui/exploit_suggestions_view.py +555 -349
  320. souleyez/ui/export_view.py +100 -66
  321. souleyez/ui/gap_analysis_view.py +315 -171
  322. souleyez/ui/help_system.py +105 -97
  323. souleyez/ui/intelligence_view.py +436 -293
  324. souleyez/ui/interactive.py +22783 -10678
  325. souleyez/ui/interactive_selector.py +75 -68
  326. souleyez/ui/log_formatter.py +47 -39
  327. souleyez/ui/menu_components.py +22 -13
  328. souleyez/ui/msf_auxiliary_menu.py +184 -133
  329. souleyez/ui/pending_chains_view.py +336 -172
  330. souleyez/ui/progress_indicators.py +5 -3
  331. souleyez/ui/recommendations_view.py +195 -137
  332. souleyez/ui/rule_builder.py +343 -225
  333. souleyez/ui/setup_wizard.py +678 -284
  334. souleyez/ui/shortcuts.py +217 -165
  335. souleyez/ui/splunk_gap_analysis_view.py +452 -270
  336. souleyez/ui/splunk_vulns_view.py +139 -86
  337. souleyez/ui/team_dashboard.py +498 -335
  338. souleyez/ui/template_selector.py +196 -105
  339. souleyez/ui/terminal.py +6 -6
  340. souleyez/ui/timeline_view.py +198 -127
  341. souleyez/ui/tool_setup.py +264 -164
  342. souleyez/ui/tutorial.py +202 -72
  343. souleyez/ui/tutorial_state.py +40 -40
  344. souleyez/ui/wazuh_vulns_view.py +235 -141
  345. souleyez/ui/wordlist_browser.py +260 -107
  346. souleyez/ui.py +464 -312
  347. souleyez/utils/tool_checker.py +427 -367
  348. souleyez/utils.py +33 -29
  349. souleyez/wordlists.py +134 -167
  350. {souleyez-2.43.29.dist-info → souleyez-2.43.32.dist-info}/METADATA +1 -1
  351. souleyez-2.43.32.dist-info/RECORD +441 -0
  352. {souleyez-2.43.29.dist-info → souleyez-2.43.32.dist-info}/WHEEL +1 -1
  353. souleyez-2.43.29.dist-info/RECORD +0 -379
  354. {souleyez-2.43.29.dist-info → souleyez-2.43.32.dist-info}/entry_points.txt +0 -0
  355. {souleyez-2.43.29.dist-info → souleyez-2.43.32.dist-info}/licenses/LICENSE +0 -0
  356. {souleyez-2.43.29.dist-info → souleyez-2.43.32.dist-info}/top_level.txt +0 -0
@@ -61,18 +61,22 @@ def parse_wpscan_output(output: str, target: str = "") -> Dict[str, Any]:
61
61
  }
62
62
  """
63
63
  result = {
64
- 'target_url': target,
65
- 'wordpress_version': None,
66
- 'version_status': None, # 'Insecure', 'Outdated', etc.
67
- 'version_release_date': None,
68
- 'users': [],
69
- 'findings': [],
70
- 'plugins': [],
71
- 'themes': [],
72
- 'info': [] # Additional findings (multisite, wp-cron, readme, headers, etc.)
64
+ "target_url": target,
65
+ "wordpress_version": None,
66
+ "version_status": None, # 'Insecure', 'Outdated', etc.
67
+ "version_release_date": None,
68
+ "users": [],
69
+ "findings": [],
70
+ "plugins": [],
71
+ "themes": [],
72
+ "info": [], # Additional findings (multisite, wp-cron, readme, headers, etc.)
73
73
  }
74
74
 
75
- lines = output.split('\n')
75
+ # Strip ANSI escape codes from output (wpscan uses colored output)
76
+ ansi_escape = re.compile(r"\x1b\[[0-9;]*m|\[[\d;]*m")
77
+ output = ansi_escape.sub("", output)
78
+
79
+ lines = output.split("\n")
76
80
  current_section = None
77
81
  # current_plugin and current_theme not used currently
78
82
 
@@ -80,173 +84,233 @@ def parse_wpscan_output(output: str, target: str = "") -> Dict[str, Any]:
80
84
  line_stripped = line.strip()
81
85
 
82
86
  # Extract WordPress version with status
83
- if 'WordPress version' in line or line_stripped.startswith('[+] WordPress version'):
84
- version_match = re.search(r'WordPress version[:\s]+(\d+\.\d+(?:\.\d+)?)', line)
87
+ if "WordPress version" in line or line_stripped.startswith(
88
+ "[+] WordPress version"
89
+ ):
90
+ version_match = re.search(
91
+ r"WordPress version[:\s]+(\d+\.\d+(?:\.\d+)?)", line
92
+ )
85
93
  if version_match:
86
- result['wordpress_version'] = version_match.group(1)
94
+ result["wordpress_version"] = version_match.group(1)
87
95
 
88
96
  # Check for insecure/outdated status
89
- if 'Insecure' in line:
90
- result['version_status'] = 'Insecure'
91
- elif 'Outdated' in line:
92
- result['version_status'] = 'Outdated'
97
+ if "Insecure" in line:
98
+ result["version_status"] = "Insecure"
99
+ elif "Outdated" in line:
100
+ result["version_status"] = "Outdated"
93
101
 
94
102
  # Extract release date
95
- date_match = re.search(r'released on (\d{4}-\d{2}-\d{2})', line)
103
+ date_match = re.search(r"released on (\d{4}-\d{2}-\d{2})", line)
96
104
  if date_match:
97
- result['version_release_date'] = date_match.group(1)
105
+ result["version_release_date"] = date_match.group(1)
98
106
 
99
107
  # Detect multisite
100
- if 'seems to be a multisite' in line.lower():
108
+ if "seems to be a multisite" in line.lower():
101
109
  # Check if not already added
102
- if not any(item['title'] == 'WordPress Multisite Detected' for item in result['info']):
103
- result['info'].append({
104
- 'type': 'config',
105
- 'title': 'WordPress Multisite Detected',
106
- 'description': 'This installation is configured as a WordPress multisite network.',
107
- 'severity': 'info'
108
- })
110
+ if not any(
111
+ item["title"] == "WordPress Multisite Detected"
112
+ for item in result["info"]
113
+ ):
114
+ result["info"].append(
115
+ {
116
+ "type": "config",
117
+ "title": "WordPress Multisite Detected",
118
+ "description": "This installation is configured as a WordPress multisite network.",
119
+ "severity": "info",
120
+ }
121
+ )
109
122
 
110
123
  # Detect WP-Cron
111
- if 'external WP-Cron seems to be enabled' in line.lower():
112
- if not any(item['title'] == 'External WP-Cron Enabled' for item in result['info']):
113
- result['info'].append({
114
- 'type': 'config',
115
- 'title': 'External WP-Cron Enabled',
116
- 'description': 'The external WP-Cron endpoint is accessible.',
117
- 'severity': 'low'
118
- })
124
+ if "external WP-Cron seems to be enabled" in line.lower():
125
+ if not any(
126
+ item["title"] == "External WP-Cron Enabled" for item in result["info"]
127
+ ):
128
+ result["info"].append(
129
+ {
130
+ "type": "config",
131
+ "title": "External WP-Cron Enabled",
132
+ "description": "The external WP-Cron endpoint is accessible.",
133
+ "severity": "low",
134
+ }
135
+ )
119
136
 
120
137
  # Detect README file
121
- if 'readme found:' in line.lower() and 'readme.html' in line.lower():
122
- if not any(item['title'] == 'WordPress README File Exposed' for item in result['info']):
123
- result['info'].append({
124
- 'type': 'disclosure',
125
- 'title': 'WordPress README File Exposed',
126
- 'description': 'The WordPress readme.html file is accessible, which can reveal version information.',
127
- 'severity': 'low'
128
- })
138
+ if "readme found:" in line.lower() and "readme.html" in line.lower():
139
+ if not any(
140
+ item["title"] == "WordPress README File Exposed"
141
+ for item in result["info"]
142
+ ):
143
+ result["info"].append(
144
+ {
145
+ "type": "disclosure",
146
+ "title": "WordPress README File Exposed",
147
+ "description": "The WordPress readme.html file is accessible, which can reveal version information.",
148
+ "severity": "low",
149
+ }
150
+ )
129
151
 
130
152
  # Detect interesting headers
131
- if line_stripped.startswith('|') and any(header in line for header in ['X-Mod-Pagespeed', 'X-Powered-By', 'Server:']):
132
- header_match = re.search(r'\|\s+[-\s]+([^:]+):\s+(.+)', line)
153
+ if line_stripped.startswith("|") and any(
154
+ header in line for header in ["X-Mod-Pagespeed", "X-Powered-By", "Server:"]
155
+ ):
156
+ header_match = re.search(r"\|\s+[-\s]+([^:]+):\s+(.+)", line)
133
157
  if header_match:
134
158
  header_name = header_match.group(1).strip()
135
159
  header_value = header_match.group(2).strip()
136
- header_title = f'HTTP Header: {header_name}'
137
- if not any(item['title'] == header_title for item in result['info']):
138
- result['info'].append({
139
- 'type': 'header',
140
- 'title': header_title,
141
- 'description': f'Server header detected: {header_name}: {header_value}',
142
- 'severity': 'info'
143
- })
160
+ header_title = f"HTTP Header: {header_name}"
161
+ if not any(item["title"] == header_title for item in result["info"]):
162
+ result["info"].append(
163
+ {
164
+ "type": "header",
165
+ "title": header_title,
166
+ "description": f"Server header detected: {header_name}: {header_value}",
167
+ "severity": "info",
168
+ }
169
+ )
144
170
 
145
171
  # Detect no API token warning
146
- if 'No WPScan API Token given' in line:
147
- if not any(item['title'] == 'No WPScan API Token' for item in result['info']):
148
- result['info'].append({
149
- 'type': 'warning',
150
- 'title': 'No WPScan API Token',
151
- 'description': 'Vulnerability data not included. Register at https://wpscan.com/register for a free API token.',
152
- 'severity': 'info'
153
- })
172
+ if "No WPScan API Token given" in line:
173
+ if not any(
174
+ item["title"] == "No WPScan API Token" for item in result["info"]
175
+ ):
176
+ result["info"].append(
177
+ {
178
+ "type": "warning",
179
+ "title": "No WPScan API Token",
180
+ "description": "Vulnerability data not included. Register at https://wpscan.com/register for a free API token.",
181
+ "severity": "info",
182
+ }
183
+ )
154
184
 
155
185
  # Detect theme detection failure
156
- if 'main theme could not be detected' in line.lower():
157
- if not any(item['title'] == 'Theme Detection Failed' for item in result['info']):
158
- result['info'].append({
159
- 'type': 'info',
160
- 'title': 'Theme Detection Failed',
161
- 'description': 'WPScan could not identify the active theme.',
162
- 'severity': 'info'
163
- })
186
+ if "main theme could not be detected" in line.lower():
187
+ if not any(
188
+ item["title"] == "Theme Detection Failed" for item in result["info"]
189
+ ):
190
+ result["info"].append(
191
+ {
192
+ "type": "info",
193
+ "title": "Theme Detection Failed",
194
+ "description": "WPScan could not identify the active theme.",
195
+ "severity": "info",
196
+ }
197
+ )
164
198
 
165
199
  # Detect no plugins found
166
- if '[i] No plugins Found' in line or 'No plugins Found' in line:
167
- if not any(item['title'] == 'No Plugins Detected' for item in result['info']):
168
- result['info'].append({
169
- 'type': 'info',
170
- 'title': 'No Plugins Detected',
171
- 'description': 'WPScan did not detect any WordPress plugins.',
172
- 'severity': 'info'
173
- })
200
+ if "[i] No plugins Found" in line or "No plugins Found" in line:
201
+ if not any(
202
+ item["title"] == "No Plugins Detected" for item in result["info"]
203
+ ):
204
+ result["info"].append(
205
+ {
206
+ "type": "info",
207
+ "title": "No Plugins Detected",
208
+ "description": "WPScan did not detect any WordPress plugins.",
209
+ "severity": "info",
210
+ }
211
+ )
174
212
 
175
213
  # Detect sections
176
- if '[+] WordPress theme in use:' in line or 'Theme(s) Detected:' in line:
177
- current_section = 'themes'
178
- elif '[+] Plugins Found:' in line or 'Plugin(s) Identified:' in line:
179
- current_section = 'plugins'
180
- elif '[+] User(s) Identified:' in line or 'Username(s) Identified:' in line:
181
- current_section = 'users'
214
+ if "[+] WordPress theme in use:" in line or "Theme(s) Detected:" in line:
215
+ current_section = "themes"
216
+ elif "[+] Plugins Found:" in line or "Plugin(s) Identified:" in line:
217
+ current_section = "plugins"
218
+ elif "User(s) Identified:" in line or "Username(s) Identified:" in line:
219
+ current_section = "users"
182
220
 
183
221
  # Parse vulnerabilities with [!] marker
184
- if line_stripped.startswith('[!]'):
185
- finding = _parse_vulnerability_line(line, lines[i:i + 10])
222
+ # WPScan outputs vulns as either "[!] Title:" or "| [!] Title:" (indented under sections)
223
+ if line_stripped.startswith("[!]") or line_stripped.startswith("| [!]"):
224
+ finding = _parse_vulnerability_line(line, lines[i : i + 10])
186
225
  if finding:
187
- result['findings'].append(finding)
188
-
189
- # Parse enumerated users
190
- if current_section == 'users':
191
- user_match = re.search(r'\|\s+([a-zA-Z0-9_-]+)', line)
192
- if user_match:
193
- username = user_match.group(1)
194
- if username not in result['users'] and username != 'Id':
195
- result['users'].append(username)
226
+ result["findings"].append(finding)
227
+
228
+ # Parse enumerated users - format: [+] username (on its own line)
229
+ if current_section == "users":
230
+ # End users section on [!] warning or [+] Finished/Requests
231
+ if (
232
+ line_stripped.startswith("[!]")
233
+ or "Finished:" in line
234
+ or "Requests Done:" in line
235
+ ):
236
+ current_section = None
237
+ else:
238
+ # Match username after [+] at start of line (username may have spaces)
239
+ user_match = re.match(r"^\[\+\]\s*(.+)$", line_stripped)
240
+ if user_match:
241
+ username = user_match.group(1).strip()
242
+ if (
243
+ username
244
+ and username not in result["users"]
245
+ and username.lower() not in ("id", "found by")
246
+ ):
247
+ result["users"].append(username)
196
248
 
197
249
  # Parse plugin info
198
- if current_section == 'plugins':
199
- plugin_match = re.search(r'\[i\]\s+Plugin\(s\)\s+Identified:\s+(.+)', line)
250
+ if current_section == "plugins":
251
+ plugin_match = re.search(r"\[i\]\s+Plugin\(s\)\s+Identified:\s+(.+)", line)
200
252
  if not plugin_match:
201
- plugin_match = re.search(r'\|\s+Name:\s+(.+)', line)
253
+ plugin_match = re.search(r"\|\s+Name:\s+(.+)", line)
202
254
 
203
255
  if plugin_match:
204
256
  plugin_name = plugin_match.group(1).strip()
205
- plugin_data = {'name': plugin_name, 'version': None, 'location': None, 'vulnerable': False}
257
+ plugin_data = {
258
+ "name": plugin_name,
259
+ "version": None,
260
+ "location": None,
261
+ "vulnerable": False,
262
+ }
206
263
 
207
264
  # Look ahead for version and location
208
265
  for j in range(i + 1, min(i + 10, len(lines))):
209
- if 'Latest version:' in lines[j] or 'Version:' in lines[j]:
210
- ver_match = re.search(r'(\d+\.\d+(?:\.\d+)?)', lines[j])
266
+ if "Latest version:" in lines[j] or "Version:" in lines[j]:
267
+ ver_match = re.search(r"(\d+\.\d+(?:\.\d+)?)", lines[j])
211
268
  if ver_match:
212
- plugin_data['version'] = ver_match.group(1)
213
- if 'Location:' in lines[j]:
214
- loc_match = re.search(r'Location:\s+(.+)', lines[j])
269
+ plugin_data["version"] = ver_match.group(1)
270
+ if "Location:" in lines[j]:
271
+ loc_match = re.search(r"Location:\s+(.+)", lines[j])
215
272
  if loc_match:
216
- plugin_data['location'] = loc_match.group(1).strip()
217
- if '[!]' in lines[j]:
218
- plugin_data['vulnerable'] = True
273
+ plugin_data["location"] = loc_match.group(1).strip()
274
+ if "[!]" in lines[j]:
275
+ plugin_data["vulnerable"] = True
219
276
 
220
277
  if plugin_name:
221
- result['plugins'].append(plugin_data)
278
+ result["plugins"].append(plugin_data)
222
279
 
223
280
  # Parse theme info
224
- if current_section == 'themes':
225
- theme_match = re.search(r'\|\s+Name:\s+(.+)', line)
281
+ if current_section == "themes":
282
+ theme_match = re.search(r"\|\s+Name:\s+(.+)", line)
226
283
  if theme_match:
227
284
  theme_name = theme_match.group(1).strip()
228
- theme_data = {'name': theme_name, 'version': None, 'location': None, 'vulnerable': False}
285
+ theme_data = {
286
+ "name": theme_name,
287
+ "version": None,
288
+ "location": None,
289
+ "vulnerable": False,
290
+ }
229
291
 
230
292
  # Look ahead for version and location
231
293
  for j in range(i + 1, min(i + 10, len(lines))):
232
- if 'Version:' in lines[j]:
233
- ver_match = re.search(r'(\d+\.\d+(?:\.\d+)?)', lines[j])
294
+ if "Version:" in lines[j]:
295
+ ver_match = re.search(r"(\d+\.\d+(?:\.\d+)?)", lines[j])
234
296
  if ver_match:
235
- theme_data['version'] = ver_match.group(1)
236
- if 'Location:' in lines[j]:
237
- loc_match = re.search(r'Location:\s+(.+)', lines[j])
297
+ theme_data["version"] = ver_match.group(1)
298
+ if "Location:" in lines[j]:
299
+ loc_match = re.search(r"Location:\s+(.+)", lines[j])
238
300
  if loc_match:
239
- theme_data['location'] = loc_match.group(1).strip()
240
- if '[!]' in lines[j]:
241
- theme_data['vulnerable'] = True
301
+ theme_data["location"] = loc_match.group(1).strip()
302
+ if "[!]" in lines[j]:
303
+ theme_data["vulnerable"] = True
242
304
 
243
305
  if theme_name:
244
- result['themes'].append(theme_data)
306
+ result["themes"].append(theme_data)
245
307
 
246
308
  return result
247
309
 
248
310
 
249
- def _parse_vulnerability_line(line: str, context_lines: List[str]) -> Optional[Dict[str, Any]]:
311
+ def _parse_vulnerability_line(
312
+ line: str, context_lines: List[str]
313
+ ) -> Optional[Dict[str, Any]]:
250
314
  """
251
315
  Parse a vulnerability line and surrounding context.
252
316
 
@@ -257,74 +321,92 @@ def _parse_vulnerability_line(line: str, context_lines: List[str]) -> Optional[D
257
321
  | - https://wpvulndb.com/vulnerabilities/xxxx
258
322
  | - CVE-2021-12345
259
323
  """
260
- # Extract title
261
- title_match = re.search(r'\[!\]\s+Title:\s+(.+)', line)
324
+ # Extract title - must have "Title:" prefix for actual vulnerabilities
325
+ # WPScan uses "[!] Title: ..." for vulns, other [!] lines are warnings/info
326
+ title_match = re.search(r"\[!\]\s+Title:\s+(.+)", line)
262
327
  if not title_match:
263
- # Sometimes just [!] Vulnerability description
264
- title_match = re.search(r'\[!\]\s+(.+)', line)
265
- if not title_match:
266
- return None
328
+ # Only accept lines with explicit "Title:" prefix - skip warnings like:
329
+ # "[!] No WPScan API Token given..."
330
+ # "[!] The version is out of date..."
331
+ # "[!] 121 vulnerabilities identified:"
332
+ return None
267
333
 
268
334
  title = title_match.group(1).strip()
269
335
 
270
336
  finding = {
271
- 'title': title,
272
- 'type': 'unknown',
273
- 'name': '',
274
- 'version': None,
275
- 'severity': 'medium', # Default
276
- 'description': title,
277
- 'references': [],
278
- 'fixed_in': None
337
+ "title": title,
338
+ "type": "unknown",
339
+ "name": "",
340
+ "version": None,
341
+ "severity": "medium", # Default
342
+ "description": title,
343
+ "references": [],
344
+ "fixed_in": None,
279
345
  }
280
346
 
281
347
  # Determine type and severity from title
282
- if 'WordPress' in title and 'Core' in title:
283
- finding['type'] = 'core'
284
- finding['name'] = 'WordPress Core'
285
- elif 'plugin' in title.lower():
286
- finding['type'] = 'plugin'
287
- elif 'theme' in title.lower():
288
- finding['type'] = 'theme'
289
- elif 'config' in title.lower() or 'header' in title.lower():
290
- finding['type'] = 'config'
348
+ if "WordPress" in title and "Core" in title:
349
+ finding["type"] = "core"
350
+ finding["name"] = "WordPress Core"
351
+ elif "plugin" in title.lower():
352
+ finding["type"] = "plugin"
353
+ elif "theme" in title.lower():
354
+ finding["type"] = "theme"
355
+ elif "config" in title.lower() or "header" in title.lower():
356
+ finding["type"] = "config"
291
357
 
292
358
  # Severity indicators
293
- if any(word in title.lower() for word in ['critical', 'rce', 'sql injection', 'remote code']):
294
- finding['severity'] = 'critical'
295
- elif any(word in title.lower() for word in ['high', 'authentication bypass', 'privilege escalation']):
296
- finding['severity'] = 'high'
297
- elif any(word in title.lower() for word in ['xss', 'csrf', 'medium']):
298
- finding['severity'] = 'medium'
299
- elif any(word in title.lower() for word in ['disclosure', 'enumeration', 'low']):
300
- finding['severity'] = 'low'
301
- elif any(word in title.lower() for word in ['info', 'version']):
302
- finding['severity'] = 'info'
303
-
304
- # Extract additional info from context
359
+ if any(
360
+ word in title.lower()
361
+ for word in ["critical", "rce", "sql injection", "remote code"]
362
+ ):
363
+ finding["severity"] = "critical"
364
+ elif any(
365
+ word in title.lower()
366
+ for word in ["high", "authentication bypass", "privilege escalation"]
367
+ ):
368
+ finding["severity"] = "high"
369
+ elif any(word in title.lower() for word in ["xss", "csrf", "medium"]):
370
+ finding["severity"] = "medium"
371
+ elif any(word in title.lower() for word in ["disclosure", "enumeration", "low"]):
372
+ finding["severity"] = "low"
373
+ elif any(word in title.lower() for word in ["info", "version"]):
374
+ finding["severity"] = "info"
375
+
376
+ # Extract additional info from context lines (stop at blank line or next vuln)
305
377
  for context_line in context_lines[1:]:
306
378
  context_stripped = context_line.strip()
307
379
 
308
- if 'Fixed in:' in context_stripped:
309
- fixed_match = re.search(r'Fixed in:\s+(\d+\.\d+(?:\.\d+)?)', context_stripped)
380
+ # Stop parsing context at blank lines or next vulnerability marker
381
+ if not context_stripped or context_stripped == "|":
382
+ break
383
+ if "[!]" in context_stripped and "Title:" in context_stripped:
384
+ break
385
+
386
+ if "Fixed in:" in context_stripped:
387
+ fixed_match = re.search(
388
+ r"Fixed in:\s+(\d+\.\d+(?:\.\d+)?)", context_stripped
389
+ )
310
390
  if fixed_match:
311
- finding['fixed_in'] = fixed_match.group(1)
391
+ finding["fixed_in"] = fixed_match.group(1)
312
392
 
313
- elif 'CVE-' in context_stripped:
314
- cve_matches = re.findall(r'CVE-\d{4}-\d+', context_stripped)
315
- finding['references'].extend(cve_matches)
393
+ elif "CVE-" in context_stripped:
394
+ cve_matches = re.findall(r"CVE-\d{4}-\d+", context_stripped)
395
+ finding["references"].extend(cve_matches)
316
396
 
317
- elif 'https://' in context_stripped or 'http://' in context_stripped:
318
- url_match = re.search(r'(https?://[^\s<>]+)', context_stripped)
397
+ elif "https://" in context_stripped or "http://" in context_stripped:
398
+ url_match = re.search(r"(https?://[^\s<>]+)", context_stripped)
319
399
  if url_match:
320
- url = url_match.group(1).rstrip(',.;)')
321
- if url not in finding['references']:
322
- finding['references'].append(url)
400
+ url = url_match.group(1).rstrip(",.;)")
401
+ if url not in finding["references"]:
402
+ finding["references"].append(url)
323
403
 
324
404
  return finding
325
405
 
326
406
 
327
- def map_to_findings(parsed_data: Dict[str, Any], engagement_id: int) -> List[Dict[str, Any]]:
407
+ def map_to_findings(
408
+ parsed_data: Dict[str, Any], engagement_id: int
409
+ ) -> List[Dict[str, Any]]:
328
410
  """
329
411
  Convert parsed WPScan data into finding records for database storage.
330
412
 
@@ -337,24 +419,30 @@ def map_to_findings(parsed_data: Dict[str, Any], engagement_id: int) -> List[Dic
337
419
  """
338
420
  findings = []
339
421
 
340
- for vuln in parsed_data.get('findings', []):
422
+ for vuln in parsed_data.get("findings", []):
341
423
  finding = {
342
- 'title': vuln['title'],
343
- 'severity': vuln['severity'],
344
- 'description': vuln['description'],
345
- 'affected_target': parsed_data.get('target_url', ''),
346
- 'tool': 'wpscan',
347
- 'category': 'web',
348
- 'remediation': f"Update {vuln['name']} to version {vuln['fixed_in']}" if vuln['fixed_in'] else "Review WordPress security settings",
349
- 'references': '\n'.join(vuln['references']) if vuln['references'] else None,
350
- 'cvss_score': _severity_to_cvss(vuln['severity']),
351
- 'metadata': json.dumps({
352
- 'wordpress_version': parsed_data.get('wordpress_version'),
353
- 'component_type': vuln['type'],
354
- 'component_name': vuln['name'],
355
- 'component_version': vuln['version'],
356
- 'fixed_in': vuln['fixed_in']
357
- })
424
+ "title": vuln["title"],
425
+ "severity": vuln["severity"],
426
+ "description": vuln["description"],
427
+ "affected_target": parsed_data.get("target_url", ""),
428
+ "tool": "wpscan",
429
+ "category": "web",
430
+ "remediation": (
431
+ f"Update {vuln['name']} to version {vuln['fixed_in']}"
432
+ if vuln["fixed_in"]
433
+ else "Review WordPress security settings"
434
+ ),
435
+ "references": "\n".join(vuln["references"]) if vuln["references"] else None,
436
+ "cvss_score": _severity_to_cvss(vuln["severity"]),
437
+ "metadata": json.dumps(
438
+ {
439
+ "wordpress_version": parsed_data.get("wordpress_version"),
440
+ "component_type": vuln["type"],
441
+ "component_name": vuln["name"],
442
+ "component_version": vuln["version"],
443
+ "fixed_in": vuln["fixed_in"],
444
+ }
445
+ ),
358
446
  }
359
447
  findings.append(finding)
360
448
 
@@ -364,14 +452,14 @@ def map_to_findings(parsed_data: Dict[str, Any], engagement_id: int) -> List[Dic
364
452
  def _severity_to_cvss(severity: str) -> Optional[float]:
365
453
  """Map severity string to approximate CVSS score."""
366
454
  severity_map = {
367
- 'critical': 9.5,
368
- 'high': 7.5,
369
- 'medium': 5.0,
370
- 'low': 3.0,
371
- 'info': 0.0
455
+ "critical": 9.5,
456
+ "high": 7.5,
457
+ "medium": 5.0,
458
+ "low": 3.0,
459
+ "info": 0.0,
372
460
  }
373
461
  return severity_map.get(severity.lower())
374
462
 
375
463
 
376
464
  # Export the main function
377
- __all__ = ['parse_wpscan_output', 'map_to_findings']
465
+ __all__ = ["parse_wpscan_output", "map_to_findings"]