1. 程式人生 > >Java安全之原生readObject方法解讀

Java安全之原生readObject方法解讀

# Java安全之原生readObject方法解讀 ## 0x00 前言 在上篇文章分析shiro中,遇到了Shiro重寫了`ObjectInputStream`的`resolveClass`導致的一些基於`InvokerTransformer`去實現的利用鏈沒法使用,因為這需要去定義一個`InvokerTrans`陣列,而該陣列傳入到Shiro重寫後的`resolveClass`方法中會報錯。但是在此之前,並沒有去對`readObject`方法去做一個解讀和分析。所以也不知道他具體的實現。包括在分析利用鏈的時候,只知道到呼叫了`ObjectInputStream.readObject`方法後,如果`readObject`被重寫的話,就會呼叫重寫後的`readObject`方法,但是我們也並不知道在內部是怎麼樣去做一個實現的。那麼下面來分析一下`readObject`的功能實現。 ## 0x01 readObject方法分析 在前面先貼一張`readObject`的執行流程圖,這是一張weblogic的反序列化執行流程圖。第一個`readObject`直接忽略,到下篇文weblogic再做講解。 ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201213092006278-1532192089.png) 這裡寫一段測試程式碼去進行反序列化操作,然後進行動態跟蹤。 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類: ```java 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`方法中,進行執行測試類程式碼動態跟蹤。 ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201213092028336-553743204.png) 這裡對`enableOverride`進行了一個判斷,不為flase的話就會去返回`readObjectOverride`方法,而在構造方法中就定義該值為flase。 ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201213092041494-85670868.png) 下面就直接執行到了這步 ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201213092051569-1715222143.png) 呼叫了`readObject0`方法,選擇跟進檢視一下內部的實現。 ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201213092101961-157410512.png) 在這裡會去獲取序列化資訊第一個位元組,如果為`TC_RESET`就會呼叫`bin.readByte()`和`handleReset();`方法。 檢視`TC_RESET`內容。 ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201213092111325-2143493435.png) 而該值轉換Byte後,為121,我們序列化資料的第一個位元組為151,這裡就跳過不執行了。 接下來程式碼中定義了一個switch去做一個判斷,`TC_OBJECT`的值轉換後剛剛好為115。那麼就會執行到這一步。 ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201213092123208-645513452.png) 在這裡面會呼叫`readOrdinaryObject`方法,進行跟進。 ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201213092156551-1313516502.png) 在該方法中還會去呼叫`readClassDesc`方法,繼續跟進。 ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201213092209483-226413370.png) 看到這裡發現就很有意思了,獲取我們序列化資料的第二個位元組,然後又進行一次switch,這次走到了`readNonProxyDesc`方法中,跟進! ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201213092218459-2065212259.png) ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201213092227652-395085938.png) 在這又呼叫了`resolveClass`方法然後傳入`readDesc`引數。還是跟進方法。 ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201213092237238-1467844993.png) 這裡返回了 ```java Class.forName(name, false, latestUserDefinedLoader()); ``` `latestUserDefinedLoader()`方法返回的是`sun.misc.VM.latestUserDefinedLoader()`說明指定了該載入器。 返回到`readOrdinaryObject`方法中繼續做分析。 ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201213092247628-1019049997.png) 直接定位到這一步,該方法對反序列化的操作進行實現。 ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201213092257492-2121134569.png) 這裡的`slotDesc.hasReadObjectMethod()`獲取的是`readObjectMethod`這個屬性,如果反序列化的類沒有重寫`readobject()`,那麼`readObjectMethod`這個屬性就是空,如果這個類重寫了`readobject()`,那麼就會進入到if之中的 ```java slotDesc.invokeReadObject(obj, this); ``` ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201213092306977-616809347.png) 如果`readobject()`方法被重寫則是走到這一步 ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201213092319505-1045899939.png) ## 0x02 Shiro resolveClass方法分析 在shiro裡面`resolveClass`方法被進行了重寫,導致大部分利用鏈都使用不了,檢視一下該方法實現。 ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201213092330152-404578530.png) 這裡去呼叫了`ClassUtils.forName`方法進行跟蹤。 ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201213092340229-1712469657.png) 這裡是呼叫了`THREAD_CL_ACCESSOR.loadClass`,檢視一下`THREAD_CL_ACCESSOR`是什麼。 ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201213092349660-774820779.png) 跟進檢視一下該類。 ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201213092409869-766617428.png) 這裡呼叫`getClassLoader`方法獲取類載入器,而在這裡獲取到的是`ParallelWebappClassLoader`,那麼下面呼叫的肯定也就是`ParallelWebappClassLoader.loadClass` ### 參考文章 ``` https://blog.csdn.net/niexinming/article/details/106665753 https://www.anquanke.com/post/id/192619#h2-2 ``` ## 0x03 結尾 其實在前面的一些cc鏈的除錯鋪墊下,再去除錯其他的一些漏洞,都會比較熟練。本文也是為了下文去做了一個較好的