EPyT-Flow has unsafe JSON deserialization (__type__)
Description
EPyT-Flow is a Python package designed for the easy generation of hydraulic and water quality scenario data of water distribution networks. Prior to 0.16.1, EPyT-Flow’s REST API parses attacker-controlled JSON request bodies using a custom deserializer (my_load_from_json) that supports a type field. When type is present, the deserializer dynamically imports an attacker-specified module/class and instantiates it with attacker-supplied arguments. This allows invoking dangerous classes such as subprocess.Popen, which can lead to OS command execution during JSON parsing. This also affects the loading of JSON files. This vulnerability is fixed in 0.16.1.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
EPyT-Flow before 0.16.1 allows remote code execution via a custom JSON deserializer that dynamically imports attacker-controlled classes like subprocess.Popen.
Vulnerability
Overview
CVE-2026-25632 is a critical deserialization vulnerability in EPyT-Flow, a Python package for water distribution network simulation. The package's REST API and JSON file loading functionality use a custom deserializer (my_load_from_json) that supports a type field in JSON payloads. When present, the deserializer dynamically imports an attacker-specified Python module and class, then instantiates it with attacker-supplied arguments [1]. This design bypasses standard Python deserialization safety checks, allowing invocation of arbitrary classes such as subprocess.Popen to be invoked directly during JSON parsing.
Exploitation
An attacker can exploit this vulnerability by sending a crafted JSON request to the REST API or by providing a malicious JSON file for loading. No authentication is required if the REST API is exposed. The attacker controls both the module/class name and the constructor arguments, enabling arbitrary class instantiation. The commit that fixes the issue shows that the previous code used importlib.import_module and getattr to resolve and call any class, with no allowlist [3].
Impact
Successful exploitation leads to OS command execution in the context of the EPyT-Flow process. This could allow an attacker to execute arbitrary commands, potentially compromising the host system, accessing sensitive data, or disrupting water distribution network simulations. The vulnerability is rated with a high CVSS score [1].
Mitigation
The vulnerability is fixed in version 0.16.1. The fix replaces the unrestricted dynamic import with a whitelist of allowed serializable classes (JSON_SERIALIZABLE), preventing instantiation of arbitrary types [3]. Users should upgrade immediately. No workaround is available for versions prior to 0.16.1 [4].
AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
epyt-flowPyPI | < 0.16.1 | 0.16.1 |
Affected products
1- WaterFutures/EPyT-Flowv5Range: < 0.16.1
Patches
13fff9151494cAdd sanity checks to JSON deserialization
1 file changed · +23 −13
epyt_flow/serialization.py+23 −13 modified@@ -5,7 +5,6 @@ from abc import abstractmethod, ABC from io import BufferedIOBase import pathlib -import importlib import json import gzip import umsgpack @@ -55,6 +54,10 @@ COLOR_SCHEMES_ID = 36 +JSON_SERIALIZABLE = { +} + + def my_packb(data: Any) -> bytes: """ Overriden `umsgpack.packb <https://msgpack-python.readthedocs.io/en/latest/api.html#msgpack.packb>`_ @@ -86,6 +89,9 @@ def serializable(my_id: int, my_file_ext: str) -> Any: File extension. """ def wrapper(my_class): + if issubclass(my_class, JsonSerializable): + JSON_SERIALIZABLE[(my_class.__module__, my_class.__name__)] = my_class + @staticmethod def unpackb(data: bytes) -> Any: return my_class(**my_unpackb(data)) @@ -274,18 +280,22 @@ def my_load_from_json(data: str) -> Any: `Any` Deserialized object. """ - def __object_hook(obj: dict) -> dict: - if "__type__" in obj: - module_name, class_name = obj["__type__"] - cls = getattr(importlib.import_module(module_name), class_name) - del obj["__type__"] - - for attr in obj: - if isinstance(attr, dict): - obj[attr] = __object_hook(obj[attr]) - - return cls(**obj) - return obj + def __object_hook(obj: dict): + t = obj.get("__type__") + if not t: + return obj + + if not (isinstance(t, (list, tuple)) and len(t) == 2 and + all(isinstance(x, str) for x in t)): + raise ValueError("Invalid __type__") + + key = (t[0], t[1]) + cls = JSON_SERIALIZABLE.get(key) + if cls is None: + raise ValueError(f"Type not allowed: {key}") + + args = {k: v for k, v in obj.items() if k != "__type__"} + return cls(**args) return json.loads(data, object_hook=__object_hook)
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-74vm-8frp-7w68ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-25632ghsaADVISORY
- github.com/WaterFutures/EPyT-Flow/commit/3fff9151494c7dbc72073830b734f0a7e550e385ghsax_refsource_MISCWEB
- github.com/WaterFutures/EPyT-Flow/releases/tag/v0.16.1ghsax_refsource_MISCWEB
- github.com/WaterFutures/EPyT-Flow/security/advisories/GHSA-74vm-8frp-7w68ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.