Where
-Infinity
0

Vendor Risk Score

See how project-monai compares to other vendors in security performance

View Risk Score →
Severity
8.8
AV:N/AC:H/PR:H/UI:R/S:C/C:H/I:H/A:H

MONAI before 1.5.2 contains a deserialization of untrusted data vulnerability in the algofrompickle function in monai/auto3dseg/utils.py. The function reads a .pkl file and passes its contents to pickle.loads without validating the data source or content. If an application invokes algofrompickle on an attacker-supplied pickle file, an object defining reduce is executed during deserialization, resulting in arbitrary code execution in the context of the application.

First published (updated )
Severity
8.5
AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

MONAI before 1.6.0 contains an unsafe deserialization vulnerability in the NumpyReader class that unconditionally uses numpy.load with allowpickle=True when loading .npy and .npz files. Attackers can craft malicious .npy files with pickle payloads that execute arbitrary code when loaded through MONAI's standard data pipeline.

First published (updated )
Severity
8.6
OS Command Injection, Command Injection
AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

MONAI before 1.6.0 is vulnerable to OS command injection in the nnUNetV2Runner component (monai.apps.nnunet.nnunetv2runner). User-controlled values taken from the YAML configuration file (notably datasetnameorid) and from CLI/kwargs arguments are concatenated into a command string without quoting or validation and then passed to subprocess with shell=True, so shell metacharacters (e.g., ';' on Linux, '&' on Windows) are interpreted. If a victim loads and processes a crafted configuration file — for example by instantiating nnUNetV2Runner with the malicious YAML and invoking a training/validation job such as trainsinglemodel() — arbitrary commands are executed with the privileges of the user running the job.

First published (updated )
Severity
7.3
AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H

MONAI through 1.6.0 contains an eval injection vulnerability in getfakespatialshape() in monai/bundle/scripts.py. The function validates shape expressions with a helper that walks the AST and only collects ast.Name nodes, rejecting any name other than 'p' or 'n', before passing the string to eval(). Expressions built solely from constants and attribute, subscript, or call nodes (for example "(1).class.bases[0].subclasses()" or "int.class.init.globals") contain no ast.Name nodes and therefore bypass the allowlist. Because the shape value originates from bundle metadata consumed by getrealinputdata and verifynetinout (reachable through the bundle 'verifynetinout' CLI flow), an attacker who can influence a bundle's metadata can escape the eval sandbox via object introspection chains and achieve code execution in this non-default flow.

First published (updated )
Severity
8.5
AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

MONAI versions before 1.6.0 contain a remote code execution vulnerability in the algofrompickle() function due to unsafe pickle.loads() deserialization in monai/auto3dseg/utils.py. Attackers can craft malicious pickle files that execute arbitrary system commands when deserialized by the vulnerable function.

First published (updated )
Severity
8.5
AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

In MONAI 1.6.0, PersistentDataset (monai/data/dataset.py) explicitly rejects the combination trackmeta=True with weightsonly=True, forcing users who cache MetaTensors (the default tensor type in MONAI >= 1.0) to run torch.load(hashfile, weightsonly=False). Related cache helpers in monai/data/utils.py also call pickle.loads on cached content and derive cache keys with hashlib.md5. As a result, a local user with write access to a shared or world-writable cachedir (e.g. /tmp/monaicache, HPC scratch, ~/.cache/monai) can place a malicious pickle file that is deserialized the next time another user's MONAI pipeline reads the cache, resulting in arbitrary code execution in that user's context. All released versions of the monai pip package are affected; no patched version is available as of the advisory.

First published (updated )
Severity
8.5
AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

MONAI through 1.6.0 contains a remote code execution vulnerability in the bundle configuration engine that resolves target values to arbitrary importable callables without an allow list and passes $ expressions to Python eval(). Attackers can publish a malicious bundle with crafted configuration containing arbitrary code that executes when a victim loads the bundle using monai.bundle.load() or monai.bundle.run().

First published (updated )
Severity
5.3
EPSS
0.04%
Path Traversal
AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N

Summary

A Path Traversal (Zip Slip) vulnerability exists in MONAI's downloadfromngcprivate() function. The function uses zipfile.ZipFile.extractall() without path validation, while other similar download functions in the same codebase properly use the existing safeextractmember() function.

This appears to be an implementation oversight, as safe extraction is already implemented and used elsewhere in MONAI.

CWE: CWE-22 (Improper Limitation of a Pathname to a Restricted Directory)

---

Details

Vulnerable Code Location

File: monai/bundle/scripts.py Lines: 291-292 Function: downloadfromngcprivate()

python monai/bundle/scripts.py - Lines 284-293 zippath = downloadpath / f"{filename}v{version}.zip" with open(zippath, "wb") as f: f.write(response.content) logger.info(f"Downloading: {zippath}.") if removeprefix: filename = removengcprefix(filename, prefix=removeprefix) extractpath = downloadpath / f"{filename}" with zipfile.ZipFile(zippath, "r") as z: z.extractall(extractpath) # <-- No path validation logger.info(f"Writing into directory: {extractpath}.")

Root Cause

The code calls z.extractall(extractpath) directly without validating that archive member paths stay within the extraction directory.

Safe Code Already Exists

MONAI already has a safe extraction function in monai/apps/utils.py (lines 125-154) that properly validates paths:

