Docker (远程) Debug 调试环境搭建
2022-7-7
| 2024-2-2
0  |  阅读时长 0 分钟
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
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
一般本地安装好 Go 会自动识别,没有的话 Add SDK 指定本地安装目录或者 Download.. 直接下载即可,版本和与一开始的Golang相同,尽量不要比一开始安装的Golang版本低即可

远程调试环境搭建(本地可跳过此步骤)

顺便介绍下远程调试配置方法,新建一个空项目
notion image
配置远程SFTP访问
notion image
notion image
notion image
设置SSH连接信息
notion image
设置远程项目部署目录,最后点击OK
notion image
notion image
最后从远程下载源码即可,新建项目时还有个 go.mod 是无用的,删除即可

项目设置

此部配置目的是为了让本地的Goland正确识别代码依赖
这里注意因为默认 Goland 开启 Go Module,要取消
notion image
并添加项目 Project GOPATH
notion image

Go Build设置 (本地可跳过此步骤)

接下来配置远程Go环境,新建一个 Go Build 项目,注意 Run on 处选择SSH
notion image
因刚刚我们配置了SFTP,直接在 Existing 中选择即可
notion image
点击 Next,这一步报错无所谓,因为我远程用的是 fish shell,用 bash 的同学应该问题不大
notion image
继续点Next即可, 确保下面红线框住的3处配置正确即可
notion image
点击 Finish 即可

调试 Go 包选择

配置Go 包为:github.com/docker/cli/cmd/docker,因为cli的入口为这个包,后面moby和containerd根据要调试的包选择即可。注意勾选下面的 Build on remote target
notion image
PS: 这里 Run kind 一定要为 Package,因为Go包构建的基础单位就是包,而不是文件,这里如果 Build 文件也可以运行,因为当前入口 cmd 下,只有一个包,在 Build moby 时,如果 Build file 会报错(问了下ssst0n3师父)
最终点击运行即可,观察运行的路径为远程路径
notion image
点击运行右边的Debug按钮也可以正常运行,moby 和 containerd 服务的debug环境重复此步骤即可,不过有点注意事项
  1. 要先启动 containerd 在启动 dockerd 服务,因为从架构图可知,dockerd 服务依赖于 containerd 服务,否则启动异常
  1. 启动 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服务启动前默认从环境变量的可执行文件目录搜索,当然也可以通过指定参数指定路径
  1. 见【一些其他问题】章节中的第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-shimrunc,里面包含了 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
containerd-shim 也是类似
此时我们通过 cli 发送一个请求,即可打通 dockerd containerd containerd-shim 和 runc 了
notion image

一些其他问题

1、启动Dockerd问题
docker 没退出干净,看残留的docker进程,然后kill掉就行
 
2、可能在运行的Linux上原本通过metarget之类的安装了Docker或者K8S,但它们有的时候卸载不会把containerd和runc给卸载掉,因此搭建前最后确保这些卸载干净
 
3、在搭建 dockerd 和 containerd 服务时候可能报错如下
这是因为相关库没安装导致的,根据错误提示安装相应的依赖即可
除了这个包我还安装了这两个包
缺的包跟操作系统有关,根据报错提示来就好啦~
 

引用

  • 开发
  • 漏洞复现
  • Golang
  • 云安全
  • CISP-PTS考证记vulnhub Linux提权靶机 | escalate_linux_1
    • GitTalk
    目录