CVE-2026-75837: Grav before 2.0.14 Privilege Escalation via Group Access Field
Summary The core Flex group blueprint system/blueprints/user/group.yaml (access field, lines 48-55) omits the security@: admin.super field guard that its sibling account blueprint carries (account.yaml:131/138/150, added by the CVE-2026-42613 fix). A delegated non-super operator holding admin.users.update can therefore save a group whose access map contains admin.super: true, which UserGroupObject::authorize then grants to every member of that group, a full privilege escalation to super-admin (scheduler/cron RCE, Twig eval). This is a distinct file, sink, and fix from all four related advisories.
Root Cause The CVE-2026-42613 fix protected the account access/groups fields with a blueprint-level security@: admin.super gate, which Blueprint::dynamicSecurity() (system/src/Grav/Common/Data/Blueprint.php:644-662) uses to mark a field validate.ignore=true for non-super users so BlueprintSchema::filterArray() (BlueprintSchema.php:263-311) drops it. The functionally-identical group access field, a permission map granted to every member of the group, is declared in system/blueprints/user/group.yaml:48-55 with checkauthorize: false and NO security@ guard. checkauthorize has ZERO PHP enforcement (grep -rn checkauthorize across the repo returns 0 PHP consumers; only two YAML blueprints reference it), so security@ is the sole real control. Because the group access field lacks security@, dynamicSecurity never flags it, filterArray retains it, and Validation::filterArray (type: array, valuetype: bool) passes the nested admin.super:true leaf through. The core save path (FlexObject::update() then Framework/Flex/FlexObject.php:683 $blueprint->filter($data,true,true) then save()) persists it to user://config/groups.yaml.
Impact A delegated admin.users operator (strictly below admin.super) escalates to super-admin, gaining the admin panel, the scheduler (cron to RCE) and Twig evaluation. Full read/write/DoS (C:H/I:H/A:H). Even absent self-escalation, arbitrary rewrite of ANY group's ACL is itself a full escalation primitive.
Proof of Concept As a non-super admin.users operator who is a member of group ops: POST /admin/accounts/groups/ops (or groups.json task:save) data[access][admin][super]=1 user/config/groups.yaml gains ops: { access: { admin: { super: true } } }, so the operator is super-admin on the next request.
Attack Chain 1. Entry: authenticated delegated admin (admin.users.update, no admin.super) POSTs the group-edit form for a group they belong to (or a new group), body access[admin][super]=true. Guard: FlexAuthorizeTrait::isAuthorizedAction to admin.users.update. Bypass proof: user-groups.yaml exposes groups at admin.users:crudl; operator legitimately holds update. 2. Check (field guard): Blueprint::dynamicSecurity marks validate.ignore only for security@ fields. Guard: none on group access (no security@). Bypass proof: group.yaml:48-55 has no security@; git log -S 'security@' -- system/blueprints/user/group.yaml is empty. 3. Filter: BlueprintSchema::filterArray retains the non-ignored field; Validation::filterArray (valuetype: bool) passes admin.super:true through. Bypass proof: field not flagged ignore/disabled; nested bool leaf preserved. 4. Sink (persist): FlexObject::update then filter then save writes to user://config/groups.yaml. Guard: none. Bypass proof: value already survived steps 2/3; storage performs no ACL filtering. 5. Impact: any member request triggers UserGroupObject::authorize('admin.super') (.../UserGroups/UserGroupObject.php:75) returning true, so the member is super-admin, enabling scheduler/Twig RCE.
Bypass Evidence - system/blueprints/user/group.yaml:48-55: access block with checkauthorize: false, no security@ (confirmed live on tag 2.0.12). - git log -S 'security@' -- system/blueprints/user/group.yaml is empty (guard never existed; a permanent gap, not a regression). - git log 2.0.12..HEAD -- system/blueprints/user/group.yaml is empty (no post-release fix). - grep -rn checkauthorize (whole repo) returns 0 PHP hits (guard unenforced in PHP). - grep -rn "typePermissions|filterPermissions" system/src/Grav/Common/Data returns 0 (permissions map falls through to array-bool filtering that keeps nested keys). - Guard asymmetry: account.yaml:131/138/150 carry security@: admin.super; group.yaml carries none. GHSA-h33v-82r9-v8pm's own text confirms the blueprint security@ gate is the sole Flex-backend strip for these keys.
Affected Versions <= 2.0.12 (latest release; the guard never existed on group.yaml, so all 2.x are affected). The missing guard and the strip logic are both in core getgrav/grav; the group-edit UI is provided by the flex-objects/admin plugin, but the fix belongs in core.
Suggested Fix Add security@: admin.super to the access field in system/blueprints/user/group.yaml (mirroring account.yaml), and/or enforce a super-only strip on group ACL saves in core so all callers are covered.
--- Reported by zx (Jace) — GitHub: @manus-use
Other sources
Grav before 2.0.14 fails to guard the access field in the core group blueprint with the required security@: admin.super restriction. A delegated admin.users operator can save a group with access[admin][super]=true to escalate to super-admin, gaining scheduler and Twig evaluation capabilities.
— MITRE
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
composer/getgrav/gravto a version that resolves this vulnerability.Fixed in 2.0.14 - Upgrade
Upgrade
getgrav/gravto a version that resolves this vulnerability.Fixed in 2.0.14 - Configuration
In system/blueprints/user/group.yaml (access block around lines 48-55), add a blueprint-level guard field `security@: admin.super` to the group `access` field, mirroring the account blueprint (account.yaml:131/138/150).
Grav core blueprint (system/blueprints/user/group.yaml) security@ = admin.super - Configuration
Enforce a core-level super-only strip/validation for group ACL saves so that group `access` cannot persist `admin.super: true` when the caller is not super-admin (covers all callers beyond the blueprint guard).
Grav core group ACL save logic (core) enforce super-only strip on group ACL saves = true
Event History
Frequently Asked Questions
Which users can exploit this issue?
Instances running Grav before 2.0.14 are affected when an account has delegated admin.users privileges. That role can modify a group and set access[admin][super]=true, elevating itself to super-admin.
What access does an attacker need to exploit it?
No user interaction is required, but the attacker must already be authenticated with delegated admin.users access. The vulnerable action is saving a modified group configuration through the group access field.
What is the recommended mitigation?
Upgrade Grav to version 2.0.14 or later. If upgrading cannot happen immediately, restrict or remove delegated admin.users permissions from untrusted accounts to prevent group modifications that grant super-admin access.
How can I check for possible exploitation?
Review group configurations for access[admin][super]=true and identify whether users with delegated admin.users access have created or modified groups. Any unexpected super-admin group assignment should be treated as evidence requiring investigation.