CVE-2025-13753

WP Table Builder <= 2.0.19 - Incorrect Authorization to Authenticated (Subscriber+) Arbitrary Table Creation

mediumIncorrect Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
2.0.20
Patched in
64d
Time to patch

Description

The WP Table Builder – Drag & Drop Table Builder plugin for WordPress is vulnerable to unauthorized modification of data due to an incorrect authorization check on the save_table() function in all versions up to, and including, 2.0.19. This makes it possible for authenticated attackers, with Subscriber-level access and above, to create new wptb-table posts.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=2.0.19
PublishedJanuary 8, 2026
Last updatedMarch 13, 2026
Affected pluginwp-table-builder

What Changed in the Fix

Changes introduced in v2.0.20

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: CVE-2025-13753 - Unauthorized Table Creation in WP Table Builder ## Vulnerability Summary The **WP Table Builder** plugin (versions <= 2.0.19) contains an incorrect authorization vulnerability in its `save_table()` AJAX handler. The function `WP_Table_Builder\Inc\Admin\Admin_Menu::…

Show full research plan

Research Plan: CVE-2025-13753 - Unauthorized Table Creation in WP Table Builder

Vulnerability Summary

The WP Table Builder plugin (versions <= 2.0.19) contains an incorrect authorization vulnerability in its save_table() AJAX handler. The function WP_Table_Builder\Inc\Admin\Admin_Menu::save_table() validates authorization using an "OR" logic between two conditions: one that checks for a specific capability (Settings_Manager::ALLOWED_ROLE_META_CAP) and a nonce, and another that checks only a nonce (wptb-import-security-nonce).

Because the second condition lacks a capability check (current_user_can), any authenticated user (including Subscribers) who can obtain the wptb-import-security-nonce can trigger the insert_table_to_db() function to create new wptb-tables posts.

Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php?action=save_table
  • Method: POST
  • Authentication: Authenticated (Subscriber level or higher)
  • Vulnerable Parameter: The request body must be a JSON object containing security_code (the nonce) and content.
  • Precondition: The attacker must be logged in as a Subscriber and extract the wptb-import-security-nonce.

Code Flow

  1. Entry Point: The AJAX action wp_ajax_save_table is registered in Inc\Admin\Admin_Menu::__construct() and points to save_table().
  2. Input Handling: save_table() reads the raw request body using file_get_contents('php://input') and decodes it via json_decode.
  3. Vulnerable Authorization Check:
    $verified = current_user_can(Settings_Manager::ALLOWED_ROLE_META_CAP) && wp_verify_nonce($params->security_code, 'wptb-security-nonce');
    $import_verified = wp_verify_nonce($params->security_code, 'wptb-import-security-nonce');
    
    if (!$verified && !$import_verified) {
        wp_die(json_encode(['security_problem', '']));
    }
    
    If $import_verified is true, the check passes regardless of the user's capabilities.
  4. Logic Path:
    • The code checks if $params->id exists and is a valid post.
    • If !property_exists($params, 'id'), it proceeds to $this->insert_table_to_db($params).
  5. Sink: insert_table_to_db() calls wp_insert_post() with 'post_type' => 'wptb-tables', creating a new table in the database.

Nonce Acquisition Strategy

The wptb-import-security-nonce is used by the plugin's import functionality. To obtain it:

  1. Identify the Script: The plugin localizes data for its admin scripts. Based on the source, Admin_Menu enqueues scripts that likely contain these nonces.
  2. Access Admin: Subscribers can access the WordPress dashboard (/wp-admin/profile.php).
  3. Extraction:
    • Navigate to /wp-admin/ (or any admin page available to a Subscriber).
    • The plugin likely uses wp_localize_script to export nonces. Based on the plugin structure, check for a global JavaScript object.
    • Target Variable: Look for wptb_admin_obj or wptb_vars (inferred from plugin conventions).
    • Nonce Key: The key is likely import_nonce or wptb_import_nonce.
    • Browser Execution:
      // Example extraction via browser_eval
      window.wptb_admin_obj?.import_nonce || window.wptb_vars?.import_nonce
      

Exploitation Strategy

  1. Log in: Use http_request to authenticate as a Subscriber.
  2. Extract Nonce:
    • Use browser_navigate to go to /wp-admin/.
    • Use browser_eval to search for the nonce. Search specifically for the string "wptb-import-security-nonce" in the page source if the variable name is unclear, or iterate through localized objects.
  3. Craft Payload:
    • Prepare a JSON body.
    • security_code: The extracted nonce.
    • title: "Exploit Table".
    • content: A JSON-encoded string representing a table structure (e.g., {"cells":[]}). Note: The code calls TableRenderer::render(json_decode($params->content, true)), so content must be a double-encoded JSON string or a valid JSON string that decodes to an array.
  4. Execute Attack:
    • Use http_request to send a POST to admin-ajax.php?action=save_table.
    • Headers: Content-Type: application/json.
    • Body: The prepared JSON payload.

