From bcb5df2d0f36a751eeba4cd52a73681e7708da42 Mon Sep 17 00:00:00 2001 From: h3rrr <81402797+h3rrr@users.noreply.github.com> Date: Wed, 27 Aug 2025 10:52:06 +0800 Subject: [PATCH 01/10] Create SECURITY.md Signed-off-by: h3rrr <81402797+h3rrr@users.noreply.github.com> --- SECURITY.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..dbacdeda04 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,18 @@ +# Security Policy + +# Reporting a Vulnerability +At MONAI, we take security seriously and appreciate your efforts to responsibly disclose vulnerabilities. If you discover a security issue, please report it as soon as possible. + +Please do not create public issues for security-related reports. + +* To report a security issue, please use the GitHub Security Advisories tab to "Open a draft security advisory". +* Include a detailed description of the issue, steps to reproduce, potential impact, and any possible mitigations. +* If applicable, please also attach proof-of-concept code or screenshots. +* We will acknowledge your report within 72 hours and provide a status update as we investigate. + +# Disclosure Policy +* We follow a coordinated disclosure approach. +* We will not publicly disclose vulnerabilities until a fix has been developed and released. +* Credit will be given to researchers who responsibly disclose vulnerabilities, if requested. +# Acknowledgements +We greatly appreciate contributions from the security community and strive to recognize all researchers who help keep Cherry Studio safe. From 929f0b721530b8e741f82aefc0e39c0d4bd43cb4 Mon Sep 17 00:00:00 2001 From: h3rrr <81402797+h3rrr@users.noreply.github.com> Date: Wed, 27 Aug 2025 11:03:03 +0800 Subject: [PATCH 02/10] Update SECURITY.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: h3rrr <81402797+h3rrr@users.noreply.github.com> --- SECURITY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SECURITY.md b/SECURITY.md index dbacdeda04..b1ab1540ac 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -15,4 +15,4 @@ Please do not create public issues for security-related reports. * We will not publicly disclose vulnerabilities until a fix has been developed and released. * Credit will be given to researchers who responsibly disclose vulnerabilities, if requested. # Acknowledgements -We greatly appreciate contributions from the security community and strive to recognize all researchers who help keep Cherry Studio safe. +We greatly appreciate contributions from the security community and strive to recognize all researchers who help keep MONAI safe. From 7160338285ed9942499a4523777a14c992c083b3 Mon Sep 17 00:00:00 2001 From: h3rrr <81402797+h3rrr@users.noreply.github.com> Date: Mon, 8 Sep 2025 12:48:38 +0800 Subject: [PATCH 03/10] Update SECURITY.md Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Signed-off-by: h3rrr <81402797+h3rrr@users.noreply.github.com> --- SECURITY.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/SECURITY.md b/SECURITY.md index b1ab1540ac..b9254888ba 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,5 +1,23 @@ # Security Policy +## Reporting a Vulnerability +MONAI takes security seriously and appreciate your efforts to responsibly disclose vulnerabilities. If you discover a security issue, please report it as soon as possible. + +To report a security issue: +* please use the GitHub Security Advisories tab to "[Open a draft security advisory](https://github.com/Project-MONAI/MONAI/security/advisories/new)". +* Include a detailed description of the issue, steps to reproduce, potential impact, and any possible mitigations. +* If applicable, please also attach proof-of-concept code or screenshots. +* We aim to acknowledge your report within 72 hours and provide a status update as we investigate. +* Please do not create public issues for security-related reports. + +## Disclosure Policy +* We follow a coordinated disclosure approach. +* We will not publicly disclose vulnerabilities until a fix has been developed and released. +* Credit will be given to researchers who responsibly disclose vulnerabilities, if requested. + +## Acknowledgements +We greatly appreciate contributions from the security community and strive to recognize all researchers who help keep MONAI safe. + # Reporting a Vulnerability At MONAI, we take security seriously and appreciate your efforts to responsibly disclose vulnerabilities. If you discover a security issue, please report it as soon as possible. From 2c72dbc089d0249124c94604745baf0c3805a825 Mon Sep 17 00:00:00 2001 From: h3rrr <81402797+h3rrr@users.noreply.github.com> Date: Thu, 11 Sep 2025 22:54:32 +0800 Subject: [PATCH 04/10] Update utils.py Path traversal security issue fix Zip Slip Signed-off-by: h3rrr <81402797+h3rrr@users.noreply.github.com> --- monai/apps/utils.py | 56 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/monai/apps/utils.py b/monai/apps/utils.py index 95c1450f2a..d871fb255a 100644 --- a/monai/apps/utils.py +++ b/monai/apps/utils.py @@ -11,6 +11,8 @@ from __future__ import annotations +import os +import shutil import hashlib import json import logging @@ -80,7 +82,6 @@ def get_logger( logger = get_logger("monai.apps") __all__.append("logger") - def _basename(p: PathLike) -> str: """get the last part of the path (removing the trailing slash if it exists)""" sep = os.path.sep + (os.path.altsep or "") + "/ " @@ -121,7 +122,32 @@ def update_to(self, b: int = 1, bsize: int = 1, tsize: int | None = None) -> Non logger.error(f"Download failed from {url} to {filepath}.") raise e - +def safe_extract_member(member, extract_to): + """Securely verify compressed package member paths to prevent path traversal attacks""" + # Get member path (handle different compression formats) + if hasattr(member, 'filename'): + member_path = member.filename # zipfile + elif hasattr(member, 'name'): + member_path = member.name # tarfile + else: + member_path = str(member) + + member_path = os.path.normpath(member_path) + + if os.path.isabs(member_path) or '..' in member_path.split(os.sep): + raise ValueError(f"Unsafe path detected in archive: {member_path}") + + full_path = os.path.join(extract_to, member_path) + full_path = os.path.normpath(full_path) + + extract_to_abs = os.path.abspath(extract_to) + full_path_abs = os.path.abspath(full_path) + + if not (full_path_abs == extract_to_abs or full_path_abs.startswith(extract_to_abs + os.sep)): + raise ValueError(f"Path traversal attack detected: {member_path}") + + return full_path + def check_hash(filepath: PathLike, val: str | None = None, hash_type: str = "md5") -> bool: """ Verify hash signature of specified file. @@ -287,14 +313,28 @@ def extractall( logger.info(f"Writing into directory: {output_dir}.") _file_type = file_type.lower().strip() if filepath.name.endswith("zip") or _file_type == "zip": - zip_file = zipfile.ZipFile(filepath) - zip_file.extractall(output_dir) - zip_file.close() + with zipfile.ZipFile(filepath, 'r') as zip_file: + for member in zip_file.infolist(): + if member.is_dir(): + continue + safe_path = safe_extract_member(member, output_dir) + os.makedirs(os.path.dirname(safe_path), exist_ok=True) + with zip_file.open(member) as source: + with open(safe_path, 'wb') as target: + shutil.copyfileobj(source, target) return if filepath.name.endswith("tar") or filepath.name.endswith("tar.gz") or "tar" in _file_type: - tar_file = tarfile.open(filepath) - tar_file.extractall(output_dir) - tar_file.close() + with tarfile.open(filepath, 'r') as tar_file: + for member in tar_file.getmembers(): + if not member.isfile(): + continue + + safe_path = safe_extract_member(member, output_dir) + os.makedirs(os.path.dirname(safe_path), exist_ok=True) + with tar_file.extractfile(member) as source: + if source: + with open(safe_path, 'wb') as target: + shutil.copyfileobj(source, target) return raise NotImplementedError( f'Unsupported file type, available options are: ["zip", "tar.gz", "tar"]. name={filepath} type={file_type}.' From bad190702b54a0d702ac39b9a0aa17a4bf491108 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 14:55:02 +0000 Subject: [PATCH 05/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- monai/apps/utils.py | 90 ++++++++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 46 deletions(-) diff --git a/monai/apps/utils.py b/monai/apps/utils.py index d871fb255a..7e75d66650 100644 --- a/monai/apps/utils.py +++ b/monai/apps/utils.py @@ -11,14 +11,12 @@ from __future__ import annotations -import os +import os import shutil import hashlib import json import logging -import os import re -import shutil import sys import tarfile import tempfile @@ -122,32 +120,32 @@ def update_to(self, b: int = 1, bsize: int = 1, tsize: int | None = None) -> Non logger.error(f"Download failed from {url} to {filepath}.") raise e -def safe_extract_member(member, extract_to): - """Securely verify compressed package member paths to prevent path traversal attacks""" - # Get member path (handle different compression formats) - if hasattr(member, 'filename'): - member_path = member.filename # zipfile - elif hasattr(member, 'name'): - member_path = member.name # tarfile - else: - member_path = str(member) - - member_path = os.path.normpath(member_path) - - if os.path.isabs(member_path) or '..' in member_path.split(os.sep): - raise ValueError(f"Unsafe path detected in archive: {member_path}") - - full_path = os.path.join(extract_to, member_path) - full_path = os.path.normpath(full_path) - - extract_to_abs = os.path.abspath(extract_to) - full_path_abs = os.path.abspath(full_path) - - if not (full_path_abs == extract_to_abs or full_path_abs.startswith(extract_to_abs + os.sep)): - raise ValueError(f"Path traversal attack detected: {member_path}") - +def safe_extract_member(member, extract_to): + """Securely verify compressed package member paths to prevent path traversal attacks""" + # Get member path (handle different compression formats) + if hasattr(member, 'filename'): + member_path = member.filename # zipfile + elif hasattr(member, 'name'): + member_path = member.name # tarfile + else: + member_path = str(member) + + member_path = os.path.normpath(member_path) + + if os.path.isabs(member_path) or '..' in member_path.split(os.sep): + raise ValueError(f"Unsafe path detected in archive: {member_path}") + + full_path = os.path.join(extract_to, member_path) + full_path = os.path.normpath(full_path) + + extract_to_abs = os.path.abspath(extract_to) + full_path_abs = os.path.abspath(full_path) + + if not (full_path_abs == extract_to_abs or full_path_abs.startswith(extract_to_abs + os.sep)): + raise ValueError(f"Path traversal attack detected: {member_path}") + return full_path - + def check_hash(filepath: PathLike, val: str | None = None, hash_type: str = "md5") -> bool: """ Verify hash signature of specified file. @@ -313,27 +311,27 @@ def extractall( logger.info(f"Writing into directory: {output_dir}.") _file_type = file_type.lower().strip() if filepath.name.endswith("zip") or _file_type == "zip": - with zipfile.ZipFile(filepath, 'r') as zip_file: - for member in zip_file.infolist(): - if member.is_dir(): - continue - safe_path = safe_extract_member(member, output_dir) - os.makedirs(os.path.dirname(safe_path), exist_ok=True) - with zip_file.open(member) as source: - with open(safe_path, 'wb') as target: + with zipfile.ZipFile(filepath, 'r') as zip_file: + for member in zip_file.infolist(): + if member.is_dir(): + continue + safe_path = safe_extract_member(member, output_dir) + os.makedirs(os.path.dirname(safe_path), exist_ok=True) + with zip_file.open(member) as source: + with open(safe_path, 'wb') as target: shutil.copyfileobj(source, target) return if filepath.name.endswith("tar") or filepath.name.endswith("tar.gz") or "tar" in _file_type: - with tarfile.open(filepath, 'r') as tar_file: - for member in tar_file.getmembers(): - if not member.isfile(): - continue - - safe_path = safe_extract_member(member, output_dir) - os.makedirs(os.path.dirname(safe_path), exist_ok=True) - with tar_file.extractfile(member) as source: - if source: - with open(safe_path, 'wb') as target: + with tarfile.open(filepath, 'r') as tar_file: + for member in tar_file.getmembers(): + if not member.isfile(): + continue + + safe_path = safe_extract_member(member, output_dir) + os.makedirs(os.path.dirname(safe_path), exist_ok=True) + with tar_file.extractfile(member) as source: + if source: + with open(safe_path, 'wb') as target: shutil.copyfileobj(source, target) return raise NotImplementedError( From a8ed1dfad1e38ddafd3f15cecf90906161aa32f9 Mon Sep 17 00:00:00 2001 From: h3rrr <81402797+h3rrr@users.noreply.github.com> Date: Fri, 12 Sep 2025 01:04:26 +0800 Subject: [PATCH 06/10] Update SECURITY.md Signed-off-by: h3rrr <81402797+h3rrr@users.noreply.github.com> --- SECURITY.md | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index b9254888ba..8b4158eb73 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -14,23 +14,5 @@ To report a security issue: * We follow a coordinated disclosure approach. * We will not publicly disclose vulnerabilities until a fix has been developed and released. * Credit will be given to researchers who responsibly disclose vulnerabilities, if requested. - ## Acknowledgements We greatly appreciate contributions from the security community and strive to recognize all researchers who help keep MONAI safe. - -# Reporting a Vulnerability -At MONAI, we take security seriously and appreciate your efforts to responsibly disclose vulnerabilities. If you discover a security issue, please report it as soon as possible. - -Please do not create public issues for security-related reports. - -* To report a security issue, please use the GitHub Security Advisories tab to "Open a draft security advisory". -* Include a detailed description of the issue, steps to reproduce, potential impact, and any possible mitigations. -* If applicable, please also attach proof-of-concept code or screenshots. -* We will acknowledge your report within 72 hours and provide a status update as we investigate. - -# Disclosure Policy -* We follow a coordinated disclosure approach. -* We will not publicly disclose vulnerabilities until a fix has been developed and released. -* Credit will be given to researchers who responsibly disclose vulnerabilities, if requested. -# Acknowledgements -We greatly appreciate contributions from the security community and strive to recognize all researchers who help keep MONAI safe. From d3c711b0c1b5d653f0cd41071b07e7e3ffd40bb6 Mon Sep 17 00:00:00 2001 From: h3rrr <81402797+h3rrr@users.noreply.github.com> Date: Fri, 12 Sep 2025 14:13:46 +0800 Subject: [PATCH 07/10] Update utils.py Changed to previous content, the fix will be filed in a new PR Signed-off-by: h3rrr <81402797+h3rrr@users.noreply.github.com> --- monai/apps/utils.py | 56 ++++++++------------------------------------- 1 file changed, 9 insertions(+), 47 deletions(-) diff --git a/monai/apps/utils.py b/monai/apps/utils.py index 7e75d66650..95c1450f2a 100644 --- a/monai/apps/utils.py +++ b/monai/apps/utils.py @@ -11,12 +11,12 @@ from __future__ import annotations -import os -import shutil import hashlib import json import logging +import os import re +import shutil import sys import tarfile import tempfile @@ -80,6 +80,7 @@ def get_logger( logger = get_logger("monai.apps") __all__.append("logger") + def _basename(p: PathLike) -> str: """get the last part of the path (removing the trailing slash if it exists)""" sep = os.path.sep + (os.path.altsep or "") + "/ " @@ -120,31 +121,6 @@ def update_to(self, b: int = 1, bsize: int = 1, tsize: int | None = None) -> Non logger.error(f"Download failed from {url} to {filepath}.") raise e -def safe_extract_member(member, extract_to): - """Securely verify compressed package member paths to prevent path traversal attacks""" - # Get member path (handle different compression formats) - if hasattr(member, 'filename'): - member_path = member.filename # zipfile - elif hasattr(member, 'name'): - member_path = member.name # tarfile - else: - member_path = str(member) - - member_path = os.path.normpath(member_path) - - if os.path.isabs(member_path) or '..' in member_path.split(os.sep): - raise ValueError(f"Unsafe path detected in archive: {member_path}") - - full_path = os.path.join(extract_to, member_path) - full_path = os.path.normpath(full_path) - - extract_to_abs = os.path.abspath(extract_to) - full_path_abs = os.path.abspath(full_path) - - if not (full_path_abs == extract_to_abs or full_path_abs.startswith(extract_to_abs + os.sep)): - raise ValueError(f"Path traversal attack detected: {member_path}") - - return full_path def check_hash(filepath: PathLike, val: str | None = None, hash_type: str = "md5") -> bool: """ @@ -311,28 +287,14 @@ def extractall( logger.info(f"Writing into directory: {output_dir}.") _file_type = file_type.lower().strip() if filepath.name.endswith("zip") or _file_type == "zip": - with zipfile.ZipFile(filepath, 'r') as zip_file: - for member in zip_file.infolist(): - if member.is_dir(): - continue - safe_path = safe_extract_member(member, output_dir) - os.makedirs(os.path.dirname(safe_path), exist_ok=True) - with zip_file.open(member) as source: - with open(safe_path, 'wb') as target: - shutil.copyfileobj(source, target) + zip_file = zipfile.ZipFile(filepath) + zip_file.extractall(output_dir) + zip_file.close() return if filepath.name.endswith("tar") or filepath.name.endswith("tar.gz") or "tar" in _file_type: - with tarfile.open(filepath, 'r') as tar_file: - for member in tar_file.getmembers(): - if not member.isfile(): - continue - - safe_path = safe_extract_member(member, output_dir) - os.makedirs(os.path.dirname(safe_path), exist_ok=True) - with tar_file.extractfile(member) as source: - if source: - with open(safe_path, 'wb') as target: - shutil.copyfileobj(source, target) + tar_file = tarfile.open(filepath) + tar_file.extractall(output_dir) + tar_file.close() return raise NotImplementedError( f'Unsupported file type, available options are: ["zip", "tar.gz", "tar"]. name={filepath} type={file_type}.' From c39db6e3857f3fbe859bc204b17ff1009646afa3 Mon Sep 17 00:00:00 2001 From: h3rrr <81402797+h3rrr@users.noreply.github.com> Date: Fri, 12 Sep 2025 22:51:28 +0800 Subject: [PATCH 08/10] Update utils.py The path traversal issue has been fixed, and detection of soft links and hard links has been added to prevent bypass. Signed-off-by: h3rrr <81402797+h3rrr@users.noreply.github.com> --- monai/apps/utils.py | 63 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/monai/apps/utils.py b/monai/apps/utils.py index 95c1450f2a..2478550919 100644 --- a/monai/apps/utils.py +++ b/monai/apps/utils.py @@ -11,12 +11,12 @@ from __future__ import annotations +import os +import shutil import hashlib import json import logging -import os import re -import shutil import sys import tarfile import tempfile @@ -80,7 +80,6 @@ def get_logger( logger = get_logger("monai.apps") __all__.append("logger") - def _basename(p: PathLike) -> str: """get the last part of the path (removing the trailing slash if it exists)""" sep = os.path.sep + (os.path.altsep or "") + "/ " @@ -121,6 +120,36 @@ def update_to(self, b: int = 1, bsize: int = 1, tsize: int | None = None) -> Non logger.error(f"Download failed from {url} to {filepath}.") raise e +def safe_extract_member(member, extract_to): + """Securely verify compressed package member paths to prevent path traversal attacks""" + # Get member path (handle different compression formats) + if hasattr(member, 'filename'): + member_path = member.filename # zipfile + elif hasattr(member, 'name'): + member_path = member.name # tarfile + else: + member_path = str(member) + + if hasattr(member, 'issym') and member.issym(): + raise ValueError(f"Symbolic link detected in archive: {member_path}") + if hasattr(member, 'islnk') and member.islnk(): + raise ValueError(f"Hard link detected in archive: {member_path}") + + member_path = os.path.normpath(member_path) + + if os.path.isabs(member_path) or '..' in member_path.split(os.sep): + raise ValueError(f"Unsafe path detected in archive: {member_path}") + + full_path = os.path.join(extract_to, member_path) + full_path = os.path.normpath(full_path) + + extract_to_abs = os.path.abspath(extract_to) + full_path_abs = os.path.abspath(full_path) + + if not (full_path_abs == extract_to_abs or full_path_abs.startswith(extract_to_abs + os.sep)): + raise ValueError(f"Path traversal attack detected: {member_path}") + + return full_path def check_hash(filepath: PathLike, val: str | None = None, hash_type: str = "md5") -> bool: """ @@ -287,14 +316,30 @@ def extractall( logger.info(f"Writing into directory: {output_dir}.") _file_type = file_type.lower().strip() if filepath.name.endswith("zip") or _file_type == "zip": - zip_file = zipfile.ZipFile(filepath) - zip_file.extractall(output_dir) - zip_file.close() + with zipfile.ZipFile(filepath, 'r') as zip_file: + for member in zip_file.infolist(): + safe_path = safe_extract_member(member, output_dir) + if member.is_dir(): + continue + + os.makedirs(os.path.dirname(safe_path), exist_ok=True) + with zip_file.open(member) as source: + with open(safe_path, 'wb') as target: + shutil.copyfileobj(source, target) return if filepath.name.endswith("tar") or filepath.name.endswith("tar.gz") or "tar" in _file_type: - tar_file = tarfile.open(filepath) - tar_file.extractall(output_dir) - tar_file.close() + with tarfile.open(filepath, 'r') as tar_file: + for member in tar_file.getmembers(): + safe_path = safe_extract_member(member, output_dir) + if not member.isfile(): + continue + + os.makedirs(os.path.dirname(safe_path), exist_ok=True) + source = tar_file.extractfile(member) + if source is not None: + with source: + with open(safe_path, 'wb') as target: + shutil.copyfileobj(source, target) return raise NotImplementedError( f'Unsupported file type, available options are: ["zip", "tar.gz", "tar"]. name={filepath} type={file_type}.' From c9d19a114d2b0603a15c87979a95fe55a792483c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 12 Sep 2025 14:51:52 +0000 Subject: [PATCH 09/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- monai/apps/utils.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/monai/apps/utils.py b/monai/apps/utils.py index 2478550919..7d6f492412 100644 --- a/monai/apps/utils.py +++ b/monai/apps/utils.py @@ -130,11 +130,11 @@ def safe_extract_member(member, extract_to): else: member_path = str(member) - if hasattr(member, 'issym') and member.issym(): - raise ValueError(f"Symbolic link detected in archive: {member_path}") - if hasattr(member, 'islnk') and member.islnk(): + if hasattr(member, 'issym') and member.issym(): + raise ValueError(f"Symbolic link detected in archive: {member_path}") + if hasattr(member, 'islnk') and member.islnk(): raise ValueError(f"Hard link detected in archive: {member_path}") - + member_path = os.path.normpath(member_path) if os.path.isabs(member_path) or '..' in member_path.split(os.sep): @@ -321,7 +321,7 @@ def extractall( safe_path = safe_extract_member(member, output_dir) if member.is_dir(): continue - + os.makedirs(os.path.dirname(safe_path), exist_ok=True) with zip_file.open(member) as source: with open(safe_path, 'wb') as target: @@ -333,7 +333,7 @@ def extractall( safe_path = safe_extract_member(member, output_dir) if not member.isfile(): continue - + os.makedirs(os.path.dirname(safe_path), exist_ok=True) source = tar_file.extractfile(member) if source is not None: From dc49f8e6b120ceee70b8dbbb4221519f45dbc202 Mon Sep 17 00:00:00 2001 From: h3rrr <81402797+h3rrr@users.noreply.github.com> Date: Fri, 12 Sep 2025 23:03:15 +0800 Subject: [PATCH 10/10] Update utils.py rollback Signed-off-by: h3rrr <81402797+h3rrr@users.noreply.github.com> --- monai/apps/utils.py | 63 +++++++-------------------------------------- 1 file changed, 9 insertions(+), 54 deletions(-) diff --git a/monai/apps/utils.py b/monai/apps/utils.py index 7d6f492412..95c1450f2a 100644 --- a/monai/apps/utils.py +++ b/monai/apps/utils.py @@ -11,12 +11,12 @@ from __future__ import annotations -import os -import shutil import hashlib import json import logging +import os import re +import shutil import sys import tarfile import tempfile @@ -80,6 +80,7 @@ def get_logger( logger = get_logger("monai.apps") __all__.append("logger") + def _basename(p: PathLike) -> str: """get the last part of the path (removing the trailing slash if it exists)""" sep = os.path.sep + (os.path.altsep or "") + "/ " @@ -120,36 +121,6 @@ def update_to(self, b: int = 1, bsize: int = 1, tsize: int | None = None) -> Non logger.error(f"Download failed from {url} to {filepath}.") raise e -def safe_extract_member(member, extract_to): - """Securely verify compressed package member paths to prevent path traversal attacks""" - # Get member path (handle different compression formats) - if hasattr(member, 'filename'): - member_path = member.filename # zipfile - elif hasattr(member, 'name'): - member_path = member.name # tarfile - else: - member_path = str(member) - - if hasattr(member, 'issym') and member.issym(): - raise ValueError(f"Symbolic link detected in archive: {member_path}") - if hasattr(member, 'islnk') and member.islnk(): - raise ValueError(f"Hard link detected in archive: {member_path}") - - member_path = os.path.normpath(member_path) - - if os.path.isabs(member_path) or '..' in member_path.split(os.sep): - raise ValueError(f"Unsafe path detected in archive: {member_path}") - - full_path = os.path.join(extract_to, member_path) - full_path = os.path.normpath(full_path) - - extract_to_abs = os.path.abspath(extract_to) - full_path_abs = os.path.abspath(full_path) - - if not (full_path_abs == extract_to_abs or full_path_abs.startswith(extract_to_abs + os.sep)): - raise ValueError(f"Path traversal attack detected: {member_path}") - - return full_path def check_hash(filepath: PathLike, val: str | None = None, hash_type: str = "md5") -> bool: """ @@ -316,30 +287,14 @@ def extractall( logger.info(f"Writing into directory: {output_dir}.") _file_type = file_type.lower().strip() if filepath.name.endswith("zip") or _file_type == "zip": - with zipfile.ZipFile(filepath, 'r') as zip_file: - for member in zip_file.infolist(): - safe_path = safe_extract_member(member, output_dir) - if member.is_dir(): - continue - - os.makedirs(os.path.dirname(safe_path), exist_ok=True) - with zip_file.open(member) as source: - with open(safe_path, 'wb') as target: - shutil.copyfileobj(source, target) + zip_file = zipfile.ZipFile(filepath) + zip_file.extractall(output_dir) + zip_file.close() return if filepath.name.endswith("tar") or filepath.name.endswith("tar.gz") or "tar" in _file_type: - with tarfile.open(filepath, 'r') as tar_file: - for member in tar_file.getmembers(): - safe_path = safe_extract_member(member, output_dir) - if not member.isfile(): - continue - - os.makedirs(os.path.dirname(safe_path), exist_ok=True) - source = tar_file.extractfile(member) - if source is not None: - with source: - with open(safe_path, 'wb') as target: - shutil.copyfileobj(source, target) + tar_file = tarfile.open(filepath) + tar_file.extractall(output_dir) + tar_file.close() return raise NotImplementedError( f'Unsupported file type, available options are: ["zip", "tar.gz", "tar"]. name={filepath} type={file_type}.'