Skip to content

Non-admin with administrateUsers permissions can mint an admin password reset URL

High
angrybrad published GHSA-6qw4-cjqw-fj72 Aug 18, 2026

Package

composer craftcms/cms (Composer)

Affected versions

>= 5.0.0-RC1, < 5.10.11

Patched versions

5.10.11

Description

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-&gt;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

  1. Log in as a non-admin holding editUsers + administrateUsers (Team+ edition).
  2. Establish an elevated session with your OWN password.
  3. 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

Severity

High

CVE ID

No known CVE

Weaknesses

Missing Authorization

The product does not perform an authorization check when an actor attempts to access a resource or perform an action. Learn more on MITRE.