0x01 前言:
最近在复现&分析 Java 领域历史相关漏洞,学习学习 Java 框架漏洞代码审计思路。分析原理少不了调试,然而每次都得搭建漏洞环境,又是构建maven, 又是引入依赖,挺繁琐的。Github 有很多其他同行利用 Docker 搭建好的漏洞环境镜像,一键拉取运行即可, 但是问题来了, 运行在 docker 容器里的程序我怎么断点调试呢?手上没有构建镜像时的源代码,该怎么办呢?
0x02 解决思路:(有兴趣可了解)
Docker 容器里面一般运行的是 java 打包的 jar 包,那么就等于要解决怎么远程调试正在运行的 jar 包?我们先看看本地项目市如何 Debug 的,仔细的你是否察觉到平时IDEA上Debug本地项目的时候都会出现的一行信息?
为什么 Debug 会出现这么一行信息呢?由于博主学过一些 JVM 虚拟机相关知识 (Java 开发必须了解 JVM😥😥),IDEA Debug 大致过程如下:
以上过程被称为 JPDA调用体系。
JPDA(Java Platform Debugger Architecture)是 sun 公司开发的 java平台调试体系, 它主要有三个层次组成,即 Java 虚拟机工具接口 (JVMTI) ,Java 调试线协议(JDWP)以及 Java 调试接口(JDI)。
- JVMTI (JVMDI): jdk1.4 之前称为JVMDI,之后改为了JVMTI,它是虚拟机的本地接口,其相当于 Thread 的 sleep、yield native 方法
- JDWP(Java Debug Wire Protocol):java调试网络协议,其描述了调试信息的格式,以及在被调试的进程(server)和调试器(client)之间传输的请求
- JDI:java调试接口,虚拟机的高级接口,调试器(client)自己实现 JDI 接口,比如 idea 等其他编译器。
综上我们知道了 IDEA 调试的原理大致如下:
1、先建立起了 socket 连接
2、将断点位置创建了断点事件通过 JDI 接口传给了 服务端(程序端)的 VM,VM 调用 suspend 将 VM 挂起
3、VM 挂起之后将客户端需要获取的 VM 信息返回给客户端,返回之后 VM resume 恢复其运行状态
4、客户端获取到 VM 返回的信息之后可以通过不同的方式展示给客户端
好了,讲了这么多,现在我们知道了本地调试其实也可以认为是远程调试,IDEA 通过 127.0.0.1:20256(端口随机)与 JVM 进行 socket 通信。那么这个端口到底怎么设置的?这就要搬出咱们的 jdk-8xxx-docs-all 官方完整文档 来查阅了👏👏。端口是由 JVM 创建的这毋庸置疑,所以直接点击最下层的 Java HotSpot Client and Server VM 进行查阅:
进入以后选择对应的操作系统 (当前需要运行的 Java 程序在什么操作系统上,以 Windows 为例):
跳转以后 Ctrl + f 搜索 Debug 关键字如下:
文档中详细描述了,启动 Java 程序之前,如果需要 Debug,需要添加参数:
-agentlib:jdwp=transport=dt_socket,server=y,address=xxxx
。adderss 填写自定义端口。
👏👏 现在咱们知道 JVM 如何手动开通 Debug 端口了,但是又一个问题来了,IDEA 如何自定义连接 JVM 的呢 ?只要不会咱们就翻官方文档,官方文档往往会带来惊喜。具体如何连接,请自行前往查阅:👉 Tutorial: Remote debug
由于咱们是引入的别人的docker镜像,咱们手上又没有构建docker镜像时的源代码,咱们最多只能提取 docker 容器中的 jar 包。突然想到我们平时 IDEA 引入第三方 jar 包,只要 Add as Library 操作,jar 包就被打开了,可以看到 “源代码”,并且 jar 包内的ClassName就可以被我们实例化调用,还可以在 jar 包的.class 文件里打断点进行调试。咱们不妨试试这样行不行得通? 事实上是可行的!
👏👏 OK ,经过这一路上的思考过程,远程调试 Docker 中运行的 Java 程序也就 so easy 啦!
🌟实现步骤:
1、在 docker-compose.yml 配置映射端口让 jvm debug 端口能外部访问。
2、在 docker-compose.yml 中使用 command 字段添加自定义启动命令:java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=xxxx -jar jar包名称.jar
。
3、容器启动后,从容器中把运行的 jar 包复制出来,新建一个文件夹 ,IDEA 点击 Open 打开这个文件夹, 复制粘贴 jar 包, 右键jar包,选择 Add as Lirary 添加到项目依赖库中,并在代码上打上几个断点。
4、 IDEA 配置 Remote Debug,点击 Debug 运行即可。
0x03 具体操作:
以 fastjson 反序列化漏洞docker镜像为例进行远程调试。
1、在 docker-compose.yml 配置映射端口让 jvm debug 端口能外部访问。
2、在 docker-compose.yml 中使用 command 字段添加自定义启动命令:java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=xxxx -jar jar包名称.jar
。
是不是突然发现自己不知道 jar 包名字叫啥?😆😆 咱们可以先启动容器,然后执行
docker ps --no-trunc
不截断输出完整的容器描述,就可以看到容器名称以及容器中的路径。
3、容器启动后,从容器中把运行的 jar 包复制出来,新建一个文件夹 (例:CVE-2017-18349),IDEA 点击 Open 打开 CVE-2017-18349 文件夹, 复制粘贴 fastjsondemo.jar, 右键jar包,选择 Add as Lirary 添加到项目依赖库中,并在代码上打上几个断点。
启动容器后,执行命令:
docker cp 容器id:jar包路径 目标路径
, 将 jar 复制出来。
4、 IDEA 配置 Remote Debug,点击 Debug 运行即可。
测试发现远程 debug 失败… 到底是什么原因呢?
问题解决: 打开一个自己原先开发的项目,瞅一瞅引入的 jar 包结构是啥样的, 比较区别:
Project Structure,Modules->Dependencies 添加要调试的class文件的目录 BOOT-INF。
再次测试远程断点调试是否生效:
但是还是有问题,断点调试无法进入第三方依赖包中,我们需要把lib文件夹复制到根目录,并右键 Add as Library 。
最终远程断点调试的前期准备全部完成,可以快快乐乐的打断点了~
0x04 提醒:
❗️❗️❗️如果你是一名Java开发人员,请注意如果生产环境下迫不得已需要远程调试,调试完一定要关闭JDWP服务,或者JDWP服务监听的端口不对公网开放。❗️❗️❗️
附一篇优秀的 JDWP 服务漏洞利用文章 ===> JDWP调试接口远程命令执行漏洞原理分析