0x00 前言
上篇文章中学习了FastJson主流的第二版漏洞,在v1.2.48中 设置cache为false修复了这个漏洞,之后在1.2.68中出现了新的利用方式。本文就FastJson1.2.68版本的漏洞进行详尽的原理分析。
0x01 导火索之expectClass
这次的罪魁祸首的确是expectClass参数,但是!expectClass的由来还得从autoType说起。
fastjson为了实现反序列化引入了autoType,之后防止进行恶意反序列化对象从而导致RCE,引入了checkAutoType(),这就引出了很多的问题,包括我们之前提到的都属于它的锅。而这次的expectClass参数也是checkAutoType()函数的一个参数,在之前的漏洞中都没有用到过,所以这次是一个全新的bypass checkAutoType()的方式,也造成了今年疯传的那个漏洞。
我们先来看一下通过checkAutoType()校验的方式有哪些:
- 白名单里的类
- 开启了autotype
- 使用了JSONType注解
- 指定了期望类(expectClass)
- 缓存在mapping中的类
- 使用ParserConfig.AutoTypeCheckHandler接口通过校验的类
我们这次用的就是第四种方式。
checkAutoType()中的expectClass参数类型为java.lang.Class,当expectClass传入checkAutoType()时不为null,并且我们要实例化的类是expectClass的子类或其实现时会将传入的类视为一个合法的类(不能在黑名单中),然后通过loadClass返回该类的class,我们就可以利用这个绕过checkAutoType()。
此外,由于checkAutoType()中黑名单的检测位于loadClass之前,所以不能在黑名单中,另外恶意类需要是expectClass的接口或是expectClass的子类。
我们查找把expectClass参数传递给checkAutoType()函数的利用类有两个:
- 在JavaBeanDeserializer类的deserialze()函数中会调用checkAutoType()并传入可控的expectClass
- 在ThrowableDeserializer类的deserialze()函数中也会调用并传入
无论是上述哪一个利用类都会将@type的值作为typeName传给expectClass并调用checkAutoType(…expectClass…),所以思路就是在poc里写两个@type,第一个正常通过checkAutoType(),然后调用上述类中的deserialze()函数,然后在其中使用expectClass绕过第二次的checkAutoType()函数。
其中对应的分别为:AutoCloseable类和Throwable类,接下来详细分析。
0x02 详细分析
AutoCloseable
首先IDEA创建1.2.68的fastjson的maven项目,之后编写Exp.java和JavaBean.java。
//Exp.java
package com;
import com.alibaba.fastjson.JSON;
public class Exp {
public static void main(String args[]){
JSON.parseObject("{\"@type\":\"java.lang.AutoCloseable\", \"@type\":\"com.JavaBean\", \"cmd\":\"calc.exe\"}");
}
}
//JavaBean.java
package com;
import java.io.IOException;
public class JavaBean implements AutoCloseable{
public JavaBean(String cmd){
try{
Runtime.getRuntime().exec(cmd);
}catch (IOException e){
e.printStackTrace();
}
}
public void close() throws Exception {
}
}
启动后弹出计算器,触发payload。
首先在JSON.parseObject()断点进入调试
同之前文章分析的大体相同,在DefaultJSONParser.java中,parseObject()对传入数据进行解析,如果是@type则获取到类型名后进行checkAutoType()的检查
单步步入后详细分析下checkAutoType()的检查逻辑
其中一处可以看到如果expectClass不为null,且不在那几个里面就会把expectClassFlag设置为true,我们第二次进入checkAutoType()就会用到
接下来会进行黑白名单查询。首先进行内部白名单,之后及进行内部黑名单,由于内部黑名单为null故跳过
之后,如果非内部白名单并且开启autoTypeSupport或者是expectClass时会进行黑白名单查找。首先在白名单内二分查找,如果在则加载后返回指定class对象,如果不在或者为空,会继续在黑名单中进行二分查找;若不在黑名单且getClassFromMapping返回值为null,就再在白名单查询,若为空则异常,否则continue
之后从缓存中得到赋值给clazz
之后从checkAutoType()返回clazz到DefaultJSONParser.java#parseObject()中,单步到deserializer.deserialze()
在deserialze()解析字段,当为@type(第二个)时,调用checkAutoType()并传入expectClass
第二次步入checkAutoType(),在这里把expectClassFlag设为true
这次进入成功之前分析的黑白名单环节
之后由于autoTypeSupport为true进入if段,又进行了一次黑白名单校验
之后resource将“.”替换为”/“得到路径,并且这里貌似有读文件的功能
之后如果autoType打开或者使用了JSONType注解,又或者 expectClassFlag为true时,并且只有在autoType打开或者使用了JSONType注解时,才会将类加入到缓存mapping中。另外没使用JSONType注解不会返回
之后expectClass不为null,加载到mapping中,并返回JavaBeanDeserializer#deserialze()
之后deserializer.deserialze(),由于将恶意类加入mapping,在反序列化解析时会绕过autoType,成功利用
Throwable
Throwable类反序列化处理为ThrowableDeserializer#deserialze(),当为@type时,同样调用checkAutoType()并传入expectClass,也就是Throwable.class类对象。之后的利用都差不多。
0x03 修复
首先对过滤的expectClass进行修改,新增3个新的类,并且将原来的Class类型的判断修改为hash的判断,通过彩虹表碰撞可以得知分别为:java.lang.Runnable,java.lang.Readable和java.lang.AutoCloseable。
此外,Fastjson 1.2.68及之后的版本引入了safeMode功能,作为控制反序列化的开关,开启后禁止反序列化,会直接抛出异常,彻底解决反序列化造成的RCE问题,一劳永逸。
0x04 结语
到目前为止,相关的FastJson漏洞都讲了一些,就告一段落了,相信都非常详细,可以简单查看并进行学习。