[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fyyc8HMWAOOTdowVaSI0aSNI5OiE-WqA6tEm1kqOlKyk":3},{"id":4,"url_slug":5,"title":6,"description":7,"plugin_slug":8,"theme_slug":9,"affected_versions":10,"patched_in_version":11,"severity":12,"cvss_score":13,"cvss_vector":14,"vuln_type":15,"published_date":16,"updated_date":17,"references":18,"days_to_patch":20,"patch_diff_files":21,"patch_trac_url":9,"research_status":27,"research_verified":28,"research_rounds_completed":29,"research_plan":30,"research_summary":31,"research_vulnerable_code":32,"research_fix_diff":33,"research_exploit_outline":34,"research_model_used":35,"research_started_at":36,"research_completed_at":37,"research_error":9,"poc_status":9,"poc_video_id":9,"poc_summary":9,"poc_steps":9,"poc_tested_at":9,"poc_wp_version":9,"poc_php_version":9,"poc_playwright_script":9,"poc_exploit_code":9,"poc_has_trace":28,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":28,"source_links":38},"CVE-2026-42750","wpcomplete-authenticated-subscriber-stored-cross-site-scripting","WPComplete \u003C= 2.9.5.4 - Authenticated (Subscriber+) Stored Cross-Site Scripting","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.","wpcomplete",null,"\u003C=2.9.5.4","2.9.5.5","medium",6.4,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:L\u002FUI:N\u002FS:C\u002FC:L\u002FI:L\u002FA:N","Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')","2026-05-29 00:00:00","2026-06-01 16:19:46",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002Fb4c9b125-4cea-448b-8b31-c033857346b3?source=api-prod",4,[22,23,24,25,26],"README.txt","admin\u002Fclass-wpcomplete-admin.php","history.txt","public\u002Fclass-wpcomplete-public.php","wpcomplete.php","researched",false,3,"```markdown\n# Exploitation Research Plan: CVE-2026-42750 - WPComplete Stored XSS\n\n## 1. Vulnerability Summary\nThe **WPComplete** plugin (versions \u003C= 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.\n\n## 2. Attack Vector Analysis\n- **Endpoint:** `wp-admin\u002Fadmin-ajax.php`\n- **Action:** `wpc_get_button` (The AJAX handler for button state updates).\n- **Vulnerable Parameter:** `button_id` and potentially `new_button_text`.\n- **Authentication:** Subscriber-level (authenticated).\n- **Preconditions:** At least one post\u002Fpage must be \"completable\" (WPComplete enabled) and accessible to the Subscriber.\n- **Payload Type:** Attribute-based XSS (breaking out of `class` or `data` attributes).\n\n## 3. Code Flow\n1.  **Entry Point:**","WPComplete \u003C= 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.","\u002F\u002F admin\u002Fclass-wpcomplete-admin.php (vulnerable rendering around line 1384)\nlist($button_post_id, $button_id) = $this->extract_button_info($button);\n$button_name = ($button === ''.$post_id) ? 'Default Button' : \"Button '$button_id'\";\n$completion .= ('\u003Ca href=\"edit.php?page=wpcomplete-buttons&post_id=' . $post_id . '&button=' . $button . '\">' . \"$button_name: $completed_users\u002F$avail_users Users (\" . round(100 * ($completed_users \u002F $avail_users), 1) . '%)\u003C\u002Fa>\u003Cbr>');\n\n---\n\n\u002F\u002F public\u002Fclass-wpcomplete-public.php (vulnerable input handling around line 785)\n$unique_button_id = $_REQUEST['button'];\nlist($post_id, $button_id) = $this->extract_button_info($unique_button_id);\n\n$course = $this->post_course($post_id);\n\n$posts = $this->get_completable_posts();\nif ( isset( $button_id ) && ( !isset( $posts[$post_id]['buttons'] ) || !in_array( $unique_button_id, $posts[$post_id]['buttons'] ) ) ) {\n  $post_meta = $posts[$post_id];\n  if ( !isset( $post_meta['buttons'] ) ) $post_meta['buttons'] = array();\n  $post_meta['buttons'][] = $unique_button_id;","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwpcomplete\u002F2.9.5.4\u002Fadmin\u002Fclass-wpcomplete-admin.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwpcomplete\u002F2.9.5.5\u002Fadmin\u002Fclass-wpcomplete-admin.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwpcomplete\u002F2.9.5.4\u002Fadmin\u002Fclass-wpcomplete-admin.php\t2025-10-09 17:16:06.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwpcomplete\u002F2.9.5.5\u002Fadmin\u002Fclass-wpcomplete-admin.php\t2026-05-04 16:35:20.000000000 +0000\n@@ -1384,8 +1384,9 @@\n                 }\n \n                 list($button_post_id, $button_id) = $this->extract_button_info($button);\n-                $button_name = ($button === ''.$post_id) ? 'Default Button' : \"Button '$button_id'\";\n-                $completion .= ('\u003Ca href=\"edit.php?page=wpcomplete-buttons&post_id=' . $post_id . '&button=' . $button . '\">' . \"$button_name: $completed_users\u002F$avail_users Users (\" . round(100 * ($completed_users \u002F $avail_users), 1) . '%)\u003C\u002Fa>\u003Cbr>');\n+                $button_name = ($button === ''.$post_id) ? 'Default Button' : \"Button '\" . esc_html( $button_id ) . \"'\";\n+                $href = esc_url( admin_url( 'edit.php?page=wpcomplete-buttons&post_id=' . absint( $post_id ) . '&button=' . rawurlencode( $button ) ) );\n+                $completion .= '\u003Ca href=\"' . $href . '\">' . esc_html( \"$button_name: $completed_users\u002F$avail_users Users (\" . round(100 * ($completed_users \u002F $avail_users), 1) . '%' ) . '\u003C\u002Fa>\u003Cbr>';\n               }\n             }\n@@ -1400,14 +1401,14 @@\n                 }\n               }\n \n-              $completion = '\u003Ca href=\"edit.php?page=wpcomplete-posts&post_id=' . $post_id . '\">' . (\"$completed_users\u002F$avail_users Users (\" . round(100 * ($completed_users \u002F $avail_users), 1) . '%)\")  . '\u003C\u002Fa>';\n+              $completion = '\u003Ca href=\"' . esc_url( admin_url( 'edit.php?page=wpcomplete-posts&post_id=' . absint( $post_id ) ) ) . '\">' . esc_html( \"$completed_users\u002F$avail_users Users (\" . round(100 * ($completed_users \u002F $avail_users), 1) . '%' ) . '\u003C\u002Fa>';\n             }\n           } else {\n             $completion = \"0 Users\";\n           }\n-          echo '\u003Cdiv id=\"completable-' . $post_id . '\">' . $completion . '\u003C\u002Fdiv>';\n+          echo '\u003Cdiv id=\"completable-' . absint( $post_id ) . '\">' . $completion . '\u003C\u002Fdiv>';\n         } else {\n-          echo '\u003Cdiv id=\"completable-' . $post_id . '\">—\u003C\u002Fdiv>';\n+          echo '\u003Cdiv id=\"completable-' . absint( $post_id ) . '\">—\u003C\u002Fdiv>';\n         }\n       }\n     }","The exploit targets the AJAX completion handlers. 1) Authenticate as a Subscriber user. 2) Target a completable post ID. 3) Send a POST\u002FREQUEST to wp-admin\u002Fadmin-ajax.php with the 'button' parameter containing a malicious string like '1-');alert(1)\u002F\u002F'. 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.","gemini-3-flash-preview","2026-06-04 15:14:37","2026-06-04 15:16:26",{"type":39,"vulnerable_version":40,"fixed_version":11,"vulnerable_browse":41,"vulnerable_zip":42,"fixed_browse":43,"fixed_zip":44,"all_tags":45},"plugin","2.9.5.4","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwpcomplete\u002Ftags\u002F2.9.5.4","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fwpcomplete.2.9.5.4.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwpcomplete\u002Ftags\u002F2.9.5.5","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fwpcomplete.2.9.5.5.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwpcomplete\u002Ftags"]