WPComplete <= 2.9.5.4 - Authenticated (Subscriber+) Stored Cross-Site Scripting
Description
The WPComplete plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 2.9.5.4 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with subscriber-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
What Changed in the Fix
Changes introduced in v2.9.5.5
Source Code
WordPress.org SVN```markdown # Exploitation Research Plan: CVE-2026-42750 - WPComplete Stored XSS ## 1. Vulnerability Summary The **WPComplete** plugin (versions <= 2.9.5.4) is vulnerable to **Stored Cross-Site Scripting (XSS)**. The issue resides in the handling of button metadata and custom text during completion…
Show full research plan
# Exploitation Research Plan: CVE-2026-42750 - WPComplete Stored XSS
## 1. Vulnerability Summary
The **WPComplete** plugin (versions <= 2.9.5.4) is vulnerable to **Stored Cross-Site Scripting (XSS)**. The issue resides in the handling of button metadata and custom text during completion actions. Specifically, the plugin uses `sanitize_text_field()` on user-provided button identifiers and labels in AJAX handlers (like `get_button`) but fails to apply context-aware escaping (such as `esc_attr()`) when rendering this data in the Admin Dashboard widgets and User Progress pages. An authenticated attacker with Subscriber-level access can inject a payload that breaks out of HTML attributes, leading to script execution when an administrator views course statistics or user reports.
## 2. Attack Vector Analysis
- **Endpoint:** `wp-admin/admin-ajax.php`
- **Action:** `wpc_get_button` (The AJAX handler for button state updates).
- **Vulnerable Parameter:** `button_id` and potentially `new_button_text`.
- **Authentication:** Subscriber-level (authenticated).
- **Preconditions:** At least one post/page must be "completable" (WPComplete enabled) and accessible to the Subscriber.
- **Payload Type:** Attribute-based XSS (breaking out of `class` or `data` attributes).
## 3. Code Flow
1. **Entry Point:**
Summary
WPComplete <= 2.9.5.4 is vulnerable to Stored Cross-Site Scripting because it fails to validate button identifiers provided by users and subsequently fails to escape these identifiers when displaying them in the admin dashboard. An authenticated subscriber can trigger completion actions with a malicious button ID, which will execute arbitrary JavaScript in the context of an administrator viewing course statistics.
Vulnerable Code
// admin/class-wpcomplete-admin.php (vulnerable rendering around line 1384) list($button_post_id, $button_id) = $this->extract_button_info($button); $button_name = ($button === ''.$post_id) ? 'Default Button' : "Button '$button_id'"; $completion .= ('<a href="edit.php?page=wpcomplete-buttons&post_id=' . $post_id . '&button=' . $button . '">' . "$button_name: $completed_users/$avail_users Users (" . round(100 * ($completed_users / $avail_users), 1) . '%)</a><br>'); --- // public/class-wpcomplete-public.php (vulnerable input handling around line 785) $unique_button_id = $_REQUEST['button']; list($post_id, $button_id) = $this->extract_button_info($unique_button_id); $course = $this->post_course($post_id); $posts = $this->get_completable_posts(); if ( isset( $button_id ) && ( !isset( $posts[$post_id]['buttons'] ) || !in_array( $unique_button_id, $posts[$post_id]['buttons'] ) ) ) { $post_meta = $posts[$post_id]; if ( !isset( $post_meta['buttons'] ) ) $post_meta['buttons'] = array(); $post_meta['buttons'][] = $unique_button_id;
Security Fix
@@ -1384,8 +1384,9 @@ } list($button_post_id, $button_id) = $this->extract_button_info($button); - $button_name = ($button === ''.$post_id) ? 'Default Button' : "Button '$button_id'"; - $completion .= ('<a href="edit.php?page=wpcomplete-buttons&post_id=' . $post_id . '&button=' . $button . '">' . "$button_name: $completed_users/$avail_users Users (" . round(100 * ($completed_users / $avail_users), 1) . '%)</a><br>'); + $button_name = ($button === ''.$post_id) ? 'Default Button' : "Button '" . esc_html( $button_id ) . "'"; + $href = esc_url( admin_url( 'edit.php?page=wpcomplete-buttons&post_id=' . absint( $post_id ) . '&button=' . rawurlencode( $button ) ) ); + $completion .= '<a href="' . $href . '">' . esc_html( "$button_name: $completed_users/$avail_users Users (" . round(100 * ($completed_users / $avail_users), 1) . '%' ) . '</a><br>'; } } @@ -1400,14 +1401,14 @@ } } - $completion = '<a href="edit.php?page=wpcomplete-posts&post_id=' . $post_id . '">' . ("$completed_users/$avail_users Users (" . round(100 * ($completed_users / $avail_users), 1) . '%)") . '</a>'; + $completion = '<a href="' . esc_url( admin_url( 'edit.php?page=wpcomplete-posts&post_id=' . absint( $post_id ) ) ) . '">' . esc_html( "$completed_users/$avail_users Users (" . round(100 * ($completed_users / $avail_users), 1) . '%' ) . '</a>'; } } else { $completion = "0 Users"; } - echo '<div id="completable-' . $post_id . '">' . $completion . '</div>'; + echo '<div id="completable-' . absint( $post_id ) . '">' . $completion . '</div>'; } else { - echo '<div id="completable-' . $post_id . '">—</div>'; + echo '<div id="completable-' . absint( $post_id ) . '">—</div>'; } } }
Exploit Outline
The exploit targets the AJAX completion handlers. 1) Authenticate as a Subscriber user. 2) Target a completable post ID. 3) Send a POST/REQUEST to wp-admin/admin-ajax.php with the 'button' parameter containing a malicious string like '1-');alert(1)//'. 4) The plugin stores this identifier in the post metadata if it's new. 5) When an administrator views the dashboard or the 'All Pages' menu, the plugin iterates over these stored button IDs and prints them directly into the HTML without escaping via the display_course_stats logic, triggering the script in the admin context.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.