python def safeextractmember(member, extractto): """Securely verify compressed package member paths to prevent path traversal attacks""" # ... path validation logic ... if os.path.isabs(memberpath) or ".." in memberpath.split(os.sep): raise ValueError(f"Unsafe path detected in archive: {memberpath}") # Ensure path stays within extraction root if os.path.commonpath([extractroot, targetreal]) != extractroot: raise ValueError(f"Unsafe path: path traversal {memberpath}")

Comparison with Other Download Functions

| Function | File | Uses Safe Extraction? | |----------|------|----------------------| | downloadfromgithub() | scripts.py:198 | ✅ Yes (via extractall() wrapper) | | downloadfrommonaihosting() | scripts.py:205 | ✅ Yes (via extractall() wrapper) | | downloadfrombundleinfo() | scripts.py:215 | ✅ Yes (via extractall() wrapper) | | downloadfromngcprivate() | scripts.py:292 | ❌ No (direct z.extractall()) |

---

PoC

Step 1: Create a Malicious Zip File

python #!/usr/bin/env python3 """Create malicious zip with path traversal entries""" import zipfile import io

def createmaliciouszip(outputpath="maliciousbundle.zip"): zipbuffer = io.BytesIO() with zipfile.ZipFile(zipbuffer, 'w', zipfile.ZIPDEFLATED) as zf: # Normal bundle file zf.writestr( "monaitestbundle/configs/metadata.json", '{"name": "testbundle", "version": "1.0.0"}' ) # Path traversal entry zf.writestr( "../../../tmp/escapedfile.txt", "This file was written outside the extraction directory.\n" ) with open(outputpath, 'wb') as f: f.write(zipbuffer.getvalue()) print(f"Created: {outputpath}") with zipfile.ZipFile(outputpath, 'r') as zf: print("Contents:") for name in zf.namelist(): print(f" - {name}")

if name == "main": createmaliciouszip()

Output: Created: maliciousbundle.zip Contents: - monaitestbundle/configs/metadata.json - ../../../tmp/escapedfile.txt

Step 2: Demonstrate the Difference

This script shows the difference between the vulnerable pattern (used in downloadfromngcprivate) and the safe pattern (used elsewhere in MONAI):

python #!/usr/bin/env python3 """Compare vulnerable vs safe extraction""" import zipfile import tempfile import os

def vulnerableextraction(zippath, extractpath): """Pattern used in monai/bundle/scripts.py:291-292""" os.makedirs(extractpath, existok=True) with zipfile.ZipFile(zippath, "r") as z: z.extractall(extractpath) print("[VULNERABLE] Extraction completed without validation")

def safeextraction(zippath, extractpath): """Pattern used in monai/apps/utils.py""" os.makedirs(extractpath, existok=True) with zipfile.ZipFile(zippath, "r") as zf: for member in zf.infolist(): memberpath = os.path.normpath(member.filename) # Check for path traversal if os.path.isabs(memberpath) or ".." in memberpath.split(os.sep): print(f"[SAFE] BLOCKED: {member.filename}") continue print(f"[SAFE] Allowed: {member.filename}")

Run demo print("=" 50) print("VULNERABLE PATTERN (scripts.py:291-292)") print("=" 50) with tempfile.TemporaryDirectory() as tmpdir: vulnerableextraction("maliciousbundle.zip", tmpdir) for root, dirs, files in os.walk(tmpdir): for f in files: relpath = os.path.relpath(os.path.join(root, f), tmpdir) print(f" Extracted: {relpath}")

print() print("=" 50) print("SAFE PATTERN (apps/utils.py)") print("=" 50) with tempfile.TemporaryDirectory() as tmpdir: safeextraction("maliciousbundle.zip", tmpdir)

Output: ================================================== VULNERABLE PATTERN (scripts.py:291-292) ================================================== [VULNERABLE] Extraction completed without validation Extracted: monaitestbundle/configs/metadata.json Extracted: tmp/escapedfile.txt

================================================== SAFE PATTERN (apps/utils.py) ================================================== [SAFE] Allowed: monaitestbundle/configs/metadata.json [SAFE] BLOCKED: ../../../tmp/escapedfile.txt

---

Impact

Conditions Required for Exploitation

1. Attacker must control or compromise an NGC private repository 2. Victim must configure MONAI to download from that repository 3. Victim must use source="ngcprivate" parameter

Potential Impact

If exploited, an attacker could write files outside the intended extraction directory. The actual impact depends on: - The permissions of the user running MONAI - The target location of the escaped files - Python version (newer versions have some built-in path normalization)

Mitigating Factors

- Requires attacker to control an NGC private repository - Modern Python versions (3.12+) have some built-in path normalization - The ngcprivate source is less commonly used than other sources

---

Recommended Fix

Replace the direct extractall() call with MONAI's existing safe extraction:

diff monai/bundle/scripts.py

+ from monai.apps.utils import extractzip

def downloadfromngcprivate(...): # ... existing code ... extractpath = downloadpath / f"{filename}" - with zipfile.ZipFile(zippath, "r") as z: - z.extractall(extractpath) - logger.info(f"Writing into directory: {extractpath}.") + extractzip(zippath, extractpath) + logger.info(f"Writing into directory: {extractpath}.")

This aligns downloadfromngcprivate() with the other download functions and ensures consistent security across all download sources.

---

Resources

- CWE-22: Improper Limitation of a Pathname to a Restricted Directory - Snyk: Zip Slip Vulnerability - Python zipfile.extractall() Warning

1 / 2
Source: GitHub
First published (updated )

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203