Java安全之原生readObject方法解讀
點擊上方藍色字體,選擇“標星公眾號”
優(yōu)質文章,第一時間送達
? 作者?|??nice_0e3
來源 |? urlify.cn/Ef2iMr
1、前言
在上篇文章分析shiro中,遇到了Shiro重寫了ObjectInputStream的resolveClass導致的一些基于InvokerTransformer去實現(xiàn)的利用鏈沒法使用,因為這需要去定義一個InvokerTrans數(shù)組,而該數(shù)組傳入到Shiro重寫后的resolveClass方法中會報錯。但是在此之前,并沒有去對readObject方法去做一個解讀和分析。所以也不知道他具體的實現(xiàn)。包括在分析利用鏈的時候,只知道到調用了ObjectInputStream.readObject方法后,如果readObject被重寫的話,就會調用重寫后的readObject方法,但是我們也并不知道在內部是怎么樣去做一個實現(xiàn)的。那么下面來分析一下readObject的功能實現(xiàn)。
2、 readObject方法分析
在前面先貼一張readObject的執(zhí)行流程圖,這是一張weblogic的反序列化執(zhí)行流程圖。第一個readObject直接忽略,到下篇文weblogic再做講解。

這里寫一段測試代碼去進行反序列化操作,然后進行動態(tài)跟蹤。
User實體類:
package?com.nice0e3;
import?java.io.Serializable;
public?class?User?implements?Serializable?{
????private?String?name;
????private?int?age;
????@Override
????public?String?toString()?{
????????return?"User{"?+
????????????????"name='"?+?name?+?'\''?+
????????????????",?age="?+?age?+
????????????????'}';
????}
????public?String?getName()?{
????????return?name;
????}
????public?void?setName(String?name)?{
????????this.name?=?name;
????}
????public?int?getAge()?{
????????return?age;
????}
????public?void?setAge(int?age)?{
????????this.age?=?age;
????}
????public?User()?{
????}
????public?User(String?name,?int?age)?{
????????this.name?=?name;
????????this.age?=?age;
????}
}
ReadTest類:
package?com.nice0e3;
import?java.io.*;
public?class?ReadTest?{
????public?static?void?main(String[]?args)?throws?IOException,?ClassNotFoundException?{
????????User?user?=?new?User();
????????user.setName("nice0e3");
????????user.setAge(20);
????????ObjectOutputStream?oos?=?new?ObjectOutputStream(new?FileOutputStream("1.txt"));
????????oos.writeObject(user);
????????ObjectInputStream?ois?=?new?ObjectInputStream(new?FileInputStream("1.txt"));
????????Object?o?=?ois.readObject();
????}
}
然后將斷點落在ObjectInputStream.readObject方法中,進行執(zhí)行測試類代碼動態(tài)跟蹤。

這里對enableOverride進行了一個判斷,不為flase的話就會去返回readObjectOverride方法,而在構造方法中就定義該值為flase。

下面就直接執(zhí)行到了這步

調用了readObject0方法,選擇跟進查看一下內部的實現(xiàn)。

在這里會去獲取序列化信息第一個字節(jié),如果為TC_RESET就會調用bin.readByte()和handleReset();方法。
查看TC_RESET內容。

而該值轉換Byte后,為121,我們序列化數(shù)據(jù)的第一個字節(jié)為151,這里就跳過不執(zhí)行了。
接下來代碼中定義了一個switch去做一個判斷,TC_OBJECT的值轉換后剛剛好為115。那么就會執(zhí)行到這一步。

在這里面會調用readOrdinaryObject方法,進行跟進。

在該方法中還會去調用readClassDesc方法,繼續(xù)跟進。

看到這里發(fā)現(xiàn)就很有意思了,獲取我們序列化數(shù)據(jù)的第二個字節(jié),然后又進行一次switch,這次走到了readNonProxyDesc方法中,跟進!


在這又調用了resolveClass方法然后傳入readDesc參數(shù)。還是跟進方法。

這里返回了
Class.forName(name,?false,?latestUserDefinedLoader());
latestUserDefinedLoader()方法返回的是sun.misc.VM.latestUserDefinedLoader()說明指定了該加載器。
返回到readOrdinaryObject方法中繼續(xù)做分析。
直接定位到這一步,該方法對反序列化的操作進行實現(xiàn)。

這里的slotDesc.hasReadObjectMethod()獲取的是readObjectMethod這個屬性,如果反序列化的類沒有重寫readobject(),那么readObjectMethod這個屬性就是空,如果這個類重寫了readobject(),那么就會進入到if之中的
slotDesc.invokeReadObject(obj,?this);

如果readobject()方法被重寫則是走到這一步

3、Shiro resolveClass方法分析
在shiro里面resolveClass方法被進行了重寫,導致大部分利用鏈都使用不了,查看一下該方法實現(xiàn)。

這里去調用了ClassUtils.forName方法進行跟蹤。

這里是調用了THREAD_CL_ACCESSOR.loadClass,查看一下THREAD_CL_ACCESSOR是什么。

跟進查看一下該類。

這里調用getClassLoader方法獲取類加載器,而在這里獲取到的是ParallelWebappClassLoader,那么下面調用的肯定也就是ParallelWebappClassLoader.loadClass
上面內容中的一些補充:
TC_NULL描述符表示空對象引用
TC_REFERENCE描述符表示引用已寫入流的對象
TC_PROXYCLASSDESC是新的代理類描述符
TC_CLASSDESC是新的類描述符
參考文章
4、 結尾
其實在前面的一些cc鏈的調試鋪墊下,再去調試其他的一些漏洞,都會比較熟練。本文也是為了下文去做了一個較好的鋪墊。
粉絲福利:Java從入門到入土學習路線圖
???

?長按上方微信二維碼?2 秒
感謝點贊支持下哈?
