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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. souleyez/__init__.py +1 -2
  2. souleyez/ai/__init__.py +21 -15
  3. souleyez/ai/action_mapper.py +249 -150
  4. souleyez/ai/chain_advisor.py +116 -100
  5. souleyez/ai/claude_provider.py +29 -28
  6. souleyez/ai/context_builder.py +80 -62
  7. souleyez/ai/executor.py +158 -117
  8. souleyez/ai/feedback_handler.py +136 -121
  9. souleyez/ai/llm_factory.py +27 -20
  10. souleyez/ai/llm_provider.py +4 -2
  11. souleyez/ai/ollama_provider.py +6 -9
  12. souleyez/ai/ollama_service.py +44 -37
  13. souleyez/ai/path_scorer.py +91 -76
  14. souleyez/ai/recommender.py +176 -144
  15. souleyez/ai/report_context.py +74 -73
  16. souleyez/ai/report_service.py +84 -66
  17. souleyez/ai/result_parser.py +222 -229
  18. souleyez/ai/safety.py +67 -44
  19. souleyez/auth/__init__.py +23 -22
  20. souleyez/auth/audit.py +36 -26
  21. souleyez/auth/engagement_access.py +65 -48
  22. souleyez/auth/permissions.py +14 -3
  23. souleyez/auth/session_manager.py +54 -37
  24. souleyez/auth/user_manager.py +109 -64
  25. souleyez/commands/audit.py +40 -43
  26. souleyez/commands/auth.py +35 -15
  27. souleyez/commands/deliverables.py +55 -50
  28. souleyez/commands/engagement.py +47 -28
  29. souleyez/commands/license.py +32 -23
  30. souleyez/commands/screenshots.py +36 -32
  31. souleyez/commands/user.py +82 -36
  32. souleyez/config.py +52 -44
  33. souleyez/core/credential_tester.py +87 -81
  34. souleyez/core/cve_mappings.py +179 -192
  35. souleyez/core/cve_matcher.py +162 -148
  36. souleyez/core/msf_auto_mapper.py +100 -83
  37. souleyez/core/msf_chain_engine.py +294 -256
  38. souleyez/core/msf_database.py +153 -70
  39. souleyez/core/msf_integration.py +679 -673
  40. souleyez/core/msf_rpc_client.py +40 -42
  41. souleyez/core/msf_rpc_manager.py +77 -79
  42. souleyez/core/msf_sync_manager.py +241 -181
  43. souleyez/core/network_utils.py +22 -15
  44. souleyez/core/parser_handler.py +34 -25
  45. souleyez/core/pending_chains.py +114 -63
  46. souleyez/core/templates.py +158 -107
  47. souleyez/core/tool_chaining.py +9526 -2879
  48. souleyez/core/version_utils.py +79 -94
  49. souleyez/core/vuln_correlation.py +136 -89
  50. souleyez/core/web_utils.py +33 -32
  51. souleyez/data/wordlists/ad_users.txt +378 -0
  52. souleyez/data/wordlists/api_endpoints_large.txt +769 -0
  53. souleyez/data/wordlists/home_dir_sensitive.txt +39 -0
  54. souleyez/data/wordlists/lfi_payloads.txt +82 -0
  55. souleyez/data/wordlists/passwords_brute.txt +1548 -0
  56. souleyez/data/wordlists/passwords_crack.txt +2479 -0
  57. souleyez/data/wordlists/passwords_spray.txt +386 -0
  58. souleyez/data/wordlists/subdomains_large.txt +5057 -0
  59. souleyez/data/wordlists/usernames_common.txt +694 -0
  60. souleyez/data/wordlists/web_dirs_large.txt +4769 -0
  61. souleyez/detection/__init__.py +1 -1
  62. souleyez/detection/attack_signatures.py +12 -17
  63. souleyez/detection/mitre_mappings.py +61 -55
  64. souleyez/detection/validator.py +97 -86
  65. souleyez/devtools.py +23 -10
  66. souleyez/docs/README.md +4 -4
  67. souleyez/docs/api-reference/cli-commands.md +2 -2
  68. souleyez/docs/developer-guide/adding-new-tools.md +562 -0
  69. souleyez/docs/user-guide/auto-chaining.md +30 -8
  70. souleyez/docs/user-guide/getting-started.md +1 -1
  71. souleyez/docs/user-guide/installation.md +26 -3
  72. souleyez/docs/user-guide/metasploit-integration.md +2 -2
  73. souleyez/docs/user-guide/rbac.md +1 -1
  74. souleyez/docs/user-guide/scope-management.md +1 -1
  75. souleyez/docs/user-guide/siem-integration.md +1 -1
  76. souleyez/docs/user-guide/tools-reference.md +1 -8
  77. souleyez/docs/user-guide/worker-management.md +1 -1
  78. souleyez/engine/background.py +1239 -535
  79. souleyez/engine/base.py +4 -1
  80. souleyez/engine/job_status.py +17 -49
  81. souleyez/engine/log_sanitizer.py +103 -77
  82. souleyez/engine/manager.py +38 -7
  83. souleyez/engine/result_handler.py +2200 -1550
  84. souleyez/engine/worker_manager.py +50 -41
  85. souleyez/export/evidence_bundle.py +72 -62
  86. souleyez/feature_flags/features.py +16 -20
  87. souleyez/feature_flags.py +5 -9
  88. souleyez/handlers/__init__.py +11 -0
  89. souleyez/handlers/base.py +188 -0
  90. souleyez/handlers/bash_handler.py +277 -0
  91. souleyez/handlers/bloodhound_handler.py +243 -0
  92. souleyez/handlers/certipy_handler.py +311 -0
  93. souleyez/handlers/crackmapexec_handler.py +486 -0
  94. souleyez/handlers/dnsrecon_handler.py +344 -0
  95. souleyez/handlers/enum4linux_handler.py +400 -0
  96. souleyez/handlers/evil_winrm_handler.py +493 -0
  97. souleyez/handlers/ffuf_handler.py +815 -0
  98. souleyez/handlers/gobuster_handler.py +1114 -0
  99. souleyez/handlers/gpp_extract_handler.py +334 -0
  100. souleyez/handlers/hashcat_handler.py +444 -0
  101. souleyez/handlers/hydra_handler.py +563 -0
  102. souleyez/handlers/impacket_getuserspns_handler.py +343 -0
  103. souleyez/handlers/impacket_psexec_handler.py +222 -0
  104. souleyez/handlers/impacket_secretsdump_handler.py +426 -0
  105. souleyez/handlers/john_handler.py +286 -0
  106. souleyez/handlers/katana_handler.py +425 -0
  107. souleyez/handlers/kerbrute_handler.py +298 -0
  108. souleyez/handlers/ldapsearch_handler.py +636 -0
  109. souleyez/handlers/lfi_extract_handler.py +464 -0
  110. souleyez/handlers/msf_auxiliary_handler.py +408 -0
  111. souleyez/handlers/msf_exploit_handler.py +380 -0
  112. souleyez/handlers/nikto_handler.py +413 -0
  113. souleyez/handlers/nmap_handler.py +821 -0
  114. souleyez/handlers/nuclei_handler.py +359 -0
  115. souleyez/handlers/nxc_handler.py +371 -0
  116. souleyez/handlers/rdp_sec_check_handler.py +353 -0
  117. souleyez/handlers/registry.py +292 -0
  118. souleyez/handlers/responder_handler.py +232 -0
  119. souleyez/handlers/service_explorer_handler.py +434 -0
  120. souleyez/handlers/smbclient_handler.py +344 -0
  121. souleyez/handlers/smbmap_handler.py +510 -0
  122. souleyez/handlers/smbpasswd_handler.py +296 -0
  123. souleyez/handlers/sqlmap_handler.py +1116 -0
  124. souleyez/handlers/theharvester_handler.py +601 -0
  125. souleyez/handlers/web_login_test_handler.py +327 -0
  126. souleyez/handlers/whois_handler.py +277 -0
  127. souleyez/handlers/wpscan_handler.py +554 -0
  128. souleyez/history.py +32 -16
  129. souleyez/importers/msf_importer.py +106 -75
  130. souleyez/importers/smart_importer.py +208 -147
  131. souleyez/integrations/siem/__init__.py +10 -10
  132. souleyez/integrations/siem/base.py +17 -18
  133. souleyez/integrations/siem/elastic.py +108 -122
  134. souleyez/integrations/siem/factory.py +207 -80
  135. souleyez/integrations/siem/googlesecops.py +146 -154
  136. souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
  137. souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
  138. souleyez/integrations/siem/sentinel.py +107 -109
  139. souleyez/integrations/siem/splunk.py +246 -212
  140. souleyez/integrations/siem/wazuh.py +65 -71
  141. souleyez/integrations/wazuh/__init__.py +5 -5
  142. souleyez/integrations/wazuh/client.py +70 -93
  143. souleyez/integrations/wazuh/config.py +85 -57
  144. souleyez/integrations/wazuh/host_mapper.py +28 -36
  145. souleyez/integrations/wazuh/sync.py +78 -68
  146. souleyez/intelligence/__init__.py +4 -5
  147. souleyez/intelligence/correlation_analyzer.py +309 -295
  148. souleyez/intelligence/exploit_knowledge.py +661 -623
  149. souleyez/intelligence/exploit_suggestions.py +159 -139
  150. souleyez/intelligence/gap_analyzer.py +132 -97
  151. souleyez/intelligence/gap_detector.py +251 -214
  152. souleyez/intelligence/sensitive_tables.py +266 -129
  153. souleyez/intelligence/service_parser.py +137 -123
  154. souleyez/intelligence/surface_analyzer.py +407 -268
  155. souleyez/intelligence/target_parser.py +159 -162
  156. souleyez/licensing/__init__.py +6 -6
  157. souleyez/licensing/validator.py +17 -19
  158. souleyez/log_config.py +79 -54
  159. souleyez/main.py +1505 -687
  160. souleyez/migrations/fix_job_counter.py +16 -14
  161. souleyez/parsers/bloodhound_parser.py +41 -39
  162. souleyez/parsers/crackmapexec_parser.py +178 -111
  163. souleyez/parsers/dalfox_parser.py +72 -77
  164. souleyez/parsers/dnsrecon_parser.py +103 -91
  165. souleyez/parsers/enum4linux_parser.py +183 -153
  166. souleyez/parsers/ffuf_parser.py +29 -25
  167. souleyez/parsers/gobuster_parser.py +301 -41
  168. souleyez/parsers/hashcat_parser.py +324 -79
  169. souleyez/parsers/http_fingerprint_parser.py +350 -103
  170. souleyez/parsers/hydra_parser.py +131 -111
  171. souleyez/parsers/impacket_parser.py +231 -178
  172. souleyez/parsers/john_parser.py +98 -86
  173. souleyez/parsers/katana_parser.py +316 -0
  174. souleyez/parsers/msf_parser.py +943 -498
  175. souleyez/parsers/nikto_parser.py +346 -65
  176. souleyez/parsers/nmap_parser.py +262 -174
  177. souleyez/parsers/nuclei_parser.py +40 -44
  178. souleyez/parsers/responder_parser.py +26 -26
  179. souleyez/parsers/searchsploit_parser.py +74 -74
  180. souleyez/parsers/service_explorer_parser.py +279 -0
  181. souleyez/parsers/smbmap_parser.py +180 -124
  182. souleyez/parsers/sqlmap_parser.py +434 -308
  183. souleyez/parsers/theharvester_parser.py +75 -57
  184. souleyez/parsers/whois_parser.py +135 -94
  185. souleyez/parsers/wpscan_parser.py +278 -190
  186. souleyez/plugins/afp.py +44 -36
  187. souleyez/plugins/afp_brute.py +114 -46
  188. souleyez/plugins/ard.py +48 -37
  189. souleyez/plugins/bloodhound.py +95 -61
  190. souleyez/plugins/certipy.py +303 -0
  191. souleyez/plugins/crackmapexec.py +186 -85
  192. souleyez/plugins/dalfox.py +120 -59
  193. souleyez/plugins/dns_hijack.py +146 -41
  194. souleyez/plugins/dnsrecon.py +97 -61
  195. souleyez/plugins/enum4linux.py +91 -66
  196. souleyez/plugins/evil_winrm.py +291 -0
  197. souleyez/plugins/ffuf.py +166 -90
  198. souleyez/plugins/firmware_extract.py +133 -29
  199. souleyez/plugins/gobuster.py +387 -190
  200. souleyez/plugins/gpp_extract.py +393 -0
  201. souleyez/plugins/hashcat.py +100 -73
  202. souleyez/plugins/http_fingerprint.py +854 -267
  203. souleyez/plugins/hydra.py +566 -200
  204. souleyez/plugins/impacket_getnpusers.py +117 -69
  205. souleyez/plugins/impacket_psexec.py +84 -64
  206. souleyez/plugins/impacket_secretsdump.py +103 -69
  207. souleyez/plugins/impacket_smbclient.py +89 -75
  208. souleyez/plugins/john.py +86 -69
  209. souleyez/plugins/katana.py +313 -0
  210. souleyez/plugins/kerbrute.py +237 -0
  211. souleyez/plugins/lfi_extract.py +541 -0
  212. souleyez/plugins/macos_ssh.py +117 -48
  213. souleyez/plugins/mdns.py +35 -30
  214. souleyez/plugins/msf_auxiliary.py +253 -130
  215. souleyez/plugins/msf_exploit.py +239 -161
  216. souleyez/plugins/nikto.py +134 -78
  217. souleyez/plugins/nmap.py +275 -91
  218. souleyez/plugins/nuclei.py +180 -89
  219. souleyez/plugins/nxc.py +285 -0
  220. souleyez/plugins/plugin_base.py +35 -36
  221. souleyez/plugins/plugin_template.py +13 -5
  222. souleyez/plugins/rdp_sec_check.py +130 -0
  223. souleyez/plugins/responder.py +112 -71
  224. souleyez/plugins/router_http_brute.py +76 -65
  225. souleyez/plugins/router_ssh_brute.py +118 -41
  226. souleyez/plugins/router_telnet_brute.py +124 -42
  227. souleyez/plugins/routersploit.py +91 -59
  228. souleyez/plugins/routersploit_exploit.py +77 -55
  229. souleyez/plugins/searchsploit.py +91 -77
  230. souleyez/plugins/service_explorer.py +1160 -0
  231. souleyez/plugins/smbmap.py +122 -72
  232. souleyez/plugins/smbpasswd.py +215 -0
  233. souleyez/plugins/sqlmap.py +301 -113
  234. souleyez/plugins/theharvester.py +127 -75
  235. souleyez/plugins/tr069.py +79 -57
  236. souleyez/plugins/upnp.py +65 -47
  237. souleyez/plugins/upnp_abuse.py +73 -55
  238. souleyez/plugins/vnc_access.py +129 -42
  239. souleyez/plugins/vnc_brute.py +109 -38
  240. souleyez/plugins/web_login_test.py +417 -0
  241. souleyez/plugins/whois.py +77 -58
  242. souleyez/plugins/wpscan.py +173 -69
  243. souleyez/reporting/__init__.py +2 -1
  244. souleyez/reporting/attack_chain.py +411 -346
  245. souleyez/reporting/charts.py +436 -501
  246. souleyez/reporting/compliance_mappings.py +334 -201
  247. souleyez/reporting/detection_report.py +126 -125
  248. souleyez/reporting/formatters.py +828 -591
  249. souleyez/reporting/generator.py +386 -302
  250. souleyez/reporting/metrics.py +72 -75
  251. souleyez/scanner.py +35 -29
  252. souleyez/security/__init__.py +37 -11
  253. souleyez/security/scope_validator.py +175 -106
  254. souleyez/security/validation.py +223 -149
  255. souleyez/security.py +22 -6
  256. souleyez/storage/credentials.py +247 -186
  257. souleyez/storage/crypto.py +296 -129
  258. souleyez/storage/database.py +73 -50
  259. souleyez/storage/db.py +58 -36
  260. souleyez/storage/deliverable_evidence.py +177 -128
  261. souleyez/storage/deliverable_exporter.py +282 -246
  262. souleyez/storage/deliverable_templates.py +134 -116
  263. souleyez/storage/deliverables.py +135 -130
  264. souleyez/storage/engagements.py +109 -56
  265. souleyez/storage/evidence.py +181 -152
  266. souleyez/storage/execution_log.py +31 -17
  267. souleyez/storage/exploit_attempts.py +93 -57
  268. souleyez/storage/exploits.py +67 -36
  269. souleyez/storage/findings.py +48 -61
  270. souleyez/storage/hosts.py +176 -144
  271. souleyez/storage/migrate_to_engagements.py +43 -19
  272. souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
  273. souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
  274. souleyez/storage/migrations/_003_add_execution_log.py +14 -8
  275. souleyez/storage/migrations/_005_screenshots.py +13 -5
  276. souleyez/storage/migrations/_006_deliverables.py +13 -5
  277. souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
  278. souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
  279. souleyez/storage/migrations/_010_evidence_linking.py +17 -10
  280. souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
  281. souleyez/storage/migrations/_012_team_collaboration.py +34 -21
  282. souleyez/storage/migrations/_013_add_host_tags.py +12 -6
  283. souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
  284. souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
  285. souleyez/storage/migrations/_016_add_domain_field.py +10 -4
  286. souleyez/storage/migrations/_017_msf_sessions.py +16 -8
  287. souleyez/storage/migrations/_018_add_osint_target.py +10 -6
  288. souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
  289. souleyez/storage/migrations/_020_add_rbac.py +36 -15
  290. souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
  291. souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
  292. souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
  293. souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
  294. souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
  295. souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
  296. souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
  297. souleyez/storage/migrations/__init__.py +26 -26
  298. souleyez/storage/migrations/migration_manager.py +19 -19
  299. souleyez/storage/msf_sessions.py +100 -65
  300. souleyez/storage/osint.py +17 -24
  301. souleyez/storage/recommendation_engine.py +269 -235
  302. souleyez/storage/screenshots.py +33 -32
  303. souleyez/storage/smb_shares.py +136 -92
  304. souleyez/storage/sqlmap_data.py +183 -128
  305. souleyez/storage/team_collaboration.py +135 -141
  306. souleyez/storage/timeline_tracker.py +122 -94
  307. souleyez/storage/wazuh_vulns.py +64 -66
  308. souleyez/storage/web_paths.py +33 -37
  309. souleyez/testing/credential_tester.py +221 -205
  310. souleyez/ui/__init__.py +1 -1
  311. souleyez/ui/ai_quotes.py +12 -12
  312. souleyez/ui/attack_surface.py +2439 -1516
  313. souleyez/ui/chain_rules_view.py +914 -382
  314. souleyez/ui/correlation_view.py +312 -230
  315. souleyez/ui/dashboard.py +2382 -1130
  316. souleyez/ui/deliverables_view.py +148 -62
  317. souleyez/ui/design_system.py +13 -13
  318. souleyez/ui/errors.py +49 -49
  319. souleyez/ui/evidence_linking_view.py +284 -179
  320. souleyez/ui/evidence_vault.py +393 -285
  321. souleyez/ui/exploit_suggestions_view.py +555 -349
  322. souleyez/ui/export_view.py +100 -66
  323. souleyez/ui/gap_analysis_view.py +315 -171
  324. souleyez/ui/help_system.py +105 -97
  325. souleyez/ui/intelligence_view.py +436 -293
  326. souleyez/ui/interactive.py +22827 -10678
  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-2.43.34.dist-info}/METADATA +1 -1
  353. souleyez-2.43.34.dist-info/RECORD +443 -0
  354. {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/WHEEL +1 -1
  355. souleyez-2.43.29.dist-info/RECORD +0 -379
  356. {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/entry_points.txt +0 -0
  357. {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/licenses/LICENSE +0 -0
  358. {souleyez-2.43.29.dist-info → souleyez-2.43.34.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"]