CVE-2026-1947

NEX-Forms – Ultimate Forms Plugin for WordPress <= 9.1.9 - Missing Authorization to Unauthenticated Arbitrary Form Entry Modification via nf_set_entry_update_id

highAuthorization Bypass Through User-Controlled Key
7.5
CVSS Score
7.5
CVSS Score
high
Severity
9.1.10
Patched in
1d
Time to patch

Description

The NEX-Forms – Ultimate Forms Plugin for WordPress plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 9.1.9 via the submit_nex_form() function due to missing validation on a user controlled key. This makes it possible for unauthenticated attackers to to overwrite arbitrary form entries via the 'nf_set_entry_update_id' parameter.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=9.1.9
PublishedMarch 14, 2026
Last updatedMarch 15, 2026

What Changed in the Fix

Changes introduced in v9.1.10

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan outlines the steps to verify the Insecure Direct Object Reference (IDOR) vulnerability in NEX-Forms (<= 9.1.9), which allows unauthenticated modification of arbitrary form entries. ### 1. Vulnerability Summary The NEX-Forms plugin contains a missing authorization check in its for…

Show full research plan

This research plan outlines the steps to verify the Insecure Direct Object Reference (IDOR) vulnerability in NEX-Forms (<= 9.1.9), which allows unauthenticated modification of arbitrary form entries.

1. Vulnerability Summary

The NEX-Forms plugin contains a missing authorization check in its form submission logic. Specifically, the function submit_nex_form() (inferred to be the primary submission handler) accepts a parameter nf_set_entry_update_id. When this parameter is provided, the plugin treats the submission as an update to an existing record rather than a new insertion. Because the plugin fails to verify if the current user has permission to modify that specific entry ID, an unauthenticated attacker can overwrite any entry in the database by guessing or enumerating its ID.

2. Attack Vector Analysis

  • Endpoint: wp-admin/admin-ajax.php
  • Action: submit_nex_form (Registered via wp_ajax_nopriv_submit_nex_form and wp_ajax_submit_nex_form)
  • Vulnerable Parameter: nf_set_entry_update_id
  • Authentication: None required (Unauthenticated).
  • Preconditions: A form must be created and published on the site. The attacker needs the ID of a target entry (entries are typically indexed incrementally).

3. Code Flow (Inferred)

  1. Entry Point: An AJAX POST request is sent to admin-ajax.php with action=submit_nex_form.
  2. Hook Registration: The plugin registers submit_nex_form for both logged-in and guest users.
  3. Function Logic:
    • The function retrieves form data from $_POST.
    • It checks for the presence of nf_set_entry_update_id.
    • If present, it uses this ID to perform a database UPDATE query on the entries table (e.g., {$wpdb->prefix}nex_forms_entries).
    • Sink: The update method in NEXForms_Database_Actions or a direct $wpdb->update call in submit_nex_form() is executed without validating user ownership of the nf_set_entry_update_id.

4. Nonce Acquisition Strategy

NEX-Forms generally uses a nonce for form submissions to prevent CSRF, but this nonce is exposed to unauthenticated users on any page where a form is displayed.

  1. Identify Shortcode: The plugin uses [nex_forms id="FORM_ID"].
  2. Setup Page: Create a public page containing a NEX-Form.
  3. Extract Nonce via Browser:
    • Navigate to the page.
    • NEX-Forms localizes its configuration in a JS object.
    • JS Variable: window.nex_forms_params (inferred from typical NEX-Forms script localization).
    • Nonce Key: window.nex_forms_params?.nonce or window.nex_forms_params?.nex_forms_nonce.
    • Alternative: The nonce may be in a hidden input field named _wpnonce or nex_forms_nonce inside the <form> element.

5. Exploitation Strategy

Step 1: Create a target entry

First, simulate a legitimate submission to create an entry that we will later "hijack" and overwrite.

Step 2: The Attack Request

