UsersController::actionGetPasswordResetUrl lets a non-admin control-panel user who holds the administrateUsers permission mint a valid password reset URL for any user, including administrators. Because actionSetPassword validates only the verification code embedded in that URL (with no tie to the caller’s session), the attacker can set a new password for an admin account and log in as that admin.
Root Cause
UsersController.php (5.10.9, lines 761-795): the action enforces userActionChecks() (editUsers + Team edition + POST), requirePermission('administrateUsers') (line 764) and _verifyElevatedSession() (line 766). Crucially, _verifyElevatedSession() re-verifies the caller’s own password (line 2666) — it protects the attacker’s session, not the target. The action then resolves an arbitrary userId (line 771) and calls getPasswordResetUrl($user) (line 778) with no if ($user->admin) requireAdmin() guard.
Every destructive sibling action DOES guard admin targets: actionDeactivateUser (lines 2199-2200, with the comment "only an admin should be able to deactivate another admin"), actionDeleteUser (line 2237), actionUnlockUser, and suspend/impersonate via canSuspend/canImpersonate (line 542). actionGetPasswordResetUrl is the outlier that omits the guard, proving the admin-target boundary is the intended design.
getPasswordResetUrl (Users.php:534) mints a valid set-password URL regardless of target status. actionSetPassword (line 855) validates ONLY isVerificationCodeValidForUser($user, $code) (line 904) — with no link to the caller’s session — then persists the new password via saveElement($user) (line 911).
Proof of Concept
- Log in as a non-admin holding
editUsers + administrateUsers (Team+ edition).
- Establish an elevated session with your OWN password.
POST users/get-password-reset-url with `userId=
Impact
A non-admin holding administrateUsers (a permission explicitly grantable to non-admins, nested under editUsers in UserPermissions.php:495) obtains a password-reset URL for an administrator, sets a new password, and authenticates as that admin → complete control-panel takeover.
Report ID: 1562
UsersController::actionGetPasswordResetUrllets a non-admin control-panel user who holds theadministrateUserspermission mint a valid password reset URL for any user, including administrators. BecauseactionSetPasswordvalidates only the verification code embedded in that URL (with no tie to the caller’s session), the attacker can set a new password for an admin account and log in as that admin.Root Cause
UsersController.php(5.10.9, lines 761-795): the action enforcesuserActionChecks()(editUsers + Team edition + POST),requirePermission('administrateUsers')(line 764) and_verifyElevatedSession()(line 766). Crucially,_verifyElevatedSession()re-verifies the caller’s own password (line 2666) — it protects the attacker’s session, not the target. The action then resolves an arbitraryuserId(line 771) and callsgetPasswordResetUrl($user)(line 778) with noif ($user->admin) requireAdmin()guard.Every destructive sibling action DOES guard admin targets:
actionDeactivateUser(lines 2199-2200, with the comment "only an admin should be able to deactivate another admin"),actionDeleteUser(line 2237),actionUnlockUser, and suspend/impersonate viacanSuspend/canImpersonate(line 542).actionGetPasswordResetUrlis the outlier that omits the guard, proving the admin-target boundary is the intended design.getPasswordResetUrl(Users.php:534) mints a valid set-password URL regardless of target status.actionSetPassword(line 855) validates ONLYisVerificationCodeValidForUser($user, $code)(line 904) — with no link to the caller’s session — then persists the new password viasaveElement($user)(line 911).Proof of Concept
editUsers+administrateUsers(Team+ edition).POST users/get-password-reset-urlwith `userId=Impact
A non-admin holding
administrateUsers(a permission explicitly grantable to non-admins, nested undereditUsersinUserPermissions.php:495) obtains a password-reset URL for an administrator, sets a new password, and authenticates as that admin → complete control-panel takeover.Report ID: 1562