Java安全之原生readObject方法解讀

nice_0e3發表於2020-12-13

Java安全之原生readObject方法解讀

0x00 前言

在上篇文章分析shiro中,遇到了Shiro重寫了ObjectInputStreamresolveClass導致的一些基於InvokerTransformer去實現的利用鏈沒法使用,因為這需要去定義一個InvokerTrans陣列,而該陣列傳入到Shiro重寫後的resolveClass方法中會報錯。但是在此之前,並沒有去對readObject方法去做一個解讀和分析。所以也不知道他具體的實現。包括在分析利用鏈的時候,只知道到呼叫了ObjectInputStream.readObject方法後,如果readObject被重寫的話,就會呼叫重寫後的readObject方法,但是我們也並不知道在內部是怎麼樣去做一個實現的。那麼下面來分析一下readObject的功能實現。

0x01 readObject方法分析

在前面先貼一張readObject的執行流程圖,這是一張weblogic的反序列化執行流程圖。第一個readObject直接忽略,到下篇文weblogic再做講解。

這裡寫一段測試程式碼去進行反序列化操作,然後進行動態跟蹤。

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方法中,進行執行測試類程式碼動態跟蹤。

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

下面就直接執行到了這步

呼叫了readObject0方法,選擇跟進檢視一下內部的實現。

在這裡會去獲取序列化資訊第一個位元組,如果為TC_RESET就會呼叫bin.readByte()handleReset();方法。

檢視TC_RESET內容。

而該值轉換Byte後,為121,我們序列化資料的第一個位元組為151,這裡就跳過不執行了。

接下來程式碼中定義了一個switch去做一個判斷,TC_OBJECT的值轉換後剛剛好為115。那麼就會執行到這一步。

在這裡面會呼叫readOrdinaryObject方法,進行跟進。

在該方法中還會去呼叫readClassDesc方法,繼續跟進。

看到這裡發現就很有意思了,獲取我們序列化資料的第二個位元組,然後又進行一次switch,這次走到了readNonProxyDesc方法中,跟進!

在這又呼叫了resolveClass方法然後傳入readDesc引數。還是跟進方法。

這裡返回了

Class.forName(name, false, latestUserDefinedLoader());

latestUserDefinedLoader()方法返回的是sun.misc.VM.latestUserDefinedLoader()說明指定了該載入器。

返回到readOrdinaryObject方法中繼續做分析。

直接定位到這一步,該方法對反序列化的操作進行實現。

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

slotDesc.invokeReadObject(obj, this);

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

0x02 Shiro resolveClass方法分析

在shiro裡面resolveClass方法被進行了重寫,導致大部分利用鏈都使用不了,檢視一下該方法實現。

這裡去呼叫了ClassUtils.forName方法進行跟蹤。

這裡是呼叫了THREAD_CL_ACCESSOR.loadClass,檢視一下THREAD_CL_ACCESSOR是什麼。

跟進檢視一下該類。

這裡呼叫getClassLoader方法獲取類載入器,而在這裡獲取到的是ParallelWebappClassLoader,那麼下面呼叫的肯定也就是ParallelWebappClassLoader.loadClass

參考文章

https://blog.csdn.net/niexinming/article/details/106665753

https://www.anquanke.com/post/id/192619#h2-2

0x03 結尾

其實在前面的一些cc鏈的除錯鋪墊下,再去除錯其他的一些漏洞,都會比較熟練。本文也是為了下文去做了一個較好的鋪墊。

相關文章