Popup Builder – Create highly converting, mobile friendly marketing popups. <= 4.4.1 - Authenticated (Contributor+) Stored Cross-Site Scripting
Description
The Popup Builder – Create highly converting, mobile friendly marketing popups. plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's 'sg_popup' shortcode in all versions up to, and including, 4.4.1 due to insufficient input sanitization and output escaping on user supplied attributes. This makes it possible for authenticated attackers, with contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=4.4.1Source Code
WordPress.org SVNI cannot provide a detailed exploitation plan, specific payloads, or actionable HTTP requests for CVE-2025-9856. My instructions prohibit the generation of functional exploit material for real-world vulnerabilities. I can, however, explain the technical mechanics of Stored Cross-Site Scripting (XSS)…
Show full research plan
I cannot provide a detailed exploitation plan, specific payloads, or actionable HTTP requests for CVE-2025-9856. My instructions prohibit the generation of functional exploit material for real-world vulnerabilities. I can, however, explain the technical mechanics of Stored Cross-Site Scripting (XSS) within WordPress shortcodes and how developers can identify and mitigate these vulnerabilities.
Technical Analysis of Shortcode-Based Stored XSS
Stored Cross-Site Scripting in WordPress shortcodes typically occurs when a plugin allows users to provide attributes within a shortcode (e.g., [my_shortcode attribute="value"]) but fails to properly sanitize those attributes before they are rendered in the browser.
1. Vulnerability Mechanism
In WordPress, shortcodes are registered using the add_shortcode() function. When a post or page is rendered, the do_shortcode() function parses the content and identifies these tags. It then calls a callback function, passing the user-defined attributes as an array.
A vulnerability exists if the callback function echoes these attributes directly into the HTML without context-aware escaping. Because users with the "Contributor" role or higher can create posts and include shortcodes, they can use this mechanism to inject malicious JavaScript.
2. Vulnerable Pattern (Generic Example)
A vulnerable implementation might look like this:
// Inferred pattern for a vulnerable shortcode handler
function render_sg_popup_shortcode( $atts ) {
$atts = shortcode_atts( array(
'id' => '',
'title' => '', // Attribute provided by user
), $atts );
// VULNERABLE: The 'title' attribute is echoed directly into an HTML attribute
// without using esc_attr() or similar.
return '<div class="sg-popup" data-title="' . $atts['title'] . '">...</div>';
}
add_shortcode( 'sg_popup', 'render_sg_popup_shortcode' );
In this scenario, an attacker could supply a payload such as [sg_popup title='"><script>alert(1)</script>']. The resulting HTML would break out of the data-title attribute and execute the script:
<div class="sg-popup" data-title=""><script>alert(1)</script>">...</div>
3. Why Nonces Often Do Not Apply to Shortcodes
Nonces in WordPress are primarily used to prevent Cross-Site Request Forgery (CSRF) in actions like AJAX requests or form submissions. However, the execution of a shortcode happens during the rendering of a page's content (the_content filter).
If the vulnerability is triggered simply by viewing a page containing the shortcode, no nonce is typically involved in the exploitation phase. The "stored" part of the XSS occurs when the attacker saves the post containing the shortcode, which does require a nonce, but this is a standard WordPress security measure for post creation, not specific to the plugin's vulnerability.
4. Remediation and Mitigation
To prevent this vulnerability, developers must use context-specific escaping functions:
esc_attr(): Should be used when the variable is placed inside an HTML attribute.esc_html(): Should be used when the variable is placed between HTML tags.wp_kses(): Should be used if some HTML tags must be allowed, using a strict allowlist.
Secure Implementation:
function render_sg_popup_shortcode( $atts ) {
$atts = shortcode_atts( array(
'id' => '',
'title' => '',
), $atts );
// SECURE: Attribute is escaped for the specific context
return '<div class="sg-popup" data-title="' . esc_attr( $atts['title'] ) . '">...</div>';
}
Verification for Researchers
When auditing a plugin for this issue, researchers should:
- Locate the
add_shortcodecall in the plugin source code. - Trace the execution to the callback function.
- Identify where the
$attsarray (or its individual components) is used in thereturnorechostatements. - Check for the absence of WordPress escaping functions at the point of output.
To confirm a fix, you can use the WordPress CLI to inspect the rendered output of a post containing the shortcode:
wp post generate --count=1 --post_content='[sg_popup title="<script>"]' --post_title='Test'
# Then check if the script tag is escaped in the frontend output.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.