跳至主要內容
>_ITDITD網站資安平台

資安指南

不安全的反序列化:讓外部資料決定物件型別的風險,以及如何防範

把位元組還原成物件時,依格式不同,外部資料可能決定要建立哪個類別。OWASP 指出,對反序列化器的攻擊曾造成阻斷服務、存取控制繞過和遠端程式碼執行,並表示 .NET 的 BinaryFormatter 無法變得安全。

發布於 2026-09-05 更新於 2026-10-06 最後核實 2026-10-06 閱讀時間 5 分鐘

把收到的位元組還原成物件之所以危險,並不是因為內容格式錯誤。而是在某些格式中,外部資料可以決定要建立哪種型別的物件。

為什麼輸入驗證來得太晚

純資料格式(JSON/XML)

外部資料只決定值 → 你接收後,再對應到自己的型別 → 驗證有效

會還原物件的格式

外部資料決定值和型別 → 還原時執行屬於所選型別的程式碼 → 執行流程根本到不了你的檢查

值的檢查發生在物件建立之後。在會選擇型別的格式中,到那時已經結束了。

多數輸入驗證問的是「這個值可以接受嗎」。但在會還原物件的格式中,還原本身(要實例化哪個類別、過程中執行什麼程式碼)是由外部資料操控的。在你的驗證被呼叫之前,某些東西可能已經執行完畢了。

這和原型污染是同樣的形態:外部資料決定的是結構,而不是值。名稱不同、語言不同,防禦的思路相同。

會出什麼問題(OWASP 列出的三項)

對反序列化器的攻擊所觀察到的影響
阻斷服務
讓還原本身變得極為耗費資源,或讓它失敗,使應用程式停止
存取控制繞過
代表權限或角色的物件,被建構成對攻擊者有利的樣子
遠端程式碼執行
串接還原過程中被呼叫的處理程序,達到任意程式碼執行,這是最嚴重的結果

「我們的框架不會這樣」不是安全的前提

這不只是舊語言的問題。我們統計 NVD 資料中的 Next.js 漏洞時,反序列化(CWE-502)在 CWE 分布中排名很高,因為現代框架也帶有反序列化請求內容的路徑,Server Actions 就是其中之一(Next.js 和 React 的漏洞很多嗎?)。「不是我寫的」不代表「它不存在」。

兩種防禦

1. 不讓任何東西選擇型別(第一選擇)

OWASP 的說法:「改用 JSON 或 XML 這類純資料格式,可以降低自訂反序列化邏輯被挪作惡意用途的機會。」

以只攜帶值的格式接收外部輸入,再用自己的程式碼對應到自己的型別。光是這樣,攻擊者就無法選擇要建立什麼。在多數程式碼中,這就能解決問題。

2. 加上簽章,並拒絕沒有簽章的

無法避免使用自訂格式時,指南指出「可以在序列化過程中為它們加上簽章」,然後「選擇不反序列化任何沒有經過驗證簽章的訊息」。

重點是在還原之前驗證。基於上述理由,先還原再檢查可能已經太遲了。順序就是控制的全部。

有些機制無法靠設定變得安全

關於 .NET 的 BinaryFormatter,OWASP 明確表示「BinaryFormatter 型別是危險的,而且無法變得安全」。

這句話在實務上很重要:確實存在「只要正確使用就沒問題」根本不成立的工具。強化選項、增加驗證、撰寫允許清單,都無法讓它變得安全。唯一的控制就是不使用它。

所以在稽核時,首先要把設定能保護的和不能保護的分開。把力氣花在後者不是緩解,而是拖延。

OWASP 依語言點名的項目

被點名為危險的項目(從防禦方的角度整理)
PHP
避免使用 unserialize();優先使用 JSON
Python
pickle/c_pickle、PyYAML 的 load 和 jsonpickle 被列為危險
Java
覆寫 ObjectInputStream#resolveClass(),限制可以還原的類別
.NET
BinaryFormatter 是危險的,而且無法變得安全,不要使用

共同點是:語言內建的方便持久化機制,正是絕不能面對外部資料的東西。它是為了在彼此信任的雙方之間搬運整個物件而設計的,並沒有假設傳送者懷有敵意。

各語言安全與不安全的 API

下表整理了各語言官方文件警告不要用在外部資料上的東西,以及建議改用的東西。如果左欄的東西出現在你的程式碼中,請查清楚它的資料來自哪裡。

