ConvertForce Popup Builder <= 0.0.7 - Stored Cross-Site Scripting via entrance_animation
Description
The ConvertForce Popup Builder plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the Gutenberg block's `entrance_animation` attribute in all versions up to, and including, 0.0.7. This is due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Author-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
<=0.0.7Source Code
WordPress.org SVN# Research Plan: CVE-2025-14506 - ConvertForce Popup Builder Stored XSS ## 1. Vulnerability Summary The **ConvertForce Popup Builder** plugin (<= 0.0.7) contains a stored Cross-Site Scripting (XSS) vulnerability within its Gutenberg block. The vulnerability resides in the handling of the `entrance_…
Show full research plan
Research Plan: CVE-2025-14506 - ConvertForce Popup Builder Stored XSS
1. Vulnerability Summary
The ConvertForce Popup Builder plugin (<= 0.0.7) contains a stored Cross-Site Scripting (XSS) vulnerability within its Gutenberg block. The vulnerability resides in the handling of the entrance_animation attribute. When a user with sufficient privileges (Author and above) saves a post containing this block, the value of entrance_animation is stored without proper sanitization. Upon rendering the post on the frontend, this value is output without adequate escaping, allowing for arbitrary JavaScript execution in the context of the victim's browser.
2. Attack Vector Analysis
- Endpoint: WordPress REST API for Post/Page creation and updates (
/wp-json/wp/v2/postsor/wp-json/wp/v2/pages). - Vulnerable Attribute:
entrance_animationwithin the Gutenberg block (likely namedconvertforce-popup-builder/popupor similar). - Required Authentication: Author-level access or higher. Authors have the
edit_postsandpublish_postscapabilities, allowing them to use the Gutenberg editor and the REST API. - Preconditions: The plugin must be active, and the attacker must have credentials for an account with at least Author permissions.
3. Code Flow (Inferred)
- Registration: The plugin registers a Gutenberg block via
register_block_type(PHP) orregisterBlockType(JS). - Attribute Definition: The block definition includes an attribute named
entrance_animation. - Storage: When a post is saved in the Gutenberg editor, the block's attributes are serialized into the
post_contentas JSON comments (e.g.,<!-- wp:convertforce-popup-builder/popup {"entrance_animation":"..."} /-->). - Rendering (Sink):
- If it is a dynamic block: The plugin uses a
render_callbackfunction in PHP. This function retrieves the$attributesarray and likely concatenatesentrance_animationdirectly into an HTML string (e.g.,<div class="cf-popup-container entrance-animation-<?php echo $attributes['entrance_animation']; ?>">) without usingesc_attr(). - If it is a static block: The
savefunction in JavaScript generates the HTML. If thesavefunction does not sanitize the attribute before including it in the returned JSX/HTML, the XSS is stored directly in thepost_contentand rendered by WordPress.
- If it is a dynamic block: The plugin uses a
- Execution: When a victim views the published post, the browser parses the unescaped payload within the HTML attribute or tag, triggering the script.
4. Nonce Acquisition Strategy
To interact with the REST API for post creation/updates, a _wpnonce for the wp_rest action is required.
- Login: Authenticate as an Author user.
- Navigate to Admin: Access the WordPress dashboard (
/wp-admin/). - Extract Nonce: The
wp_restnonce is typically localized in the page source within thewpApiSettingsobject.- Tool:
browser_eval - Script:
window.wpApiSettings?.nonce
- Tool:
- Alternative: If the plugin enqueues specific scripts for its block, a nonce might also be available in a localized object like
window.convertforce_builder_data?.nonce.
5. Exploitation Strategy
- Setup User: Create a user with the
Authorrole. - Get REST Nonce: Use
browser_navigateto/wp-admin/post-new.phpandbrowser_evalto get thewp_restnonce. - Craft Payload:
- Gutenberg blocks store attributes in JSON within comments. The payload must break out of the HTML attribute where
entrance_animationis rendered. - Target Payload:
"><script>alert(document.domain)</script>(breaks out of aclassordataattribute). - Alternative Payload:
none" onmouseover="alert(1)" data-x="(attribute breakout).
- Gutenberg blocks store attributes in JSON within comments. The payload must break out of the HTML attribute where
- Send REST Request: Use
http_requestto create a new post with the malicious block.- Method:
POST - URL:
https://[target]/wp-json/wp/v2/posts - Headers:
Content-Type: application/jsonX-WP-Nonce: [extracted_nonce]
- Body:
(Note: The block name{ "title": "XSS Test Page", "content": "<!-- wp:convertforce-popup-builder/popup {\"entrance_animation\":\"\\\"><script>alert(document.domain)</script>\"} /-->", "status": "publish" }convertforce-popup-builder/popupis an inference based on the plugin slug. This must be verified by inspecting the plugin'sblock.jsonor registration code if available).
- Method:
- Trigger: Navigate to the URL of the newly created post (returned in the REST response).
6. Test Data Setup
- Plugin Installation: Ensure ConvertForce Popup Builder version 0.0.7 is installed and active.
- User Creation:
wp user create attacker attacker@example.com --role=author --user_pass=password123
- Identify Block Slug: Search the plugin directory for the block registration:
grep -r "register_block_type" .grep -r "registerBlockType" .- Look for
block.jsonfiles to find the exact name/slug.
7. Expected Results
- The REST API returns a
201 Createdresponse. - Upon visiting the post's permalink, the HTML source contains:
<div ... class="... "><script>alert(document.domain)</script>"> - An alert box appears showing the document domain.
8. Verification Steps
- Check Post Content: Use WP-CLI to verify the payload is stored exactly as sent.
wp post get [ID] --field=post_content
- Frontend Inspection: Fetch the rendered post and check for the unescaped script.
http_request GET https://[target]/path-to-post- Verify that
esc_attroresc_htmlwas not applied to theentrance_animationvalue.
9. Alternative Approaches
- Attribute Breakout: If the script is rendered inside a
data-attribute or aclass, tryanimation-name" onmouseover="alert(1). - Shortcode (if applicable): If the plugin also provides a shortcode equivalent (common for popup builders), test if the same attribute name is vulnerable in a shortcode:
[convertforce_popup entrance_animation='"><script>alert(1)</script>']. - REST API Update: If creating a new post fails due to permissions, try updating an existing post owned by the Author.
POST /wp-json/wp/v2/posts/[id]
Summary
The ConvertForce Popup Builder plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'entrance_animation' attribute in its Gutenberg block. Authenticated attackers with Author-level access or higher can inject arbitrary JavaScript into posts by saving a crafted block attribute, which executes in the browser of any user viewing the page because the value is rendered without proper output escaping.
Exploit Outline
1. Authenticate as an Author or Editor to obtain the necessary privileges for post creation. 2. Access the WordPress admin dashboard and retrieve the REST API nonce (found in the 'wpApiSettings' object in the HTML source). 3. Craft a REST API request to create or update a post (POST to /wp-json/wp/v2/posts). 4. In the request body, set the 'content' field to include the ConvertForce Popup block with a malicious payload in the 'entrance_animation' attribute. A payload like '\"><script>alert(document.domain)</script>' is designed to break out of the HTML attribute context when the block is rendered. 5. Publish the post and navigate to its permalink. The injected script will execute in the browser of anyone viewing the post, including site administrators.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.