Kate Li (Taiwan)的部落格

首頁

java反序列化過程深究

作者 lace 時間 2020-03-08
all

0x01起因

    關於Java反序列化的文章已經相當的多了,而且大家也對於這個東西說的很清楚了,所以今年我想換個角度來看看這個東西。我們都知道Java反序列化漏洞的產生原因在於開發者在重寫readObject方法的時候,寫入了漏洞程式碼,這個和PHP的反序列化漏洞很像,在反序列化的時候出發了在__destruct等魔術函數中的漏洞程式碼。這裡就有一個問題了,先看一下我的demo吧,ObjectCalc.java為重寫readobject方法檔案。    那麼我通過下麵的程式碼可以觸發反序列化漏洞,彈出小算盘。    那麼問題來了我們通過第8行ois.readObject獲取到的輸入流過程中調用了readObject方法,為什麼最後會調用到被反序列化類(ObjectCalc)中的readObject方法,這個readObject調用過程到底是怎麼樣的。

0x02深入分析

    為了弄清楚這個問題,我决定在ObjectCalc.java檔案中的命令執行位置下一個中斷點,好的相關調用棧已經出來了,這時候我們跟進一下。    先跟進一下ObjectInputStream.readObject,這裡我簡化了一下程式碼,關鍵位置在第431行調用了readObject0方法,並且傳入false。    繼續跟進一下readObject0方法,關鍵在下麵這兩行,此時的TC_OBJECT的值為115,且調用了readOrdinaryObject方法。

    跟進readOrdinaryObject方法,調用了readSerialData方法。

    繼續跟進一下readSerialData方法,該方法的實現如下所示。    從動態調試結果來看,重寫readObject會進入第14行的slotDesc.invokeReadObject方法中,再跟進一下slotDesc.invokeReadObject方法,該方法主要代碼如下:

    其中readObjectMethod.invoke這個方法很熟悉了,java的反射機制,也就說通過重寫readObject的整個調用流程會進過java的反射機制。    這裡再看一個不通過重寫readObject反序列化的調用過程,我省略了前面的跟踪調試過程,大家看下圖。    不通過重寫readObject的反序列化過程一樣是進入readSerialData中,但是是通過defaultReadFields進行處理,這裡有個關注點是slotDesc.hasReadObjectMethod()返回的結果是false,也就是下麵這個if判斷的結果,我簡化了一下流程。

    也就是說實際上是否重寫了readObject影響的是slotDesc.hasReadObjectMethod()的結果,那麼跟進一下hasReadObjectMethod方法,這裡我在return(readObjectMethod!= null);下了一個中斷點,對比一下重寫readObject結果和不重寫readObject結果的差別,第一張圖是不重寫readObject,第二張圖是重寫readObject。    很明顯我們發現了返回結果不一樣,第一張圖的結果自然return為false,第二張圖return結果自然為true,也就是說重寫readObject結果和不重寫readObject結果的差別本質上在於進入的迴圈不一樣。

return (readObjectMethod != null);

0x03小結

    根據上面的動態調試結果,簡單做個小結,也就是說如果反序列化的過程中被反序列化類重寫了readObject,該數據在反序列化的過程中覈心流程走到readSerialData方法中的slotDesc.invokeReadObject方法,通過反射機制觸發相關流程,並且調用重寫的readObject。如果沒有重寫readObject,則調用ObjectInputStream類中的readObject方法,並且執行反序列化。