type
status
date
slug
summary
tags
category
icon
password
0x01 前言
为了更深入和清晰的理解 Docker K8S 等组件漏洞的原理,通过我会选择 Debug 它们的源码进行断点调试追踪代码层面的调用逻辑。
刚开始学Go的时候一直很不理解Go的项目结构,跑或者去调试开源Go项目有隔阂感,低版本Docker因没有Go Module,采用较为原始的Go Vendor依赖管理方式,最近才开始学Go的我来说也是陌生,在加上Docker 20.10[1]项目重构之后分为多个组件(不直接用docker-ce源码是因为用新的可以一套代码新旧代码一起调),搭建起来也麻烦少许,Docker之间各个组件之间的调用方式又有点不同,导致搭建起来比较麻烦。后面学K8S这些发现调试环境搭建思路一样,一通百通,于是就总结一下。
本文环境
- Goland IDE
- Ubuntu 18.04
相信大多数人和我一样主力机用Win或者Mac,但Docker底层依赖Linux命名空间,所以在runC层面的漏洞需要Linux环境,这里通过本机Goland远程连至Ubuntu进行。直接在Ubuntu装Goland也可以。不是Ubuntu也不要紧,只是下文中安装的依赖包名可能有点不同。
0x02 Docker架构组成
调试之前,需要了解下 Docker 的组成,即组件的作用,这样有助于复现时理解漏洞所处在Docker组件
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fc4700c50-55d2-46be-b32b-10b96bfc65cf%2FUntitled.png?table=block&id=6ded183e-3ec7-4907-a1a4-ed00c4520d58&t=6ded183e-3ec7-4907-a1a4-ed00c4520d58&width=1708&cache=v2)
Docker 架构可分为 4 个仓库
- https://github.com/docker/cli - 上图中的 Docker
- https://github.com/moby/moby - 即我们熟知的 Dockerd 守护进程,Docker服务
- https://github.com/containerd/containerd - 包含 Containerd 和 Containerd-shim
- https://github.com/opencontainers/runc
Docker 于 2013年开源一种可移植、灵活且易于部署的容器化项目 libcontainer,并于 2015年把代码捐赠给OCI(Open Container Initiative)以助力容器生态标准化。Contianerd 最初也是 Docker 的核心库,于 2017 年捐赠给 CNCF(Cloud Native Computing Foundation)。[2]
他们最初都为 Docker 的一部分,为更好构建云原生生态最终演变为几个不同的组件。
0x03 Goland Docker调试环境搭建
因为 docker engine 需要 root 权限,请以 root 用户身份执行以下命令
Golang 安装
许多漏洞环境需要的 go 版本较高,源安装较低,先手动安装 go
解压
让 go 可执行文件能在
$PATH
索引命令行运行
go
能正常回显即可Goland Docker 源码下载
如0x02节介绍,要完整调试 Docker 源码,我们需要把四个仓库都拷贝下来,又因Docker低版本使用Go Vendor进行依赖管理,因此需要创建Go 项目源码结构
完成后目录结构如下
这里使用 Goland 进行远程开发,通过 ssh 连接,配置好后,(如果本机环境为Linux,则直接)打开两个IDE依次打开的项目文件夹为
- /opt/docker-moby/
- /opt/docker-cli/
- /opt/docker-containerd/
- /opt/docker-runc/
非远程调试可跳过下面这步
本地Goland Go SDK 设置
不管是不是远程,都需要设置本地Go SDK,否则运行的时候会挂在编译前的本地检查阶段
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fe11706eb-abbe-42a7-a79a-b6b627a1bf79%2FUntitled.png?table=block&id=909625b8-e96a-4764-87e6-8654eb14e08e&t=909625b8-e96a-4764-87e6-8654eb14e08e&width=1974&cache=v2)
一般本地安装好 Go 会自动识别,没有的话
Add SDK
指定本地安装目录或者 Download..
直接下载即可,版本和与一开始的Golang相同,尽量不要比一开始安装的Golang版本低即可远程调试环境搭建(本地可跳过此步骤)
顺便介绍下远程调试配置方法,新建一个空项目
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fb0c75b20-238a-456c-a5b4-712801bf2538%2FUntitled.png?table=block&id=615f9d93-311b-434f-a74f-7b8ced892fd5&t=615f9d93-311b-434f-a74f-7b8ced892fd5&width=1824&cache=v2)
配置远程SFTP访问
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fd7c9a796-ad93-4156-983e-30c2a8009c2a%2FUntitled.png?table=block&id=004e9b0c-e165-4a95-ba07-1c4f87af9492&t=004e9b0c-e165-4a95-ba07-1c4f87af9492&width=2226&cache=v2)
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Faf07cc8c-5db2-4974-8da8-fd8fbb863a07%2FUntitled.png?table=block&id=6c02da7b-b7ad-4311-87e0-6a9a60d93d2b&t=6c02da7b-b7ad-4311-87e0-6a9a60d93d2b&width=1708&cache=v2)
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F3ae63eec-1176-47d0-aa73-b86cedd11c65%2FUntitled.png?table=block&id=bd2c8482-8cd3-4b47-a8bb-e5c9d9552f55&t=bd2c8482-8cd3-4b47-a8bb-e5c9d9552f55&width=1162&cache=v2)
设置SSH连接信息
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F41648fdc-7d0c-4df7-878e-fa6de89da70a%2FUntitled.png?table=block&id=17e7d9b1-f456-41a3-bbf3-6c03ff698354&t=17e7d9b1-f456-41a3-bbf3-6c03ff698354&width=2336&cache=v2)
设置远程项目部署目录,最后点击OK
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fa271296a-2eea-4feb-8c9c-179624d010d5%2FUntitled.png?table=block&id=8399cbe9-4847-4358-ae1f-efca861d72a6&t=8399cbe9-4847-4358-ae1f-efca861d72a6&width=1824&cache=v2)
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fbdd1076e-db83-4eb2-a4b8-d470d895de87%2FUntitled.png?table=block&id=32197089-442d-4a48-b6ed-534305d6edf6&t=32197089-442d-4a48-b6ed-534305d6edf6&width=2272&cache=v2)
最后从远程下载源码即可,新建项目时还有个 go.mod 是无用的,删除即可
项目设置
此部配置目的是为了让本地的Goland正确识别代码依赖
这里注意因为默认 Goland 开启 Go Module,要取消
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F38fe07fd-bc84-46d3-9702-3cf9422b39b8%2FUntitled.png?table=block&id=4366f2cb-b3e6-4ed7-9ccc-68bafe783af2&t=4366f2cb-b3e6-4ed7-9ccc-68bafe783af2&width=2024&cache=v2)
并添加项目 Project GOPATH
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fd80fa8ca-b446-4445-8166-e91c51589eb5%2FUntitled.png?table=block&id=9db0797d-ab61-4dde-a9f7-140ae85f65a5&t=9db0797d-ab61-4dde-a9f7-140ae85f65a5&width=2024&cache=v2)
Go Build设置 (本地可跳过此步骤)
接下来配置远程Go环境,新建一个 Go Build 项目,注意 Run on 处选择SSH
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F5ea4b8ff-0298-4d63-a5be-85403bec6411%2FUntitled.png?table=block&id=691bdc58-abbb-4260-86cc-4b2b86695e10&t=691bdc58-abbb-4260-86cc-4b2b86695e10&width=3442&cache=v2)
因刚刚我们配置了SFTP,直接在 Existing 中选择即可
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F28729954-e7ba-480b-bd8d-e54094cf6eea%2FUntitled.png?table=block&id=88290d67-5f62-4dbf-b149-ab941c2936a1&t=88290d67-5f62-4dbf-b149-ab941c2936a1&width=1288&cache=v2)
点击 Next,这一步报错无所谓,因为我远程用的是 fish shell,用 bash 的同学应该问题不大
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F1bf13224-1903-4ab8-851e-f4ce79498fad%2FUntitled.png?table=block&id=2bf23356-dc62-42af-a4fe-55ccd66ef6ca&t=2bf23356-dc62-42af-a4fe-55ccd66ef6ca&width=1288&cache=v2)
继续点Next即可, 确保下面红线框住的3处配置正确即可
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Ff5a004f2-2ec2-420c-b675-29659708139c%2FUntitled.png?table=block&id=e3517c8d-4052-46fe-b29d-11535dae87e9&t=e3517c8d-4052-46fe-b29d-11535dae87e9&width=1288&cache=v2)
点击 Finish 即可
调试 Go 包选择
配置Go 包为:github.com/docker/cli/cmd/docker,因为cli的入口为这个包,后面moby和containerd根据要调试的包选择即可。注意勾选下面的
Build on remote target
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fcbb4bf74-094a-4ebb-a731-96a45f1044de%2FUntitled.png?table=block&id=dfc6e8ff-022e-4283-9d90-be655f8442bc&t=dfc6e8ff-022e-4283-9d90-be655f8442bc&width=2304&cache=v2)
PS: 这里 Run kind 一定要为 Package,因为Go包构建的基础单位就是包,而不是文件,这里如果 Build 文件也可以运行,因为当前入口 cmd 下,只有一个包,在 Build moby 时,如果 Build file 会报错(问了下ssst0n3师父)
最终点击运行即可,观察运行的路径为远程路径
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F5dbc61ba-97b4-461c-b5d8-bbb514f4dbeb%2FUntitled.png?table=block&id=dfafc23f-791b-424b-95f0-ba6c4daa4418&t=dfafc23f-791b-424b-95f0-ba6c4daa4418&width=3446&cache=v2)
点击运行右边的Debug按钮也可以正常运行,moby 和 containerd 服务的debug环境重复此步骤即可,不过有点注意事项
- 要先启动 containerd 在启动 dockerd 服务,因为从架构图可知,dockerd 服务依赖于 containerd 服务,否则启动异常
- 启动 containerd 服务前,要保证本机装有 containerd-shim 和 runc,也是重复上面的步骤,go build 一下即可,从上面运行 cli 可知,运行默认指定了 -o 选项
/opt/docker-cli/executables-4kyIqDG6jx/___10go_build_github_com_docker_cli_cmd_docker_linux
,把cntainerd-shim和runc运行后的路径文件复制一份至/usr/bin
之类的目录即可,containerd服务启动前默认从环境变量的可执行文件目录搜索,当然也可以通过指定参数指定路径
- 见【一些其他问题】章节中的第3点
containerd-shim 和 runc debug 配置
至此,表面上远程Debug就这么轻松的完成了,凡事就怕有但是…
细心的同学看了0x02的架构图会发现,Containerd调Containerd-shim 是通过命令行调用的,Dockerd和Containerd好说,是HTTP和gRPC服务,直接Debug启动等待请求即可触发Debug,但命令行调用,不能事前Debug启动要咋整?一开始我用了最原始的print大法
后面看了下 ssst0n3 调 kubelet的文章[3] 和搜狗团队对runc源码分析文章[4],发现 Containerd-shim 和 runC 两个 cli 可以通过安装和启动 dlv ,通过 Goland 连接进行远程debug。原理是先写一个我们自定义的脚本替换
/usr/bin/containerd-shim
或 runc
,里面包含了 dlv 启用相应命令 debug 服务监听,当 containerd 调用时,调用的是我们脚本,运行命令并阻塞在我们下的断点处,这时我们通过Goland remote debug连接即可这里顺便给出具体的方法
设置源并安装 dlv,任意目录运行
默认的 GOPATH 在
家目录/go
我这里安装到了 /root/go/bin/dlv
到
docker-runc/src/github.com/opencontainers/runc
目录执行把编译出来的 runc 移动到默认环境变量找到的地方,方便其他组件调用,并写一个脚本通过 dlv 去调用
Goland端配置
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F01d0e69a-b11f-4cf5-a89f-a33e9f9a7e18%2FUntitled.png?table=block&id=4f51280c-05ff-4f5a-bba6-89d14e64b46f&t=4f51280c-05ff-4f5a-bba6-89d14e64b46f&width=2166&cache=v2)
containerd-shim 也是类似
此时我们通过 cli 发送一个请求,即可打通 dockerd containerd containerd-shim 和 runc 了
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fa268ff2e-88d1-42d0-b548-a9e6b17693cd%2FUntitled.png?table=block&id=0054e9cc-1ec3-49a2-82b9-552bf59d47e2&t=0054e9cc-1ec3-49a2-82b9-552bf59d47e2&width=2612&cache=v2)
一些其他问题
1、启动Dockerd问题
docker 没退出干净,看残留的docker进程,然后kill掉就行
2、可能在运行的Linux上原本通过metarget之类的安装了Docker或者K8S,但它们有的时候卸载不会把containerd和runc给卸载掉,因此搭建前最后确保这些卸载干净
3、在搭建 dockerd 和 containerd 服务时候可能报错如下
这是因为相关库没安装导致的,根据错误提示安装相应的依赖即可
除了这个包我还安装了这两个包
缺的包跟操作系统有关,根据报错提示来就好啦~