Java安全之原生readObject方法解讀
阿新 • • 發佈:2020-12-13
# 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鏈的除錯鋪墊下,再去除錯其他的一些漏洞,都會比較熟練。本文也是為了下文去做了一個較好的