The Exploit
An unauthenticated attacker can invoke the REST API endpoint without authentication and request an impersonation link for any user, including administrators.
GET /wp-json/multi-manager-wp/v1/users/impersonate?id=1 HTTP/1.1
Host: target.local
User-Agent: curl/7.68.0
Accept: */*
The endpoint returns a signed impersonation token or redirects directly to an authenticated session as the target user (typically user ID 1, the initial administrator). An attacker observes a 200 response containing session credentials or an immediate redirect to the WordPress dashboard logged in as the administrator. The attacker can now perform any action the impersonated user could perform, including modifying site settings, creating backdoor accounts, or exfiltrating data.
What the Patch Did
Before
register_rest_route( $namespace, '/' . $users . '/impersonate', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'impersonate_user' ),
'permission_callback' => array( $this, 'check_permissions' ),
'args' => array(
'id' => array(
'description' => __( 'The ID of the user to impersonate.' ),
'type' => 'integer',
'required' => true,
),
),
),
) );
After
/*register_rest_route( $namespace, '/' . $users . '/impersonate', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'impersonate_user' ),
'permission_callback' => array( $this, 'check_permissions' ),
'args' => array(
'id' => array(
'description' => __( 'The ID of the user to impersonate.' ),
'type' => 'integer',
'required' => true,
),
),
),
) );*/
The patch entirely disables the REST route by commenting out the register_rest_route() call. This removes the entire endpoint from the WordPress REST API surface and prevents any request from reaching the impersonate_user callback, regardless of whether the check_permissions callback was robust. Rather than attempting to strengthen the permission check, the vendor chose complete feature removal, indicating the impersonation mechanism itself was considered too high-risk to maintain in production.
Root Cause
CWE-284: Improper Access Control. The REST endpoint /wp-json/multi-manager-wp/v1/users/impersonate accepted a user-supplied integer parameter id in the query string without enforcing authentication or authorization prior to route registration. Although a permission_callback function (check_permissions) was specified in the route definition, the vulnerability likely stemmed from that callback failing to verify the current user's authentication state or capabilities. The attacker-controlled id parameter flows directly into the impersonate_user callback, which trusts it to specify which user session to create. The trust boundary crossed is the REST API entry point itself: unauthenticated requests should never reach a route that performs privilege escalation, yet the endpoint accepted them and processed the impersonation request.
Why It Works
The load-bearing change is the removal of the register_rest_route() call entirely. If only the permission_callback had been strengthened—for instance, by adding a check like current_user_can( 'manage_users' )—the endpoint would remain registered and callable; an attacker might still discover it, attempt probing, or (in scenarios where the permission callback was misconfigured or had a logic bug) exploit it. By commenting out the route registration, WordPress never adds the endpoint to its routing table, so no HTTP request ever reaches the callback function at all. The permission callback becomes irrelevant because the route does not exist. The vendor's decision to disable rather than harden suggests they recognized that user impersonation, even when gated by a permission check, is fundamentally at odds with REST API best practices—sessions created outside normal authentication flows cannot be properly audited, and the feature is difficult to secure. The engineer added nothing else because nothing else was needed: the entire route registration block is the vulnerability, and its removal is the complete fix.
Hardening Checklist
-
Require explicit authentication before route registration. Use
'permission_callback' => function() { return is_user_logged_in(); }at minimum, or applyrest_ensure_logged_in()helper to reject unauthenticated requests before any callback executes. Do not rely solely oncheck_permissionsif route registration itself is unconditional. -
Audit
check_permissionscallback implementation. If the callback exists, examine its source code in the context of this vulnerability. Confirm it callscurrent_user_can()with the appropriate capability (e.g.,manage_users) and does not fall through to a defaulttrueorreturnwith no value. -
Disable high-risk features by default. User impersonation, session hijacking, or privilege-escalation utilities should be opt-in and off by default. Wrap
register_rest_route()in a conditional:if ( apply_filters( 'enable_impersonate_endpoint', false ) ) { ... }to allow advanced users to enable it only in trusted environments. -
Implement nonce validation on state-changing routes. Even if impersonation were re-enabled, add a
wp_verify_nonce()check inside the callback to ensure the request originated from a form or admin page, not a cross-site attack vector:wp_verify_nonce( $_REQUEST['_wpnonce'], 'impersonate_user' ). -
Log and rate-limit impersonation attempts. If the feature must exist, record every impersonation event (timestamp, impersonator, impersonated user, source IP) to an audit table and implement rate limiting via
wp_cache_get()andwp_cache_set()to block repeated failed or suspicious requests from a single IP.
References
- https://nvd.nist.gov/vuln/detail/CVE-2024-11028