NEX-Forms – Ultimate Forms Plugin for WordPress <= 9.1.9 - Missing Authorization to Unauthenticated Arbitrary Form Entry Modification via nf_set_entry_update_id
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:NTechnical Details
<=9.1.9What Changed in the Fix
Changes introduced in v9.1.10
Source Code
WordPress.org SVNThis 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 viawp_ajax_nopriv_submit_nex_formandwp_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)
- Entry Point: An AJAX POST request is sent to
admin-ajax.phpwithaction=submit_nex_form. - Hook Registration: The plugin registers
submit_nex_formfor both logged-in and guest users. - 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
UPDATEquery on the entries table (e.g.,{$wpdb->prefix}nex_forms_entries). - Sink: The
updatemethod inNEXForms_Database_Actionsor a direct$wpdb->updatecall insubmit_nex_form()is executed without validating user ownership of thenf_set_entry_update_id.
- The function retrieves form data from
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.
- Identify Shortcode: The plugin uses
[nex_forms id="FORM_ID"]. - Setup Page: Create a public page containing a NEX-Form.
- 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?.nonceorwindow.nex_forms_params?.nex_forms_nonce. - Alternative: The nonce may be in a hidden input field named
_wpnonceornex_forms_nonceinside 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_formnex_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_datastring. If the plugin uses individual POST keys, they usually look likenex_forms_field_[FIELD_ID].
6. Test Data Setup
- 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', '...')" - Place Shortcode: Create a page with
[nex_forms id="1"]. - 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_entriescorresponding tonf_set_entry_update_idwill be updated with the new values provided in the attack request, effectively overwriting the original user's submission.
8. Verification Steps
- Identify Table: Check for the entries table, likely
wp_nex_forms_entriesorwp_wap_nex_forms_entries(based onNF_TABLE_PREFIXinmain.php). - Check Content:
Confirm that the fields (stored as serialized data or in columns) reflect the "HACKED" values sent in the exploit.wp db query "SELECT * FROM wp_nex_forms_entries WHERE m_id = [TARGET_ENTRY_ID]"
9. Alternative Approaches
- Direct Update Action: Check if
wp_ajax_nopriv_nf_update_recordis accessible. While the constructor inclass.db.phpregisterswp_ajax_nf_update_record, check if there is a correspondingnoprivversion. - Parameter Fuzzing: If
field_datais 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.
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
@@ -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')); @@ -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.