浅谈、总结docker逃逸的姿势
docker逃逸的复现以及研究
docker 挂载逃逸
以privileged权限进入docker
docker run --privileged -it --name docker_escape ubuntu /bin/bash这里我命名的容器是test
进来之后发现没有fdisk命令,怀疑自己是否以privileged权限成功启动
输入命令:
capsh --print | grep "Current" | grep -q "= cap_" && echo "非特权模式" || echo "特权模式"或者是查看能力集
cat /proc/self/status | grep CapEff- 特权模式输出:
CapEff: 0000003fffffffff(全能力) - 非特权模式输出:较短的值(如
00000000a80425fb)
建议是查看能力集,因为有可能是工具没装:
但是:
确保了在特权里面后,但是是用不了fdisk,那么就是没装,装一下:
apt update && apt install -y fdisk安装完之后输入
fdisk -l发现这里我的默认磁盘命名和真实机不一样
很多文章告诉你这里进去可以用fdisk来看,然后挂在,但是你要明白原理,是容器机子的某个文件夹挂载到主机上,所以应该去理解宿主机的/dev下的分区,但是在privileged里面,执行fdisk是直接列出宿主机的/dev,并且能通过挂载等行为进行操作,这就是漏洞点所在
然后创建一个文件夹:
选择最大的分区来挂在
mount /dev/nvme0n1p3 /test
为了证明成功逃逸,我在宿主机的根目录下创建flag,容器机里面也能列出来:
对于其他形式的挂载:
/var/run/docker.sock:/var/run/docker.sock
网上还有其他的复现方式:把宿主机的docker.sock挂载到容器的某个文件里面,实现容器和宿主机通信,但是除了一些极个别的自主研发脚本,我还真想不到有什么里有能挂这么危险的docker.sock通信文件,所以此处我懒得操作复现,只给大家说说其中的原理
在docker 的架构当中,存在这样的一种关系:
由daemon来充当server,容器123来充当client
当你正常的修改相关的docker配置文件时,例如 vim /etc/docker/daemon.json,实际上是在通过daemon来向各个容器发起通信
这种通信的方法有三:
- unix:///var/run/docker.sock(默认
- tcp://host:port
- fd://socketfd
那么如果我们能控制daemon和各个容器的通信接口,那么就可以反向控制宿主机了
通过这个原理,成功诞生了以下漏洞:
Docker Remote API 未授权访问:
使用docker swarm的时候,管理的docker 节点上会开放一个TCP端口2375,绑定在0.0.0.0上,http访问会返回 404 page not found ,其实这是 Docker Remote API,可以执行docker命令,比如访问 http://host:2375/containers/json 会返回服务器当前运行的 container列表,和在docker CLI上执行 docker ps的效果一样
挂载Docker Socket
有些特殊情况运维大哥在创建容器的时候,挂载了一下docker.sock:
docker run -itd -v /var/run/docker.sock:/var/run/docker.sock ubuntu此处实际上是运行控制了unix这种通信方式
什么?你问我有没有控制fd://socketfd这种方式
有的兄弟,有的......
首先你要明白,fd://实际上是附属于docker.sock通信的一部分,全称为fileDescriptor
docker 在管理的过程中会预先创建docker.sock来进行统一的套接字管理,当有客户端进行链接的时候,system会动态启动docker的守护进程,并且通过fd和各个容器进行通信
啊,那么又有人会问了:有没有存在一种可能除了docker.sock之外去调用fd呢?
有的兄弟,有的......
// 示例代码:获取宿主机 /etc/passwd 的文件描述符
#define _GNU_SOURCE
#include <fcntl.h>
#include <sys/types.h>
int main() {
struct file_handle *handle;
int mount_id;
char buf[256];
// 获取挂载点信息(需 CAP_DAC_READ_SEARCH)
sprintf(buf, "/etc/passwd");
handle = (struct file_handle *)malloc(sizeof(struct file_handle) + 256);
handle->handle_bytes = 256;
// 调用 open_by_handle_at
int fd = open_by_handle_at(AT_FDCWD, handle, O_RDWR);
return fd; // 返回文件描述符
}- 编译并运行:
gcc get_fd.c -o get_fd
./get_fd......
其余类型的逃逸:
现存的其他逃逸方法利用的方向有以下几点:
利用docker容器和宿主机公用一套内核,容器内执行内核漏洞:如dirtycow(windows的docker是通过wsl来实现的,所以如果你在windows上面复现,寻找的应该是wsl的内核漏洞)
应用软件本身的漏洞,例如堆栈溢出等直接操纵系统内存的行为
