CVE-2026-57672

wpDataTables (Premium) <= 6.5.1.1 - Unauthenticated Stored Cross-Site Scripting

highImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
7.2
CVSS Score
7.2
CVSS Score
high
Severity
6.5.1.2
Patched in
8d
Time to patch

Description

The wpDataTables (Premium) plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 6.5.1.1 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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=6.5.1.1
PublishedJune 30, 2026
Last updatedJuly 7, 2026
Affected pluginwpdatatables

What Changed in the Fix

Changes introduced in v6.5.1.2

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-57672 (wpDataTables Stored XSS) ## 1. Vulnerability Summary The **wpDataTables (Premium)** plugin (versions <= 6.5.1.1) is vulnerable to **Unauthenticated Stored Cross-Site Scripting (XSS)**. The vulnerability exists because the plugin registers AJAX handlers …

Show full research plan

Exploitation Research Plan: CVE-2026-57672 (wpDataTables Stored XSS)

1. Vulnerability Summary

The wpDataTables (Premium) plugin (versions <= 6.5.1.1) is vulnerable to Unauthenticated Stored Cross-Site Scripting (XSS). The vulnerability exists because the plugin registers AJAX handlers for manual table editing (wp_ajax_nopriv_wpdt_save_manual_edits) that do not adequately verify user permissions or sanitize input data. An unauthenticated attacker can inject malicious JavaScript into a "Manual" table, which then executes in the context of any user (including administrators) viewing the table on the frontend or backend.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: wpdt_save_manual_edits (via wp_ajax_nopriv_ hook)
  • Vulnerable Parameter: Input data fields (e.g., column values) sent in the POST request.
  • Authentication: None (Unauthenticated).
  • Preconditions:
    1. A "Manual" table must exist (Table Type: manual).
    2. "Allow frontend editing" must be enabled for the table.
    3. The table must be published on a public page/post via shortcode.

3. Code Flow

  1. Entry Point: An unauthenticated user sends a POST request to admin-ajax.php with action=wpdt_save_manual_edits.
  2. AJAX Hook: The request is caught by the hook registered in the plugin (usually in a main controller or wdt_functions.php):
    add_action('wp_ajax_nopriv_wpdt_save_manual_edits', 'wdt_save_manual_edits');
  3. Processing: The handler (inferred function name wdt_save_manual_edits) retrieves the table ID from $_POST['table_id'].
  4. Logic Failure: The plugin checks if the table is "Editable" but fails to verify if the current unauthenticated user has the manage_options or appropriate edit permissions. It relies solely on a nonce that is exposed to public users.
  5. Storage: The raw payload in the data columns is saved into the database table defined in the schema: {$wpdb->prefix}wpdatatables_rows (or via update_post_meta for simple tables).
  6. Sink: When a user views the table, WPDataTable::render() (in source/class.wpdatatable.php) fetches the data. The data is echoed to the page without context-aware escaping (e.g., using esc_html or wp_kses).

4. Nonce Acquisition Strategy

The wpdt_save_manual_edits action requires a WordPress nonce. This nonce is generated for unauthenticated users (UID 0) and localized into the frontend.

  1. Identify Shortcode: The plugin uses [wpdatatable id=ID] to render tables.
  2. Create Test Page:
    wp post create --post_type=page --post_title="Table Page" --post_status=publish --post_content='[wpdatatable id=1]'
    
  3. Navigate & Extract: Navigate to the page containing the table. The plugin enqueues a script that localizes settings into a global JS object.
  4. JS Variable: Based on plugin standards, the variable is wpdtFrontendConfig or wpDataTablesL10n.
  5. Extraction Command:
    // Recommended browser_eval logic
    window.wpdtFrontendConfig?.nonce || window.wpDataTablesL10n?.wpdt_nonce
    

5. Exploitation Strategy

Step 1: Data Setup

Prepare a manual table and enable editing.

# This is usually done via the UI, but we ensure a manual table exists
# Table ID 1, Type: manual, Editable: 1
wp db query "UPDATE wp_wpdatatables SET table_type='manual', editable=1 WHERE id=1"

Step 2: Extract Nonce

Use the browser to find the nonce required for the wpdt_save_manual_edits action.

Step 3: Send Malicious Payload

Send an AJAX request to inject the XSS payload.

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Content-Type: application/x-www-form-urlencoded
  • Body Parameters:
    • action: wpdt_save_manual_edits
    • table_id: 1
    • nonce: [EXTRACTED_NONCE]
    • wdt_id: 0 (indicates a new row)
    • values[0]: <img src=x onerror=alert(document.domain)> (assuming column index 0 is a string type)

Step 4: Trigger Execution

Navigate to the table page (or the admin "Browse Tables" page at admin.php?page=wpdatatables-administration).

6. Test Data Setup

  1. User: No specific user needed (unauthenticated).
  2. Table: A manual table with at least one string column.
  3. Page: A public page containing the shortcode [wpdatatable id=1].

