thorsten/phpmyfaq: phpMyFAQ deleteGroup API lacks a privilege check, letting a non-SuperAdmin GROUP_DELETE admin destroy higher-privileged groups
What fixes it
- 4.2.0-betathorsten/phpmyfaq, composerGitHub
composer require "thorsten/phpmyfaq:^4.2.0-beta"
A fixed version on one release line is not the upgrade target for every line. Check your exact version and any vendor backports.
Description
Summary
phpMyFAQ's admin API endpoint for deleting a user group (POST /admin/api/group/delete, handled by GroupController::deleteGroup()) enforces only a coarse binary permission check that the caller holds the delegable GROUP_DELETE right. It performs no privilege-level comparison between the acting administrator and the target group. As a result, an authenticated, non-SuperAdmin administrator who holds GROUP_DELETE can permanently destroy any group, including groups that carry high-privilege rights (for example configuration editing or backup) that the attacker does not hold and cannot otherwise grant, join, or manage. Every other group-write endpoint in the same controller gates the operation on a privilege-level check; group deletion is the sole exception.
Root Cause
The application's group-write operations are designed to enforce a privilege-containment invariant: a non-SuperAdmin may only operate on a group whose rights the acting user fully holds, preventing privilege escalation through group membership inheritance. This invariant is enforced inline by the sibling write endpoints but is entirely absent from the deletion endpoint.
deleteGroup() calls userHasPermission(PermissionType::GROUP_DELETE), which resolves to a binary right-ownership check that does not consider the target group's privilege level. It then passes the attacker-controlled groupId directly to MediumPermission::deleteGroup(), which executes unconditional DELETE statements against the group, its memberships, its category and language restrictions, and its right assignments. No layer between the controller entry and the SQL sink compares the acting user's privileges against the target group's rights.
Vulnerable endpoint (paths relative to the repository root):
phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/GroupController.php
- deleteGroup(): gate is userHasPermission(GROUP_DELETE) only; no privilege-level check
- sink: $currentUser->perm->deleteGroup($groupId)
phpmyfaq/src/phpMyFAQ/Permission/MediumPermission.php
- deleteGroup(): raw DELETE statements, no authorization check
Sibling endpoints in the same controller that DO enforce the invariant:
updateGroup(), updateMembers(), updatePermissions() each check
isSuperAdmin() plus a per-right hasPermission() loop over the target group's rights.Impact
An authenticated administrator who holds the delegable GROUP_DELETE right, but who is not a SuperAdmin and does not hold the target group's rights, can permanently delete any group in the installation. Deleting a group destroys that group's permission grants, its member associations, and its category and language restrictions. Members of the deleted group immediately lose every right they inherited through it.
The attacker gains a destructive capability over privilege infrastructure that exceeds their own authority: they cannot grant themselves the target group's rights, cannot add themselves to the group, and cannot enable auto-join on it (each of those paths is privilege-gated), yet they can destroy the group outright. The demonstrated impact is unauthorized destruction of group records and the associated rights of other users; it does not by itself grant the attacker any new right or read access. Group permissions mode (the medium permission model) must be active for the endpoint to operate.
Proof of Concept
Preconditions:
- phpMyFAQ configured in medium permission mode (group permissions enabled).
- Attacker authenticated as an administrator holding the GROUP_DELETE right,
but not a SuperAdmin, and not holding the rights of the target group.
- Attacker holds a valid admin session and CSRF token (readable from any
admin page the attacker can already load).
- A target group (for example group id 5) exists that carries higher-privilege
rights the attacker lacks (for example configuration editing, backup).Reproduction:
- Authenticate to the admin panel as the non-SuperAdmin administrator holding
GROUP_DELETE. - Read the CSRF token for the group-delete action from an admin page.
- Send the deletion request against the higher-privileged group id.
POST /admin/api/group/delete HTTP/1.1
Host: <target>
Cookie: <authenticated admin session>
Content-Type: application/json
{"csrfToken": "<valid token>", "groupId": 5}Observed result: HTTP 200 with the group-deleted success message. Group 5 and all of its rows in faqgroup, faquser_group, faqgroup_right, and its category/language restriction tables are removed. Members of group 5 lose the rights they inherited from it, even though the acting administrator could not grant, join, or manage that group through any privilege-gated endpoint.
Attack Chain
- Exposure: The admin API route
POST /admin/api/group/deleteships enabled and is reachable by any authenticated administrator session. - Control: The attacker controls the JSON body, including
groupId, which is validated only as a positive integer. - Path: The request reaches
GroupController::deleteGroup(), passes the CSRF check with the attacker's valid token, and passes the medium-permission-mode instance check. - Guard: The only authorization gate is
userHasPermission(GROUP_DELETE), a binary right-ownership check. The privilege-level comparison that the sibling write endpoints apply is missing entirely. - Primitive: The attacker-controlled
groupIdflows toMediumPermission::deleteGroup(), which executes unconditionalDELETEstatements across the group's records and rights. - Result: Any group, including one holding rights the attacker cannot obtain, is permanently destroyed, and its members lose those inherited rights.
Bypass Evidence
The relevant guard is the privilege-containment check that gates group-write operations. In the published artifact, updateGroup(), updateMembers(), and updatePermissions() each enforce it inline: for a non-SuperAdmin caller they iterate the target group's rights and reject the operation unless the acting user holds every one of those rights. Their inline comments document the exact threat model, that a delegable group-management right must not let an administrator reach privileges they do not already hold.
deleteGroup() omits this check. It runs only userHasPermission(GROUP_DELETE) before invoking the destructive sink. The following disproof attempts were made and each failed to invalidate the finding:
- Checked whether
GROUP_DELETEis effectively SuperAdmin-equivalent: it is a narrow, individually delegable right in the permission enumeration, distinct from configuration, backup, and group-administration rights, so holding it does not already grant the destroyed capability. - Checked for a compensating guard deeper in the stack:
MediumPermission::deleteGroup(), its repository layer,AbstractController::userHasPermission(), the administration API base controller, and the kernel event listeners were all inspected; none performs a privilege-level comparison for this operation. - Checked whether the capability is merely denial rather than a boundary crossing: the attacker can destroy a group they cannot create, join, or manage through any authorized path, which is a capability beyond their granted authority.
- Checked whether front-end restrictions or the audit log prevent exploitation: the API is called directly, and the audit-log entry is written after the deletion, so it is detection rather than prevention.
Affected Versions
Ecosystem: composerPackage: thorsten/phpmyfaqConfirmed affected range: >= 4.2.0-alpha, <= 4.2.0-alpha.2Latest release checked: 4.2.0-alpha.2 (Composer / Packagist, published 2026-08-20)Fix status: not fixed
The group-write endpoints, including deleteGroup(), were introduced during the 4.2.0 development cycle. The last stable release, 4.1.8, is not affected: its GroupController exposes only read-only endpoints and contains no group-deletion method. Both published pre-releases, 4.2.0-alpha and 4.2.0-alpha.2, were inspected directly and both ship the vulnerable endpoint without a privilege-level check. The introduction boundary is 4.2.0-alpha; no published or later development artifact yet contains a fix. The same Composer artifact is also published under the coordinate phpmyfaq/phpmyfaq.
Suggested Fix
Enforce the same privilege-containment invariant on group deletion that the sibling group-write endpoints already apply. Before deleting, require that the acting user is a SuperAdmin, or that the acting user holds every right carried by the target group; reject the request otherwise. The check must live in the deletion controller path (or, more robustly, inside the permission layer's deleteGroup() so every caller is covered), not in the front-end. As an interim mitigation, operators can restrict the GROUP_DELETE right to fully trusted SuperAdmin-equivalent administrators until a code fix is available.
Reported by zx (GitHub: @manus-pi).
Text from GitHub Advisory Database
Affected versions 1
- thorsten/phpmyfaq, composer>= 4.2.0-alpha, <= 4.2.0-alpha.2GitHub