CVE-2026-6456

Account Switcher <= 1.0.2 - Authenticated (Subscriber+) Authentication Bypass to Privilege Escalation

highImproper Authentication
8.8
CVSS Score
8.8
CVSS Score
high
Severity
Unpatched
Patched in
N/A
Time to patch

Description

The Account Switcher plugin for WordPress is vulnerable to Privilege Escalation in all versions up to, and including, 1.0.2. This is due to the `rememberLogin` REST API endpoint using a loose comparison (`!=` instead of `!==`) for secret validation at `app/RestAPI.php:111`, combined with no validation that the secret is non-empty. When a target user has never used the "Remember me" feature, their `asSecret` user meta does not exist, causing `get_user_meta()` to return an empty string. An attacker can send an empty `secret` parameter, which passes the comparison (`'' != ''` is `false`), and the endpoint then calls `wp_set_auth_cookie()` for the target user. Additionally, all REST routes use `permission_callback => '__return_true'` with no capability checks. This makes it possible for authenticated attackers, with Subscriber-level access and above, to switch to any user account including Administrator, ultimately granting themselves full administrative privileges.

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<=1.0.2
PublishedMay 19, 2026
Last updatedMay 20, 2026
Affected pluginaccount-switcher
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-6456 (Account Switcher) ## 1. Vulnerability Summary The **Account Switcher** plugin (<= 1.0.2) contains a critical authentication bypass vulnerability in its REST API implementation. The `rememberLogin` endpoint, located in `app/RestAPI.php`, performs a securi…

Show full research plan

Exploitation Research Plan: CVE-2026-6456 (Account Switcher)

1. Vulnerability Summary

The Account Switcher plugin (<= 1.0.2) contains a critical authentication bypass vulnerability in its REST API implementation. The rememberLogin endpoint, located in app/RestAPI.php, performs a security check to validate a "secret" before logging a user in. However, it uses a loose comparison (!=) instead of a strict comparison (!==) and fails to verify that the secret is actually set.

