Java反序列化協議解析 一


年前開始研究Java反序列化,一直沒有研究Java反序列化文件的格式。最近閑來無事,研究一下java反序列化文件的格式。扯這么多的原因主要是要達成兩個目標

  1. 嘗試使用python讀取java反序列化文件,並轉換為Json格式以方便閱讀(其實沒多少用,還是一樣難懂)。
  2. json定義反序列化,隨意編輯反序列化流,通過json生成序列化文件。縮小某些payload工具的大小,生成一個exp需要N多依賴的jar包,這不講伍德

首先我們先大致講解一下java序列化以及相關東西。

  1. java序列化功能只傳遞對象的屬性,不傳遞對象的方法,靜態變量等等。你可以把java序列化功能類比Json傳輸。
  2. java存在八種基本類型,分別是字節,整型數字,浮點數,布爾值。至於對象,其實是這八種基本類型的復雜組合。無論多么復雜的對象,其實都可以被解析為這八種基本類型。當然這八種基本類型在其他語言中也存在,為我們使用python解析反序列化文件提供了理論基礎。

1 基礎類型的讀取

序列化中也有基礎類型。我們需要定義幾個函數用來讀取基礎類型。
例如

  • 一個Byte一般作為指令,標記后面的數據流是什么
  • int為4個Byte,也就是32位byte
  • Short為2個Byte,是16位
  • 以此類推,按照c語言的數據類型長度定義,千萬別亂寫
  • 字符串類型,在這里我們只討論ascii編碼的情況,暫時不討論Utf編碼的情況。在序列化中,一個字符串類型,首先讀取兩個Byte,也就是short類型,作為字符串的長度。隨后按照字符串的長度,讀取Byte作為字符串。

2 序列化中控制指令

前面我們提到,讀取一個Byte作為控制指令,控制指令的作用是標記后面的數據類型,序列化中控制指令如下

  • TC_NULL = b'\x70' 標記后面的數據為空,對應java就是Null
  • TC_REFERENCE = b'\x71 java序列化協議是一個格式十分緊湊的協議,是不會出現兩個一摸一樣的對象,類等。如果第二次出現,則會通過reference去指向之前的那個內容。你可以把這個類比為指針。
  • TC_CLASSDESC = b'\x72' 這個是處理並返回類描述符。。與下面的class的區別在於,這個返回的是描述類的一個對象,主要包括類的名稱,suid等各種屬性。
  • TC_CLASS = b'\x76'在java序列化中,類的傳輸通過名稱,suid等屬性,對端通過名稱查找classpath中該類。而這個TC_CLASS將會根據上面的類描述符,通過Class.forName去查找這個類。
  • TC_OBJECT = b'\x73' 標記后面的數據為Object對象
  • TC_STRING = b'\x74'標記后面的數據字符串。與基本類型中字符串的區別在於,這里面讀取的字符串將會被緩存,如果出現第二個一模一樣的字符串,則通過reference的方式,直接讀取緩存中的字符串
  • TC_ARRAY = b'\x75'標記后面的數據為數組類型
  • TC_BLOCKDATA = b'\x77' 在對象的WriteObject方法中,我們可以自定義的寫入數據,除了非Object數據,其他所有數據將會被寫在一起,也就是BlockData。當然,只有readObject方法中,合適的讀取順序才可以成功還原blockdata。
  • TC_ENDBLOCKDATA = b'\x78' 在readObject中,表明數據已經讀取完畢
  • TC_EXCEPTION = b'\x7B' 表明后面需要讀取一個exception類型的對象
  • TC_PROXYCLASSDESC = b'\x7D' 讀取一個動態代理的對象

3 還原反序列化流

有了上面的知識作為基礎,下面我們嘗試還原反序列化流中的內容。當然我們並沒有按照某些特定的順序去講解。

3.1 還原類的描述符(ClassDesc

類的描述符為序列化協議中的基石,它表明了后面的數據類型以及讀取方法。類的描述符,一共有以下幾個字段

  • name 字符串類型,類的名稱
  • suid long類型,類的suid,為了防止兼容性而設置的一個值。同一類的不同版本可能suid不一直,這樣防止不兼容的情況發生
  • flag 表明類是否為反序列化,是否存在writeObject方法,也就是額外寫入數據等等
  • field 類中包含的數據類型列表,
  • 父類,父類也是類的描述符
  • 類的額外信息

field

這里只包含類的數據類型的名稱與類型,不包括值。一定注意

讀取父類

由於java不支持多繼承,所以在這里只有兩種情況,繼承自一個父類和沒有父類這兩種情況。在這里我們只需要遞歸讀取,直到控制指令為TC_NULL,即父類為空,作為結束遞歸的條件。

類的描述符,記得要緩存。計算handle的時候,后面的值可能會引用該類的描述符

下面用一圖總結類的描述符的的協議結構

| utf 類名|4Byte suid|1Byte flags|2Byte 字段數量|字段詳細內容|父類|類的額外數據|

3.2 還原數組(TC_ARRAY

我們知道,java的數組中的內容只能為同一類型。所以在處理數組信息的時候,首先讀取類描述符,表明數組中的內容的數據類型。然后讀取數組的長度。最后按照數組長度以及數組類型,去讀取並還原數組中的數據。

|ClassDesc|Int length|數組數據|

數組也會被緩存,並被計算為handle

3.3 還原對象

還原對象的數據其實特別簡單。首先讀取類的描述符,然后緊接着按照類描述符的字段讀取數據即可。所以在這里,一個byte出錯,將會導致后面的數據全部讀取錯誤。
在這里需要注意幾點

  1. 首先讀取父類中字段的值,然后再讀子類的字段值,在這里我們使用棧數據結構去解決讀取
  2. 如果父類包含額外信息(例如writeObject寫入),則首先讀取父類包含的額外信息,再去讀取子類的額外信息
  3. 如果對象繼承自EXTERNALIZABLE接口,則無法單純通過流中數據還原對象中的值。因為java將不會負責字段值的讀取寫入,這一切都由開發人員決定哪些字段被保存以及保存的方法。這也就是weblogic中反序列化觸發XXE的漏洞原理,出現問題的類基本都繼承自EXTERNALIZABLE接口,且通過xml定義被保存的對象
  4. 如果對象繼承自Serializable接口,且存在writeObject方法。當writeObject方法中沒有通過ObjectOutputStream.defaultWriteObject將類的默認字段寫入到序列化流中,也無法還原對象的值。原因在於,反序列化中讀取類的值按照類的字段順序去讀取,如果沒有調用defaultWriteObject寫入,則相當於順序不可知,也是無法單純通過流中數據去還原對象

當然,對象中字段的值有可能還是對象,需要遞歸讀取,直到讀取所有的字段為基礎數據類型。在這里建議設置遞歸的最大深度,防止出現爆棧的異常。

4. 總結

目前完成各種復雜對象的讀取,例如x友的exp讀取,weblogic 反序列化Exp的讀取,並轉換為json。不過代碼還未寫完,地址暫時為https://github.com/potats0/javaSerializationDump/blob/main/main.py

截圖如下,讀取x友exp


x友exp轉json的結果
https://gist.github.com/potats0/108fe530350d14b11aba79736d6a3f0c#file-ncexp-json

下篇文章將談一下如何將json轉為序列化文件以及相關數據結構的設計。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM