CVE-2026-97266 A style attribute inside a class attribute in Virtue Toolkit
Intrudify's autonomous pentesting engine discovered a stored cross-site scripting vulnerability in Virtue/Ascend/Pinnacle Toolkit, affecting all versions up to and including 4.9.12.1. The [kad_vimeo] shortcode assembles a complete style="..." attribute and then concatenates that whole string inside the wrapper div's class="..." attribute. The style attribute's own quote closes the class value early, and everything after it is parsed as further attributes.
Summary
Virtue Toolkit adds portfolio and shortcode functionality to a family of free WordPress themes. Intrudify's autonomous testing engine found that its responsive Vimeo shortcode places a fully formed style attribute inside the class attribute of the wrapper element.
Because that inner string carries its own double quotes, a Contributor can supply a maxwidth value that turns the remainder into attributes on the div, including an event handler that fires on page load.
Technical detail
The sink
In shortcodes.php, virtue_toolkit_vimeo_shortcode_function():
if ($atts['maxwidth']) {
$maxwidth = 'style="max-width:' . esc_attr($atts['maxwidth']) . 'px;"';
} else {
$maxwidth = '';
}
...
$return[] = '<div class="kad-vimeo-shortcode videofit ' . $maxwidth . '">';$maxwidth is not a value. It is the complete string style="max-width:VALUE px;", quotes included, and it is placed inside the div's class attribute.
esc_attr is applied, and it works. It encodes angle brackets, ampersands and both quote characters in the value. The breakout does not come from the value at all. It comes from the double quote the plugin writes itself, immediately after style=, which esc_attr never sees because it is part of the surrounding literal.
What the browser parses
The injected attributes need only spaces and equals signs, both of which esc_attr passes through untouched. A maxwidth of 1 tabindex=1 autofocus onfocus=... produces:
<div class="kad-vimeo-shortcode videofit style="max-width:1 tabindex=1 autofocus onfocus=... px;"">The browser reads class="kad-vimeo-shortcode videofit style=", then treats max-width:1, tabindex=1, autofocus and the event handler as separate attributes of the div. The autofocus attribute focuses the element as soon as the page loads, so the handler runs with no click and no mouse movement.
Why the payload survives sanitisation
The entire payload is a shortcode containing no <, > or & characters. wp_filter_post_kses, the filter applied to a Contributor's post content, therefore stores it unchanged. Nothing needs to be smuggled past the sanitiser, because from the sanitiser's point of view there is nothing to smuggle.
Two details worth noting
This is a separate sink from CVE-2026-65473, the button-colour XSS fixed in 4.9.12.1 with sanitize_hex_color. That fix did not touch the Vimeo maxwidth handling, which was still present in the release that shipped it.
And the sibling [kad_youtube] shortcode handles the same value correctly, keeping it inside its own properly quoted style attribute. The right pattern exists in the same file; the Vimeo branch simply does not use it. [kt_vimeo] is an alias and reaches the same sink.
Impact
A Contributor cannot publish, but the shortcode renders wherever the post is displayed. That includes the preview an Editor or Administrator opens to review the pending submission, which is a guaranteed step in the workflow, and every visitor once the post is published.
We confirmed execution in the site origin on a default installation with no plugin setting changed. When the viewer is privileged, replacing the handler body with a request to the user-creation screen creates an administrator using the reviewing admin's own session.
Remediation
Update Virtue/Ascend/Pinnacle Toolkit to 4.9.12.2 or later.
For maintainers, there are two independent fixes and either breaks the chain. Emit the style as its own attribute on the div rather than concatenating it inside the class attribute, which is what the YouTube shortcode already does. And reject a maxwidth that is not numeric, since the only legitimate value is a number of pixels.
The wider lesson
This is an escaping failure where the escaping is correct. esc_attr is present, applied to the right variable, and does exactly what it is documented to do. The problem is that the developer escaped for one context and then placed the result in another, so the protection was aimed at the wrong quote.
That is worth generalising. An escaping function protects the boundary it was chosen for. esc_attr assumes its output will sit inside an attribute value, so when it is used to build a string that itself contains attribute syntax, the syntax it generates is outside its own protection. Any code that concatenates a variable into a literal containing quotes, and then concatenates that whole thing into another quoted context, has the same shape.
Two contextual signals make this finding easier to trust than a bare claim. The plugin's own YouTube shortcode does it correctly, so the vulnerable branch is an inconsistency rather than a design choice. And the plugin had just received a security fix for a different XSS in the same file, which means a reviewer had recently looked at this code and this sink survived. A recent patch is a reason to examine neighbouring sinks, not a reason to consider them settled.
Disclosure timeline
- 2026-09-20 Vulnerability identified and validated by Intrudify, reported via Patchstack
- 2026-09-30 Public disclosure · CVE-2026-97266 assigned
Questions
What is CVE-2026-97266?
CVE-2026-97266 is a stored cross-site scripting vulnerability in the Virtue/Ascend/Pinnacle Toolkit WordPress plugin, versions 4.9.12.1 and earlier. It was discovered autonomously by the Intrudify AI penetration testing engine. The kad_vimeo shortcode builds a style attribute and concatenates the whole thing inside a class attribute, so the style attribute's own quote terminates the class value and the remaining tokens become real HTML attributes.
Does esc_attr prevent CVE-2026-97266?
No. esc_attr is applied to the maxwidth value and works correctly, encoding angle brackets, ampersands and quotes. The breakout quote is one the plugin itself adds around the style attribute, which esc_attr never sees, and the injected attributes need only spaces and equals signs, which esc_attr preserves.
How do I fix CVE-2026-97266?
Update Virtue/Ascend/Pinnacle Toolkit to version 4.9.12.2 or later. The underlying fix is to emit the style as its own attribute on the div rather than concatenating it inside the class attribute, and to reject a maxwidth value that is not numeric.