深入Fastjson源碼命令執(zhí)行調(diào)試
寫(xiě)作背景
之前寫(xiě)過(guò)一篇fastjson漏洞文章,但是當(dāng)時(shí)在復(fù)現(xiàn)利用鏈的過(guò)程中一直沒(méi)有彈出計(jì)算器,而且利用鏈的代碼單步調(diào)試也沒(méi)有給出來(lái),這次我要通過(guò)底層代碼把漏洞實(shí)現(xiàn)過(guò)程展現(xiàn)出來(lái)。
fastjson漏洞demo
上次不是沒(méi)有彈出計(jì)算器嗎,這次我先把可以彈出計(jì)算器的漏洞demo先講解一下。
- 創(chuàng)建一個(gè)普通類(lèi)Person
import java.util.Properties;
//創(chuàng)建一個(gè)普通類(lèi)
public class Person {
private String name;
private int age;
private String sex;
private Properties properties;
public Person() {
System.out.println("構(gòu)造方法");
}
//Setter Getter方法
public String getName() {
System.out.println("getName");
return name;
}
public void setName(String name) {
System.out.println("setName");
this.name = name;
}
public int getAge() {
System.out.println("getAge");
return age;
}
public String getSex(){
System.out.println("getAddress");
return sex;
}
public Properties getProperties() throws Exception {
System.out.println("getProperties");
Runtime.getRuntime().exec("open -a /System/Applications/Calculator.app");
return properties;
}
}
- 反序列化測(cè)試
import com.alibaba.fastjson.JSON;
public class Demo {
public static void main(String[] args){
String jsonstring ="{\"@type\":\"com.FastjsonDemo.Person\",\"age\":18,\"name\":\"lili\",\"address\":\"china\",\"properties\":{}}";
//JSON.parseObject() 方法將 jsonstring 字符串解析并轉(zhuǎn)換為一個(gè)通用的 Object 對(duì)象
Object obj = JSON.parseObject(jsonstring, Object.class);
System.out.println(obj);
}
}
- 測(cè)試結(jié)果
1687937316_649be124cc1a2eef0df3e.png!small
代碼調(diào)試講解
- 我們?cè)贘SON.parseObject位置處打上斷點(diǎn)
1687937342_649be13ef0c5cd18a34cb.png!small
- f7步入,進(jìn)入到parseObject(String text, Class<T> clazz)方法中,text為要解析的JSON字符串;clazz為要轉(zhuǎn)換的目標(biāo)類(lèi)型,Class<T>中的T為范型參數(shù),表示要轉(zhuǎn)換成的具體類(lèi)型。在這個(gè)方法的實(shí)現(xiàn)中,它再次調(diào)用自己,通過(guò)遞歸無(wú)限循環(huán)調(diào)用自身。
public static <T> T parseObject(String text, Class<T> clazz) {
return parseObject(text, clazz);
}
1687937363_649be1531fc289d742fdc.png!small
- f7繼續(xù)步入,進(jìn)入parseObject(String json, Class<T> clazz, Feature... features)方法,這里又是一個(gè)重載方法,接受了三個(gè)參數(shù):一個(gè)JSON字符串json、目標(biāo)類(lèi)型的Class<T>對(duì)象clazz和可選的特性(features)數(shù)組。在方法的實(shí)現(xiàn)中,它掉用了另一個(gè)重載的'parseObject'方法,將解析過(guò)程委托給它。這個(gè)新的重載方法接受了更多的參數(shù),包括:ParserConfig對(duì)象、ParseProcess對(duì)象以及默認(rèn)的解析特性(DEFAULT_PARSER_FEATURE)。
public static <T> T parseObject(String json, Class<T> clazz, Feature... features) {
return parseObject(json, clazz, ParserConfig.global, (ParseProcess)null, DEFAULT_PARSER_FEATURE, features);
}
1687937388_649be16c9b766b3612ff6.png!small
- f7步入到 public static <T> T parseObject(String input, Type clazz, ParserConfig config, ParseProcess processor, int featureValues, Feature... features) {...}方法中
1687937405_649be17d73e3f75a24438.png!small
- 我們?cè)敿?xì)看一下這段代碼,目的是將輸入的input字符串解析為指定類(lèi)型 'clazz' 的java對(duì)象。
public static <T> T parseObject(String input, Type clazz, ParserConfig config, ParseProcess processor, int featureValues, Feature... features) {
//input為{"@type":"com.FastjsonDemo.Person","age":18,"name":"lili","address":"china","properties":{}}
if (input == null) {
return null;
} else {
//features為Feature[0]@497
if (features != null) {
Feature[] var6 = features;
//定義特性數(shù)組長(zhǎng)度var7
int var7 = features.length;
for(int var8 = 0; var8 < var7; ++var8) {
//獲取當(dāng)前索引var8位置的特性對(duì)象,并將其復(fù)制給變量feature
Feature feature = var6[var8];
//使用按位或操作符(|=)將當(dāng)前特性的掩碼(feature.mask)與featureValues進(jìn)行按位或運(yùn)算,并將結(jié)果重新賦值給featureValues
featureValues |= feature.mask;
}
}
//創(chuàng)建一個(gè)DefaultJSONParser對(duì)象parser,用于解析JSON字符串。這個(gè)對(duì)象使用傳入的輸入字符串、配置對(duì)象config和特性值featureValues進(jìn)行初始化。
DefaultJSONParser parser = new DefaultJSONParser(input, config, featureValues);
//處理解析過(guò)程中的處理器processor,此時(shí)processor為null
if (processor != null) {
if (processor instanceof ExtraTypeProvider) {
parser.getExtraTypeProviders().add((ExtraTypeProvider)processor);
}
if (processor instanceof ExtraProcessor) {
parser.getExtraProcessors().add((ExtraProcessor)processor);
}
if (processor instanceof FieldTypeResolver) {
parser.setFieldTypeResolver((FieldTypeResolver)processor);
}
}
//解析JSON字符串并得到解析結(jié)果對(duì)象
T value = parser.parseObject(clazz, (Object)null);
//處理解析結(jié)果中的后續(xù)任務(wù)
parser.handleResovleTask(value);
//關(guān)閉解析器
parser.close();
//返回解析結(jié)果對(duì)象
return value;
}
}
- 跟著f8步過(guò)代碼
- 首先執(zhí)行循環(huán)
for(int var8 = 0; var8 < var7; ++var8) {
Feature feature = var6[var8];
featureValues |= feature.mask;
}
- 隨后跳轉(zhuǎn)創(chuàng)建DefaultJSONParser對(duì)象
DefaultJSONParser parser = new DefaultJSONParser(input, config, featureValues);
1687937426_649be192bdf7d20aea2f2.png!small
- 繼續(xù)執(zhí)行processor的判斷,因?yàn)閜rocessor為空,跳過(guò)判斷
if (processor != null) {...}
- 隨后進(jìn)行解析JSON字符串并得到解析結(jié)果對(duì)象
這里是使用解析器parser對(duì)JSON字符串進(jìn)行解析,并將解析結(jié)果賦值給變量value。解析的目標(biāo)類(lèi)型由參數(shù)clazz指定,該方法返回了一個(gè)泛型類(lèi)型T的對(duì)象。
T value = parser.parseObject(clazz, (Object)null);
- 直接f8跳轉(zhuǎn)下一步,可以發(fā)現(xiàn)計(jì)算器被彈出來(lái),造成命令執(zhí)行
1687937451_649be1ab335e64a288968.png!small
- 重點(diǎn)調(diào)試parser.parseObject(clazz, (Object)null)代碼
T value = parser.parseObject(clazz, (Object)null);
- f7步入代碼
1687937468_649be1bcbd81d8fb44dfd.png!small
public <T> T parseObject(Type type, Object fieldName) {
//獲取當(dāng)前JSON的token類(lèi)型
int token = this.lexer.token();
if (token == 8) {//如果當(dāng)前token是JSON的null值(8代表null)
this.lexer.nextToken();//跳過(guò)null值解析
return null;
} else {
if (token == 4) {//如果當(dāng)前token是JSON的字符串值(4代表字符串)
if (type == byte[].class) {//判斷期望類(lèi)型是否為 'byte[]'
byte[] bytes = this.lexer.bytesValue();//如果是,獲取字節(jié)數(shù)組值
this.lexer.nextToken();//跳過(guò)字符串值的解析
return bytes;//返回字節(jié)數(shù)組值
}
if (type == char[].class) {//判斷其我想是否為 'char[]'
String strVal = this.lexer.stringVal();
this.lexer.nextToken();
return strVal.toCharArray();//將字符串轉(zhuǎn)換為字符數(shù)組并返回
}
}
//以上都不滿足,根據(jù)期望類(lèi)型獲取相應(yīng)的'ObjectDeserializer',并使用它來(lái)解析
ObjectDeserializer derializer = this.config.getDeserializer(type);
try {
return derializer.deserialze(this, type, fieldName);//通過(guò)config(ParserConfig對(duì)象)獲取相應(yīng)類(lèi)型的反序列化器,并調(diào)用deserialze方法進(jìn)行解析。該方法會(huì)根據(jù)給定的JSON類(lèi)型和字段名,將當(dāng)前解析器作為參數(shù),返回解析后的Java對(duì)象。
} catch (JSONException var6) {
throw var6;
} catch (Throwable var7) {
throw new JSONException(var7.getMessage(), var7);
}
}
}
- 初始化token值為12
1687937487_649be1cfdddaa35c28dae.png!small
- 不滿足if循環(huán)。會(huì)步入ObjectDeserializer derializer = this.config.getDeserializer(type);
1687937506_649be1e228bb9e77e936c.png!small
- 分析這段代碼實(shí)現(xiàn)細(xì)節(jié)
ObjectDeserializer derializer = this.config.getDeserializer(type);
- f7步入,這段代碼是 'ParseConfig' 類(lèi)中的 'getDeserializer(Type type)'方法,目的是根據(jù)給定的類(lèi)型(Type)獲取相應(yīng)的反序列化器(ObjectDeserializer)。初始 derializers 為默認(rèn)值IdentityHashMap@659,derializer的值為null。隨后進(jìn)行一個(gè)緩存類(lèi)型的判斷:(1)如果類(lèi)型type屬于 'Class' 類(lèi)型,調(diào)用 getDeserializer(Class, Type)方法,傳入類(lèi)型(Class)和類(lèi)型(Type)進(jìn)行處理;(2)如果類(lèi)型(Type)是ParameterizedType類(lèi)型,獲取原始類(lèi)型(RawType)。隨后做了一個(gè)三元運(yùn)算符判斷:如果原始類(lèi)型是Class類(lèi)型就調(diào)用getDeserializer(Class, Type)方法,對(duì)傳入原始類(lèi)型(Class)和類(lèi)型(Type)進(jìn)行處理;否則就遞歸調(diào)用getDeserializer(Type)方法,對(duì)傳入原始類(lèi)型(RawType)進(jìn)行處理。(3)其他情況就返回默認(rèn)的反序列化器 'JavaObjectDeserializer.instance'。
1687937526_649be1f63d5dfc461fc30.png!small
- f7繼續(xù)步入后是一段基于身份比較hash映射的 'get' 方法
1687937545_649be2098a637e5a484fe.png!small
- 最終獲取的derializer值為JavaObjectDeserializer@660
1687937561_649be21924eec7e17bac3.png!small
- 返回反序列化器具體值:當(dāng)前對(duì)象DefaultJSONParser@542、類(lèi)型type為Object對(duì)象、fieldName字段名為null、config屬性值為ParseConfig@540
1687937578_649be22a3181634b60767.png!small
- 返回值的具體實(shí)現(xiàn)可以f7繼續(xù)步入查看
1687937595_649be23befa5d3db58b9d.png!small
- 底層是一個(gè)JSON解析器的 'parse' 方法,lexer的初始化默認(rèn)值為JSONScanner@655
public Object parse(Object fieldName) {
JSONLexer lexer = this.lexer;
switch(lexer.token()) {
case 1:
case 5:
case 10:
case 11:
case 13:
case 15:
case 16:
case 17:
case 18:
case 19:
default:
throw new JSONException("syntax error, " + lexer.info());
case 2:
Number intValue = lexer.integerValue();
lexer.nextToken();
return intValue;
case 3:
Object value = lexer.decimalValue(lexer.isEnabled(Feature.UseBigDecimal));
lexer.nextToken();
return value;
case 4:
String stringLiteral = lexer.stringVal();
lexer.nextToken(16);
if (lexer.isEnabled(Feature.AllowISO8601DateFormat)) {
JSONScanner iso8601Lexer = new JSONScanner(stringLiteral);
try {
if (iso8601Lexer.scanISO8601DateIfMatch()) {
Date var11 = iso8601Lexer.getCalendar().getTime();
return var11;
}
} finally {
iso8601Lexer.close();
}
}
return stringLiteral;
case 6:
lexer.nextToken();
return Boolean.TRUE;
case 7:
lexer.nextToken();
return Boolean.FALSE;
case 8:
lexer.nextToken();
return null;
case 9:
lexer.nextToken(18);
if (lexer.token() != 18) {
throw new JSONException("syntax error");
}
lexer.nextToken(10);
this.accept(10);
long time = lexer.integerValue().longValue();
this.accept(2);
this.accept(11);
return new Date(time);
case 12:
JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField));
return this.parseObject((Map)object, fieldName);
case 14:
JSONArray array = new JSONArray();
this.parseArray((Collection)array, (Object)fieldName);
if (lexer.isEnabled(Feature.UseObjectArray)) {
return array.toArray();
}
return array;
case 20:
if (lexer.isBlankInput()) {
return null;
}
throw new JSONException("unterminated json string, " + lexer.info());
case 21:
lexer.nextToken();
HashSet<Object> set = new HashSet();
this.parseArray((Collection)set, (Object)fieldName);
return set;
case 22:
lexer.nextToken();
TreeSet<Object> treeSet = new TreeSet();
this.parseArray((Collection)treeSet, (Object)fieldName);
return treeSet;
case 23:
lexer.nextToken();
return null;
}
}
- f8步過(guò),進(jìn)入到case12。這里首先創(chuàng)建了一個(gè)新的JSONObject對(duì)象,并啟用了Feature.OrderedField特性(這個(gè)特性是用于指示是否保留JSON對(duì)象中字段的順序);隨后調(diào)用了當(dāng)前對(duì)象的parseObject方法,將剛剛創(chuàng)建的object對(duì)象作為參數(shù)傳遞進(jìn)去,并傳遞fileName作為另一個(gè)參數(shù)。
case 12:
JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField));
return this.parseObject((Map)object, fieldName);
1687937621_649be2557609d8bd6b304.png!small
- 繼續(xù)f7步入,查看parseObject反序列化的具體實(shí)現(xiàn)
1687937638_649be2661f477f3ae3cd3.png!small
- 底層對(duì)key做了個(gè)判斷
1687937659_649be27b4ae64330f43f7.png!small
- 繼續(xù)步過(guò)可以在底層發(fā)現(xiàn)從當(dāng)前對(duì)象的配置中獲取一個(gè)用于指定類(lèi) 'clazz' 的反序列化器,這個(gè)clazz就是我們要反序列化的Person類(lèi)。
ObjectDeserializer deserializer = this.config.getDeserializer(clazz);
1687937707_649be2ab4ca2056ff197f.png!small
- 回到getDeserializer代碼的實(shí)現(xiàn)位置,這里我們重新帶入要反序列化的type(Person類(lèi))
1687937723_649be2bbb78f3011e7864.png!small
- f8步過(guò),依然是要從當(dāng)前對(duì)象的配置中獲取與指定類(lèi) 'clazz'相關(guān)聯(lián)的反序列化器 ObjectDeserializer
1687937739_649be2cbbdacbf8317390.png!small
- f8步過(guò)查看 thisObj = deserializer.deserialze(this, clazz, fieldName); 的具體實(shí)現(xiàn)
public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
return this.deserialze(parser, type, fieldName, 0);
}
public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName, int features) {
return this.deserialze(parser, type, fieldName, (Object)null, features);
}
- 相當(dāng)于對(duì)Person類(lèi)做一遍和Object類(lèi)一樣的調(diào)試,這里的token值為16
1687937757_649be2ddc1bba19ba32ea.png!small
- f8步過(guò)token=16的循環(huán)體
1687937783_649be2f7307263c3af1de.png!small
- 繼續(xù)步過(guò)走到自增fieldIndex
1687937809_649be311a31ab9eaef38f.png!small
- 通過(guò)多次遞增,直到fileIndex為5
1687937832_649be32871d8efdcca30f.png!small
1687937864_649be3489d454e94368c4.png!small
......
1687937882_649be35a80d859b7490fc.png!small
- 再看這段代碼,判斷當(dāng)前解析操作是否成功,只要解析成功即可彈出計(jì)算器
boolean match = this.parseField(parser, key, object, type, fieldValues);
- 仔細(xì)查看代碼的實(shí)現(xiàn)過(guò)程
public boolean parseField(DefaultJSONParser parser, String key, Object object, Type objectType, Map<String, Object> fieldValues) {
//創(chuàng)建了一個(gè)JSONLexer對(duì)象,并將其初始化為 'parser'對(duì)象的 'lexer'屬性
JSONLexer lexer = parser.lexer;
//smartMatch方法用于根據(jù)提供的字段名key匹配對(duì)應(yīng)的字段解析器
FieldDeserializer fieldDeserializer = this.smartMatch(key);
//創(chuàng)建一個(gè)mask的整型變量,并設(shè)置為 'Feature.SupportNonPublicField.mask'的值
int mask = Feature.SupportNonPublicField.mask;
if (fieldDeserializer == null && (parser.lexer.isEnabled(mask) || (this.beanInfo.parserFeatures & mask) != 0)) {
if (this.extraFieldDeserializers == null) {
ConcurrentHashMap extraFieldDeserializers = new ConcurrentHashMap(1, 0.75F, 1);
Field[] fields = this.clazz.getDeclaredFields();
Field[] var11 = fields;
int var12 = fields.length;
//遍歷當(dāng)前類(lèi)聲明的字段,對(duì)每個(gè)字段進(jìn)行一下操作:(1)獲取字段名并檢查是否已存在對(duì)應(yīng)的字段解析器;(2)檢查字段的修飾符,判斷是否為非公共字段和非靜態(tài)字段;(3)如果滿足條件,則將字段添加到extraFieldDeserializers中。
for(int var13 = 0; var13 < var12; ++var13) {
Field field = var11[var13];
String fieldName = field.getName();
if (this.getFieldDeserializer(fieldName) == null) {
int fieldModifiers = field.getModifiers();
if ((fieldModifiers & 16) == 0 && (fieldModifiers & 8) == 0) {
extraFieldDeserializers.put(fieldName, field);
}
}
}
this.extraFieldDeserializers = extraFieldDeserializers;
}
Object deserOrField = this.extraFieldDeserializers.get(key);
if (deserOrField != null) {
if (deserOrField instanceof FieldDeserializer) {
fieldDeserializer = (FieldDeserializer)deserOrField;
} else {
Field field = (Field)deserOrField;
field.setAccessible(true);
FieldInfo fieldInfo = new FieldInfo(key, field.getDeclaringClass(), field.getType(), field.getGenericType(), field, 0, 0, 0);
fieldDeserializer = new DefaultFieldDeserializer(parser.getConfig(), this.clazz, fieldInfo);
this.extraFieldDeserializers.put(key, fieldDeserializer);
}
}
}
//如果fieldDeserializer為空,繼續(xù)判斷:
if (fieldDeserializer == null) {//如果lexer未啟用Feature.IgnoreNotMatch,拋出異常
if (!lexer.isEnabled(Feature.IgnoreNotMatch)) {
throw new JSONException("setter not found, class " + this.clazz.getName() + ", property " + key);
} else {//否則調(diào)用'parser.parseExtra(object,key)'方法,將額外的字段解析給 object
parser.parseExtra(object, key);
return false;//false表示未成功解析字段
}
} else {//filedDeserializer不為空,通過(guò)下列方法將解析器移到字段值的冒號(hào)位置,并解析字段值
lexer.nextTokenWithColon(((FieldDeserializer)fieldDeserializer).getFastMatchToken());
((FieldDeserializer)fieldDeserializer).parseField(parser, object, objectType, fieldValues);
return true;//返回true表示成功解析
}
}
- 此時(shí)fieldDeserializer不為空
1687937908_649be374d92b4791d8cc9.png!small
- 跟進(jìn) ((FieldDeserializer)fieldDeserializer).parseField(parser, object, objectType, fieldValues); 最后使用字段值的反序列化器從解析器中解析字段的值
1687937927_649be3878876a960d1fb7.png!small
- 繼續(xù)步過(guò),將解析得到的字段值 value 設(shè)置到目標(biāo)對(duì)象 object 的對(duì)應(yīng)字段上
1687937955_649be3a3ca8f30bead06d.png!small
- 跟進(jìn)setValue,可以清晰的看見(jiàn)通過(guò)反射調(diào)用了Person類(lèi)的getProperties() throws Exception
1687937974_649be3b66336d9ef07807.png!small
寫(xiě)作感悟
- 調(diào)試的過(guò)程,底層其實(shí)就是先對(duì)Object進(jìn)行反序列化,再對(duì)具體的Person類(lèi)進(jìn)行反序列化,中間有一些重復(fù)的過(guò)程,當(dāng)然底層有很多過(guò)程其實(shí)很難一個(gè)個(gè)去分析出來(lái),重點(diǎn)一定要先自己先調(diào)試完,自己多走幾遍可能就知道它流程了。
- 這篇文章寫(xiě)了大概得有四五天,其實(shí)調(diào)試什么的就是跟著代碼往下走,不會(huì)的代碼就查詢(xún)。這里就是給出了我在調(diào)試過(guò)程中遇到的情況,也是想告訴大家代碼審計(jì)、代碼調(diào)試就是一個(gè)耐心的活。
本文作者:ZavaSec, 轉(zhuǎn)載請(qǐng)注明來(lái)自FreeBuf.COM