【入坑JAVA安全】序列化與反序列化

0x01

  • 什麼是序列化與反序列化?
  • 序列化與反序列化的關鍵函數?
  • 反序列化過後的數據有啥特徵?
  • java反序列化漏洞與php反序列化漏洞的相似之處?

這一章,我們只需要搞清楚前面三個問題就行了,其實java反序列化漏洞的原理很簡單,只是各個POP鏈比較複雜。我會很淺顯的介紹一下java的序列化~

0x02

在我看來,java的序列化機制就是爲了持久化存儲某個對象或者在網絡上傳輸某個對象。我們都知道,一旦jvm關閉,那麼java中的對象也就銷燬了,所以要想保存它,就需要把他轉換爲字節序列寫到某個文件或是其它哪裏。

序列化:把對象轉換爲字節序列
反序列化:吧字節序列轉換爲對象

0x03

一個類對象要想實現序列化,必須滿足兩個條件:

1、該類必須實現 java.io.Serializable 對象。

2、該類的所有屬性必須是可序列化的。如果有一個屬性不是可序列化的,則該屬性必須註明是短暫的。(這個咱先不關注)

0x04

要序列化一個對象,首先要創建OutputStream對象,再將其封裝在一個ObjectOutputStream對象內,接着只需調用writeObject()即可將對象序列化,並將其發送給OutputStream(對象是基於字節的,因此要使用InputStream和OutputStream來繼承層次結構)。

要反序列化出一個對象,需要將一個InputStream封裝在ObjectInputStream內,然後調用readObject()即可。

看文字不夠直觀,咱們直接上代碼(注意看註釋):

import java.io.*;

public class Test {

    public static void main(String[] args){
        User user = new User("axin", 18, 180);
        try {
            // 創建一個FIleOutputStream
            FileOutputStream fos = new FileOutputStream("./user.ser");
            // 將這個FIleOutputStream封裝到ObjectOutputStream中
            ObjectOutputStream os = new ObjectOutputStream(fos);
            // 調用writeObject方法,序列化對象到文件user.ser中
            os.writeObject(user);
            
            System.out.println("讀取數據:");
            //  創建一個FIleInutputStream
            FileInputStream fis = new FileInputStream("./user.ser");
            // 將FileInputStream封裝到ObjectInputStream中
            ObjectInputStream oi = new ObjectInputStream(fis);
            // 調用readObject從user.ser中反序列化出對象,還需要進行一下類型轉換,默認是Object類型
            User user1 = (User)oi.readObject();
            
            user1.info();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

class User implements Serializable{
    private String name;
    private int age;
    private float height;

    public User(String name, int age, float height) {
        this.name = name;
        this.age = age;
        this.height = height;
    }

    public void info(){
        System.out.println("Name: "+name+", Age: "+age+", Height: "+height);
    }

    // private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException{
    //     System.out.println("[*]執行了自定義的readObject函數");
    // }
}

程序執行過後會在當前目錄下生成一個user.ser文件,並且反序列化過後會執行info方法,在終端上打印出User的信息:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-i2xOGYMA-1586689192839)(serialization/ser.png)]

可以看到按照預期執行了,成功生成了一個user.ser文件,這個文件裏存放的就是反序列化過後的User類對象,我們看一下內容,這裏藉助一個linux下的小工具xxd查看內容:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-0qdxA1YY-1586689192840)(serialization/xxd.png)]

xxd顯示的結果,中間那一欄是文件的十六進制顯示,最右邊是字符顯示。這裏需要注意的特徵值就是16進制顯示時的前32位:

AC ED:STREAM_MAGIC,聲明使用了序列化協議,從這裏可以判斷保存的內容是否爲序列化數據。 (這是在黑盒挖掘反序列化漏洞很重要的一個點)

00 05:STREAM_VERSION,序列化協議版本。

0x05

上面已經說完了序列化的基礎了,大家也應該知道如何實現一個對象的序列化與反序列化了,那麼,漏洞點到底在哪裏呢?如果你瞭解php的反序列化,那麼你應該知道php反序列化一個對象時會自動觸發__weakup__destruct這些函數,如果這些函數當中有一些危險的操作,那麼就可能導致漏洞的發生,同樣的,java反序列化時會自動觸發哪個函數呢?沒錯,就是readObject(),但是上面demo中的readObject()函數不是ObjectInputStream的方法嗎,開發者又不可以控制,怎麼會導致漏洞呢?

其實,java是支持自定義readObject與writeObject方法的,只要某個類中按照特定的要求實現了readObject方法,那麼在反序列化的時候就會自動調用它,如果這個自定義的readObject方法裏進行了一些危險操作,那麼就會導致反序列化漏洞的發生了。試驗一下:我們還是用上面的類,不過這次自定義User類的readObject方法,也就是去掉最後一點代碼的註釋,再次執行,查看結果:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-0QqWYEnm-1586689192841)(serialization/custom_readobject.png)]

可以看到,自定義的readObject的確執行了!

現在,我們在readObject中寫上危險操作,比如執行系統命令,彈個wireshark:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-YLqeVW2x-1586689192841)(serialization/wireshark.png)]

當然,真實的應用中不會有人這麼寫,但是理兒就是這麼個理兒,只是真實應用中危險操作比較隱蔽,不像我寫的這麼赤裸裸

0x06

我想,應該有人和我一樣搞不太清楚java中的各種stream(FileOutputStream/BufferedOutputStream/DataOutputStream/ObjectOutputStream),這裏放個參考資料,對理解序列化的代碼有所幫助:
https://www.cnblogs.com/shitouer/archive/2012/12/19/2823641.html


我的公衆號,一起來玩呀

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章