語言不要用在外部資料上改用官方文件的說法
Pythonpickle.load/pickle.loads、marshal、yaml.load(使用 Loader=yaml.Loader 時)和 yaml.unsafe_load、jsonpickle.decodejson.loads、yaml.safe_loadpickle「不安全」;需要偵測竄改時用 hmac 簽章
PHPunserialize()(即使設定了 allowed_classes)json_decode()/json_encode()不論 allowed_classes 如何,都不要傳入不可信任的輸入
RubyMarshal.loadJSON,或其他只載入基本型別的格式從不可信任的來源載入可能導致遠端程式碼執行
Java對外部資料使用 ObjectInputStream#readObject、XMLDecoder、XStream#fromXML把 JSON 對應到自己的類別(DTO)無法避免時,用 ObjectInputFilter 限制允許的類別
.NETBinaryFormatter、SoapFormatter、NetDataContractSerializer、LosFormatter、ObjectStateFormatter、Json.NET 的 TypeNameHandling 設為 None 以外的值System.Text.Json、XmlSerializer、DataContractSerializer從 .NET 9 起,使用 BinaryFormatter 會擲出例外

並排比較,差別如下。不安全的版本把收到的東西直接變回物件;安全的版本把它當成值讀取,只把需要的欄位移到自己的型別中。

# 避免:把收到的位元組直接變回物件
order = pickle.loads(request_body)
 
# 改用:讀取值,再只把需要的欄位移到自己的型別
data = json.loads(request_body)
order = Order(id=int(data["id"]), quantity=int(data["quantity"]))

在安全的版本中,由你的程式碼(Order)決定要建立哪個類別。傳入的資料只決定 id 和 quantity 的值,而一般的輸入驗證對這些值是有效的。

在自己的程式碼中要檢查什麼

1

列出外部資料最先到達的所有地方

請求內容、Cookie、工作階段、佇列、上傳、Webhook、快取:所有先被儲存、之後再被還原的地方。工作階段和快取最容易漏掉:你以為是自己的資料,依路徑不同也可能從外部到達。

2

找出其中使用會還原型別的格式的地方

搜尋上面清單中點名的函式和類別。這些是最先要移除的東西。如果正在使用無法變得安全的機制(BinaryFormatter 等),那就是最優先事項,除了遷移之外沒有其他解法。

3

改用純資料格式

接收 JSON 或 XML,並用自己的程式碼對應到自己的型別。在對應時加上結構描述(schema)驗證,就能同時擋下非預期的鍵(與原型污染的對策相同)。

4

無法改用時,用簽章保護

如果格式無法改變,就在序列化時加上簽章,並在還原前驗證。把**「沒有簽章就不還原」**設為預設。金鑰的處理方式請參考.env 和 API 金鑰危險在哪裡,絕不要和程式碼放在同一個地方。

5

清點相依套件所做的還原

即使你自己一行都沒寫,相依套件也可能在還原物件。被歸類為 CWE-502 的 CVE 不斷出現,所以請交給機器監控(osv-scanner 入門/實用的 CVE 修補手冊)。

如何檢查自己的環境

以下把上述步驟中「找出來」的部分,轉換成具體的指令和設定。所有檢查都只讀取你自己的儲存庫和伺服器。

1

文字搜尋原始碼

先列出所有候選位置。使用 ripgrep(rg)時,搜尋方式如下(grep -rnE 也可以使用相同的模式)。

# Python
rg -n "pickle\.loads?\(|marshal\.loads?\(|yaml\.load\(|yaml\.unsafe_load|jsonpickle\.decode"
# PHP
rg -n "unserialize\("
# Ruby
rg -n "Marshal\.load"
# Java
rg -n "ObjectInputStream|readObject\(|XMLDecoder|fromXML\("
# .NET
rg -n "BinaryFormatter|SoapFormatter|NetDataContractSerializer|LosFormatter|ObjectStateFormatter|TypeNameHandling"

並不是每個搜尋結果都危險。對每一個結果,寫下它接收的資料來自哪裡,並優先處理與外部可到達的路徑相連的部分:請求、Cookie、上傳、共用儲存空間。

2

尋找被壓下的警告

在 .NET 中,使用 BinaryFormatter 會產生 SYSLIB0011 警告或錯誤。壓下這個警告的設定,就標示出危險呼叫仍留在程式碼中的地方。

