文章目录
Fastjson is a Java library that can be used to convert Java Objects into their JSON representation. It can also be used to convert a JSON string to an equivalent Java object. Fastjson can work with arbitrary Java objects including pre-existing objects that you do not have source-code of.
可以由JSON.toJSONString(object)将对象序列化为json字符串,由JSON.parseObject/parseArray或JSON.parse从json字符串中反序列化还原为对象。在序列化时指定开启SerializerFeature.WriteClassName功能,会在生成的json字段中加入@type用于指定具体类名,这样可以避免继承类被误反序列化为父类,从而丢失子类特有属性。
1.2.24一般流程简述
根据方法与传参的不同,入口可能对应多个重载方法,但总体上与漏洞相关的逻辑大同小异:
解析器初始化,调用解析方法
加载
@type指定的类对象和与之对应的Deserializer如果类名在
denyList中就抛出异常(黑名单)如果是
JavaBeanDeserializer,会通过内省获取方法和属性获取到
defaultConstructor则会进入第六步将满足条件的setter与getter及相关属性,封装为
FieldInfo类并加入fieldList


将
Deserializer缓存进IdentityHashMap获取各个属性对应的
FieldValueDeserilizer,反序列化属性对象由
setValue根据属性类型,按照不同方式还原类对象属性

调用getter
第六步getter对于返回值类型要求比较严格,可以利用Fastjson一些特性扩大范围。
对于JSON.parseObject(String text)会先调用parse解析,如果返回类型不是JSONObject就通过toJSON转换:

