Skip to content

Commit f793740

Browse files
authored
Path traversal issue fix
The path traversal issue in the previously submitted vulnerability report has been fixed and has been verified and fixed locally. Signed-off-by: h3rrr <81402797+h3rrr@users.noreply.github.com>
1 parent d3c711b commit f793740

File tree

1 file changed

+47
-9
lines changed

1 file changed

+47
-9
lines changed

monai/apps/utils.py

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@
1111

1212
from __future__ import annotations
1313

14+
import os
15+
import shutil
1416
import hashlib
1517
import json
1618
import logging
17-
import os
1819
import re
19-
import shutil
2020
import sys
2121
import tarfile
2222
import tempfile
@@ -80,7 +80,6 @@ def get_logger(
8080
logger = get_logger("monai.apps")
8181
__all__.append("logger")
8282

83-
8483
def _basename(p: PathLike) -> str:
8584
"""get the last part of the path (removing the trailing slash if it exists)"""
8685
sep = os.path.sep + (os.path.altsep or "") + "/ "
@@ -121,6 +120,31 @@ def update_to(self, b: int = 1, bsize: int = 1, tsize: int | None = None) -> Non
121120
logger.error(f"Download failed from {url} to {filepath}.")
122121
raise e
123122

123+
def safe_extract_member(member, extract_to):
124+
"""Securely verify compressed package member paths to prevent path traversal attacks"""
125+
# Get member path (handle different compression formats)
126+
if hasattr(member, 'filename'):
127+
member_path = member.filename # zipfile
128+
elif hasattr(member, 'name'):
129+
member_path = member.name # tarfile
130+
else:
131+
member_path = str(member)
132+
133+
member_path = os.path.normpath(member_path)
134+
135+
if os.path.isabs(member_path) or '..' in member_path.split(os.sep):
136+
raise ValueError(f"Unsafe path detected in archive: {member_path}")
137+
138+
full_path = os.path.join(extract_to, member_path)
139+
full_path = os.path.normpath(full_path)
140+
141+
extract_to_abs = os.path.abspath(extract_to)
142+
full_path_abs = os.path.abspath(full_path)
143+
144+
if not (full_path_abs == extract_to_abs or full_path_abs.startswith(extract_to_abs + os.sep)):
145+
raise ValueError(f"Path traversal attack detected: {member_path}")
146+
147+
return full_path
124148

125149
def check_hash(filepath: PathLike, val: str | None = None, hash_type: str = "md5") -> bool:
126150
"""
@@ -287,14 +311,28 @@ def extractall(
287311
logger.info(f"Writing into directory: {output_dir}.")
288312
_file_type = file_type.lower().strip()
289313
if filepath.name.endswith("zip") or _file_type == "zip":
290-
zip_file = zipfile.ZipFile(filepath)
291-
zip_file.extractall(output_dir)
292-
zip_file.close()
314+
with zipfile.ZipFile(filepath, 'r') as zip_file:
315+
for member in zip_file.infolist():
316+
if member.is_dir():
317+
continue
318+
safe_path = safe_extract_member(member, output_dir)
319+
os.makedirs(os.path.dirname(safe_path), exist_ok=True)
320+
with zip_file.open(member) as source:
321+
with open(safe_path, 'wb') as target:
322+
shutil.copyfileobj(source, target)
293323
return
294324
if filepath.name.endswith("tar") or filepath.name.endswith("tar.gz") or "tar" in _file_type:
295-
tar_file = tarfile.open(filepath)
296-
tar_file.extractall(output_dir)
297-
tar_file.close()
325+
with tarfile.open(filepath, 'r') as tar_file:
326+
for member in tar_file.getmembers():
327+
if not member.isfile():
328+
continue
329+
safe_path = safe_extract_member(member, output_dir)
330+
os.makedirs(os.path.dirname(safe_path), exist_ok=True)
331+
source = tar_file.extractfile(member)
332+
if source is not None:
333+
with source:
334+
with open(safe_path, 'wb') as target:
335+
shutil.copyfileobj(source, target)
298336
return
299337
raise NotImplementedError(
300338
f'Unsupported file type, available options are: ["zip", "tar.gz", "tar"]. name={filepath} type={file_type}.'

0 commit comments

Comments
 (0)