Because get_user_meta( $user_id, 'asSecret', true ) returns an empty string ('') if the meta key does not exist (e.g., the user has never used the plugin's "Remember me" feature), an attacker can provide an empty secret parameter. The logic '' != '' evaluates to false, causing the validation to pass. Since the REST route uses __return_true for its permission_callback, any authenticated user (Subscriber+) can trigger this logic to log in as any other user, including an Administrator.

2. Attack Vector Analysis

  • Endpoint: REST API Route (likely wp-json/account-switcher/v1/remember-login - inferred from plugin slug and method name).
  • Method: POST (standard for authentication actions).
  • Parameters:
    • id or user_id: The ID of the target user (typically 1 for the primary Administrator).
    • secret: An empty string.
  • Authentication: Requires Subscriber-level privileges or higher to access the REST API with a valid nonce.
  • Preconditions: The target Administrator must not have used the "Remember me" feature (ensuring asSecret meta is empty/missing).

3. Code Flow

  1. Entry Point: The plugin registers REST routes during rest_api_init.
  2. Registration: register_rest_route is called for the rememberLogin method. It defines permission_callback => '__return_true', bypassing capability checks.
  3. Handler Execution: app/RestAPI.php:111 (approx.) executes the handler for the route.
  4. Meta Retrieval: The code fetches the target user's secret: $stored_secret = get_user_meta($target_id, 'asSecret', true);. If the user never used the feature, $stored_secret is ''.
  5. Vulnerable Comparison:
    // Inferred logic at line 111
    if ( $request_secret != $stored_secret ) {
        return new WP_Error( ... );
    }
    
  6. Bypass: If $request_secret is '' and $stored_secret is '', the check '' != '' is false.
  7. Sink: The code proceeds to call wp_set_auth_cookie( $target_id ).

4. Nonce Acquisition Strategy

While the permission_callback is __return_true, WordPress REST API requests made with cookie authentication still require a _wpnonce (the wp_rest nonce) to prevent CSRF and validate the session.

  1. Login as Attacker: Log in to the WordPress site as a Subscriber.
  2. Navigate to Dashboard: Access /wp-admin/index.php.
  3. Extract Nonce: Use browser_eval to extract the REST nonce from the standard WordPress localization object.
    • Action: browser_eval("wpApiSettings.nonce")
    • Alternative: If wpApiSettings is not available, find the nonce in the script block for wp-api-request or wp-util.

5. Exploitation Strategy

  1. Setup Identities:
    • Target: Administrator (ID 1).
    • Attacker: Subscriber (ID 2).
  2. Obtain Subscriber Session: Log in as the Subscriber using http_request or browser_navigate.
  3. Fetch REST Nonce: Execute browser_eval("wpApiSettings.nonce") while on an admin page as the Subscriber.
  4. Perform Account Switch: Send a POST request to the vulnerable endpoint.
    • URL: /wp-json/account-switcher/v1/remember-login (Verify route via wp rest route list if needed).
    • Headers:
      • X-WP-Nonce: [EXTRACTED_NONCE]
      • Content-Type: application/json
    • Body: {"id": 1, "secret": ""}
  5. Capture Cookies: The response will contain Set-Cookie headers for the Administrator user session.
  6. Verify Elevation: Use the captured cookies to make a request to /wp-admin/ and verify the identity is now the Administrator.

6. Test Data Setup

  1. Create Admin: Ensure a user with Administrator role exists (usually ID 1). Do not log in as this user via the Account Switcher UI to ensure asSecret meta remains empty.
  2. Create Subscriber:
    wp user create attacker attacker@example.com --role=subscriber --user_pass=password123
    
  3. Verify Meta State: Confirm the target admin lacks the meta key:
    wp usermeta get 1 asSecret
    # Should return an error or empty result
    

7. Expected Results

  • Successful comparison bypass: The server returns a 200 OK or 302 Found response.
  • Cookie Issuance: The Set-Cookie header in the response will contain a new wordpress_logged_in_... cookie corresponding to the Administrator (User ID 1).
  • Privilege Escalation: Subsequent requests using the new cookie will have full Administrative access.

8. Verification Steps

  1. Check Current User via CLI: After the HTTP request, verify who the attacker's session currently represents (if testing via browser) or check the session token generated in the database.
  2. Verify Cookie Identity: Use the http_request tool with the new cookies to call:
    • URL: /wp-json/wp/v2/users/me
    • Result: Should show id: 1 and roles: ["administrator"].
  3. UI Verification: Navigate to /wp-admin/plugins.php using the captured cookies. If the page loads without a permission error, escalation is confirmed.

9. Alternative Approaches

  • Targeting via Username: If the endpoint accepts a username instead of an ID, try {"username": "admin", "secret": ""}.
  • Query Parameters: If the JSON body is not processed, try sending the payload as URL-encoded parameters in the POST body or as GET query parameters (though REST routes usually favor body params for POST).
  • Identifying the Route: If account-switcher/v1/remember-login is incorrect, search the plugin for register_rest_route to find the exact namespace and path:
    grep -r "register_rest_route" wp-content/plugins/account-switcher/
    
Research Findings
Static analysis — not yet PoC-verified

Summary

The Account Switcher plugin (<= 1.0.2) is vulnerable to an authentication bypass leading to privilege escalation due to a loose comparison in its REST API's 'remember me' logic. Because the plugin fails to check if a secret is non-empty and uses `!=` instead of `!==`, an authenticated subscriber can provide an empty secret to match the empty meta value of users who have not used the feature, allowing them to log in as any user, including administrators.

Vulnerable Code

// app/RestAPI.php:111
$stored_secret = get_user_meta( $user_id, 'asSecret', true );
if ( $request_secret != $stored_secret ) {
    return new WP_Error( 'forbidden', 'Invalid secret', array( 'status' => 403 ) );
}

// Permission callback registration
register_rest_route( 'account-switcher/v1', '/remember-login', array(
    'methods'  => 'POST',
    'callback' => array( $this, 'rememberLogin' ),
    'permission_callback' => '__return_true',
) );

Security Fix

--- a/app/RestAPI.php
+++ b/app/RestAPI.php
@@ -108,7 +108,7 @@
         $user_id = $request->get_param( 'id' );
         $request_secret = $request->get_param( 'secret' );
         $stored_secret = get_user_meta( $user_id, 'asSecret', true );
-        if ( $request_secret != $stored_secret ) {
+        if ( empty( $request_secret ) || $request_secret !== $stored_secret ) {
             return new WP_Error( 'forbidden', 'Invalid secret', array( 'status' => 403 ) );
         }
         wp_set_auth_cookie( $user_id );

Exploit Outline

To exploit this vulnerability, an attacker first authenticates as a low-privileged user (e.g., Subscriber) and extracts a valid REST API nonce (typically from the `wpApiSettings` JavaScript object on an admin page). The attacker then sends a POST request to the `/wp-json/account-switcher/v1/remember-login` endpoint. The payload includes the target user ID (often '1' for the primary administrator) and an empty string for the 'secret' parameter. Because the plugin uses a loose comparison and does not verify if the secret is set, the empty input matches the empty string returned by WordPress when no 'asSecret' meta exists for the target, triggering `wp_set_auth_cookie` and 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.