目录
[五、案例二:docker.sock 挂载与 Docker-in-Docker](#五、案例二:docker.sock 挂载与 Docker-in-Docker)
五、案例二:docker.sock 挂载与 Docker-in-Docker
第二个案例表面上叫 Docker-in-Docker,核心仍是管理接口被带入了不应访问它的容器。实验同时挂载宿主机的 Docker 二进制文件和 /var/run/docker.sock。二进制文件解决"容器里没有 docker 命令"的问题,套接字则提供了与宿主机 daemon 通信的入口。两者作用不同,不能只看到其中一个就下结论。
图 60:docker.sock 挂载、嵌套容器和宿主机目录验证

图 61:docker.sock 挂载、嵌套容器和宿主机目录验证

图 62:docker.sock 挂载、嵌套容器和宿主机目录验证

图 63:docker.sock 挂载、嵌套容器和宿主机目录验证

图 64:docker.sock 挂载、嵌套容器和宿主机目录验证
套接字案例的判断从文件存在开始,但文件存在也需要确认它指向谁。原稿把宿主机的 Docker 二进制和套接字同时挂进容器,随后在容器中列出挂载点,确认二进制位于可执行路径、套接字位于预期目录。若只有二进制而没有套接字,容器只能执行本地客户端,却不能控制宿主机 daemon;若只有套接字而没有客户端,也需要其他支持 Unix 套接字的工具。
执行 docker info 时,关键观察不是输出内容有多少,而是输出对象属于哪台机器。容器内如果返回外层宿主机的存储驱动、镜像数量和容器列表,就说明命令已经跨过了容器边界。随后创建新容器并挂载宿主机根目录,是把"能够查询管理面"推进到"能够改变文件视图"。如果新容器没有启动或挂载路径写错,后续看到的仍然只是内层容器文件。
原稿记录了退出嵌套容器时的层次感。第一次 exit 只结束内层容器的 shell,外层容器的提示符仍然存在;第二次退出才回到最初环境。通过 .dockerenv 是否存在、当前容器 ID 是否变化、根目录文件是否属于宿主机三组证据,可以避免把"目录切换"误判为"已经逃逸"。
套接字暴露与 2375 暴露的共同点是调用者能够请求 daemon 创建容器,真正危险的动作仍然是创建时指定的挂载、能力和用户身份。排查不能只停在"是否存在 docker.sock",还应继续查看哪些容器挂载了它、哪些进程可以读取它,以及 daemon 是否以 root 运行。
课程先解释 docker.sock 的角色。它是 Docker 客户端与 daemon 之间的 Unix 套接字,相当于本机命令进入守护进程的通信门。能访问该套接字,就可以向宿主机 daemon 发送 API 请求;能向 daemon 发请求,就可能创建新的容器、指定挂载点并改变宿主机文件视图。因此,套接字被挂载进容器后,容器中的权限边界实际上被提升到了宿主机 Docker 管理面的权限。

图 65:docker.sock 挂载、嵌套容器和宿主机目录验证

图 66:docker.sock 挂载、嵌套容器和宿主机目录验证

图 67:docker.sock 挂载、嵌套容器和宿主机目录验证

图 68:docker.sock 挂载、嵌套容器和宿主机目录验证
实验从容器内部使用挂载进来的 Docker 二进制,通过 -H 参数指向套接字,再执行 docker info。返回结果展示的是宿主机 Docker 环境,而不是外层容器的本地环境。随后再次创建容器,把宿主机根目录挂载到新容器,再进入新容器观察目录。两次进入分别对应两层容器,第一次退出只离开内层,第二次退出才回到外层环境;这也是原稿用"嵌套空间"反复核对的原因。

图 69:docker.sock 挂载、嵌套容器和宿主机目录验证

图 70:docker.sock 挂载、嵌套容器和宿主机目录验证

图 71:docker.sock 挂载、嵌套容器和宿主机目录验证
在内层目录中再次检查 .dockerenv,没有发现该文件,说明当前看到的已经是宿主机根目录。由于精简容器中可能没有 vim,实验使用 echo 追加文件来验证写入能力。这个步骤没有引入新的原理,只是说明工具缺失不等于能力缺失:只要接口允许创建容器并挂载根目录,文件写入仍可通过其他已有命令完成。
讲师评价:
2375暴露和docker.sock挂载可以归并为同一类问题,即 Docker daemon 的控制接口被未授权主体取得。前者是网络接口直接开放,后者是本地套接字被带入容器;路径不同,后果相近。