本文主要針對FastJson的反序列化過程進行詳細分析,以及poc案例分析,留作學(xué)習(xí)筆記,以供日后復(fù)習(xí)
前言:網(wǎng)上關(guān)于FastJson的分析文章一大片,本文只是筆者在實踐操作中理解的一些東西,不算特別詳細,留作日后復(fù)習(xí),歡迎一起交流
什么是FastJson?
Fastjson是一個由阿里巴巴維護的一個json庫。它采用一種“假定有序快速匹配”的算法,是號稱Java中最快的json庫。
0x01 反序列化分析
先來看看一張反序列化流程圖
可以看到其主要的功能都是在DefaultJSONParser類中實現(xiàn)的,在這個類中會應(yīng)用其他的一些外部類來完成后續(xù)操作。ParserConfig主要是進行配置信息的初始化,JSONLexer主要是對json字符串進行處理并分析,反序列化在JavaBeanDeserializer中處理。
本文中會先通過一些簡單的案例來幫助理解FastJson在反序列化數(shù)據(jù)時都做了哪些事情。
先來創(chuàng)建一個類,用于反序列化
package demo2;
import java.util.Properties;
public class User {
private int age;
private String name;
private Properties properties;
public int getAge() {
System.out.println("getAge()");
return age;
}
public String getName() {
System.out.println("getName");
return name;
}
public void setName(String name) {
System.out.println("setName");
this.name = name;
}
public Properties getProperties() {
System.out.println("getProperties()");
return properties;
}
@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
", properties=" + properties +
'}';
}
}
然后再來創(chuàng)建一個測試類
package demo2;
import com.alibaba.fastjson.JSON;
public class Demo1 {
public static void main(String[] args) {
//String strU = "{\"@type\" : \"demo2.User\", \"age\" : 18, \"name\" : \"kevin\", \"properties\" : {}}";
String strU = "{\"age\" : 18, \"name\" : \"kevin\", \"properties\" : {}}";
Object obj = JSON.parse(strU);
System.out.println(obj);
}
}
無@type分析
我們先來看看默認不加@type的情況下,parse是如何解析字符串的
首先在11行打一個斷點,如圖
然后debug模式啟動,進入到parse方法,如圖
因為一開始只傳了一個參數(shù),所以調(diào)用重載的parse方法,F(xiàn)7點進去
一開始text不為null,進入else方法??梢钥吹竭@里創(chuàng)建了一個DefaultJSONParser對象,這里其實做了很多事情,我們一個一個來看,之前我的很大一部分困惑也就是在這里解開的。
先通過F7進入ParserConfig.getGlobalInstance()方法,看看里面做了什么操作
可以看到這里返回了一個global對象,global對象就是new ParserConfig()對象,我們看看對象實例化時做了哪些操作。
可以看到在無參的構(gòu)造方法里調(diào)用了另外一個構(gòu)造方法,我們再來看看另外一個構(gòu)造方法做了什么操作。
可以看出來,這一步主要就是為ParserConfig對象的成員變量初始化,第二張圖就是將相應(yīng)類型的類和相對應(yīng)的反序列化器綁定在一起,通過key/value的形式。個人感覺理解這里的操作對后面的過程會比較重要,不然會有點懵。
然后回到JSON.class文件的else判斷,我們再進入DefaultJSONParser的構(gòu)造函數(shù),看看做了哪些操作
這里做的操作就多了點,我們一步一步來分析,首先明白input變量就是我們傳入parse方法的值,然后我們進入JSONScanner構(gòu)造函數(shù),傳入了input和features
可以看到我們進入了JSONScanner.class類中,這里的features不是我們關(guān)注的重點,主要看構(gòu)造函數(shù)里做了什么。
先是給this.text成員變量賦值為input,也就是我們需要反序列化的值,然后取出長度賦值給this.len,這里的this.bp是父類的成員變量,我門再JSONScanner類中是看不到定義的,按住Ctrl,然后鼠標(biāo)左鍵就可以點進去
可以看到是在JSONLexerBase.class類文件中定義的,還有ch變量也是都是我們后面會用到的
回到JSONScanner.class往下走,可以看到調(diào)用了this.next(); 我們F7跟進去看看
可以看到,先是將this.bp+1然后賦值給index變量,之后通過判斷給this.ch賦值并返回,如果index >= this.len,也就是>=我們反序列化的值的長度,返回\u001a,說明讀到末尾了,不再繼續(xù)讀了。否則的話,返回this.text.charAt(index),根據(jù)index取出text字符串中指定位置的字符并返回。
不難理解,其實每一次調(diào)用next()就意味著,遍歷獲取字符串中的值,然后將這個值返回給this.ch,用于作比較。
然后我們回到DefaultJSONParser.class,進入另外一個構(gòu)造函數(shù)
可以看到,核心的初始化都是在這里完成的,這里的int ch = lexer.getCurrent(),我們跟進去看一下
可以看到ch的值就是我們反序列化字符串的第一位字符,是通過JSONScanner.class類中的next()方法獲取返回的,我們剛剛也分析過。因為JSONScanner繼承自JSONLexerBase,所以給this.ch賦值也就等同于給JSONLexerBase的ch變量賦值
然后往下走,因為ch == “{”,所以會進入第一個if判斷,獲取下一個字符,設(shè)置token為12,tonken就是在這里初始化賦值的。
回到創(chuàng)建對象的地方,往下走,我們來看看創(chuàng)建的DefaultJSONParser對象
可以看到里面的值都已經(jīng)初始化好了,這里有一點漏講了,就是symbolTable中的symbols對象里面的數(shù)據(jù)也是初始化的時候賦值的,小細節(jié)。
然后調(diào)用DefaultJSONParser#parse()方法,跟進去
將JSONScanner對象賦值給lexer變量,然后switch判斷token,通過前面分析已知token為12,所以會進入case 12:里面
我們接著跟進構(gòu)造方法
可以看到通過構(gòu)造方法給JSONObject成員變量this.map賦值為HashMap對象,初始大小為16
回到DefaultJSONParser.class,調(diào)用了this.parseObject((Map)object, fieldName); 跟進去
因為token為12,所以會進入else方法
這里主要還有一個知識點要講,就是lexer.scanSymbol(this.symbolTable, ‘"’); 這個方法主要作用是做字符截取的操作,截取"key"當(dāng)中的key,添加并返回key,我們跟進去看看
這里主要就是循環(huán)截取要反序列化的字符,直到截取到",然后會進入addSymbol方法面對我們傳入的字符進行截取,取出"key",中的key值。
我們跟進去
可以很清楚的看到,根據(jù)前面記錄的下標(biāo)位置,截取buffer字符串,也就是獲取age字符串然后返回。
后面但凡是調(diào)用lexer.scanSymbol(this.symbolTable, ‘"’),都是做這樣的處理,截取"中間的值"。
可以看到value的值為age,后面是一樣的操作,就不重復(fù)截圖了。然后會將value的值也取出來,最后存放到map集合中,也就是JSONObject對象中
直到遇到"}",表示到結(jié)尾了,會將JSONObject對象返回
最后會回到JSON#parse方法中,返回封裝后的JSONObject對象
至此,不加@type情況下的反序列化解析就分析完了,看控制臺輸出
加@type分析
也是一樣debug,進入parse方法
初始化我們前面講過了,就不多說了,接著往下走,進入parser.parse()方法
這時會調(diào)用DefaultJSONParser#parseObject,跟進去
這里會對字符串進行截取,按照上面分析的"@type",通過lexer.scanSymbol(this.symbolTable, ‘"’); 將@type截取出來,作為key,接著往下看
往下會有一個判斷,因為key == JSON.DEFAULT_TYPE_KEY,所以會進入該方法,通過同樣的截取字符串的方式,將"demo2.User"中的demo2.User截取出來,賦值給ref,這也就是我們可控的類。
然后會調(diào)用TypeUtils.loadClass(ref, this.config.getDefaultClassLoader()); 通過類加載器加載類并返回對應(yīng)的類對象,我們跟進去看看
可以看到經(jīng)過一些列判斷,最后會進入else方法,然后通過當(dāng)前線程獲取類加載器,加載我們指定的類,最后返回類對象,這里其實就是后期繞過autoType的地方,不過現(xiàn)在我們不深究。
然后我們回到DefaultJSONParser#parseObject
可以看到這里會調(diào)用ParseConfig#getDeserializer方法,根據(jù)給定類型返回指定的反序列化器,我們跟進去看看
這里時根據(jù)type類型,去this.derializers.get(type) 中取相對應(yīng)的反序列化器,如果沒有,返回null,這個this.derializers 我們上面有分析過,將指定類型作為key,指定類型的反序列化器作為value存到了IdentityHashMap集合中。
接著往下走
因為type類型為Class,所以進入方法,調(diào)用this.getDeserializer((Class)type, type),我們繼續(xù)跟進去
程序一路向下,進入到else,因為className為demo2.User所以不會退出,接著往下
又經(jīng)過了一些列類型判斷,因為都不匹配,所以會走到else,調(diào)用this.createJavaBeanDeserializer(clazz, (Type)type),我們跟進去看看
前面一些不重要的步驟跳過了,因為asmEnable為true,所以會進入if方法,調(diào)用JavaBeanInfo.build(clazz, type, this.propertyNamingStrategy),這個方法也很關(guān)鍵,封裝成員變量和方法等操作都是在這個方法里完成的,我們得跟一下
可以看到這里通過反射將我們傳入的clazz,也就是demo2.User的類對象中的所有成員變量和所有public的方法都獲取了出來,繼續(xù)往下看
這里就是通過循環(huán)將所有的方法都取出來,進行相應(yīng)的判斷,符合條件的才會被添加到List fieldList = new ArrayList() 這個list集合中,留作后續(xù)處理,這里應(yīng)該也就是網(wǎng)上分析最多的一個地方了吧。
我們把代碼拷貝出來,看一看完整的判斷是什么樣的
這個不難理解,就不過多分析,從我們給出的測試類來看,滿足條件的只有public void setName(String name)方法,因為方法名稱大于4,非靜態(tài)方法且返回值類型為void,其他的幾個方法在這個判斷中是不成立的,所以不會進入該方法。
可以看到和我們分析的一樣,setName進入了if方法,接著往下走
可以看到這里還有個判斷,如果方法名以set開頭,則進入該方法
接著往下會看到,通過substring截取,將Name的N轉(zhuǎn)換為小寫n,然后截取n后面的字符,就是提取屬性名,賦值給propertyName。
然后調(diào)用TypeUtils.getField(clazz, propertyName, declaredFields),我們跟進去
可以看到通過遍歷成員變量,和我們傳入的名稱做判斷,如果相匹配則返回名稱相對應(yīng)的成員變量對象
再往下我們可以看到,field已經(jīng)賦值為相對應(yīng)的成員變量對象了,然后調(diào)用add(fieldList, new FieldInfo(propertyName, method, field, clazz, type, ordinal, serialzeFeatures, parserFeatures, annotation, fieldAnnotation, (String)null)) 方法,將這些類里面的信息都存入fieldList中,我們先跟到FieldInfo構(gòu)造方法中
可以看到在構(gòu)造方法里面給成員變量賦值,可以看到有哪些參數(shù),接著往下
可以看到這里進行了一些訪問權(quán)限設(shè)置,然后回到for循環(huán)
可以看到封裝好的FieldInfo對象里面包含哪些對象
之后會繼續(xù)循環(huán)判斷,知道方法都判斷完,然后會往下走,還有一個for循環(huán)和一個判斷
也是一樣拷貝出來,分析一下
和前面的判斷有點小區(qū)別,可以看出這個主要是針對get方法的,我們的測試類中,符合條件的只有public Properties getProperties() 方法,非靜態(tài),方法名長度大于4且第四個字符為大寫,無形參,最后返回值類型是屬于Map,因為Properties extends Hashtable -> Hashtable implements Map,所以滿足要求
可以看到進到了if方法里,接著往下走
可以看到也是一樣的操作,對數(shù)據(jù)進行封裝,添加到fieldList集合中
可以看到也是和name是一樣的,后面的循環(huán)就不截圖了,可以直接跳出方法回到beanInfo = JavaBeanInfo.build(clazz, type, this.propertyNamingStrategy) 方法
可以看到返回的beanInfo對象,封裝了類中的一些成員變量和方法對象
接著往下走,會在這里創(chuàng)建一個JavaBeanDeserializer對象,我們跟進去
這里的this.sortedFieldDeserializers我們要留一下,它是一個FieldDeserializer[]數(shù)據(jù)對象,我們后面會用到。
這里就是通過遍歷JavaBeanInfo對象中的成員變量sortedFields數(shù)組中的FieldInfo對象,使用config.createFieldDeserializer(config, beanInfo, fieldInfo) 創(chuàng)建FieldDeserializer對象,之后存入this.sortedFieldDeserializers數(shù)組中,以供后續(xù)使用
可以看到返回的是一個DefaultFieldDeserializer對象
回到ParseConfig#getDeserializer,可以看到返回的是JavaBeanDeserializer對象
再回到DefaultJSONParser#parseObject 方法,可以看到調(diào)用了deserializer.deserialze(this, clazz, fieldName),繼續(xù)跟進去
由于這段代碼實在是又臭又長,所以截取核心功能代碼
這個方法是通過反射將屬性的值設(shè)置到相應(yīng)的對象上,我們跟進去
這里也有個小知識點,this.smartMatch(key) 方法會對key做一些處理,我們跟進去看一下
可以看到如果key的第一個字符為 _ 或者 - ,那么將會被替換為空,這也就解釋了為什么有些payload會在變量名上加一個 _ 了,加不加都不受影響
可以看到這里也調(diào)用了一次parseField(parser, object, objectType, fieldValues)方法,這里的Object為我們指定反序列化的User類,成員變量還沒賦值,跟進去
可以看到是根據(jù)特定的類型獲取的特定反序列化器,因為name是String類型,所以獲取的就是StringCodec
繼續(xù)往下走
到了關(guān)鍵的一步,我們跟進去
先是做了不為空和類權(quán)限判斷,然后獲取方法對象,因為前面條件都不滿足,所以進入else,通過反射調(diào)用User對象的setName方法,給成員變量賦值
可以看到已經(jīng)成功為name變量賦值,并且觸發(fā)了setName方法
然后我們再來看看properties的賦值
跟進去,直接進入核心方法
因為getProperties方法的返回值符合這條判斷,所以會進入該方法,通過反射執(zhí)行g(shù)etProperties方法,這也就解釋了TemplatesImpl反序列化利用鏈?zhǔn)侨绾斡|發(fā)的了。
可以看到觸發(fā)了getProperties方法
最后反序列化利用鏈我有時間再寫吧,太晚了,其實能把這一套看懂,反序列化利用鏈也就沒什么問題了,就是解析時觸發(fā)了特定的方法而已
完結(jié)撒花
最后附上借鑒的博客
Fastjson 流程分析及RCE分析
FastJson 反序列化學(xué)習(xí)
可能還有一些,沒記錄,學(xué)習(xí)就是這樣,付出越多回報也才越多!
免責(zé)聲明:本站提供安全工具、程序(方法)可能帶有攻擊性,僅供安全研究與教學(xué)之用,風(fēng)險自負!
轉(zhuǎn)載聲明:著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。
訂閱查看更多復(fù)現(xiàn)文章、學(xué)習(xí)筆記
公眾號:偉盾網(wǎng)絡(luò)安全
專注網(wǎng)絡(luò)安全,用心做好安全這件事。
個人博客:博客文章來源:http://www.zghlxwxcb.cn/news/detail-464353.html
個人知乎:知乎文章來源地址http://www.zghlxwxcb.cn/news/detail-464353.html
到了這里,關(guān)于FastJson反序列化分析的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!