满足判断时可由JSONSerializer.getObjectWriter->TypeUtils.buildBeanInfo->computeGetters获取getter且无返回值类型限制。因为JSONObject.toString被重写成了toJSONString,只需构造形如{{Gadgets}:"str"}的结构即可触发。
此外对于JSON中$ref的值,会在parse完成后作为JSONPath解析,传入$.valuea.valueb可以链式触发getValuea和getValueb,这样就能调用到指定getter。
指定还原类
当parseObject方法指定的还原类与JSON中@type指定的还原类不一致时,根据@香依香偎师傅的实验结果来看,如果两个类存在继承关系或是能由toString等方法转换,则通常可以正常反序列化并触发Payload;否则要具体情况具体分析。
TemplatesImpl利用链
1 | { |
outputProperties属性在setValue时,会调用TemplatesImpl#getOutputProperties,进而调用到TransletClassLoader#defineClass触发类加载。
Payload几个关键属性都是private且无相应setter/getter,因此反序列化时需要指定1.2.22版本引入的Feature.SupportNonPublicField:

JdbcRowSetImpl利用链
1 | { |
autoCommit属性在setValue时,会调用JdbcRowSetImpl#setAutoCommit,进而调用到InitialContext#lookup触发JNDI注入。
checkAutoType绕过
从1.2.25开始先进入checkAutoType检查,再调用TypeUtils.loadClass加载类。
绕过黑名单
在手动开启autoTypeSupport时,会将类名与内置黑名单进行前缀匹配。利用TypeUtils.loadClass剔除L前缀和;后缀的特点,在原类名前后添加字符即可绕过内置黑名单的判断。
L ClassName ;、[分别是JVM的类、数组表示符
1.2.42先剔除一次类名前后的L和;再进行黑名单判断,同时将黑名单从明文改为哈希形式,可以双写字符绕过。
1.2.43检查两次前缀,拦截LL开头的情况,可以构造[ClassName[{...的数组形式,利用内部逻辑调用到parseArray实现绕过。
1.2.44增加了对[开头的检查,终结了已有黑名单绕过。如果存在某些依赖可以利用新链突破,后续更新也在继续完善黑名单。
利用缓存机制
1 | { |
以1.2.47为例,java.lang.Class对应的deserializer是MiscCodec,反序列化会解析JSON中val对应的值并生成对象,之后通过TypeUtils.loadClass加载java.lang.Class。loadClass方法的cache参数的缺省值为true,会将val值生成的对象存入mappings中。
下一轮checkAutoType时如果手动开启了autoTypeSupport,恶意类先黑名单匹配还是会被检测到,但只要在mappings缓存中匹配到了就不会抛出异常。之后无论checkAutoType是否开启,都能拿到缓存中的恶意类。

要注意的是虽然1.2.25就加入了缓存机制,但1.2.33之前匹配黑名单就会抛出异常。1.2.48将cache缺省值改为false,新增了Class.forName前的判断,同时追加了黑名单以及对expectClass的检测。
利用期望类
期望类可以由类间关系隐式确定,也可以由两个@type显式指定。一个期望类为Foo的例子:
1 | class User { |
- 由类间关系确定
1 | { |
- 由JSON显式指定
1 | {"@type":"Foo","@type":"FooImpl","fooId":"abc"} |
从玄武实验室梳理的checkAutoType的流程可以看出,期望类及其派生的子类不在黑名单中就能通过检查:

expectClass由checkAutoType的二参传入,多数情况下是null,先要找出存在有效参数的调用,一个是ThrowableDeserializer、一个是JavaBeanDeserializer

在ParserConfig#getDeserializer可以看到Throwable.class的派生类由ThrowableDeserializer处理、没有专属Deserializer的类由JavaBeanDeserializer处理

哪些类会由JavaBeanDeserializer处理呢?以TypeUtils#addBaseClassMappings的classes作为样本,提取getDeserializer方法稍作改动并筛除expectClassFlag为false的类后得到:
1 | java.lang.AutoCloseable |
- 1.2.68更新的
safeMode机制用于完全禁用autoType,手动开启后遇到@type直接抛异常
寻找Gadgets
派生类中有可被自动调用的敏感方法,就是一个可选的潜在期望类,最好是一个被广泛使用的接口(AutoCloseable表示你直接报我哈希得了(✪ω✪))。而魔术方法除了setter、getter、无参构造函数外,还可以调用 能通过ASMUtils.lookupParameterNames获取到参数名 且重载参数最多的构造函数:

- 参数名存放于
LocalVariableTable,只在编译时带调试信息才会有
OutputStream利用链
1 | { |
通过
FileOutputStream(File file, boolean append)创建文件并作为InflaterOutputStream(OutputStream out, Inflater infl, int bufLen)的一参二参
Inflater类中存在ByteBuffer input属性,经过JSONScanner#bytesValueBase64解码得到byte后,会在ByteBufferCodec$ByteBufferBean#byteBuffer完成组装(其中array是zip压缩后byte的Base64编码,limit是字节长度)最终由
MarshalOutputStream的父类构造函数ObjectOutputStream->this.bout.setBlockDataMode->this.drain->this.out.write触发写入
要注意的是1.2.57才开始支持ByteBuffer反序列化,有调试信息的部分JDK8版本与这个OpenJDK11input属性有不同。
MysqlJDBC利用链
1 | { |
JDBC4Connection类构造函数初始化时会使用父类ConnectionImpl的构造函数,进一步通过ServerStatusDiffInterceptor#populateMapWithSessionStatusValues->Util.resultSetToMap->rs.getObject触发MysqlJDBC反序列化。
双亲委派类加载
通过System.getProperty可以获取sun.boot.class.path、java.ext.dirs的值,根据双亲委派的特点如果能将恶意类上传/写入jre/classes(这个目录似乎默认不存在,需要新建),或者覆盖已有的jar包(比如charsets.jar),就可以由Fastjson触发恶意类加载。
1 |
|
参考链接
fastjson一种利用$ref几乎任意getter触发的方法
fastjson 1.2.68 反序列化漏洞 gadgets 挖掘笔记
Fastjson 1.2.68 反序列化漏洞 Commons IO 2.x 写文件利用链挖掘分析
eu-19-Zhang-New-Exploit-Technique-In-Java-Deserialization-Attack