Java JRMP反序化
RMI依赖的通信协议为JRMP(Java Remote Message Protocol ,Java 远程消息交换协议),该协议为Java定制,基于TCP/IP之上,RMI协议之下,当需要进行RMI远程方法调用通信的时候要求服务端与客户端都为Java编写。、
这个协议就像HTTP协议一样,规定了客户端和服务端通信要满足的规范,RMI底层默认使用的JRMP进行传递数据,并且JRMP协议只能作用于RMI协议。
通过DGCImpl来实现攻击的也有两种,DGCImpl_Stub#dirty
(服务端攻击客户端),还有个就是DGCImpl_Skel#dispatch
(客户端攻击服务端)
之间看 DGCImpl_Skel#dispatch
方法:
存在个 case1 和 case2,分别对应了 clear
和 dirty
方法,和 RgestryImpl_Skel#dispatch
中异曲同工。而且这里的两种都有反序列化,
ysoserial程序分析
payload/JRMPListener
调用链
UnicastRemoteObject.readObject()
UnicastRemoteObject.reexport()
UnicastRemoteObject.exportObject()
UnicastServerRef.exportObject()
LiveRef.exportObject()
TCPEndpoint.exportObject()
TCPTransport.exportObject()
TCPTransport.listen()
通过 createWithConstructor
方法来实例化了一个 UnicastRemoteObject
对象,
看到就是通过反射调用进行实例化对象,看到其第四个参数是构造函数所需的具体参数也就是 consArgs
,跟进第四个参数 UnicastServerRef
构造方法,
调用了其父类的构造方法,继续跟进。
调用了其其他构造方法。
嗯,和前面的 rmi 服务类注册没什么区别,TCPEndpoint.getLocalEndpoint(port)
就是一些对网络请求的处理,继续向下
其实也就没什么了,结束返回对象 UnicastServerRef
,其中包含的 ref 如下
接着就是进入createWithConstructor方法了,这个就是刚才说的通过反射来进行实例化对象的方法,
首先进行获取consArgsTypes类型参数的构造方法,传入的constructorClass
为RemoteObject
,所以获得的其构造方法。
然后执行 ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons)
这样就可以绕过构造器直接进行实例化并且不会对进行 ref 进行初始化。最后获得实列 ActivationGroupImpl
,这里还向上转型了 UnicastRemoteObject
(这样可以避免直接实例化 UnicastRemoteObjec
t对象直接触发监听)。
当正常对 UnicastRemoteObject
反序列化,会发现端口并不是指定的,而是一个随机端口,最后通过反射进行修改,传入的参数就是端口
Reflections.getField(UnicastRemoteObject.class, "port").set(uro, jrmpPort);
到这里构造JRMP Listener的payload就已经结束了。
payload/JRMPClient
反序列化UnicastRef类,UnicastRef实现了RemoteRef接口,RemoteRef接口又实现了Externalizable接口,Externalizable接口又实现了Serializable接口(这个简单跟进一下就知道了)
其中在 Externalizable
接口定义了 writeExternal
和 readExternal
方法,
这两个方法分别实现了序列化和反序列化,UnicastRef
类中对这两个方法进行了重写。
先看 UnicastRef.readExternal
方法:
调用了LiveRef.read方法,跟进
其中 TCPEndpoint.readHostPortFormat
就是获取host和port,返回一个封装了host和port的TCPEndpoint。
然后看到 new 了个 ref 对象。以前 RMI 中经常遇见这个对象,里面就是封装的一些 host,port,objid 等信息。看到如果输入流的类型就可以不是 ConnectionInputStream
类型(这个输出流我们是可以进行控制的),那么就会进入else语句
调用 DGCClient.registerRefs()
方法,跟进该方法
看到调用 lookup 方法,然后返回的 EndpointEntry
对象调用 registerRefs()
方法,在该方法结尾处调用
跟进makeDirtyCall方法,其中会调用DGC.dirty方法
实际会调用 DGCImpl_Stub.dirty
方法,这个方法下调用了newCall 方法建立一次连接,还会会对remoteCall进行一次反序列化
所以这里利用下面方法方法创建了个 UnicastRef
对象
ObjID id = new ObjID(new Random().nextInt()); // RMI registry
TCPEndpoint te = new TCPEndpoint(host, port);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
因为创建 UnicastRef
对象需要 LiveRef
对象,而 LiveRef 对象里的参数有需要 TCPEndpoint
对象。
然后创建 UnicastRef
的动态代理。
RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
Registry proxy = (Registry) Proxy.newProxyInstance(JRMPClient.class.getClassLoader(), new Class[] {
Registry.class
}, obj);
这个类的关键代码就是这些了。
exploit/JRMPListener
exploit/JRMPListener开启监听,客户端向exploit/JRMPListener进行连接时,会返回给客户端一个序列化对象,服务器接收一个对象后会进行反序列化操作
这个恶意对象就是这里payloadObject
有什么办法让客户端主动向exploit/JRMPListener进行连接?
那就要用到payload/JRMPClient,UnicastRef对象封装了host、port等信息,反序列化UnicastRef对象后,会触发DGC通信,与指定host的指定port进行连接
导致恶意服务端传输恶意数据流,在客户端造成反序列化
exploit/JRMPClient
而在ysoserial中,exploit/JRMPClient
调用了makeDGCCall
主要为了调用dirty方法触发反序列化,传递一个用于反序列化的对象
最后在远程DGC层触发反序列化,以达到攻击远程DGC层的目的
关于JRMP的两种攻击流程如下
第一种攻击方式
个人理解:基于RMI的反序列化中的客户端打服务端的类型
我们需要先发送指定的payload(JRMPListener)到存在漏洞的服务器中,使得该服务器反序列化完成我们的payload后会开启一个RMI的服务监听在设置的端口上。
我们还需要在我们自己的服务器使用exploit(JRMPClient)与存在漏洞的服务器进行通信,并且发送一个利用链,达到一个命令执行的效果。
简单来说就是将一个payload(JRMPListener)发送到存在漏洞的服务器,存在漏洞的服务器反序列化操作该payload(JRMPListener)过后会在指定的端口开启RMI监听,然后再通过exploit(JRMPClient) 去发送利用链载荷,最终在存在漏洞的服务器上进行反序列化操作。
第二种攻击方式
个人理解:基于RMI的反序列化中的服务端打客户端的类型,这种攻击方式在实战中比较常用
将exploit(JRMPListener)作为攻击方进行监听。
我们发送指定的payloads(JRMPClient)使得存在漏洞的服务器向我们的exploit(JRMPListener)进行连接,连接后exploit(JRMPListener)则会返回给存在漏洞的服务器序列化的对象,而存在漏洞的服务器接收到了则进行反序列化操作,从而进行命令执行的操作。
PS:这里的payload和exploit就是指的不同包下的JRMPListener和JRMPClient!
第一种攻击方式(客户端攻击服务端)
payloads.JRMPListener+exploit.JRMPClient
看到上面 yso 中的四个类分析,可以先利用 payloads.JRMPListener
让客户端开启监听端口,在 yso 的运行下,这个对象将会被序列化处理,然后被进行传输,那么既然被序列化了,那么肯定是需要被触发的。
先通过yso来进行生成这个序列化对象:java -jar ysoserial-0.0.6-SNAPSHOT-all.jar JRMPListener 1099 > payload1.txt
然后创建个可以进行反序列化的服务器来继续测试
package org.example;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class jrmptest {
public static void main(String[] args) {
deserialize();
}
public static void serialize(Object obj) {
try {
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("jrmplistener_payload.txt"));
os.writeObject(obj);
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void deserialize() {
try {
ObjectInputStream is = new ObjectInputStream(new FileInputStream("D:\\yingwenmingthree\\ysoserial-master\\target\\payload1.txt"));
is.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行
虽然这里没有什么显示,但是去查看端口发现 1099 端口已经开始监听了。
说明已经成功反序列化了我们的 payload1.txt,
具体RMI服务端和注册端如何绑定远程对象的详细过程参考:https://www.cnblogs.com/zpchcbd/p/13517074.html
当前面的payloads/JRMPListener
作用了之后,那么对方就已经开启了RMI服务,接下来我们就可以通过exploit/JRMPClient
发送gadgets
来进行利用了(前提对方存在可以利用的gadgets)
该方法中的两个注解:
一、其功能和 RMIRegistryExpoit
类似,但是 RMIRegistryExpoit
主要目标是 rmi 的 Registry 模块,而 JRMPClient攻击的目标是的 rmi 中的 DGC 模块(Distributed Garbage Collection),只要有RMI服务监听了,都会有DGC的存在!
二、因为它Client全都是向server发送数据,没有接受过任何来自server端的数据。在 exploit/JRMPListener 和 payloads/JRMPClient 的利用过程中,这个 server 端和 client 端,攻击者和受害者的角色是可以互换的,在你去打别人的过程中,很有可能被反手一下,所以最好的情况就是,只是发送数据,不去接受另一端传过来的信息,所以说用这个 exploit/JRMPClient
是不会自己打自己的)
先来看下yso的exploit/JRMPClient
的攻击复现,这里接着上面反序列化了payload/JRMPListener模块,开启了一个1099端口
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPClient 127.0.0.1 1099 CommonsCollections5 calc
直接就攻击成功了。
其调用栈
其实该 payload 最主要的就是利用的 DGCImpI_Skel 的 dispatch 方法中的分支的反序列化进行攻击。剩下的我感觉其实就是 RMI 服务端接收来自客户端的数据的一些处理。
第二种攻击方式(服务端攻击客户端)
exploit.JRMPListener+payloads.JRMPClient
这个服务端打客户端的类型比起客户端打服务端的类型会更加的常用,一方面是外连,另一方面更多的因为是该 exploit/JRMPListener+payloads/JRMPClient
还存在绕过 jep290
的限制,所以往往会更加的通用。
payloads.JRMPClient
:携带指定的攻击机 ip 和端口生成受害机第一次反序列化(需要代码中存在一个反序列化点)时的payload,受害机反序列化该payload时会向指定的攻击机ip+端口发起RMI通信,在通信阶段攻击机会将第二次反序列化的payload(如CommonCollections1)发送给受害机,此时发生第二次反序列化,执行真正的恶意命令。
找到一个反序列化点,然后将其payloads/JRMPClient
发送,自己本地开启一个exploit/JRMPListener
监听,如果不在JEP290的限制下的话,就能攻击成功
参考:https://www.cnblogs.com/zpchcbd/p/14934168.html
参考:https://xz.aliyun.com/t/12780
标签:序列化,java,RMI,JRMP,学习,exploit,JRMPClient,JRMPListener,payload From: https://www.cnblogs.com/gaorenyusi/p/18396965