Java JRMP
概念
Java Remote Method Protocol,Java远程方法协议。
RMI依赖的通信协议为JRMP,该协议用于查找和引用远程对象,是运行在RMI之下、TCP/IP之上的线路层协议。
一个RMI的过程,需要用到JRMP这个协议去组织数据格式然后通过TCP进行传输、从而达到调用远程方法的目的。
当服务端与客户端之间通过socket建立连接,其中通信的协议就是通过JRMP协议格式来进行通信的
JRMP接口的两种常见实现方法:
- JRMP协议(Java Remote Message Protocol):RMI专用的Java远程消息交换协议
- IIOP协议(Internet Inter-ORB Protocol),基于CORBA实现的对象请求代理协议
DGCImpl_Stub和DGCImpl_Skel
前面我们在利用RMI攻击时都是围绕着RegistryImpl_Stub和RegistryImpl_Skel之间来讲的
而其中还有一种JRMP的攻击没有进行讲解,前面我们在将DGC分布式垃圾回收的时候也讲过,在执行RegistryImpl_Stub.lookup方法中,在接受服务端的返回值后会通过done的后续调用创建DGCImpl_Stub,并调用DGCImpl_Stub.dirty方法,该方法中同样会调用invoke进行传输然后将返回内容进行反序列化。
而处理请求对应于DGCImpl_Skel.dispatch方法,当DGC调用完DGCImpl_Stub.dirty方法,DGCImpl_Skel.dispatch会处理这个方法的请求,其中同样存在反序列化,还原lease对象
0对应处理clean请求,1对应处理dirty请求
ysoserial程序分析
在ysoserial中有exploit模块和payload模块,每个模块下都有JRMPClient和JRMPListener的脚本,对应着两种攻击方式
payloads/JRMPListener+exploit/JRMPClient
第一种方法是基于RMI反序列化的客户端打服务器类型。通过将一个payload(JRMPListener)发送到存在漏洞的服务器,存在漏洞的服务器反序列化该payload(JRMPListener)后在指定端口开启RMI监听,然后再通过exploit(JRMPClient)去发送利用链载荷,最终在存在漏洞的服务器上进行反序列化操作实现攻击。
我们从代码的角度上来分析,先看ysoserial.payloads.JRMPListener的利用链:
跟过远程对象创建的师傅们应该都比较熟悉这个链子,链子后面不就是创建远程对象的部分过程吗,只不过是通过UnicastRemoteObject.readObject这个反序列化入口来进行的:
调用UnicastRemoteObject.reexport()方法:
接着调用UnicastRemoteObject.exportObject()方法,后面就是创建远程对象时的流程了,后面将封装好的target对象通过exportObject发布出去,其中会调用listen()方法创建socket并开启监听等待连接
看一下ysoserial的操作,先看一下继承链:
在ysoserial中用到了UnicastRemoteObject的子类ActivationGroupImpl
调用了Reflections.createWithConstructor方法,这是自定义的方法,有四个参数,跟进下:
逻辑大概就是:
1 | int port = 1099; |
先获取RemoteObject的私有构造器,参数为RemoteRef对象:
然后调用ReflectionFactory.getReflectionFactory()获取ReflectionFactory对象,再调用newConstructorForSerialization方法来获取构造方法,这里将ActivationGroupImpl的序列化构造行为劫持到RemoteObject的构造器,这样能绕过JVM对构造器的安全检测,同时获取到ActivationGroupImpl对象后,向上转型就能获取到UnicastRemoteObject,避免了直接实例化UnicastRemoteObject对象直接触发监听。
之所以选择ActivationGroupImpl是因为在继承链中,ActivationGroupImpl 是 RMI 中唯一同时满足:
- 非抽象类
- 继承自
UnicastRemoteObject - 在标准JDK中预加载(避免 ClassNotFoundException)
- 实现
Serializable接口
对这个构造方法调用newInstance(new UnicastServerRef(port)方法来实例化远程对象,传入了UnicastServerRef
当反序列化UnicastRemoteObject对象字节流,就会触发它的构造方法,从而在指点端口开启监听
当正常对UnicastRemoteObject反序列化,会发现端口并不是指定的,而是一个随机端口,所以需要通过反射指定端口
最后返回UnicastRemoteObject对象
此时将payload/JRMPListener注入到服务器后就已经开启了RMI服务,我们就可以通过exploit/JRMPClient发送gadgets来进行利用了(前提对方存在可以利用的gadgets),先看ysoserial:
意思是其攻击手法大致与 {@link RMIRegistryExploit} 相同,只不过:
- 攻击目标 是远程 DGC(分布式垃圾回收服务——只要存在远程对象监听,该服务必然存在)
- 不执行反序列化操作(避免自身被反制 )
在RMIRegistryExploit中我们主要目标是攻击rmi的Registry模块,这里是攻击DGC,且该操作不执行反序列化操作,它Client全都是向server发送数据,没有接受过任何来自server端的数据,这样自己就不会被反制了。
前面在讲DGC时也说过,当为RMI注册端口时,TableObject就存在有DGC了:
1 | LocateRegistry.createRegistry(PORT); |
在ysoserial中,exploit/JRMPClient调用了makeDGCCall:
主要是为了调用dirty方法触发反序列化,传递一个用于反序列化的对象导致命令执行
而这种客户端打服务端的方式虽然也是二次反序列化,但比较鸡肋,因为本身就是一个反序列化的点,结果还需要再去开个rmi服务,然后再次进行攻击,这就显得没必要,且后面存在jep290的限制,但这个二次反序列化可以起到绕过黑名单的效果
payloads/JRMPClient+exploit/JRMPListener
这种就类似于服务端打客户端类型,同样也是二次反序列化,也有绕过黑名单的作用。且这种服务端打客户端的类型比客户端打服务端的类型更加常用,它一方面能外连,另一方面能绕过jep290的限制。
先看payloads/JRMPClient:
反序列化UnicastRef类
UnicastRef实现了RemoteRef接口,RemoteRef接口又实现了Externalizable接口,Externalizable接口又实现Serializable
Externalizable接口定义了writeExternal和readExternal方法,用于实现序列化和反序列化
UnicastRef.readExternl方法:
调用对序列化数据流调用LiveRef.read方法:
useNewFormat为false,会调用TCPEndpoint.readHostPortFormat(in)方法:
通过输入流来获取host和port,返回一个封装了host和port的TCPEndpoint对象
然后创建一个LiveRef对象将ObjId、host、port等信息封装进去
而如果我们控制输入流不为ConnectionInputStream类,那么就会调用DGCClient.registerRefs(ep, Arrays.asList(new LiveRef[] { ref }));方法:
首先会执行一次EndpointEntry.lookup(ep)方法,返回EndpointEntry对象,然后会调用EndpointEntry.registerRefs方法:
EndpointEntry.registerRefs最后会调用一次makeDirtyCall方法,跟进:
可以看到会调用dirty方法,实际上是调用DGCImpl_Stub.dirty方法,这个方法下调用newCall方法建立连接,还会对remoteCall进行一次反序列化
在注册远程对象时,利用RemoteObjectInvocationHandler来为UnicastRef创建动态代理,这个过程类似于RMI创建远程对象的部分:
1 | RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref); |
还需要一个UnicastRef对象:
需要LiveRef对象对host、port、ObjId等远程标识对象进行封装:
所以:
1 | ObjID objID = new ObjID(new Random().nextInt()); |
所以payloads/JRMPClient主要功能就是生成一个向指定攻击机IP和端口发起RMI通信。
再看exploit/JRMPListener:
就是一个通用JRMP侦听器,大致逻辑就是打开一个JRMP侦听器,该侦听器会将指定的有效负载传递给连接到它并进行调用的任何客户端。
ysoserial中,获取一个用于反序列化对象:
这样当客户端向exploit/JRMPListener进行连接时,就会返回一个序列化对象,客户端接受对象后会进行反序列化等操作,而这个恶意对象就是这里payloadObject
所以这个攻击手法整体的流程就是通过在受害机找到反序列化注入点,然后将payloads/JRMPClient写入,其反序列化时会与攻击机建立RMI通信,攻击机开启监听后生成第二次反序列化的payload,返回恶意对象,受害机收到对象后会进行反序列化造成攻击
参考
https://www.cnblogs.com/zpchcbd/p/14934168.html
https://xz.aliyun.com/news/6860
https://xz.aliyun.com/news/6675
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/

