rg -n "SYSLIB0011|CA23[0-3][0-9]" --glob "*.cs" --glob "*.csproj" --glob ".editorconfig" --glob "*.props"

如果找到 #pragma warning disable SYSLIB0011,或 <NoWarn> 中有這個 ID,請確認它為什麼在那裡,以及何時會遷移。

3

啟用靜態分析規則

Python 相關的 Bandit 檢查是 B301(pickle 等)、B302(marshal)和 B506(yaml.load);bandit -r . -t B301,B302,B506 只會執行這三項。.NET 相關的程式碼分析規則是 CA2300 系列(例如會標示 Json.NET 的 TypeNameHandling 的 CA2326)。即使在 .NET 10 中 CA2326 預設也沒有啟用,所以請在 .editorconfig 中加入 dotnet_diagnostic.CA2326.severity = warning 之類的一行來開啟。在 CI 中執行這些檢查,新加入的呼叫也會被擋下。

4

Java 要檢查執行時期的篩選設定

Java 可以在執行時期限制反序列化能建立哪些類別(序列化篩選器)。這個機制在 JDK 9(JEP 290)加入,並在 Java 17(JEP 415)可以依情境切換。對於執行中的程序,請在 jcmd <PID> VM.system_properties 的輸出中尋找 jdk.serialFilter。它也可以在 java.security 檔案中設為安全性屬性,所以兩處都要檢查。如果兩處都沒有,而你的程式碼也沒有呼叫 ObjectInputFilter.Config.setSerialFilter,就代表沒有整個程序層級的篩選器。

5

檢查相依套件的已知漏洞

即使你自己的程式碼中沒有這類呼叫,相依套件也可能在還原物件。用 osv-scanner 或類似工具列出相依套件的已知漏洞,並優先更新被歸類為 CWE-502(不可信任資料的反序列化)的漏洞。

常見的錯誤與修正方法

錯誤為什麼有問題修正方法
因為「是內部的」或「這個檔案是我自己存的」就信任資料Microsoft 舉出了資料在開發者沒注意到的情況下跨越信任邊界的例子:共用的存檔、雲端同步、遭入侵的員工電腦只要有任何路徑能從外部到達,就當成外部資料處理。不還原任何沒有簽章的東西
一個一個把危險的類別加進封鎖清單對清單以外或尚未發現的組合毫無作用。PHP 手冊指出即使設定了 allowed_classes 也不要傳入不可信任的輸入把格式改成 JSON 等。只有在無法避免時,才列出允許的類別
先還原,再驗證內容還原時就會執行程式碼,所以驗證來得太晚把簽章驗證放在還原之前
壓下警告就當成已修正讓 SYSLIB0011 或 CA2326 不再出現,危險的呼叫仍然存在搜尋被壓下的警告,並訂出遷移期限
以為「是 JSON 所以安全」讓 JSON 自己指定型別的設定(例如 Json.NET 的 TypeNameHandling)會把它變成會選擇型別的格式讓 TypeNameHandling 維持 None(預設值)
在 PyYAML 中使用 yaml.loadPyYAML 的文件說 yaml.load 和 pickle 一樣強大,可能呼叫任何 Python 函式換成 yaml.safe_load

真實案例:擴充套件內的還原

本站關於 CVE-2026-45247 的警示,介紹了 Magento 2 擴充套件「Mirasvit Full Page Cache Warmer」1.11.12 之前版本中的 PHP 物件注入(CWE-502)。它可以在不經驗證的情況下遠端執行程式碼,CVSS 分數為 9.3,並被列入 CISA 的已知遭利用漏洞目錄(KEV)。

教訓是,還原的程式碼不是網站營運者寫的,而是在他們安裝的擴充套件裡。這就是上面「檢查相依套件」步驟重要的原因。修正方法是更新擴充套件,而搜尋自己的程式碼是找不到它的。

本站的看法:要問輸入被允許決定什麼,而不是值是否有效

一說到「輸入驗證」,多數人想到的是檢查值:長度、格式、範圍。本文的威脅在那之前就已經發生了。本站的看法是:關於輸入,真正的問題不是「這個值是否正確」,而是「我讓這個輸入決定了多少事」。

只有值?連結構也是(原型污染)?連型別也是(本文)?甚至連放在哪裡也是(檔案上傳漏洞)?數一數你交給外部的決定,危險的地方自然就會浮現。

