fix: prevent cloud storage download path traversal

This commit is contained in:
jxxghp
2026-06-05 17:42:43 +08:00
parent 871d1ec0d8
commit a0b3800f6b
6 changed files with 275 additions and 5 deletions

View File

@@ -1,5 +1,5 @@
from abc import ABCMeta, abstractmethod
from pathlib import Path
from pathlib import Path, PurePosixPath
from typing import Optional, List, Dict, Tuple, Callable, Union
from tqdm import tqdm
@@ -105,6 +105,38 @@ class StorageBase(metaclass=ABCMeta):
self.storagehelper.reset_storage(self.schema.value)
self.init_storage()
@staticmethod
def _safe_download_name(name: Optional[str]) -> Optional[str]:
"""
提取可安全落盘的文件名。
"""
if not name:
return None
safe_name = PurePosixPath(str(name).replace("\\", "/")).name
if safe_name in ("", ".", ".."):
return None
return safe_name
def _build_download_path(
self, fileitem: schemas.FileItem, path: Path
) -> Optional[Path]:
"""
构造本地下载路径,避免远端文件名携带目录片段时越过目标目录。
"""
safe_name = self._safe_download_name(fileitem.name)
if not safe_name:
logger.error(f"【存储】下载文件名无效:{fileitem.name}")
return None
local_path = path / safe_name
try:
local_path.resolve().relative_to(path.resolve())
except ValueError:
logger.error(f"【存储】下载路径越界:{fileitem.name} -> {local_path}")
return None
return local_path
@abstractmethod
def check(self) -> bool:
"""

View File

@@ -741,7 +741,9 @@ class AliPan(StorageBase, metaclass=WeakSingleton):
logger.error(f"【阿里云盘】下载链接为空: {fileitem.name}")
return None
local_path = (path or settings.TEMP_PATH) / fileitem.name
local_path = self._build_download_path(fileitem, path or settings.TEMP_PATH)
if not local_path:
return None
# 获取文件大小
file_size = fileitem.size

View File

@@ -340,7 +340,9 @@ class Rclone(StorageBase):
"""
带实时进度显示的下载
"""
local_path = (path or settings.TEMP_PATH) / fileitem.name
local_path = self._build_download_path(fileitem, path or settings.TEMP_PATH)
if not local_path:
return None
# 初始化进度条
logger.info(f"【rclone】开始下载: {fileitem.name} -> {local_path}")

View File

@@ -511,7 +511,9 @@ class SMB(StorageBase, metaclass=WeakSingleton):
"""
带实时进度显示的下载
"""
local_path = (path or settings.TEMP_PATH) / fileitem.name
local_path = self._build_download_path(fileitem, path or settings.TEMP_PATH)
if not local_path:
return None
smb_path = self._normalize_path(fileitem.path)
try:
self._check_connection()

View File

@@ -830,7 +830,9 @@ class U115Pan(StorageBase, metaclass=WeakSingleton):
logger.error(f"【115】下载链接为空: {fileitem.name}")
return None
local_path = (path or settings.TEMP_PATH) / fileitem.name
local_path = self._build_download_path(fileitem, path or settings.TEMP_PATH)
if not local_path:
return None
# 获取文件大小
file_size = detail.size