背景
fastjson远程代码执行安全漏洞(以下简称RCE漏洞),最早是官方在2017年3月份发出的声明,
security_update_20170315(如果链接不生效,可以点击下面 了解更多 看原文)
没错,强如阿里这样的公司也会有漏洞。代码是人写的,有漏洞是难免的。关键是及时的修复。
![a354bab0b38ca5534ca86f969fc4d3e9.png](https://img-blog.csdnimg.cn/img_convert/a354bab0b38ca5534ca86f969fc4d3e9.png)
声明中,官方指出:
最近发现fastjson在1.2.24以及之前版本存在远程代码执行高危安全漏洞,为了保证系统安全,请升级到1.2.28/1.2.29/1.2.30/1.2.31或者更新版本。
你会注意到这份声明中并没有对漏洞的细节有详细的描述,原因是官方担心透漏了细节会扩散漏洞。这个是可以理解的,警方公布案件的消息一般都不会透露犯罪细节,就是防止有人模仿犯罪。
公布漏洞之后,阿里进行了升级修复,并给出了防漏洞的建议。然后针对这一版的修复方案,黑客或者安全测试人又找到绕过防御的方案。于是在很长一段时间里,阿里的大佬们不断的修复漏洞,优化升级,黑客们不断的尝试新的攻击方法。整个就是一部fastjson RCE漏洞版的谍战剧。
![19f083789d5da29d4941b1bd6a18d772.png](https://img-blog.csdnimg.cn/img_convert/19f083789d5da29d4941b1bd6a18d772.png)
由于篇幅限制,本文并不打算详细介绍Fastjson RCE漏洞的进化史,而是只关注第一次漏洞分析。
漏洞分析
为了能重现漏洞,本文示例代码都是基于fastjson 1.2.23版本。
如果你是maven的工程,可以直接引入下面这个依赖。
com.alibaba fastjson 1.2.23
fastjson基本使用
为了照顾有读者可能没有使用过fastjson,我们先来看看fastjson的基本用法。
首先我们定义一个实体类,
public class Student { private String name;//姓名 private String number;//学号 private int age; //年龄 private String secret; public String getName() { System.out.println("getName"); return name; } public void setName(String name) { System.out.println("setName"); this.name = name; } public String getNumber() { System.out.println("getNumber"); return number; } public void setNumber(String number) { System.out.println("setNumber"); this.number = number; } public int getAge() { System.out.println("getAge"); return age; } public void setAge(int age) { System.out.println("setAge"); this.age = age; } public String getSecret() { System.out.println("getSecret"); return secret; } public void setSecret(String secret) { System.out.println("setSecret"); this.secret = secret; }}
为了便于分析问题,我在gettter和setter方法中都加了打印日志。
然后我们看使用的示例,
![eec970570bef6fc851761c7a4a394bb8.png](https://img-blog.csdnimg.cn/img_convert/eec970570bef6fc851761c7a4a394bb8.png)
运行输出结果是,
jsonStr1:{"age":25,"name":"lucas","number":"001"}jsonStr2{"@type":"com.app.fastjson.Student","age":25,"name":"lucas","number":"001"}lucas25001
我们把上面的代码改下,使用jsonStr2进行反序列化的,
JSONObject jsonObject = JSON.parseObject(jsonStr2);
此时输出的结果是,
...jsonStr2{"@type":"com.app.fastjson.Student","age":25,"name":"lucas","number":"001"}setAgesetNamesetNumbergetAgegetNamegetNumbergetSecretlucas25001
序列化的结果一个带了类信息(使用SerializerFeature.WriteClassName可以输出类信息),一个没有带。这两个字符串都可以进行反序列成JsonObject对象,但是你应该已经注意到了,使用前者序列化的时候,调用了类对象的getter和setter方法。
因为fastjson在处理以@type形式传入的类的时候,会默认调用该类的共有setgetis函数。
请你记住这个结论,因为它正是漏洞的关键所在。
攻击
既然反序列化的时候会执行getter和setter方法,那我们是不是可以在这些方法里加入攻击的代码,然后fastjson返序列化的时候调用,不就达到了攻击的目的了吗。
我们先来构造一次攻击,等下再解释原理。
首先构造一段恶意代码,很简单就是在构造函数里打开本地的一个计算器应用。
![a44e8d9c6e703d3d45d7505c6d2e2a7c.png](https://img-blog.csdnimg.cn/img_convert/a44e8d9c6e703d3d45d7505c6d2e2a7c.png)
运行的效果如下图所示,
![d1b12fb4dc6e45339499159ec967b5d5.png](https://img-blog.csdnimg.cn/img_convert/d1b12fb4dc6e45339499159ec967b5d5.png)
我们把编译好的EvilCode.class文件留着备用。接着我们写一段poc证明漏洞的存在,
![a5514dccc17dbe3601656493757457d4.png](https://img-blog.csdnimg.cn/img_convert/a5514dccc17dbe3601656493757457d4.png)
运行这段代码,你会发现计算器的引用也会弹出来。这就相当于在反序列的时候执行了我们的恶意代码。
攻击原理分析
我们来分析下上面那段poc代码,首先是读入我们的EvilCodeclass文件,这里有一个小细节就是读入的class文件内容会被base64编码。为什么要这么处理呢?
答案其实也很简单,因为FastJson提取byte[]数组字段值时会进行Base64解码,这个大家看下源码就知道了,不是本文的重点这里不展开了。
接下来就是执行反序列了,我们的EvilCode的class文件是赋值给了TemplatesImpl的_bytecodes属性,_bytecodes却是私有属性,_name也是私有域,所以在parseObject的时候需要设置Feature.SupportNonPublicField,这样_bytecodes字段才会被反序列化。
前面提到过,反序列化的时候会调用成员变量的getter和setter方法,这其中就包括_outputProperties的getter方法,
![1f0c19b4afd5fa9d28a51c7a80204e56.png](https://img-blog.csdnimg.cn/img_convert/1f0c19b4afd5fa9d28a51c7a80204e56.png)
![ae9b7e0a92d4bd5689379cefaed0232f.png](https://img-blog.csdnimg.cn/img_convert/ae9b7e0a92d4bd5689379cefaed0232f.png)
![533ce612de8042b483b105755eee0b45.png](https://img-blog.csdnimg.cn/img_convert/533ce612de8042b483b105755eee0b45.png)
![c1af7def9b0e56a9119475bc661b7b8b.png](https://img-blog.csdnimg.cn/img_convert/c1af7def9b0e56a9119475bc661b7b8b.png)
不知道你看明白了没有。
在getTransletInstance调用defineTransletClasses,在defineTransletClasses方法中会根据_bytecodes来生成一个java类,生成的java类随后会被getTransletInstance方法用到生成一个实例,也也就到了最终的执行命令的位置Runtime.getRuntime.exec()
总结起来,调用的链路是这样:
![391a3a2c64dcafee0970f4cc443e24c7.png](https://img-blog.csdnimg.cn/img_convert/391a3a2c64dcafee0970f4cc443e24c7.png)
解决方案
好了,到这里你已经知道了关于漏洞的细节。那么官方是如何解决这个漏洞的呢?
Fastjson官方在1.2.24版本后默认关闭autotype功能,并且启用黑名单功能。请用户确保该功能关闭。我们简单了解下解决方案的细节。
解决方案的关键是新增了一个checkAutoType方法,早期版本的方法实现如下,
public Class> checkAutoType(String typeName, Class> expectClass, int features) { if (this.autoTypeSupport || expectClass != null) { for(mask = 0; mask
这个denyList你可以理解成黑名单,也就是这个名单里的类都不允许反序列化。如果上面我们用来攻击的TemplatesImpl被加入到这个黑名单自然就不能被恶意代码攻击了。早期的黑名单如下:
this.denyList = "bsh,com.mchange,com.sun.,java.lang.Thread,java.net.Socket,java.rmi,javax.xml,org.apache.bcel,org.apache.commons.beanutils,org.apache.commons.collections.Transformer,org.apache.commons.collections.functors,org.apache.commons.collections4.comparators,org.apache.commons.fileupload,org.apache.myfaces.context.servlet,org.apache.tomcat,org.apache.wicket.util,org.apache.xalan,org.codehaus.groovy.runtime,org.hibernate,org.jboss,org.mozilla.javascript,org.python.core,org.springframework".split(",");
后面可以不断的更新这个黑名单来抵御攻击(当然只是抵御针对黑名的攻击,其它攻击手段无法通过更新黑名单解决)。
其实机智如你应该能想到这里好像有个坑,就是黑名单公布出来,本来大家想不到的一些payload就可以被拿来攻击低版本的fastjson了。阿里应该是也意识到这个问题了,新版本的源码黑名单都是以hashCode的方式存放在源码里,如下所示,
![ab5fe9cb28008a256f90f43c21e8262e.png](https://img-blog.csdnimg.cn/img_convert/ab5fe9cb28008a256f90f43c21e8262e.png)
后来黑客界,安全界的大佬们还想出其它各种攻击手段,也不断的促进者fastjson原来越好,这里不表。
不过如果你使用场景中包括了这个功能,请参考:
enable_autotype (如果链接不生效,可以点击下面 了解更多 看原文)
这里如何添加白名单或者打开autotype功能。
总结
fastjson在1.2.24以及之前版本存在远程代码执行高危安全漏洞。
开发中应严格控制AutoType开关,保持fastjson为最新版本。保持最新的版本这个通常要注意生产上使用稳定版本而不是beta版。
参考
- http://xxlegend.com/2017/04/29/title-%20fastjson%20远程反序列化poc的构造和分析/