CVE-2025-68588

TS Poll <= 2.5.5 - Missing Authorization

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
2.6.0
Patched in
115d
Time to patch

Description

The TS Poll plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 2.5.5. This makes it possible for authenticated attackers, with subscriber-level access and above, to perform an unauthorized action.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=2.5.5
PublishedDecember 22, 2025
Last updatedApril 15, 2026
Affected pluginpoll-wp

What Changed in the Fix

Changes introduced in v2.6.0

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan targets **CVE-2025-68588**, a Missing Authorization vulnerability in the **TS Poll** plugin. The vulnerability allows Subscriber-level users to perform unauthorized actions, likely modifying or creating poll data, due to missing capability checks in AJAX handlers and `init` reques…

Show full research plan

This research plan targets CVE-2025-68588, a Missing Authorization vulnerability in the TS Poll plugin. The vulnerability allows Subscriber-level users to perform unauthorized actions, likely modifying or creating poll data, due to missing capability checks in AJAX handlers and init request processors.

1. Vulnerability Summary

The TS Poll plugin (<= 2.5.5) fails to implement adequate authorization checks on several administrative functions. Specifically, functions hooked to wp_ajax_ (authenticated AJAX) and the init hook (global request processing) lack current_user_can() checks. While these functions are restricted to "admin" contexts via is_admin(), WordPress considers any request to wp-admin/admin-ajax.php or wp-admin/admin.php as being in the admin context, regardless of the user's role.

2. Attack Vector Analysis

  • Endpoints:
    • /wp-admin/admin.php (via ts_poll_admin::tsp_process_requests on init)
    • /wp-admin/admin-ajax.php (via various wp_ajax_ hooks)
  • Vulnerable Functions:
    • tsp_process_requests (Processes template imports and other builder logic)
    • tsp_save_question (Saves poll data)
    • tspoll_dashboard_update_callback (Updates dashboard settings)
  • Required Role: Subscriber or higher (Authenticated).
  • Preconditions: The attacker must be logged in.

3. Code Flow (Template Import via init)

  1. Registration: In admin/class-ts_poll-admin.php, the constructor registers tsp_process_requests on the init hook.
  2. State Initialization: The constructor sets $this->tsp_page_slug if $_GET['page'] matches plugin slugs (e.g., ts-poll-builder).
  3. Request: A Subscriber sends a GET request to /wp-admin/admin.php?page=ts-poll-builder&tsp-template-id=1.
  4. Logic Trigger: tsp_process_requests checks if ( 'ts-poll-builder' === $this->tsp_page_slug && is_admin() ). This evaluates to true for a Subscriber because they are accessing wp-admin/.
  5. Execution: The code proceeds to process tsp-template-id and triggers the tsp_import_template filter logic, resulting in database modification without verifying administrative permissions.

4. Nonce Acquisition Strategy

The vulnerability is "Missing Authorization," which in many TS Poll components implies a lack of both capability checks and nonce verification for these specific actions.

  • tsp_process_requests: The provided source shows no nonce check surrounding the tsp-template-id logic.
  • tsp_save_question: Not enqueued with a nonce in the Gutenberg block localization (ts_poll_block::ts_poll_block_editor_scripts).
  • Verification: The PoC will first attempt the actions without a nonce. If a "forbidden" or "0" response is received specifically due to nonce failure, the browser_navigate tool will be used to visit the Subscriber's profile or dashboard to see if any nonces are leaked in the footer or localized scripts.

5. Exploitation Strategy

