系统:运行中
← 返回所有攻击
INFRASTRUCTURE CRITICAL NEW

LightLLM CVE-2026-26220:服务端强制对外暴露的 WebSocket 上的 pickle 反序列化

CVE-2026-26220(2026 年 2 月 15 日披露)将 pickle.loads() 置于 LightLLM prefill-decode 模式的两个未认证 WebSocket 端点上——而服务端拒绝绑定 localhost,因此攻击面始终面向网络。

2026-06-02 // 5 min affects: lightllm, vllm, gpu-inference-nodes, ml-serving-infrastructure

这是什么?

2026 年 2 月 15 日,安全研究员 Valentin Lobstein(Chocapikk)公开披露LightLLM(一个 Python LLM 推理引擎,GitHub 约 3,900 颗星)中的一个未认证远程代码执行漏洞。该漏洞编号为 CVE-2026-26220,由 VulnCheck 于 2 月 16 日分配,评分 CVSS 4.0 9.3(严重),归类为 CWE-502 —— 不可信数据的反序列化。受影响版本为 LightLLM ≤ 1.1.0

这是一个教科书式的案例,但带有现代化的变体。LightLLM 的 prefill-decode(PD)解耦模式暴露了两个 WebSocket 端点,它们在完全没有认证的情况下对原始二进制帧调用 pickle.loads()——而且服务端明确拒绝绑定 localhost,因此在 PD 模式下,攻击面在设计上始终可经网络到达。它与 vLLM 的 CVE-2025-32444(CVSS 最高 10.0)属于同一反复出现的类别——不可信的 pickle 经由节点间通道传输,产生相同的结果。

工作原理

Python 的 pickle 模块是一个序列化器,按设计可以重建任意对象——包括那些重建过程会执行代码的对象。把攻击者控制的字节交给 pickle.loads(),等同于把一段脚本交给进程去执行。这不是 LightLLM 特有的细节:这是 pickle 已被记录的特性,也正是 CWE-502 存在的原因。

LightLLM 的 PD 模式将推理拆分到多个节点:一个 PD master 负责编排 worker 注册以及在独立的 prefill 与 decode GPU 节点之间传输 KV-cache。worker 通过 WebSocket 连接到 master 进行注册并上报状态。根据披露内容和 CVE 跟踪 issue,反序列化位于 lightllm/server/api_http.py

# /pd_register  (api_http.py ~第 310 行)
data = await websocket.receive_bytes()
obj = pickle.loads(data)          # untrusted network bytes -> object graph

# /kv_move_status  (api_http.py ~第 331 行) —— 相同模式
upkv_status = pickle.loads(data)

worker 侧的 PD 循环中还有另外两处 pickle.loads() 调用。访问它们无需任何凭据:/pd_register 会先要求一个 JSON 注册帧,但 node_id 是一个未经校验的整数,mode 只是一个字符串检查——都算不上认证。/kv_move_status 在第一帧就直接接受 pickle。

更严重的细节是写死在启动逻辑中的部署约束:

assert manager.args.host not in ["127.0.0.1", "localhost"]

master 必须绑定到可路由的网络接口,因为远程 worker 需要连上它。PD 模式不存在「仅本地回环」的安全配置。研究员的文章中给出了一个可用的概念验证;其机制是 pickle 经典的 reduce gadget——一个精心构造的对象,其反序列化会调用一条系统命令 [REDACTED]——本文不复现可运行的载荷。结构性结论已足够:任何能向 PD master 打开 socket 的主机,都能在模型进行任何推理之前,以服务进程的权限获得代码执行。

为何重要

这不是一种新颖的攻击——它是 ML 服务生态反复重新交付的一类漏洞中的一个高影响实例。同样的根因(在被默认信任的内部通道上使用 pickle)也造就了 vLLM 的 CVE-2025-32444。该模式之所以反复出现,是因为解耦式服务成倍增加了节点间通道,而每一个使用 pickle 的通道都是一处潜伏的 RCE。

影响半径就是推理层本身:GPU 节点通常持有模型权重、API 密钥、云凭据,以及网络内部的特权位置。在 PD master 上执行代码的攻击者,已经越过了真正要紧的边界。又因为 PD master 端点在设计上面向网络暴露,任何可从不可信网段(扁平的内网、过宽的安全组、暴露的集群端口)到达的实例,无需任何凭据即可被利用。

这里还有一个流程教训。披露指出,该项目此前已收到过反序列化报告——#784(ZMQ recv_pyobj,2025 年 3 月)以及 #1102(2025 年 11 月)中的一份私密报告——均未得到处理,因此这次才升级为 CVE,而非私下处置。请默认把快速演进的 ML 基础设施视为安全上不成熟:代码迭代速度很高,而安全审查的节奏往往跟不上。

防御

无需等待厂商修复即可化解此问题。这些缓解措施属于标准的基础设施卫生,适用于任何「pickle 上网」的场景,而不仅是 LightLLM:

  1. 不要把 PD 端点暴露给不可信网络。 既然 master 无法绑定 localhost,就用下层来约束它:绑定到专用私有接口,通过防火墙/安全组将端口限制为已知的 worker IP,并将 PD 流量放到隔离的 VLAN 或 mesh 上。绝不能让 /pd_register/kv_move_status 从通用子网可达。

  2. 对节点间通道做认证。 worker 注册与 KV-status 属于节点间 IPC,而非公开 API。在前面加上 mTLS(客户端证书)或共享密钥/令牌校验,使未注册的对端连 socket 都打不开。

  3. 网络 IPC 替换掉 pickle。 这里交换的数据是简单的结构化状态(整数、字符串、字典)。JSON、MessagePack 或 protobuf 都能承载它们,而不会授予代码执行。这正是 #1213 中要求维护者做出的长期修复。

  4. 如果非用 pickle 不可,就约束它。 使用带有明确安全类白名单的 RestrictedUnpickler 并拒绝其余一切,并/或用 HMAC 签名包裹帧,使服务端只反序列化它能验证的消息。

  5. 先检测部署,再检测滥用。 清点所有以 --run_mode pd_master | prefill | decode 运行的 LightLLM 实例。对来自 worker 白名单之外、流向 PD 端口的入站连接告警,并对推理进程派生 shell(sh -cbashos.system 式子进程)告警——这是反序列化 gadget 触发时高信号的运行时痕迹。

  6. 把审计推广开来。 这是覆盖整个集群的一类问题,而非单个 CVE。在你的服务栈中搜索对不可信输入调用的 pickle.loadsrecv_pyobjtorch.load(...),以及任何 socket、队列或 HTTP 边界上的 joblib 加载。每一处都是 CWE-502 的候选。

状态

项目参考日期备注
公开披露 + PoCChocapikk(V. Lobstein)2026-02-15PD 模式下 WebSocket 的 pickle.loads()
分配 CVEVulnCheck 公告2026-02-16CVSS 4.0 9.3,CWE-502,影响 ≤ 1.1.0
跟踪 + 拟议修复GitHub issue #12132026-02已请求维护者修复;该项目在安全响应上有迟缓的历史
同类,邻近项目CVE-2025-32444(vLLM)2025节点间 IPC 上的 pickle,CVSS 最高 10.0

诚实的总结:CVE-2026-26220 并不新颖——它是 ML 服务世界重新认识到:未认证网络 socket 上的 pickle 就是远程代码执行。 在节点间传输放弃 pickle 之前,防御取决于你自己:隔离 PD 平面、对对端做认证,并默认认为任何能够重建任意对象的序列化器,终有一天会重建出攻击者的对象。

Sources