Securely verify compressed package member paths to prevent path traversal attacks
(member, extract_to)
| 123 | |
| 124 | |
| 125 | def safe_extract_member(member, extract_to): |
| 126 | """Securely verify compressed package member paths to prevent path traversal attacks""" |
| 127 | # Get member path (handle different compression formats) |
| 128 | if hasattr(member, "filename"): |
| 129 | member_path = member.filename # zipfile |
| 130 | elif hasattr(member, "name"): |
| 131 | member_path = member.name # tarfile |
| 132 | else: |
| 133 | member_path = str(member) |
| 134 | |
| 135 | if hasattr(member, "issym") and member.issym(): |
| 136 | raise ValueError(f"Symbolic link detected in archive: {member_path}") |
| 137 | if hasattr(member, "islnk") and member.islnk(): |
| 138 | raise ValueError(f"Hard link detected in archive: {member_path}") |
| 139 | |
| 140 | member_path = os.path.normpath(member_path) |
| 141 | |
| 142 | if os.path.isabs(member_path) or ".." in member_path.split(os.sep): |
| 143 | raise ValueError(f"Unsafe path detected in archive: {member_path}") |
| 144 | |
| 145 | full_path = os.path.join(extract_to, member_path) |
| 146 | full_path = os.path.normpath(full_path) |
| 147 | |
| 148 | extract_root = os.path.realpath(extract_to) |
| 149 | target_real = os.path.realpath(full_path) |
| 150 | # Ensure the resolved path stays within the extraction root |
| 151 | if os.path.commonpath([extract_root, target_real]) != extract_root: |
| 152 | raise ValueError(f"Unsafe path: path traversal {member_path}") |
| 153 | |
| 154 | return full_path |
| 155 | |
| 156 | |
| 157 | def check_hash(filepath: PathLike, val: str | None = None, hash_type: str = "md5") -> bool: |
no test coverage detected
searching dependent graphs…