Approach A: Unauthorized Template Import (via init)

  • Target: Create a new poll by importing a template.
  • HTTP Request:
    • Tool: http_request
    • Method: GET
    • URL: {{base_url}}/wp-admin/admin.php?page=ts-poll-builder&tsp-template-id=1
    • Authentication: Subscriber cookies.
  • Expected Result: A 302 redirect back to the referer (via `wp_redirect
Research Findings
Static analysis — not yet PoC-verified

Summary

The TS Poll plugin for WordPress is vulnerable to unauthorized access and data modification due to missing capability checks on several functions hooked to AJAX and the 'init' action. This allows authenticated attackers with subscriber-level permissions to import templates, update dashboard settings, and modify poll questions.

Vulnerable Code

// admin/class-ts_poll-admin.php (Line 135)
public function tsp_process_requests(){
	if ( 'ts-poll-builder' === $this->tsp_page_slug && is_admin() ) {
		// ... logic for template import
		if(isset( $_GET['tsp-template-id'] ) && is_numeric( $_GET['tsp-template-id'] ) && is_int( (int) $_GET['tsp-template-id'] )){
			$tsp_insert_id = apply_filters( "tsp_import_template",sanitize_text_field( $_GET['tsp-template-id'] ));

---

// admin/class-ts_poll-admin.php (Line 526)
function tspoll_dashboard_fetch_callback() {
	$tsp_dashboard_list_table = new ts_poll_dashboard();
	$tsp_dashboard_list_table->ajax_response();
}

function tspoll_dashboard_update_callback() {
	check_ajax_referer( 'tsp-dashboard-nonce', 'ts_poll_dashboard_widget_nonce' );
	$tsp_dashboard_list_table = new ts_poll_dashboard();
	$tsp_dashboard_list_table->prepare_items();

---

// admin/class-ts_poll-admin.php (Line 894)
function tsp_save_question() {
	if ( ! isset( $_POST['tsp_nonce'] ) || $_POST['tsp_nonce'] == '' || ! wp_verify_nonce( $_POST['tsp_nonce'], 'tsp_builder_nonce_field' ) ) {
		wp_send_json_error( 'TS Poll nonce error.' );
	}
	// ... logic to save question data

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/poll-wp/2.5.5/admin/class-ts_poll-admin.php	2026-01-20 10:59:52.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/poll-wp/2.6.0/admin/class-ts_poll-admin.php	2026-04-13 11:05:42.000000000 +0000
@@ -134,6 +98,9 @@
 		}
 	}
 	public function tsp_process_requests(){
+		if ( ! current_user_can( 'manage_options' ) ) {
+			return;
+		}
 		if ( 'ts-poll-builder' === $this->tsp_page_slug && is_admin() ) {
 			$this->tsp_themes = array(
 				'standard_poll'           => array( "name" => 'Standard Poll', "free" => true),
@@ -146,7 +113,13 @@
 				'video_in_question'       => array( "name" => 'Video in Question', "free" => true),
 				'versus_poll'             => array( "name" => 'Versus Poll', "free" => false)
 			);
-			if(isset( $_GET['tsp-template-id'] ) && is_numeric( $_GET['tsp-template-id'] ) && is_int( (int) $_GET['tsp-template-id'] )){
+			if (
+				isset( $_GET['tsp-template-id'] )
+				&& is_numeric( $_GET['tsp-template-id'] )
+				&& is_int( (int) $_GET['tsp-template-id'] )
+				&& isset( $_GET['tsp_import_nonce'] )
+				&& wp_verify_nonce( sanitize_text_field( $_GET['tsp_import_nonce'] ), 'tsp_import_template' )
+			){
 				$tsp_insert_id = apply_filters( "tsp_import_template",sanitize_text_field( $_GET['tsp-template-id'] ));
 				if ($tsp_insert_id !== false) {
 					wp_safe_redirect( add_query_arg( 'tsp-id', $tsp_insert_id, admin_url( 'admin.php?page=ts-poll-builder' ) ) );
@@ -524,10 +492,17 @@
 		<?php
 	}
 	function tspoll_dashboard_fetch_callback() {
+		if ( ! current_user_can( 'manage_options' ) ) {
+			wp_send_json_error( array( 'message' => 'Forbidden' ), 403 );
+		}
+		check_ajax_referer( 'tsp-dashboard-nonce', 'ts_poll_dashboard_widget_nonce', true );
 		$tsp_dashboard_list_table = new ts_poll_dashboard();
 		$tsp_dashboard_list_table->ajax_response();
 	}
 	function tspoll_dashboard_update_callback() {
+		if ( ! current_user_can( 'manage_options' ) ) {
+			wp_send_json_error( array( 'message' => 'Forbidden' ), 403 );
+		}
 		check_ajax_referer( 'tsp-dashboard-nonce', 'ts_poll_dashboard_widget_nonce', true );
 		$tsp_dashboard_list_table = new ts_poll_dashboard();
 		$tsp_dashboard_list_table->prepare_items();
@@ -888,6 +863,9 @@
 		wp_send_json_success( $data );
 	}
 	function tsp_save_question() {
+		if ( ! current_user_can( 'manage_options' ) ) {
+			wp_send_json_error( array( 'message' => 'Forbidden' ), 403 );
+		}

Exploit Outline

The vulnerability can be exploited by an authenticated user with Subscriber-level permissions. For the 'init' hook bypass, an attacker can send a GET request to '/wp-admin/admin.php?page=ts-poll-builder&tsp-template-id=1'. Because WordPress evaluates 'is_admin()' as true for any request to the wp-admin directory, and the plugin lacks a capability check (current_user_can), the plugin will process the request and import the template, creating new poll entries in the database. For AJAX-based actions like saving questions or updating the dashboard, an attacker can send POST requests to '/wp-admin/admin-ajax.php' with the corresponding 'action' parameter (e.g., 'tsp_save_question'). Although some actions require a nonce, the lack of capability checks means any user who can obtain or guess the nonce can perform administrative actions.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.