Percivalll/Copy-Fail-CVE-2026-31431-Kubernetes-PoC

GitHub: Percivalll/Copy-Fail-CVE-2026-31431-Kubernetes-PoC

演示无特权容器利用 Linux 内核 CVE-2026-31431 页缓存损坏漏洞,通过共享镜像层在 Kubernetes 上实现节点级代码逃逸的完整 PoC。

Stars: 32 | Forks: 10

# Copy Fail (CVE-2026-31431) — Kubernetes 容器逃逸 PoC 这是一个概念验证,演示了**完全无特权的容器**如何通过共享容器镜像层,利用 CVE-2026-31431 Linux 内核页缓存损坏漏洞,在 Kubernetes 上实现**节点级代码执行**。 ## 背景 CVE-2026-31431 ("Copy Fail") 是 Linux 内核页缓存写时复制路径中的一个漏洞。`AF_ALG` 的 splice 竞争允许无特权进程损坏**只读**文件的页缓存页。这种损坏会驻留在内核页缓存中,并且对随后读取或执行该文件的每个进程可见——包括其他容器或宿主机上的进程。 有关原始漏洞的完整详细信息,请参阅 [copy.fail](https://copy.fail/)。 ## 工作原理 攻击链分为三个阶段:**页缓存损坏**、**跨容器传播**和**特权执行**。 ### 1. 通过 AF_ALG Splice 竞争损坏页缓存 内核的 `AF_ALG` (crypto) 子系统为用户空间加密操作提供了一个基于套接字的接口。该漏洞利用了内核在处理从文件到 AF_ALG 套接字的 `splice()` 操作时的竞争条件: 1. **只读**打开目标二进制文件(例如 `/usr/sbin/ipset`)。 2. 创建一个绑定到 `authencesn(hmac(sha256),cbc(aes))` 的 AF_ALG AEAD 套接字。 3. 通过 AF_ALG 套接字发送一个带有 `MSG_MORE` 标志的小型有效载荷块,告知内核期望接收更多数据。 4. 通过 `splice()` 将目标文件的内容从 fd 传递到管道,再到 AF_ALG 套接字。 5. 由于 CoW 漏洞,内核**将攻击者的有效载荷字节写入了目标文件的页缓存页**,而不是正确地进行隔离。 该漏洞利用程序对每个 4 字节窗口重复此操作,直到目标二进制文件的所有缓存页都被自定义有效载荷完全覆盖。 这不需要对文件的写权限。磁盘上的文件保持不变——只有内存中的页缓存被损坏。 ### 2. 通过镜像层共享实现跨容器传播 容器运行时 (containerd, CRI-O) 使用 overlay 文件系统。当两个容器共享相同的镜像层时,内核会从**相同的页缓存页**为它们的文件读取提供服务。 此 PoC 镜像基于 `registry.k8s.io/kube-proxy:v1.35.2` 构建。每个 Kubernetes 节点上的 kube-proxy DaemonSet 使用完全相同的基础层。因此,两个容器中的 `/usr/sbin/ipset` 映射到**完全相同的页缓存页集合**。 当无特权的 PoC 容器损坏 ipset 的页缓存时,损坏会立即对同一节点上特权的 kube-proxy 容器可见——无需任何跨容器通信。 ### 3. 由 kube-proxy 进行特权执行 kube-proxy 作为具有 `hostNetwork: true` 的**特权** DaemonSet 运行。它会定期调用 `/usr/sbin/ipset` 来管理 iptables/ipset 规则。当它下一次执行 ipset 时,内核会加载损坏的页缓存页,并以 kube-proxy 的完全特权执行攻击者的有效载荷: - 节点上的完整 root 权限 - 所有 capabilities - 访问宿主机命名空间 此 PoC 中的有效载荷 (`payload/payload.c`) 只是简单地挂载宿主机根文件系统,并向 `/root/res` 写入一个标记文件,作为节点级代码执行的证明。 ### 攻击流程图 ``` ┌──────────────────────────┐ ┌──────────────────────────┐ │ PoC Container │ │ kube-proxy Container │ │ (unprivileged) │ │ (privileged) │ │ │ │ │ │ 1. Open /usr/sbin/ipset │ │ │ │ (read-only) │ │ │ │ │ │ │ │ 2. AF_ALG splice race │ │ │ │ corrupts page cache │ │ │ │ │ │ │ │ └──────────┼───────────────┘ └──────────────────────────┘ │ │ ▼ │ ┌─────────────────────┐ │ │ Kernel Page Cache │ │ │ /usr/sbin/ipset │◄────────────────────┘ │ (CORRUPTED) │ 3. kube-proxy executes ipset │ contains attacker's │ → loads corrupted pages │ payload bytes │ → payload runs as root └─────────────────────┘ on the host ``` ## 仓库结构 ``` . ├── cmd/copyfail/main.go # Entry point; embeds compiled payload ├── internal/ │ ├── exploit/ │ │ ├── exploit.go # Core exploit: AF_ALG splice race loop │ │ └── patch.go # Splits payload into 4-byte patch windows │ └── alg/ │ └── alg.go # AF_ALG AEAD socket abstraction ├── payload/ │ ├── payload.c # Validation payload (mount host fs, write marker) │ └── nolibc/ # Kernel's tiny libc for static, no-dependency payloads ├── deploy/ │ └── poc.yaml # Kubernetes Deployment manifest ├── Dockerfile # Built FROM kube-proxy to share image layers ├── Makefile # Build orchestration └── docs/ # Validation evidence from ACK (Alibaba Cloud) ``` ## 前置条件 - Go 1.25+ - 用于 nolibc 有效载荷的交叉编译器(默认:`x86_64-linux-gnu-gcc`) - Docker / Buildx - 一个以 DaemonSet 方式运行 kube-proxy 且 `imagePullPolicy: IfNotPresent`(默认值)的 Kubernetes 集群 - **早于** CVE-2026-31431 修复补丁的 Linux 内核 ## 构建 ``` # 构建 payload + Go 二进制文件 make build # 构建 Docker 镜像 make docker-build # 构建并推送到 GHCR make docker-push IMAGE=ghcr.io//copy-fail-poc TAG=latest ``` 针对 `arm64` 目标: ``` make build CC=aarch64-linux-gnu-gcc GOARCH=arm64 ``` ## 用法 ### 部署 PoC ``` kubectl apply -f deploy/poc.yaml ``` 该 Deployment 创建了一个单独的无特权 Pod。它会: 1. 运行 `/bin/copyfail -target /usr/sbin/ipset` 来损坏页缓存。 2. 无限期休眠,以便 Pod 保持运行状态以供观察。 ### 验证逃逸 在 kube-proxy 下一次执行 ipset 后(由于其协调循环,这通常会在几秒钟内发生,或者在其下一次重启时发生),检查**节点**: ``` # 通过 SSH 连接到节点,或使用特权 debug pod cat /root/res # 预期输出:[*] success ``` 宿主机文件系统上存在 `/root/res` 证明攻击者提供的代码以节点级权限执行了——这是从 kube-proxy 特权容器上下文内部写入的。 ### 清理 ``` kubectl delete -f deploy/poc.yaml # 在受影响的节点上,移除标记并重启 kube-proxy: rm -f /root/res systemctl restart kubelet # or delete the kube-proxy pod to force re-pull ``` ## 为什么选择 kube-proxy + ipset? kube-proxy 是一个理想的攻击目标,因为它: 1. **存在于每个节点上** —— 作为 DaemonSet 运行。 2. **具有高特权** —— `privileged: true`, `hostNetwork: true`。 3. **其镜像中包含 ipset** —— ipset 是一个用于 iptables 管理的 setuid 二进制文件。 4. **使用 `imagePullPolicy: IfNotPresent`** —— 一旦拉取了攻击者的镜像并共享了相同的基础层,overlay lower-dir 的页就会实现共享。 任何镜像中包含可预测二进制文件的特权 DaemonSet 都可以成为同样的攻击目标。 ## 自定义有效载荷 默认的有效载荷 (`payload/payload.c`) 是一个仅用于验证的程序,它会写入一个标记文件。要构建自定义有效载荷: 1. 编辑 `payload/payload.c`。该程序针对 `nolibc`(内核的最小 C 库)构建,以生成静态的、无依赖的二进制文件。 2. 运行 `make payload` 进行交叉编译。 3. 编译后的有效载荷通过 `//go:embed` 嵌入到 Go 二进制文件中。 ## 受影响的版本 - **Linux 内核**:CVE-2026-31431 补丁之前的所有版本。 - **Kubernetes**:任何使用未修补节点内核的版本。该漏洞存在于内核中,而不是 Kubernetes 本身。Kubernetes 仅仅提供了执行上下文(共享镜像层 + 特权 DaemonSets),将影响从本地页缓存损坏提升为完整的容器逃逸。 ## 缓解措施 - **修补内核。** 这是决定性的修复方法。 - **启用镜像层隔离。** 某些运行时支持按容器的文件系统快照,可以防止页缓存共享。 - **为 kube-proxy 使用只读根文件系统**(不能完全缓解,但会限制有效载荷的能力)。 - **限制 Pod 调度**,以防止不受信任的工作负载被分配到运行具有共享基础镜像的特权 DaemonSets 的节点上。 ## 致谢 - **CVE-2026-31431 发现与披露**:[Theori / Xint](https://copy.fail/) - **跨平台 C 语言有效载荷**:[Tony Gies](https://github.com/tgies/copy-fail-c) (LGPL-2.1-or-later OR MIT) - **nolibc**:Linux 内核自测程序 (`tools/include/nolibc/`) ## 许可证 本仓库中的 Go 漏洞利用代码按原样提供,仅供研究使用。 有效载荷 (`payload/payload.c`) 派生自 [copy-fail-c](https://github.com/tgies/copy-fail-c),并在 **LGPL-2.1-or-later** OR **MIT** 双重许可下发布。请参阅 [LICENSE-LGPL](LICENSE-LGPL) 和 [LICENSE-MIT](LICENSE-MIT)。
标签:AF_ALG, CISA项目, Copy Fail, Copy-on-Write, CoW, CVE-2026-31431, K8s安全, Linux内核漏洞, Maven, Page-Cache, PoC, splice, Web报告查看器, 代码执行, 内存损坏, 子域名枚举, 子域名突变, 安全漏洞, 客户端加密, 容器逃逸, 提权, 数据展示, 日志审计, 暴力破解, 漏洞验证, 竞态条件, 系统安全, 红队, 节点级控制, 请求拦截, 跨容器攻击, 零日漏洞, 页缓存污染