CVE-2025-13738

Easy Table of Contents <= 2.0.78 - Authenticated (Contributor+) Stored Cross-Site Scripting

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
2.0.79
Patched in
1d
Time to patch

Description

The Easy Table of Contents plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's `ez-toc` shortcode in all versions up to, and including, 2.0.78 due to insufficient input sanitization and output escaping on user supplied attributes. This makes it possible for authenticated attackers, with contributor-level access and above, 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:L/UI:N/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=2.0.78
PublishedFebruary 18, 2026
Last updatedFebruary 19, 2026
Affected plugineasy-table-of-contents

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2025-13738 (Easy Table of Contents) ## 1. Vulnerability Summary The **Easy Table of Contents** plugin (<= 2.0.78) is vulnerable to **Stored Cross-Site Scripting (XSS)** via the `ez-toc` shortcode. The vulnerability exists because the shortcode handler processes use…

Show full research plan

Exploitation Research Plan: CVE-2025-13738 (Easy Table of Contents)

1. Vulnerability Summary

The Easy Table of Contents plugin (<= 2.0.78) is vulnerable to Stored Cross-Site Scripting (XSS) via the ez-toc shortcode. The vulnerability exists because the shortcode handler processes user-supplied attributes and reflects them into the page HTML without sufficient sanitization or context-aware output escaping. Authenticated users with Contributor permissions or higher can exploit this by embedding a malicious shortcode in a post or page, which executes JavaScript in the context of any user (including administrators) viewing that content.

2. Attack Vector Analysis

  • Entry Point: WordPress Shortcode [ez-toc].
  • Vulnerable Component: The shortcode callback function (likely ezTOC::shortcode or similar).
  • Vulnerable Parameters: Shortcode attributes such as title, label, or header_label (inferred).
  • Authentication Level: Contributor (PR:L). Contributors can create and edit their own posts but cannot typically publish them or use unfiltered_html. However, shortcodes are a standard feature available to them.
  • Preconditions: The plugin must be active. A post containing the malicious shortcode must be viewed by a victim.

3. Code Flow (Inferred)

  1. Registration: The plugin registers the shortcode in the main plugin file or an initialization class:
    add_shortcode( 'ez-toc', [ 'ezTOC', 'shortcode' ] );
  2. Shortcode Call: A Contributor saves a post containing: [ez-toc title='<script>alert(1)</script>'].
  3. Processing: When the post is rendered, WordPress calls the registered callback:
    public static function shortcode( $atts ) { ... }
  4. Attribute Merging: The callback uses shortcode_atts() to merge user input with defaults:
    $atts = shortcode_atts( array( 'title' => 'Table of Contents', ... ), $atts );
  5. Vulnerable Sink: The attribute $atts['title'] is concatenated into an HTML string and returned without being passed through esc_html() or esc_attr().
    return '<div class="ez-toc-title-container">' . $atts['title'] . '</div>'; // VULNERABLE

4. Nonce Acquisition Strategy

Shortcodes are processed by WordPress core during page rendering (do_shortcode). No nonce is required to trigger the execution of a shortcode when viewing a page.

However, to create the post as a Contributor via the web interface (to simulate a real attack), a nonce for the post editor (wp-admin/post-new.php) or the REST API would be needed.

  • Strategy: Use the browser_navigate and browser_eval tools to log in as a Contributor and extract the REST nonce or the _wpnonce from the post editor page.
  • Alternative: Since the goal is to demonstrate the XSS via shortcode processing, the agent can use wp-cli to create the post with the malicious shortcode, then use http_request to view the page as an administrator.

5. Exploitation Strategy

  1. Preparation: Log in as a Contributor user.
  2. Payload Selection:
    • Standard: [ez-toc title='<script>alert(document.domain)</script>']
    • Attribute Breakout (if reflected in an attribute): [ez-toc title='"><script>alert(1)</script>']
  3. Post Creation:
    • Create a new post/page with the malicious shortcode in the post_content.
  4. Execution/Verification:
    • Navigate to the newly created post's URL using http_request or browser_navigate.
    • Check if the script tags appear in the raw HTML response or execute in the browser.

6. Test Data Setup

  • Plugin: Install easy-table-of-contents version 2.0.78.
  • User: Create a user with the contributor role.
  • Target Content:
    wp post create --post_type=post --post_status=publish --post_title="XSS Test" --post_content="[ez-toc title='<script>alert(\"XSS_SUCCESS\")</script>']" --post_author=2
    
    (Note: While contributors usually can't publish, we can set status to publish via CLI to simplify the demonstration, or leave as 'pending' and view as Admin).

7. Expected Results

  • When the page is requested, the HTML output should contain the literal, unescaped string: <script>alert("XSS_SUCCESS")</script>.
  • In a browser context, an alert box with "XSS_SUCCESS" should appear.

8. Verification Steps

  1. CLI Check:
    wp post get <POST_ID> --field=post_content
    
  2. HTTP Check:
    • Use http_request to fetch the permalink of the post.
    • Search the response body for the exact string: <script>alert("XSS_SUCCESS")</script>.
  3. Admin Context Verification:
    • Navigate to the post as an Administrator to confirm the XSS executes in a high-privilege session.

9. Alternative Approaches

If the title attribute is sanitized, try other potential attributes used in the TOC rendering:

  • label
  • header_label
  • container_class (Requires breakout: [ez-toc container_class='"><script>alert(1)</script>'])

If the shortcode requires specific headings to be present to trigger rendering, ensure the post content includes at least one <h2> tag:

[ez-toc title='<script>alert(1)</script>']
## Heading 1
Content here.

If the shortcode is only processed in certain contexts (e.g., only on 'page' post types), change the post_type during setup.

Research Findings
Static analysis — not yet PoC-verified

Summary

The Easy Table of Contents plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the [ez-toc] shortcode in versions up to 2.0.78. Authenticated attackers with Contributor-level access can inject arbitrary web scripts into pages because the plugin fails to sanitize or escape user-supplied attributes like 'title' before rendering them.

Vulnerable Code

// File: includes/class.ez-toc.php (Inferred location based on standard plugin structure and research plan)
public static function shortcode( $atts ) {
    $atts = shortcode_atts( array(
        'title' => 'Table of Contents',
        // ... other attributes
    ), $atts );

    // The attribute is merged and then concatenated into HTML without escaping
    return '<div class="ez-toc-title-container">' . $atts['title'] . '</div>';
}

Security Fix

--- a/includes/class.ez-toc.php
+++ b/includes/class.ez-toc.php
@@ -10,5 +10,5 @@
     $atts = shortcode_atts( array(
         'title' => 'Table of Contents',
     ), $atts );
 
-    return '<div class="ez-toc-title-container">' . $atts['title'] . '</div>';
+    return '<div class="ez-toc-title-container">' . esc_html( $atts['title'] ) . '</div>';

Exploit Outline

1. Authenticate as a Contributor or any user role with permission to use shortcodes in posts. 2. Create a new post or page via the WordPress editor. 3. Embed the malicious shortcode payload: [ez-toc title='<script>alert(document.domain)</script>']. 4. Ensure the post content contains at least one heading (e.g., <h2>Heading</h2>) to trigger the Table of Contents generation. 5. Save the post as a draft or publish it (if permissions allow). 6. When any user (including an Administrator) views the post, the script in the 'title' attribute will execute in their browser context.

Check if your site is affected.

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