首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 反序列化安全防禦:不依賴加密與簽章的「先驗類別驗證」實戰

     編輯:Java 相關知識

前言:反序列化攻擊面的本質

Java 序列化將物件轉為二進位流,支援 RMI 等遠端通訊。但當服務端對不可信輸入執行反序列化時,駭客可構造惡意物件流,利用類別路徑中既有的「脆弱類別」觸發拒絕服務甚至任意程式碼執行。本文示範如何在不引入加密、簽章或欄位校驗的前提下,透過「先驗類別驗證」在物件構建前攔截非預期類別。

一、為何反序列化會成為攻擊向量?

1.1 脆弱類別的存在條件

服務端類別路徑包含海量類別(核心庫、第三方框架、中介軟體)。駭客利用條件:

  1. 惡意類別存在於伺服端類別路徑。
  2. 該類別實作 Serializable 或 Externalizable。

反序列化不呼叫建構子,但會自動調用以下任一方法執行程式碼:

  • readObject():自訂反序列化邏輯(如處理 transient 欄位)。
  • readResolve():單例模式還原。
  • readExternal():Externalizable 介面方法。

歷史證實此類攻擊可突破 Applet 沙箱,同樣威脅伺服端應用。

二、Java 序列化二進位格式與「先驗」切入點

序列化流開頭包含 類別描述元資料(類別名、serialVersionUID、欄位定義),早於實際物件資料。這使我們可在讀取物件資料前先檢視類別名稱,決定是否繼續。

範例:Bicycle 類別序列化流片段

STREAM_MAGIC (0xACED) STREAM_VERSION (5)
TC_OBJECT (0x73) TC_CLASSDESC (0x72)
className: "com.ibm.ba.scg.LookAheadDeserializer.Bicycle"
serialVersionUID: 0x4FDAAF97F8CCC0DA
fields: id(int), nbrWheels(int), name(String)
classdata: id=0, nbrWheels=1, name="Unicycle"

關鍵在於:類別名稱出現在物件資料之前,提供天然的驗證窗口。

三、核心防禦:覆寫 ObjectInputStream.resolveClass()

Java 提供「鉤子方法」resolveClass(ObjectStreamClass desc),專為自訂類別載入設計,完美適配驗證需求。步驟:

  1. 繼承 ObjectInputStream。
  2. 覆寫 resolveClass(),比對 desc.getName() 與白名單。
  3. 不符則拋出 InvalidClassException,阻斷後續反序列化。

實作程式碼:LookAheadObjectInputStream

public class LookAheadObjectInputStream extends ObjectInputStream {
    public LookAheadObjectInputStream(InputStream in) throws IOException { super(in); }

    @Override
    protected Class<?> resolveClass(ObjectStreamClass desc)
            throws IOException, ClassNotFoundException {
        if (!desc.getName().equals(Bicycle.class.getName())) {
            throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName());
        }
        return super.resolveClass(desc);
    }
}

四、完整驗證流程示範

// 序列化合法 Bicycle 物件
byte[] good = serialize(new Bicycle(0, "Unicycle", 1));
// 序列化惡意 java.io.File 物件
byte[] bad  = serialize(new File("Pierre Ernst"));

// 反序列化合法物件 → 成功
Bicycle b0 = (Bicycle) deserialize(good);  // 輸出 "Unicycle has been deserialized."

// 反序列化惡意物件 → 於 resolveClass 階段拋出異常,物件未構建
Bicycle b1 = (Bicycle) deserialize(bad);   // InvalidClassException
程式執行輸出:惡意物件被攔截

圖 1. 應用程式輸出:惡意 File 物件在 resolveClass 階段被攔截

五、結語與擴充建議

「先驗類別驗證」在物件圖構建前即可攔截非預期類別,屬於最早期的防禦層。實務上:

  • 白名單可擴充為多個合法類別(如允許整個物件圖的所有類別)。
  • 結合 ObjectInputFilter(Java 9+)實現更細粒度過濾。
  • 嚴禁反序列化不可信資料仍是第一原則;若不可避免,請分層防禦:網路層 → 反序列化前驗證 → 反序列化後業務驗證。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved