GHSA-xhfv-7758-r9hx: Critical severity composer/getgrav/grav vulnerability

Published Sep 17, 2026
·
Updated

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

Affected Software

1 affected componentFixes available
composer/getgrav/grav<2.0.14
2.0.14

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade composer/getgrav/grav to a version that resolves this vulnerability.

    Fixed in 2.0.14
  2. Configuration

    In system/blueprints/user/group.yaml (lines 48-55), add the blueprint-level security gate to the group 'access' field so it is protected like account.yaml (account.yaml:131/138/150). Specifically, mirror the access guard by adding `security@: admin.super` to the `access` field mapping.

    getgrav/grav core (Flex group blueprint) - system/blueprints/user/group.yaml access.security@ (for access field guard) = admin.super
  3. Compensating control

    Temporarily restrict delegated management of group ACLs: ensure only super-admins can access the endpoint/UI that saves group access (e.g., POST /admin/accounts/groups/ops or groups.json task save), because the group access field guard is missing (no security@) and group members may become super-admin on next request.

  4. Operational

    After applying the core blueprint guard fix, rotate/reevaluate any affected delegated admin and group membership, and re-check for persisted super-admin elevation in user://config/groups.yaml (e.g., groups with `ops: { access: { admin: { super: true }}}`) before further admin actions.

Event History

Sep 17, 2026
Advisory Published
via GitHub·08:45 PM
Data Sourced
via GitHub·08:45 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

What access does an attacker need before exploiting this issue?

The attacker must already be a delegated, non-super operator with the admin.users.update permission. No user interaction is required.

2

Who receives the elevated privileges after a malicious group is saved?

Every member of the modified group is granted the permissions in its access map. If the map includes admin.super: true, all group members become super-admins.

3

What capabilities could super-admin access enable in this case?

The described impact includes full privilege escalation, with examples including scheduler or cron remote code execution and Twig evaluation.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203