CVE-2026-1948

NEX-Forms – Ultimate Forms Plugin for WordPress <= 9.1.9 - Missing Authorization to Authenticated (Subscriber+) License Deactivation via deactivate_license

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
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 unauthorized modification of data due to a missing capability check on the deactivate_license() function in all versions up to, and including, 9.1.9. This makes it possible for authenticated attackers, with Subscriber-level access and above, to to deactivate the plugin license.

CVSS Vector Breakdown

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

Technical Details

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

What Changed in the Fix

Changes introduced in v9.1.10

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-1948 (NEX-Forms License Deactivation) ## 1. Vulnerability Summary The **NEX-Forms – Ultimate Forms Plugin for WordPress** (up to 9.1.9) contains a missing authorization vulnerability in its license deactivation logic. The function `deactivate_license` in the `…

Show full research plan

Exploitation Research Plan: CVE-2026-1948 (NEX-Forms License Deactivation)

1. Vulnerability Summary

The NEX-Forms – Ultimate Forms Plugin for WordPress (up to 9.1.9) contains a missing authorization vulnerability in its license deactivation logic. The function deactivate_license in the NEXForms_Database_Actions class is registered as a WordPress AJAX action (wp_ajax_deactivate_license) but fails to implement any capability checks (like current_user_can()) or nonce verification. This allows any authenticated user, including those with Subscriber roles, to deactivate the plugin's license, potentially disabling premium features across the site.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Method: POST
  • Action: deactivate_license
  • Parameter: action=deactivate_license
  • Authentication: Required (Subscriber-level or higher)
  • Preconditions: The plugin must be "activated" (license key present or nf_activated option set).

3. Code Flow

  1. Hook Registration: In includes/classes/class.db.php, the __construct method of NEXForms_Database_Actions registers the AJAX hook:
    add_action('wp_ajax_deactivate_license', array($this,'deactivate_license'));
    
  2. AJAX Entry: When a request is sent to admin-ajax.php with action=deactivate_license, WordPress invokes NEXForms_Database_Actions::deactivate_license().
  3. Missing Check: Unlike other methods in the same class (e.g., get_c_logic_ui which checks current_user_can( NF_USER_LEVEL )), deactivate_license lacks this check.
  4. Vulnerable Sink: The function (located in includes/classes/class.db.php, though truncated in the snippet) proceeds to communicate with the license server (likely Freemius or a custom Basix endpoint) and updates local options like nf_activated or nf_fs_activated to a deactivated state.

4. Nonce Acquisition Strategy

Based on the vulnerability description ("Missing Authorization") and analysis of the get_c_logic_ui function in the source code, it is highly likely that no nonce check is performed. However, if the agent needs to verify the presence of a nonce or find localized data:

  1. Shortcode: The plugin scripts typically load when the [nex_forms] shortcode is present.
  2. Creation: wp post create --post_type=page --post_status=publish --post_content='[nex_forms id="1"]'
  3. Localization: NEX-Forms often localizes data in a variable named nex_forms_admin or nf_ajax_obj.
  4. Extraction:
    • browser_navigate to the created page or /wp-admin/.
    • browser_eval("window.nf_ajax_obj?.nonce") or browser_eval("window.nex_forms_admin?.security").
    • Note: Since the vulnerability is a missing authorization check on an AJAX action, the most likely path is that the action is the only requirement.

5. Exploitation Strategy

The exploit will be performed by an authenticated Subscriber user sending a direct AJAX request.

Step-by-Step Plan:

  1. Login: Authenticate as the Subscriber user.
  2. Trigger Deactivation: Send a POST request to admin-ajax.php.
    • URL: http://localhost:8080/wp-admin/admin-ajax.php
    • Content-Type: application/x-www-form-urlencoded
    • Body: action=deactivate_license
  3. Analyze Response: A successful deactivation might return a 1, a JSON success message, or a redirect URL.