Test Data Setup

  1. User Creation: Create a user with the subscriber role.
    • wp user create attacker attacker@example.com --role=subscriber --user_pass=password
  2. Plugin Activation: Ensure wp-table-builder is active.

Expected Results

  • The HTTP response should be a JSON array: ["saved", <POST_ID>].
  • A new post of type wptb-tables should exist in the database with the title "Exploit Table".

Verification Steps

  1. Check Database via CLI:
    wp post list --post_type=wptb-tables --field=post_title
    
    Confirm "Exploit Table" appears in the list.
  2. Check Post Existence:
    wp post exists <POST_ID_FROM_RESPONSE>
    

Alternative Approaches

  • If Subscriber access to admin is restricted: Check if the plugin enqueues the import nonce on the frontend when certain shortcodes are present.
  • Shortcode Search: grep -r "add_shortcode" . to find elements that might trigger script loading.
  • Nonce Bypass via Action Mismatch: If wptb-import-security-nonce is not found, check if wp_verify_nonce($params->security_code, -1) (the default action) is ever triggered or if the nonce check is absent in other AJAX actions like wp_ajax_get_table. (However, the primary vulnerability identified is the OR logic in save_table).
Research Findings
Static analysis — not yet PoC-verified

Summary

The WP Table Builder plugin is vulnerable to unauthorized table creation due to flawed authorization logic in the `save_table()` AJAX handler. Authenticated users, including those with Subscriber-level permissions, can bypass capability checks by using a valid import nonce, allowing them to create new 'wptb-tables' posts via a crafted AJAX request.

Vulnerable Code

// inc/admin/class-admin-menu.php line 69
	public function save_table()
	{
		$params = json_decode(file_get_contents('php://input'));

		$verified = current_user_can(Settings_Manager::ALLOWED_ROLE_META_CAP) && wp_verify_nonce($params->security_code, 'wptb-security-nonce');
		$import_verified = wp_verify_nonce($params->security_code, 'wptb-import-security-nonce');

		if (!$verified && !$import_verified) {
			wp_die(json_encode(['security_problem', '']));
		}

		try {
			$params->content = TableRenderer::render(json_decode($params->content, true), $params->id ?? 'startedid-0');
		} catch (\Exception $e) {
			wp_die(json_encode(['error', $e->getMessage()]));
		}

		if (!property_exists($params, 'id') || !absint($params->id) || get_post_status(absint($params->id)) != 'draft') {
			$id = $this->insert_table_to_db($params);
			wp_die(json_encode(['saved', $id]));
		}

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/wp-table-builder/2.0.19/inc/admin/class-admin-menu.php	2025-08-08 14:18:46.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-table-builder/2.0.20/inc/admin/class-admin-menu.php	2025-12-15 04:07:54.000000000 +0000
@@ -68,9 +68,14 @@
 
 	public function save_table()
 	{
+
+		if (!\WPTableBuilder\Admin\Authorization::can_edit()) {
+			wp_die(json_encode(['security_problem', '']));
+		}
+
 		$params = json_decode(file_get_contents('php://input'));
 
-		$verified = current_user_can(Settings_Manager::ALLOWED_ROLE_META_CAP) && wp_verify_nonce($params->security_code, 'wptb-security-nonce');
+		$verified = wp_verify_nonce($params->security_code, 'wptb-security-nonce');
 		$import_verified = wp_verify_nonce($params->security_code, 'wptb-import-security-nonce');
 
 		if (!$verified && !$import_verified) {

Exploit Outline

To exploit this vulnerability, an attacker first authenticates as a Subscriber and navigates to any admin page (like `/wp-admin/profile.php`) to extract the `wptb-import-security-nonce` from localized JavaScript objects (such as `wptb_admin_obj`). The attacker then constructs a POST request to `/wp-admin/admin-ajax.php?action=save_table` with a JSON-encoded body. This body includes the extracted nonce as the `security_code` and a table structure in the `content` parameter. Because the server-side check allows authorization if the import nonce is valid—without verifying if the user has the required capabilities to edit tables—the plugin processes the request and creates a new table post in the database.

Check if your site is affected.

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