GHSA-XHFV-7758-R9HX
Vulnerability from github – Published: 2026-09-17 20:45 – Updated: 2026-09-17 20:45Summary
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 check_authorize: false and NO security@ guard. check_authorize has ZERO PHP enforcement (grep -rn check_authorize 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, value_type: 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
- Entry: authenticated delegated admin (
admin.users.update, noadmin.super) POSTs the group-edit form for a group they belong to (or a new group), bodyaccess[admin][super]=true. Guard:FlexAuthorizeTrait::isAuthorizedActiontoadmin.users.update. Bypass proof:user-groups.yamlexposes groups atadmin.users:crudl; operator legitimately holds update. - Check (field guard):
Blueprint::dynamicSecuritymarksvalidate.ignoreonly forsecurity@fields. Guard: none on group access (nosecurity@). Bypass proof:group.yaml:48-55has nosecurity@;git log -S 'security@' -- system/blueprints/user/group.yamlis empty. - Filter:
BlueprintSchema::filterArrayretains the non-ignored field;Validation::filterArray(value_type: bool) passesadmin.super:truethrough. Bypass proof: field not flagged ignore/disabled; nested bool leaf preserved. - Sink (persist):
FlexObject::updatethenfilterthensavewrites touser://config/groups.yaml. Guard: none. Bypass proof: value already survived steps 2/3; storage performs no ACL filtering. - 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 withcheck_authorize: false, nosecurity@(confirmed live on tag 2.0.12).git log -S 'security@' -- system/blueprints/user/group.yamlis empty (guard never existed; a permanent gap, not a regression).git log 2.0.12..HEAD -- system/blueprints/user/group.yamlis empty (no post-release fix).grep -rn check_authorize(whole repo) returns 0 PHP hits (guard unenforced in PHP).grep -rn "typePermissions|filterPermissions" system/src/Grav/Common/Datareturns 0 (permissions map falls through to array-bool filtering that keeps nested keys).- Guard asymmetry:
account.yaml:131/138/150carrysecurity@: admin.super;group.yamlcarries none. GHSA-h33v-82r9-v8pm's own text confirms the blueprintsecurity@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": [
{
"package": {
"ecosystem": "Packagist",
"name": "getgrav/grav"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.0.14"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-75837"
],
"database_specific": {
"cwe_ids": [
"CWE-269",
"CWE-862"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-17T20:45:15Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "## Summary\nThe 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.\n\n## Root Cause\nThe 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 `check_authorize: false` and NO `security@` guard. `check_authorize` has ZERO PHP enforcement (`grep -rn check_authorize` 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, value_type: bool`) passes the nested `admin.super:true` leaf through. The core save path (`FlexObject::update()` then `Framework/Flex/FlexObject.php:683` `$blueprint-\u003efilter($data,true,true)` then `save()`) persists it to `user://config/groups.yaml`.\n\n## Impact\nA 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\u0027s ACL is itself a full escalation primitive.\n\n## Proof of Concept\nAs a non-super `admin.users` operator who is a member of group `ops`:\n```\nPOST /admin/accounts/groups/ops (or groups.json task:save)\ndata[access][admin][super]=1\n```\n`user/config/groups.yaml` gains `ops: { access: { admin: { super: true } } }`, so the operator is super-admin on the next request.\n\n## Attack Chain\n1. 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.\n2. 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 \u0027security@\u0027 -- system/blueprints/user/group.yaml` is empty.\n3. Filter: `BlueprintSchema::filterArray` retains the non-ignored field; `Validation::filterArray` (`value_type: bool`) passes `admin.super:true` through. Bypass proof: field not flagged ignore/disabled; nested bool leaf preserved.\n4. 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.\n5. Impact: any member request triggers `UserGroupObject::authorize(\u0027admin.super\u0027)` (`.../UserGroups/UserGroupObject.php:75`) returning true, so the member is super-admin, enabling scheduler/Twig RCE.\n\n## Bypass Evidence\n- `system/blueprints/user/group.yaml:48-55`: access block with `check_authorize: false`, no `security@` (confirmed live on tag 2.0.12).\n- `git log -S \u0027security@\u0027 -- system/blueprints/user/group.yaml` is empty (guard never existed; a permanent gap, not a regression).\n- `git log 2.0.12..HEAD -- system/blueprints/user/group.yaml` is empty (no post-release fix).\n- `grep -rn check_authorize` (whole repo) returns 0 PHP hits (guard unenforced in PHP).\n- `grep -rn \"typePermissions|filterPermissions\" system/src/Grav/Common/Data` returns 0 (permissions map falls through to array-bool filtering that keeps nested keys).\n- Guard asymmetry: `account.yaml:131/138/150` carry `security@: admin.super`; `group.yaml` carries none. GHSA-h33v-82r9-v8pm\u0027s own text confirms the blueprint `security@` gate is the sole Flex-backend strip for these keys.\n\n## Affected Versions\n`\u003c= 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.\n\n## Suggested Fix\nAdd `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.\n\n---\nReported by **zx (Jace)** \u2014 GitHub: @manus-use",
"id": "GHSA-xhfv-7758-r9hx",
"modified": "2026-09-17T20:45:15Z",
"published": "2026-09-17T20:45:15Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/getgrav/grav/security/advisories/GHSA-xhfv-7758-r9hx"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-75837"
},
{
"type": "PACKAGE",
"url": "https://github.com/getgrav/grav"
},
{
"type": "WEB",
"url": "https://www.vulncheck.com/advisories/grav-before-privilege-escalation-via-group-access-field"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:H/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Grav: Missing admin.super guard on core group blueprint access field allows admin.users operator to escalate to super-admin"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.