6. Test Data Setup

  1. Install Plugin: Ensure version 9.1.9 is installed.
  2. Simulate Activation: Since we cannot use a real license key in a test environment, we must manually set the activation options to simulate a "Pro" state:
    • wp option update nf_activated 'true'
    • wp option update nf_fs_activated 'true'
  3. Create User: Create a Subscriber user:
    • wp user create attacker attacker@example.com --role=subscriber --user_pass=password

7. Expected Results

  • The HTTP response from admin-ajax.php should be 200 OK.
  • The plugin state should change from "Activated" to "Deactivated".
  • Premium features (if accessible) should become locked or the license status in the DB should be cleared.

8. Verification Steps

After the HTTP request, use WP-CLI to check the database state:

  1. Check License Option:
    • wp option get nf_activated
    • wp option get nf_fs_activated
  2. Expected Outcome: These options should return an empty string, false, or be deleted entirely (depending on the exact implementation in the truncated source). If the value changed from 'true' to something else, the exploit is confirmed.

9. Alternative Approaches

If the action=deactivate_license requires additional parameters, they might be:

  • license_key: (though usually the plugin knows its own key).
  • nf_license_deactivate: A common alternate parameter name in some versions.

If the request requires a nonce despite the "Missing Authorization" label:

  • Identify the admin menu page for NEX-Forms (usually ?page=nex-forms-dashboard).
  • Navigate there as a Subscriber (if NF_USER_LEVEL permits) or as Admin to extract the nonce, then try to use it with the Subscriber's session. (NEX-Forms sometimes accidentally exposes admin nonces to lower users if their dashboards overlap).
Research Findings
Static analysis — not yet PoC-verified

Summary

The NEX-Forms plugin for WordPress fails to perform authorization or nonce checks on its license deactivation AJAX endpoint. This allows any authenticated user, such as a Subscriber, to deactivate the plugin's premium license, potentially disabling premium functionality across the site.

Vulnerable Code

// includes/classes/class.db.php line 14
add_action('wp_ajax_deactivate_license', array($this,'deactivate_license'));

---

// includes/classes/class.db.php line 3216
function deactivate_license(){
	$theme = wp_get_theme();
	if($theme->Name!='NEX-Forms Demo')
		{
		global $wpdb;
		$delete = $wpdb->query('DELETE FROM '.$wpdb->prefix.'options WHERE option_name LIKE "1983017%"'); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
		$api_params = array( 'client_deactivate_license' => 1,'version' => '9','key'=>get_option('7103891'));
		$response = wp_remote_post( 'https://basixonline.net/activate-license-new-api-v3', array('timeout'   => 10,'sslverify' => false,'body'  => $api_params) );
		update_option( 'nf_activated', false );
		update_option( 'nf_fs_activated', false );
		}
}

Security Fix

--- /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'));
@@ -3213,18 +3176,7 @@
 		die();
 	}
 	
-	function deactivate_license(){
-		$theme = wp_get_theme();
-		if($theme->Name!='NEX-Forms Demo')
-			{
-			global $wpdb;
-			$delete = $wpdb->query('DELETE FROM '.$wpdb->prefix.'options WHERE option_name LIKE "1983017%"'); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
-			$api_params = array( 'client_deactivate_license' => 1,'version' => '9','key'=>get_option('7103891'));
-			$response = wp_remote_post( 'https://basixonline.net/activate-license-new-api-v3', array('timeout'   => 10,'sslverify' => false,'body'  => $api_params) );
-			update_option( 'nf_activated', false );
-			update_option( 'nf_fs_activated', false );
-			}
-	}

Exploit Outline

The exploit is performed by an authenticated user (Subscriber-level is sufficient) sending a POST request to the WordPress AJAX endpoint. 1. Log in as a Subscriber user. 2. Send a POST request to /wp-admin/admin-ajax.php with the 'action' parameter set to 'deactivate_license'. 3. No additional parameters or security nonces are required because the 'deactivate_license' function lacks any authorization (current_user_can) or integrity (check_ajax_referer) checks. 4. Upon execution, the server deletes license-related options and sets 'nf_activated' and 'nf_fs_activated' to false, disabling the plugin's premium state.

Check if your site is affected.

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