Send a malicious AJAX request targeting the ID of the entry created in Step 1.

  • URL: http://TARGET_SITE/wp-admin/admin-ajax.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body Parameters:
    • action: submit_nex_form
    • nex_forms_nonce: [EXTRACTED_NONCE]
    • nf_set_entry_update_id: [TARGET_ENTRY_ID]
    • form_id: [FORM_ID]
    • field_data: (Serialized string of form fields, e.g., phone_number=HACKED&email=hacked@example.com)
    • Note: NEX-Forms often expects fields in a specific format or a single field_data string. If the plugin uses individual POST keys, they usually look like nex_forms_field_[FIELD_ID].

6. Test Data Setup

  1. Create a Form:
    # Using WP-CLI to ensure a form exists (exact table name may vary based on version)
    wp db query "INSERT INTO wp_nex_forms (title, form_data) VALUES ('Test Form', '...')"
    
  2. Place Shortcode: Create a page with [nex_forms id="1"].
  3. Create Victim Entry: Manually submit the form once or insert a record into wp_nex_forms_entries.

7. Expected Results

  • Response: The server should return a success message (often JSON, e.g., {"status":"success"}).
  • Impact: The database record in wp_nex_forms_entries corresponding to nf_set_entry_update_id will be updated with the new values provided in the attack request, effectively overwriting the original user's submission.

8. Verification Steps

  1. Identify Table: Check for the entries table, likely wp_nex_forms_entries or wp_wap_nex_forms_entries (based on NF_TABLE_PREFIX in main.php).
  2. Check Content:
    wp db query "SELECT * FROM wp_nex_forms_entries WHERE m_id = [TARGET_ENTRY_ID]"
    
    Confirm that the fields (stored as serialized data or in columns) reflect the "HACKED" values sent in the exploit.

9. Alternative Approaches

  • Direct Update Action: Check if wp_ajax_nopriv_nf_update_record is accessible. While the constructor in class.db.php registers wp_ajax_nf_update_record, check if there is a corresponding nopriv version.
  • Parameter Fuzzing: If field_data is not the correct parameter, observe a legitimate submission using the browser's Network tab to identify the exact field names (e.g., nex_forms_field_1, nex_forms_field_2).
  • ID Enumeration: If entry IDs are not known, attempt to overwrite IDs 1 through 100 to prove the vulnerability exists without prior knowledge of specific entries.
Research Findings
Static analysis — not yet PoC-verified

Summary

The NEX-Forms plugin for WordPress is vulnerable to an unauthenticated Insecure Direct Object Reference (IDOR) via the nf_set_entry_update_id parameter. Because the plugin fails to verify user ownership or permissions when updating records, an attacker can overwrite arbitrary form entries in the database by enumerating their IDs.

Vulnerable Code

// main.php line 2945
$check_entry_update = isset($_REQUEST['nf_set_entry_update_id']) ? sanitize_text_field($_REQUEST['nf_set_entry_update_id']) : false;

// ---

