{"schema":"threatline.record/2","record":{"id":"GHSA-8f32-8649-rv87","aliases":["GHSA-8f32-8649-rv87"],"summary":"Arbitrary code execution via unguarded pickle.load of attacker-influenceable postprocessing_file in nnUNetV2Runner","details":"## Summary\r\n`monai.apps.nnunet.nnunetv2_runner.nnUNetV2Runner` unpickles an attacker-influenceable file path with no safe-loader, allowlist, or environment gate. At `nnunetv2_runner.py:29` MONAI imports `load_pickle` as an alias of `batchgenerators.utilities.file_and_folder_operations.load_pickle` — a thin `pickle.load(open(path,'rb'))` wrapper. In `predict_ensemble_postprocessing(run_postprocessing=True)` the runner unpickles the file named by `self.best_configuration[\"best_model_or_ensemble\"][\"postprocessing_file\"]`, a string read verbatim from `inference_information.json`. A malicious `.pkl` whose `__reduce__` runs a payload yields arbitrary code execution as the inference user. This is a novel variant of MONAI's known pickle-deserialization class that the maintainers' grep-based hardening missed because the sink is an aliased import (invisible to a `pickle.load`/`torch.load` grep).\r\n\r\n## Root Cause\r\n- `nnunetv2_runner.py:29`: `load_pickle, _ = optional_import(\"batchgenerators.utilities.file_and_folder_operations\", name=\"load_pickle\")` — batchgenerators' `load_pickle` is an unguarded `pickle.load`.\r\n- `best_configuration` is loaded (~line 958) from `os.path.join(self.nnunet_results, self.dataset_name, \"inference_information.json\")` via `ConfigParser.load_config_file`, which validates JSON syntax / duplicate keys only; string values are trusted and unrestricted.\r\n- The sink (line 994 on the `1.6.0` release tag) is `load_pickle(self.best_configuration[\"best_model_or_ensemble\"][\"postprocessing_file\"])`. There is NO `MONAI_ALLOW_PICKLE` gate anywhere under `monai/apps/nnunet/` (grep is empty), although MONAI uses exactly this env-gated pickle guard elsewhere (`monai/utils/misc.py`, `monai/auto3dseg/utils.py`) — proving the sink was simply missed.\r\n\r\n## Impact\r\nArbitrary code execution as the training/inference user when the runner is executed against attacker-influenced nnU-Net results (shared/multi-tenant results directory, or a second-party nnU-Net artifact). Controlling a results/model artifact is NOT root-equivalent, so a genuine trust boundary is crossed (same class as the accepted GHSA-p8cm-mm2v-gwjm / GHSA-89gg-p5r5-q6r4 pickle advisories, in a new unhardened file).\r\n\r\n## Proof of Concept\r\n1. Plant `inference_information.json` in `nnunet_results/<dataset>/` with `best_model_or_ensemble.postprocessing_file = \"/path/evil.pkl\"`, plus a malicious `evil.pkl` whose `__reduce__` executes a command.\r\n2. Victim runs `nnUNetV2Runner(...).predict_ensemble_postprocessing(run_postprocessing=True)` (also reachable via `run(run_predict...=True)`).\r\n3. `load_pickle` → `pickle.load` → `__reduce__` executes.\r\n\r\n## Attack Chain\r\n1. Entry: attacker plants `inference_information.json` (postprocessing_file → evil.pkl) + `evil.pkl` in the nnUNet results dir. Guard: `ConfigParser.load_config_file` (config_parser.py). Bypass proof: validates JSON syntax + duplicate keys only; string values are trusted/unrestricted.\r\n2. Processing: victim runs `predict_ensemble_postprocessing(run_postprocessing=True)`. Guard: none. Bypass proof: control flows straight to the sink; grep of `monai/apps/nnunet/` for `MONAI_ALLOW_PICKLE` is empty — no allowlist/weights_only/env gate.\r\n3. Sink: `load_pickle(postprocessing_file)` → `pickle.load(f)` → `__reduce__` executes (nnunetv2_runner.py:994 on tag 1.6.0).\r\n4. Impact: arbitrary code execution as the inference user.\r\n\r\n## Bypass Evidence\r\n- Alias at `nnunetv2_runner.py:29` confirmed to resolve to batchgenerators `load_pickle` (== `pickle.load`).\r\n- No `MONAI_ALLOW_PICKLE`/safe-loader/allowlist present under `monai/apps/nnunet/`.\r\n- `ConfigParser.load_config_file` does not constrain string values.\r\n\r\n## Affected Versions\r\n`<= 1.6.0` (sink confirmed present at the `1.6.0` release tag via `git show 1.6.0:monai/apps/nnunet/nnunetv2_runner.py`; no post-1.6.0 fix — later commits touching the file are CLI-flag/type-checker changes only). Not covered by GHSA-p8cm-mm2v-gwjm (`data/utils.py` pickle_operations) or GHSA-89gg-p5r5-q6r4/GHSA-qxq5-qhx6-94qw (`auto3dseg` algo_from_pickle).\r\n\r\n## Suggested Fix\r\nGate the unpickle behind an explicit opt-in (reuse the existing `MONAI_ALLOW_PICKLE` pattern), and/or validate/allowlist `postprocessing_file` against a trusted directory before deserialization.\r\n\r\n---\r\nReported by **zx (Jace)** — GitHub: @manus-use","categories":["Unclassified"],"product":"monai","vendor":"","ecosystem":null,"affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/Project-MONAI/MONAI/security/advisories/GHSA-8f32-8649-rv87","source_modified_at":"2026-09-27T19:50:08Z","fetched_at":"2026-10-24T20:40:58.529015Z","package":{"ecosystem":"pip","name":"monai"},"range":"<= 1.6.0"}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/Project-MONAI/MONAI/security/advisories/GHSA-8f32-8649-rv87","source_modified_at":"2026-09-27T19:50:08Z","fetched_at":"2026-10-24T20:40:58.529015Z","package":{"ecosystem":"pip","name":"monai"},"version":"1.6.1"}],"cvss":7.3,"severity":"High","kev":{"listed":false},"epss":{"probability":null,"percentile":null,"score_date":null},"published_at":"2026-09-27T19:50:08Z","modified_at":"2026-09-27T19:50:08Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/Project-MONAI/MONAI/security/advisories/GHSA-8f32-8649-rv87","fetched_at":"2026-10-24T20:40:58.529015Z","source_modified_at":"2026-09-27T19:50:08Z"}],"sources":["GitHub Advisory Database"],"references":[],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":null,"confidence":"unknown","reviewed":false},"change_timestamp":"2026-09-27T19:50:08Z","content_hash":"a1f5857a797409c5a0150357d29baba7b549daab97939074ffa524e7cf2adb71"}}
