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报告查看器, 代码执行, 内存损坏, 子域名枚举, 子域名突变, 安全漏洞, 客户端加密, 容器逃逸, 提权, 数据展示, 日志审计, 暴力破解, 漏洞验证, 竞态条件, 系统安全, 红队, 节点级控制, 请求拦截, 跨容器攻击, 零日漏洞, 页缓存污染