// main.php line 2981
else if($check_entry_update)
	{
	$insert = $wpdb->update($wpdb->prefix.'wap_nex_forms_entries', // phpcs:ignore WordPress.DB.DirectDatabaseQuery
	array(								
		'nex_forms_Id'			=>	$set_nex_forms_id,
		'page'					=>	sanitize_text_field($_POST['page']),
		'ip'					=>  sanitize_text_field($_POST['ip']),
        // ... (truncated attributes)
		'form_data'				=>	json_encode($data_array),
        // ...
		), array(	'Id' => sanitize_text_field($check_entry_update))
	 );

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/nex-forms-express-wp-form-builder/9.1.9/includes/classes/class.db.php /home/deploy/wp-safety.org/data/plugin-versions/nex-forms-express-wp-form-builder/9.1.10/includes/classes/class.db.php
--- /home/deploy/wp-safety.org/data/plugin-versions/nex-forms-express-wp-form-builder/9.1.9/includes/classes/class.db.php	2026-01-28 13:21:10.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/nex-forms-express-wp-form-builder/9.1.10/includes/classes/class.db.php	2026-02-27 07:34:38.000000000 +0000
@@ -11,7 +11,6 @@
 		
 		public function __construct(){
 			
-			add_action('wp_ajax_deactivate_license', array($this,'deactivate_license'));
 			add_action('wp_ajax_nf_insert_record', array($this,'insert_record'));
 			add_action('wp_ajax_nf_update_record', array($this,'update_record'));
 			add_action('wp_ajax_nf_delete_record', array($this,'delete_record'));
@@ -48,7 +47,6 @@
 			add_action('wp_ajax_nf_send_test_email', array($this,'nf_send_test_email'));
 			
 			add_action('wp_ajax_update_paypal', array($this,'update_paypal'));
-			add_action('wp_ajax_get_data', array($this,'NEXForms_get_data'));
 			add_action('wp_ajax_get_c_logic_ui', array($this,'get_c_logic_ui'));
 						
 		
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/nex-forms-express-wp-form-builder/9.1.9/main.php /home/deploy/wp-safety.org/data/plugin-versions/nex-forms-express-wp-form-builder/9.1.10/main.php
--- /home/deploy/wp-safety.org/data/plugin-versions/nex-forms-express-wp-form-builder/9.1.9/main.php	2026-01-28 13:21:10.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/nex-forms-express-wp-form-builder/9.1.10/main.php	2026-02-27 07:34:38.000000000 +0000
@@ -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.9
+Version: 9.1.10
 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
@@ -2701,16 +2701,7 @@
 		 );
 	die();
 }
-$update_entry = isset($_REQUEST['nf_update_entry']) ? sanitize_text_field($_REQUEST['nf_update_entry']) : false;
-$create_entry = isset($_REQUEST['nf_create_entry']) ? sanitize_text_field($_REQUEST['nf_create_entry']) : false;
 
-if($update_entry || $create_entry)
-	{
-	if($update_entry)
-		submit_nex_form($entry_action = 'update_entry');	
-	if($create_entry)
-		submit_nex_form($entry_action = 'update_entry');	
-	}
 	
 
 function submit_nex_form($entry_action = false){
@@ -2942,85 +2933,9 @@
 	
 	if($save_to_db)
 		{
-		$check_entry_update = isset($_REQUEST['nf_set_entry_update_id']) ? sanitize_text_field($_REQUEST['nf_set_entry_update_id']) : false;
-		$check_entry_redirect_update = isset($_REQUEST['nf_entry_redirect_id']) ? sanitize_text_field($_REQUEST['nf_entry_redirect_id']) : false;
 		
 		
-		if($check_entry_redirect_update)
-			{
-            // ... (removed update logic)
-			}
 		
-		else if($check_entry_update)
-			{
-            // ... (removed update logic)
-			}
-		else
-			{
 			$insert = $wpdb->insert($wpdb->prefix.'wap_nex_forms_entries', // phpcs:ignore WordPress.DB.DirectDatabaseQuery
 				array(								
 					'nex_forms_Id'			=>	$set_nex_forms_id,
@@ -3050,9 +2965,6 @@
 			$update = $wpdb->update ( $wpdb->prefix . 'wap_nex_forms', array('entry_count'=>$set_count), array(	'Id' => sanitize_text_field($form_attr->Id)) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
 			
 			$entry_id = $wpdb->insert_id;
-			}
-		
-		
 		
 		}

Exploit Outline

1. Identify a page on the target WordPress site where a NEX-Form is published. 2. Extract the `nex_forms_nonce` required for AJAX submissions. This is typically found in the localized JavaScript object `nex_forms_params` or within a hidden input field in the form HTML. 3. Prepare a POST request to `wp-admin/admin-ajax.php` with the parameter `action` set to `submit_nex_form`. 4. Include the parameter `nf_set_entry_update_id` set to the integer ID of the submission entry you wish to overwrite. 5. Include the desired form field data (e.g., `nex_forms_field_1=NewValue`). 6. Send the request. Because the plugin lacks a permission check for the provided ID, it will execute a SQL UPDATE instead of an INSERT, replacing the existing entry's data with the attacker's payload.

Check if your site is affected.

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