7. Expected Results

  • The AJAX request should return a JSON response with success: true or a row ID.
  • When an admin views the table, a JavaScript alert showing the document domain should appear.
  • The payload remains stored in the database, affecting all subsequent viewers.

8. Verification Steps

After performing the HTTP request, verify the injection via WP-CLI:

# Check the wp_wpdatatables_rows table for the payload
wp db query "SELECT data FROM wp_wpdatatables_rows WHERE table_id=1"

Or check if the payload exists in the rendered HTML:

# Use browser_navigate to the table page and check for the alert/payload
browser_eval "document.body.innerHTML.includes('<img src=x onerror=alert')"

9. Alternative Approaches

  • Simple Tables: If the vulnerability affects "Simple Tables" (a premium feature), the entry point might be wpdt_save_simple_table and the payload would be stored in the content column of the wp_wpdatatables_templates table.
  • Admin-Side Trigger: If the frontend is protected, the payload can still be triggered when an admin visits the Dashboard or Browse Tables pages, as source/class.wdtbrowsetable.php returns item[$column_name] unsanitized in the column_default method.
Research Findings
Static analysis — not yet PoC-verified

Summary

The wpDataTables (Premium) plugin is vulnerable to unauthenticated Stored and Reflected Cross-Site Scripting (XSS) due to insufficient input sanitization and output escaping. Attackers can exploit the 'wpdt_save_manual_edits' AJAX action to store malicious scripts or use the 'wdt_search' parameter to reflect scripts that execute when users or administrators view the table.

Vulnerable Code

// source/class.wdtbrowsetable.php
// Admin-side sink for stored table data
            case 'id':
            case 'title':
            default:
                return $item[$column_name];

---

// templates/frontend/table_main.inc.php (Line 22)
// Reflected sink for table description data via value attribute
    <input type="hidden" id="<?php echo esc_attr($this->getId()) ?>_desc" value='<?php echo $this->getJsonDescription(); ?>'/>

---

// source/class.wpdatatable.php (Line 2545)
// Lack of sanitization for the search parameter before being passed to the description
        if (isset($_GET['wdt_search'])) {
            $this->setDefaultSearchValue($_GET['wdt_search']);
        }

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wpdatatables/6.5.1.1/source/class.wpdatatable.php /home/deploy/wp-safety.org/data/plugin-versions/wpdatatables/6.5.1.2/source/class.wpdatatable.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wpdatatables/6.5.1.1/source/class.wpdatatable.php	2026-03-30 06:29:36.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wpdatatables/6.5.1.2/source/class.wpdatatable.php	2026-06-24 08:07:26.000000000 +0000
@@ -2543,7 +2543,7 @@
         $columnIndex = 1;
         // Check the search values passed from URL
         if (isset($_GET['wdt_search'])) {
-            $this->setDefaultSearchValue($_GET['wdt_search']);
+            $this->setDefaultSearchValue(sanitize_text_field(wp_unslash($_GET['wdt_search'])));
         }
 
         // Define all column-dependent rendering rules
@@ -2953,7 +2953,7 @@
 
         $obj = apply_filters('wpdatatables_filter_table_description', $obj, $this->getWpId(), $this);
 
-        return json_encode($obj, JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG);
+        return json_encode($obj, JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG | JSON_HEX_AMP);
     }
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wpdatatables/6.5.1.1/templates/frontend/table_main.inc.php /home/deploy/wp-safety.org/data/plugin-versions/wpdatatables/6.5.1.2/templates/frontend/table_main.inc.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wpdatatables/6.5.1.1/templates/frontend/table_main.inc.php	2023-08-07 05:59:38.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wpdatatables/6.5.1.2/templates/frontend/table_main.inc.php	2026-06-24 08:07:26.000000000 +0000
@@ -19,7 +19,7 @@
 ?>
 <?php do_action('wpdatatables_before_table', $this->getWpId()); ?>
 <?php wp_nonce_field('wdtFrontendEditTableNonce', 'wdtNonceFrontendEdit'); ?>
-    <input type="hidden" id="<?php echo esc_attr($this->getId()) ?>_desc" value='<?php echo $this->getJsonDescription(); ?>'/>
+    <input type="hidden" id="<?php echo esc_attr($this->getId()) ?>_desc" value='<?php echo esc_attr($this->getJsonDescription()); ?>'/>

Exploit Outline

To exploit the Stored XSS, an unauthenticated attacker identifies a page with a manual table and extracts the 'wdtNonceFrontendEdit' nonce from the localized JavaScript (e.g., wpdtFrontendConfig). They then send a POST request to '/wp-admin/admin-ajax.php' with 'action=wpdt_save_manual_edits' containing a malicious script in the column values. This script executes whenever an administrator views the table in the backend. For Reflected XSS, an attacker crafts a URL with the 'wdt_search' parameter containing a payload designed to break out of the single-quoted HTML 'value' attribute in the table's hidden description input (e.g., using a closing quote and a tag like <img onerror=alert(1)>), which executes upon page load.

Check if your site is affected.

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