Submit #1019652: ZenTao ZenTao PMS 22.6 Privilege Escalationinfo

TitleZenTao ZenTao PMS 22.6 Privilege Escalation
DescriptionA vertical privilege escalation vulnerability exists in ZenTao PMS 22.6 (Open Source Edition). The root cause is in userModel::checkGroupChange (module/user/model.php, lines 765-785), which applies security-sensitive group membership changes without any authorization ceiling check. An authenticated non-administrative user who holds the user.edit method privilege (for example, members of group 9 in the default permission matrix) can submit an arbitrary group ID array through the group[] POST parameter of the user edit form (user/config/form.php line 66 defines edit['group'] as an array). The entry point user::edit (module/user/control.php, lines 561-580) only collects form data with form::data and adds the id field of the target userID, then calls userModel::update without object-level ownership or administrator checks, so any userID can be edited. userModel::update (module/user/model.php, line 658) excludes the group field from direct writes to the zt_user table and instead applies it through checkGroupChange at line 700. checkGroupChange diffs the old and new group arrays, deletes the old rows in zt_usergroup, and writes the new groups via createUserGroup (line 577) using dao->replace. The entire flow performs no hasPriv check, no admin check, and no group-level ceiling validation. Group IDs are not validated for legitimacy either, so arbitrary values are written to the database as-is. As a result, a low-privileged user can submit group[] = 1 and insert themselves into the administrator group. On subsequent requests, getUserPriv (module/common/model.php, line 1407) reloads rights from zt_grouppriv for group 1 (1547 privilege items in the instance database backup, including admin.safe and group.managePriv), so the attacker obtains full administrator capabilities such as managing permission groups, security policy, backups and extensions. The userID parameter is not restricted to the attacker's own account, so any user can be moved into any group, persistently stored in zt_usergroup. The authorization gate commonModel::checkPriv (module/common/model.php, line 1215) with hasPriv (line 1363) performs only method-level authorization for user.edit. The instance database backup confirms that groups 9, 16 and 20 legitimately hold user.edit in the default permission matrix while the ordinary group 2 does not, so passing the gate is legitimate authorization rather than a bypass. The defect is that the group change operation itself lacks a privilege ceiling: only super administrators or holders of a group management privilege such as group.manageMember should be allowed to change group membership, and no user should be able to assign a group higher than their own. Exploitation preconditions: the attacker must be a logged-in non-admin user whose group holds the user.edit method privilege. The POST must carry verifyPassword computed as md5 of the attacker's own password hash plus the session rand, which the attacker can compute; the GET form refreshes the rand, so a fresh value must be taken. The POST must also include password1 and password2 fields (otherwise control.php line 576 raises an error on substr(false)). These conditions are proof-of-concept friction, not effective protections. Design contrast: my::editProfile (module/my/control.php, line 1483) pins the group field with the current user's own groups, proving that self-service profile editing is not intended to allow group changes. The user module routes group changes into checkGroupChange without any equivalent guard. getGroupsByVisions (module/user/model.php, line 1316) only filters dropdown display items by visions; the server never validates that the submitted group set is a subset of the displayed set, and no ceiling is enforced. Impact: a single POST request elevates a user who can only manage user profiles to full administrator group rights and allows arbitrary users to be assigned to arbitrary groups in a persistent way. The issue was independently confirmed against source code and an instance database backup, and reproduced live against a deployed instance using the script repro/af_56ae5bd2/repro_checkgroupchange_privesc.py, which elevated a group 9 account to group 1 and verified that access to admin.safe changed from HTTP 302 to HTTP 200. Severity is rated high with CVSS 8.8 (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H). A related issue in createUserGroup reached from the user::create entry point is tracked as a separate finding. Both locations need a ceiling fix, otherwise only half of the attack surface is closed. Remediation: enforce an assignment ceiling in checkGroupChange or at the user::edit entry point so that only super administrators or holders of a group management privilege can change group membership and cannot assign groups higher than their own; alternatively require the group manage privilege for any group change.
User
 benjamin_123 (UID 101995)
Submission10/08/2026 08:23 (28 days ago)
Moderation11/03/2026 15:19 (26 days later)
StatusAccepted
VulDB entry430286 [EasySoft ZenTao PMS up to 22.6 User module/user/model.php checkGroupChange group privileges management]
Points17

Interested in the pricing of exploits?

See the underground prices here!