Age Verification & Identity Verification by Token of Trust <= 3.32.3 - Unauthenticated Stored Cross-Site Scripting via 'description' Parameter
Description
The Age Verification & Identity Verification by Token of Trust plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the ‘description’ parameter in all versions up to, and including, 3.32.3 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=3.32.3What Changed in the Fix
Changes introduced in v3.32.4
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-2834 (Stored XSS in Token of Trust) ## 1. Vulnerability Summary The **Age Verification & Identity Verification by Token of Trust** plugin is vulnerable to unauthenticated stored cross-site scripting (XSS). The vulnerability exists in the handling of the `tot_e…
Show full research plan
Exploitation Research Plan: CVE-2026-2834 (Stored XSS in Token of Trust)
1. Vulnerability Summary
The Age Verification & Identity Verification by Token of Trust plugin is vulnerable to unauthenticated stored cross-site scripting (XSS). The vulnerability exists in the handling of the tot_error_log AJAX action. An unauthenticated attacker can send a request to admin-ajax.php with a malicious payload in the description parameter. This payload is stored in the WordPress database (within the tot_logs option) and is subsequently rendered in the plugin's administration dashboard without proper sanitization or output escaping.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
tot_error_log(registered as unauthenticated/nopriv) - Vulnerable Parameter:
description - Authentication: None required (Unauthenticated)
- Preconditions: The
Debuggerclass requires "debug mode" to be active to record logs. However,Modules/Verification/Shared/Debugger.phpchecks for adebug_modeparameter or cookie, which an attacker can easily provide to bypass this check.
3. Code Flow
- Entry Point: The frontend JavaScript
Modules/Shared/Assets/tot-error-log.jsdefines thetotErrorLogfunction, which makes a POST request toadmin-ajax.phpwith the actiontot_error_logand parametersdescription,severity,module, anderror. - AJAX Handling: The plugin registers
wp_ajax_nopriv_tot_error_log. This handler calls theDebugger::log()method found inModules/Verification/Shared/Debugger.php. - Logic Bypass: In
Debugger.php, line 99:
An attacker can satisfy this condition by providing aif ( ! tot_debug_mode() && ! \TOT\Shared\Settings::get_param_or_cookie( 'debug_mode' ) ) { return; }debug_modecookie or POST parameter. - Storage: The
Debugger::logmethod stores thedescription(passed as$head) into the$new_logsarray. Upon shutdown,Debugger::store_logs_to_db()(line 147) is called:$db_logs = get_option( 'tot_logs', array() ); // ... unshifts new logs ... update_option( 'tot_logs', $db_logs, false ); - Execution Sink: When an administrator visits the plugin settings or logging page (likely handled by
Modules/Shared/Settings/Page.phpor a dedicated log view), the values in thetot_logsoption are retrieved and echoed into the HTML response without using escaping functions likeesc_html().
4. Nonce Acquisition Strategy
Based on the analysis of Modules/Shared/Assets/tot-error-log.js, the tot_error_log action does not use a nonce. The $.ajax call specifically omits any nonce or security tokens, which is common for frontend error logging where nonces might expire or not be available to unauthenticated users.
Conclusion: No nonce is required for this exploit.
5. Exploitation Strategy
The exploit will be performed using a single unauthenticated HTTP POST request.
Step 1: Inject Payload
Send a POST request to admin-ajax.php to trigger the logging mechanism.
- URL:
http://[target]/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded
- Body Parameters:
action:tot_error_logdescription:<script>alert("XSS_EXPLOITED")</script>severity:errormodule:frontend-loggerdebug_mode:1(This bypasses thetot_debug_mode()check)error:{}
Step 2: Trigger Execution
Log in as an administrator and navigate to the plugin's main settings page where logs are displayed.
- URL:
http://[target]/wp-admin/admin.php?page=tot_settings_tot_settings(The slug is derived fromPage.phpline 52:tot_settings_+ slugified title).
6. Test Data Setup
- Install and activate Age Verification & Identity Verification by Token of Trust version 3.32.3.
- Ensure the plugin is initialized (usually occurs upon visiting the settings page once as admin).
- No special content or shortcodes are required because the endpoint is an AJAX action available globally via
admin-ajax.php.
7. Expected Results
- The AJAX request should return a successful JSON response:
{"success": true}or similar, and theconsole.logintot-error-log.jswould indicate success. - The database option
tot_logswill now contain the malicious<script>tag. - Upon navigating to the plugin's settings/logs page as an admin, a JavaScript alert box with "XSS_EXPLOITED" will appear.
8. Verification Steps
After sending the HTTP request, verify the injection using WP-CLI:
# Check if the payload is present in the tot_logs option
wp option get tot_logs --format=json | grep "XSS_EXPLOITED"
To verify the "debug mode" bypass:
# Check if logs were written even if global debug mode is off
wp option get tot_options | grep "debug_mode" # Should be empty or 0 if default
9. Alternative Approaches
If the description parameter is sanitized, try injecting through the error parameter.
- Payload 2: Inject into the
errorparameter.Debugger.phpline 125 usesprint_r($log, true)for the body. If theerrordata is displayed alongside the description, XSS may trigger there. - Bypass Check: If
debug_modeas a POST parameter fails, try setting it as a cookie:# Using browser_navigate or http_request with cookies Cookie: debug_mode=1 - Payload 3: If
<script>tags are blocked, use attribute-based XSS:<img src=x onerror=alert(1)>
Summary
The Token of Trust plugin for WordPress (<= 3.32.3) allows unauthenticated attackers to perform stored cross-site scripting (XSS) via the 'tot_error_log' AJAX action. Malicious JavaScript injected into the 'description' parameter is stored in the database and executed when an administrator views the plugin's settings or error logs.
Vulnerable Code
// admin/error-log.php (approx lines 7-14 in 3.32.3) tot_add_action( 'wp_ajax_nopriv_tot_error_log', 'tot_handle_error_log' ); function tot_handle_error_log() { $max_post_size = 64000; // 64KB if ( ! isset( $_POST['description'] ) || empty( $_POST['description'] ) ) { wp_send_json_success( array( 'message' => 'Empty error description, not logged.' ) ); wp_die(); } $description = trim( $_POST['description'] ); --- // Modules/Verification/Shared/Debugger.php (approx lines 99-102 in 3.32.3) // Don't store if the debug mode is not active if ( ! tot_debug_mode() && ! \TOT\Shared\Settings::get_param_or_cookie( 'debug_mode' ) ) { return; } --- // Modules/Verification/Shared/Debugger.php (approx lines 130-143 in 3.32.3) $new_log = array( 'timestamp' => current_time( 'mysql' ), 'body' => print_r( $log, true ), 'type' => $type, ); if ( ! empty( $head ) ) { $new_log['head'] = $head; $this->new_heads[] = $head; } $this->new_logs[] = $new_log;
Security Fix
@@ -42,6 +42,7 @@ 'appDomain' => tot_get_setting_prod_domain() ?: parse_url( home_url( '/' ) )['host'], 'restUrl' => esc_url_raw( rest_url() ), 'nonce' => wp_create_nonce( 'tot_rest' ), + 'errorLogNonce' => wp_create_nonce( 'tot-error-log' ), 'appUserEmail' => $app_user_email, 'verificationRequiredPageLink' => $verification_required_edit_post_link, ) @@ -5,33 +5,32 @@ tot_add_action( 'wp_ajax_nopriv_tot_error_log', 'tot_handle_error_log' ); function tot_handle_error_log() { - $max_post_size = 64000; // 64KB + $max_post_size = 64000; - if ( ! isset( $_POST['description'] ) || empty( $_POST['description'] ) ) { - wp_send_json_success( array( 'message' => 'Empty error description, not logged.' ) ); + if ( ! wp_verify_nonce( $_POST['_ajax_nonce'] ?? '', 'tot-error-log' ) ) { + wp_send_json_error( 'Invalid security token.' ); wp_die(); } - $description = trim( $_POST['description'] ); + $description = sanitize_textarea_field( trim( $_POST['description'] ?? '' ) ); - // Double-check after trimming if ( empty( $description ) ) { wp_send_json_success( array( 'message' => 'Empty error description, not logged.' ) ); wp_die(); } - $module = $_POST['module'] ?? null; - $severity = $_POST['severity'] ?? 'error'; + $module = sanitize_text_field( $_POST['module'] ?? '' ) ?: null; + $severity = tot_sanitize_severity( $_POST['severity'] ?? 'error' ); $error_data = array(); - ( isset( $_POST['error'] ) ) { + if ( ! empty( $_POST['error'] ) ) { $posted_error = json_decode( stripslashes( $_POST['error'] ), true ); if ( is_array( $posted_error ) ) { - $error_data = $posted_error; - } elseif ( ! empty( $_POST['error'] ) ) { - $error_data['error'] = $posted_error; + $error_data = tot_sanitize_error_data( $posted_error ); + } else { + $error_data['error'] = wp_strip_all_tags( (string) $posted_error ); } } @@ -39,12 +38,10 @@ $error_data['module'] = $module; } - // Serialize and truncate if necessary - $serialized = print_r( $error_data, true ); + $serialized = wp_strip_all_tags( print_r( $error_data, true ) ); + if ( strlen( $serialized ) > $max_post_size ) { - $truncated = substr( $serialized, 0, $max_post_size - 1000 ); - $truncated .= "\n\n[Truncated: data exceeded 64KB limit]"; - $serialized = $truncated; + $serialized = substr( $serialized, 0, $max_post_size - 1000 ) . "\n\n[Truncated: data exceeded 64KB limit]"; } Debugger::inst()->log( $description, $serialized, $severity );
Exploit Outline
The exploit is executed via an unauthenticated AJAX request. 1. An attacker sends a POST request to `/wp-admin/admin-ajax.php` with the `action` parameter set to `tot_error_log`. 2. The `description` parameter is populated with a malicious script payload (e.g., `<script>alert("XSS")</script>`). 3. To satisfy the plugin's debug check, the attacker includes a `debug_mode` parameter or cookie set to `1`. 4. The plugin stores this payload in the `tot_logs` option in the WordPress database. 5. The XSS executes when an administrator visits the plugin's settings page at `admin.php?page=tot_settings_tot_settings` (or similar logging views), as the stored description is rendered without proper output escaping.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.