CVE-2026-94168 Zero-click stored XSS in Premium Addons for Elementor

Intrudify's autonomous pentesting engine discovered a stored cross-site scripting vulnerability in Premium Addons for Elementor, affecting all versions up to and including 4.11.105. A Contributor can place arbitrary text in an Elementor-internal repeater id, which the Premium Bullet List widget concatenates into a class attribute with no escaping. The payload survives WordPress sanitisation because it contains no tags, and executes on page load with no user interaction.

Discovered by
Intrudify autonomous engine
Validated & reported by
Tudor Lasuschevici
Disclosure
23 September 2026
Severity
Medium · CVSS 6.5
Vulnerability class
Stored XSS, CWE-79
Affected
Premium Addons ≤ 4.11.105
Active installs
600,000+
Fixed in
4.11.106
Affected widget
Premium Bullet List (enabled by default)
Privilege required
Contributor
User interaction
None, fires on page load

Summary

Premium Addons for Elementor is a widget library for the Elementor page builder. Intrudify's autonomous testing engine found that the Premium Bullet List widget builds its "Random Badges" markup by string concatenation and inserts it with jQuery .after(), which parses the string as HTML.

The value concatenated into the class attribute is the badge's Elementor-internal repeater _id. That field is chosen by whoever saves the document and is never validated server-side, so an _id containing a double quote breaks out of the attribute and adds arbitrary attributes to the element, event handlers included.

Technical detail

The sink

At assets/frontend/js/premium-icon-list.js:150, with an identical copy at premium-addons.js:3480:

var badgeText = '<div class="premium-bullet-list-badge elementor-repeater-item-'
    + badge._id
    + '"><span>' + badge.badge_title + '</span></div>';
...
$(notBadgedItems[randomIndex]).after(badgeText); // parsed as HTML

The rbadges_repeater control is declared 'frontend_available' => true, so the entire repeater, including the Elementor-internal _id field, is serialised into the element's data-settings attribute and read back by this handler.

Why it survives Elementor's sanitiser

Elementor runs Utils::kses_post_deep() over everything a Contributor saves. But wp_kses_post() only rewrites tags. A string containing no < and no > passes through byte for byte, quotes included.

The payload here is attribute-only. It never needs a tag, because the widget's own concatenation supplies the surrounding <div>. So it survives kses intact and only becomes markup once the browser parses the concatenated string.

// abbreviated. no angle brackets anywhere in the stored value.
"_id": "zz\" style=\"animation-name:spin;animation-duration:0.1s\" onanimationstart=\"..."

This also explains why the neighbouring field is not exploitable. badge_title sits in a text context, so a payload there would need an img or svg tag, and kses strips the event handlers those require. The attribute-context _id is the whole bug.

Why it needs no interaction

The payload sets animation-name to spin, a @keyframes rule that the plugin's own generated stylesheet emits at /wp-content/uploads/premium-addons-elementor/pafe-<id>.css. Because the animation starts as soon as the element renders, onanimationstart fires on page load. No hover, no click, and no theme support required, since the keyframes come from the plugin itself.

Verification

We confirmed the rendered DOM in Chrome on a published post, anonymously, with no interaction: the style and onanimationstart attributes are parsed as real attributes on the badge div, and the handler executes.

As a negative control, an identical post using a benign _id renders the badge normally with zero onanimationstart handlers in the DOM and nothing executing. The behaviour is attributable to the payload rather than to the widget in general.

Impact

A Contributor cannot publish and holds no unfiltered_html capability, but the payload runs in the browser of anyone who renders the post. That includes the editor or administrator previewing the submitted article before approving it, which is a guaranteed step in the review workflow, and every visitor once the post is published.

We demonstrated script execution in the site origin with no interaction, along with content injection into the rendered page. From there the usual consequences follow when the viewer is privileged: session-scoped administrative actions, account creation, or plugin installation.

Remediation

Update Premium Addons for Elementor to 4.11.106 or later.

For maintainers, there are two independent defects and fixing either breaks the chain. Escape _id before concatenating it into the attribute, or validate it server-side against the format Elementor actually generates, which is a short alphanumeric string. Separately, build the element with DOM methods and textContent rather than concatenating a string and handing it to a parsing insertion method.

If you cannot update immediately, audit who holds Contributor accounts and treat pending-review posts as untrusted, since preview alone is enough to trigger the payload.

The wider lesson

Framework-internal identifiers are easy to treat as trusted because the framework normally generates them. Here _id is an Elementor implementation detail, not a user-facing field, and nothing in the editor invites anyone to type into it. But it arrives over the save endpoint like any other value and is never validated, so "the framework generates it" is an assumption rather than a guarantee.

The generalisable point about sanitisation: a tag-oriented filter such as wp_kses_post() is the wrong control for a value that will be concatenated into an attribute. It inspects for tags, the payload contains none, and the quote that actually matters passes through untouched. Any place a plugin builds HTML by concatenation and relies on kses upstream deserves the same check.

Disclosure timeline

  • 2026-09-18 Vulnerability identified and validated by Intrudify, reported via Patchstack
  • 2026-09-23 Public disclosure · CVE-2026-94168 assigned

Questions

What is CVE-2026-94168?

CVE-2026-94168 is a stored cross-site scripting vulnerability in the Premium Addons for Elementor WordPress plugin, versions 4.11.105 and earlier. It was discovered autonomously by the Intrudify AI penetration testing engine. A Contributor can place attacker-controlled text in an Elementor repeater id, which the Premium Bullet List widget concatenates into a class attribute with no escaping, allowing arbitrary attributes including event handlers. The payload runs with no user interaction.

Which versions of Premium Addons for Elementor are affected?

All versions up to and including 4.11.105 are affected. The Premium Bullet List widget is enabled by default, so no special configuration is required.

How do I fix CVE-2026-94168?

Update Premium Addons for Elementor to 4.11.106 or later. If you cannot update immediately, review who holds Contributor accounts and treat pending-review posts as untrusted content, since the payload executes on preview as well as after publication.

References

More advisories from Intrudify

Join the Future of
AI-Driven Pentesting