資料來源(一手資料)

  • OWASP Cheat Sheet Series, "Deserialization Cheat Sheet" — cheatsheetseries.owasp.org(三項影響、改用純資料格式、為完整性加上簽章、各語言的注意事項,以及關於 BinaryFormatter 的說明,皆出自這份文件;2026 年 9 月 5 日確認)
  • Python documentation, "pickle" — docs.python.org(「不安全」的警告;用 hmac 簽章、優先使用 JSON)
  • PyYAML Documentation — pyyaml.org(yaml.load 與 yaml.safe_load 的差別)
  • PHP Manual, "unserialize" — php.net(不論 allowed_classes 如何都不要傳入不可信任的輸入;使用 JSON)
  • Ruby documentation, "Marshal" — docs.ruby-lang.org
  • Microsoft Learn, "Deserialization risks in use of BinaryFormatter and related types" — learn.microsoft.com(同樣危險的型別、建議的替代方案、.NET 9 起的行為、跨越信任邊界的例子)
  • Microsoft Learn, "SYSLIB0011" and "CA2326" — SYSLIB0011/CA2326
  • OpenJDK, "JEP 290: Filter Incoming Serialization Data" and "JEP 415: Context-Specific Deserialization Filters" — JEP 290/JEP 415
  • Bandit documentation (B301, B302, B506) — bandit.readthedocs.io
  • 語言對照表、搜尋步驟和常見錯誤,已於 2026 年 10 月 6 日依上述文件確認

延伸閱讀

FAQ

Q反序列化為什麼這麼危險?不就是資料轉換嗎?
A

因為在某些格式中,它不是資料轉換,而是物件的重建。當「要建立哪個類別」的資訊夾帶在資料裡時,攻擊者就能選擇你要建立什麼。OWASP 表示,已發現對反序列化器的攻擊可以造成阻斷服務、存取控制繞過或遠端程式碼執行攻擊。難以防禦的原因在於,攻擊在你的值檢查執行之前就已經成功了。

Q最可靠的防禦是什麼?
A

把來自外部的資料限制在無法選擇型別的格式。OWASP 的說法是:改用 JSON 或 XML 這類純資料格式,可以降低自訂反序列化邏輯被挪作惡意用途的機會。如果無法避免使用自訂格式,同一份指南指出,可以在序列化過程中為訊息加上簽章,並選擇不反序列化任何沒有經過驗證簽章的訊息。先改變格式;做不到時,再用簽章加以約束。

Q可以靠強化設定讓它變得安全嗎?
A

取決於機制。OWASP 明確表示,.NET 的 BinaryFormatter 型別是危險的,而且無法變得安全。有些機制不會因為設定或額外的驗證而變得安全,唯一的解決方法是停止使用。請把設定能保護的和不能保護的分開。確實存在「只要正確使用就沒問題」這句話不成立的工具。

Q在我使用的語言中該注意什麼?
A

OWASP 直接點名的包括:PHP 避免使用 unserialize(),優先使用 JSON;Python 的 pickle/c_pickle、PyYAML 的 load 和 jsonpickle 是危險的;Java 要覆寫 ObjectInputStream#resolveClass() 來限制可以還原的類別;.NET 不要使用 BinaryFormatter。共同點是,語言內建的方便持久化機制,正是絕不能用在外部資料上的東西。

Q如何在自己的程式碼中找出不安全的反序列化?
A

分三步。首先,用 ripgrep 或 grep 文字搜尋 pickle.loads、unserialize、Marshal.load、ObjectInputStream 和 BinaryFormatter 等呼叫。其次,啟用靜態分析規則(Python 用 Bandit 的 B301、B302 和 B506;.NET 用 CA2300 系列的程式碼分析規則)。第三,用 osv-scanner 等工具掃描相依套件的已知漏洞,優先更新 CWE-502 的漏洞。對每個找到的呼叫,寫下它接收的資料來自哪裡,並優先修正從外部可以到達的那些。

Q只要用 JSON 就一定安全嗎?
A

如果你啟用了讓資料自己指定型別的設定,就不安全。常見的例子是 .NET 中 Json.NET 的 TypeNameHandling;Microsoft 的程式碼分析規則 CA2326 指出不要使用 None 以外的任何值。JSON 的好處是它只攜帶值,所以由接收端的程式碼決定要建立哪個類別。請確認你沒有開啟會抵銷這個好處的設定。