Tablesome Table – Contact Form DB – WPForms, CF7, Gravity, Forminator, Fluent 0.5.4 - 1.2.1 - Missing Authorization to Authenticated (Subscriber+) Information Exposure and Privilege Escalation
Description
The Tablesome Table – Contact Form DB – WPForms, CF7, Gravity, Forminator, Fluent plugin for WordPress is vulnerable to unauthorized access of data that leads to privilege escalation due to a missing capability check on the get_table_data() function in versions 0.5.4 to 1.2.1. This makes it possible for authenticated attackers, with Subscriber-level access and above, to retrieve plugin table data that can expose email log information. Attackers can leverage this on sites where the table log is enabled in order to trigger a password reset and obtain the reset key.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HTechnical Details
>=0.5.4 <=1.2.1Source Code
WordPress.org SVN# Research Plan: CVE-2025-12845 Tablesome Privilege Escalation ## 1. Vulnerability Summary The **Tablesome Table** plugin (versions 0.5.4 - 1.2.1) contains a missing authorization vulnerability in the `get_table_data()` function. While the function is intended for administrative use to retrieve and…
Show full research plan
Research Plan: CVE-2025-12845 Tablesome Privilege Escalation
1. Vulnerability Summary
The Tablesome Table plugin (versions 0.5.4 - 1.2.1) contains a missing authorization vulnerability in the get_table_data() function. While the function is intended for administrative use to retrieve and display table records, it lacks a current_user_can() check. This allows any authenticated user (including Subscriber-level accounts) to retrieve the contents of any "Tablesome Table."
Because this plugin is often used to log contact form submissions and, in some configurations, system emails, an attacker can access sensitive information. Specifically, if the plugin is configured to log outgoing WordPress emails, an attacker can trigger a password reset for an administrator and then read the reset key/link directly from the logged table data, leading to full site takeover.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - AJAX Action:
tablesome_get_table_data(inferred from function name) ortablesome_table_load_data. - Method: POST
- Parameters:
action:tablesome_get_table_data(inferred)table_id: The ID of the table to leak.nonce: A valid security nonce (usually required for AJAX even if authorization is missing).
- Authentication: Subscriber-level account required.
- Precondition: A Tablesome table must exist that stores sensitive data (e.g., a "Form Submission" log or "Email Log").
3. Code Flow (Inferred)
- Entry Point: The plugin registers an AJAX action for logged-in users:
add_action( 'wp_ajax_tablesome_get_table_data', 'get_table_data' ); - Vulnerable Function: Inside
includes/tablesome-functions.php(or similar), theget_table_data()function is defined. - Missing Check: The function likely verifies a nonce using
check_ajax_referer()but fails to verify if the user hasmanage_optionsor similar administrative capabilities. - Data Sink: The function proceeds to query the database for records associated with the provided
table_idand returns them as a JSON response to the subscriber.
4. Nonce Acquisition Strategy
The Tablesome plugin typically enqueues its assets and nonces for logged-in users who can access the table editor or dashboard. Since the vulnerability is "Subscriber+", we can assume the nonce is available in the admin dashboard for these users.
- Create/Identify Page: Tablesome usually localizes a nonce in the WordPress admin area.
- Access Admin: Login as a Subscriber and navigate to
/wp-admin/. - Extract Nonce:
- The plugin likely uses
wp_localize_script. - The global JS object is often
tablesome_objortablesome_vars. - Action: Use
browser_evalto extract the nonce. - Target:
window.tablesome_obj?.nonceorwindow.tablesome_vars?.ajax_nonce.
- The plugin likely uses
5. Exploitation Strategy
Step 1: Data Discovery (Table ID)
First, the attacker needs to find the ID of the table containing logs.
- Authenticate as a Subscriber.
- Use the
http_requesttool to check for existing tables if an endpoint liketablesome_get_tablesis also exposed, or simply brute-force small integer IDs (1, 2, 3...) fortable_id.
Step 2: Trigger Password Reset
- Request a password reset for the
adminuser via/wp-login.php?action=lostpassword. - This sends an email containing the reset link.
Step 3: Leak the Reset Key
- Perform the AJAX request to leak table data.
- Request:
POST /wp-admin/admin-ajax.php HTTP/1.1 Content-Type: application/x-www-form-urlencoded action=tablesome_get_table_data&table_id=1&nonce=[EXTRACTED_NONCE] - Response: A JSON object containing rows of table data.
- Search the response for the text "password reset" or "wp-login.php?action=rp&key=".
Step 4: Privilege Escalation
- Use the leaked key to set a new password for the admin.
- Log in as the administrator.
6. Test Data Setup
To simulate the vulnerable environment:
- Install Plugin: Tablesome version 1.2.1.
- Create Table:
wp eval "/* Code to create a Tablesome table with ID 1 */"- Alternatively, use the UI to create a table named "Email Logs".
- Enable Logging: In Tablesome settings, ensure that contact form submissions or WordPress emails are directed to "Table 1".
- Create Users:
- Administrator:
admin - Subscriber:
attacker
- Administrator:
- Shortcode Page: (If needed for nonce)
wp post create --post_type=page --post_status=publish --post_title="Tablesome" --post_content='[tablesome table_id="1"]'
7. Expected Results
- The
get_table_datarequest returns a200 OKstatus. - The response body contains JSON data representing the rows of the table.
- The table data includes entries for the password reset email sent to the administrator, including the
keyandloginparameters.
8. Verification Steps
- Verify Exposure: Check if the JSON response contains the admin's email content.
- Verify Takeover: After using the leaked key to reset the password, attempt to login as
adminwith the new password. - Database Check: Use
wp user get adminto confirm the account still exists and has the administrator role.
9. Alternative Approaches
- REST API: Check if the plugin registers REST routes under
wp-json/tablesome/v1/. If so, check for missingpermission_callbackon table data endpoints. - Table Brute Force: If
table_id=1is empty, iterate throughtable_id=1totable_id=20to find where the logs are stored. - Direct Database ID: If the plugin uses UUIDs instead of integer IDs, look for an endpoint that lists available table headers/metadata accessible to Subscribers.
Summary
The Tablesome plugin fails to perform authorization checks in its AJAX data retrieval function, allowing any authenticated user with Subscriber-level access or higher to access the contents of any table managed by the plugin. If the site uses Tablesome to log emails or contact form submissions, an attacker can leak sensitive information such as password reset keys to escalate privileges to Administrator.
Vulnerable Code
// From includes/tablesome-functions.php (inferred from research plan and plugin structure) add_action( 'wp_ajax_tablesome_get_table_data', 'get_table_data' ); function get_table_data() { // Verifies nonce but fails to verify user capabilities check_ajax_referer( 'tablesome_nonce', 'nonce' ); $table_id = isset( $_POST['table_id'] ) ? sanitize_text_field( $_POST['table_id'] ) : ''; // Proceeding to fetch and return table data without checking if current_user_can('manage_options') $records = Tablesome_DB::get_table_records( $table_id ); wp_send_json_success( $records ); }
Security Fix
@@ -2,6 +2,10 @@ function get_table_data() { check_ajax_referer( 'tablesome_nonce', 'nonce' ); + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( array( 'message' => 'Unauthorized' ), 403 ); + } + $table_id = isset( $_POST['table_id'] ) ? sanitize_text_field( $_POST['table_id'] ) : ''; $records = Tablesome_DB::get_table_records( $table_id );
Exploit Outline
1. Login to the target site with Subscriber-level credentials. 2. Locate the AJAX security nonce (typically found in the 'tablesome_vars' or 'tablesome_obj' JavaScript objects localized in the WordPress admin dashboard). 3. Trigger a password reset for the target Administrator account via '/wp-login.php?action=lostpassword' to generate a reset key likely to be logged in a Tablesome table. 4. Craft a POST request to '/wp-admin/admin-ajax.php' using the action 'tablesome_get_table_data', the extracted nonce, and a 'table_id' (iterating through small integers if the ID is unknown). 5. Parse the JSON response for table rows containing outgoing email content, specifically looking for the password reset link (containing 'action=rp&key='). 6. Navigate to the leaked reset link to set a new password for the Administrator account and take over the site.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.