This CVE exposes a flaw in how WordPress plugins handle data across function boundaries — a pattern that's been quietly lurking in the ecosystem for over a decade. The plugin uses a global variable ($wpml_target) to pass the 'target' attribute from one function (posts_single, which sanitizes the value) to another (shortcode_posts, which consumes it unsanitized in HTML output). The sanitization in the first function provides no protection because the second function never applies it. This is the classic 'global-as-intermediate-state' failure: developers treat globals as convenient shorthand for sharing state, but this decouples the validator from the escaper in ways that are easy to miss in code review.

The exploitation path is more dangerous than it first appears. Contributor-level users can inject malicious scripts through this shortcode in their draft posts. While Contributors can't publish directly, the shortcode handler processes their content as if it were trusted — meaning the injected script executes every time the post is rendered, whether as preview, in RSS feeds, or after an editor upgrades the post to published status. This reveals a deeper threat model failure: WordPress security assessments routinely treat Contributor access as negligible, but this class of vulnerability shows that low-privilege users can achieve persistent script execution through shortcode pathways that bypass the 'can't publish directly' control.

The four-year window isn't coincidental. Once posts_single() reached 'stable' status and the surrounding code settled, the function entered a forgotten layer that stopped receiving security scrutiny. The global variable wasn't dangerous on the day it was introduced — it became dangerous as the codebase evolved and nobody traced the new data flows crossing that boundary. This is an entropy problem, not just a negligence problem: forgotten code accrues systemic debt as the ecosystem evolves around it, and no automated tool catches globals-as-attack-surface because they look like normal state management.

For defenders: audit your shortcode handlers for globals that carry user input between functions. Check whether any function in the chain applies sanitization — not just the entry point. Treat Contributor-authored content as potentially untrusted for output purposes. Review which shortcodes accept attributes that derive from post content, and ensure every consumption point applies output encoding. The fix is a single esc_attr() call, but finding it requires tracing data across function boundaries that the codebase never made explicit.