Repository navigation
Closed
Description
SQLBot Version
最新 main(核实于 a0b6145)
Run Mode
Docker 或源码运行均适用(问题在请求处理层)
Describe the bug
POST /datasource/parseExcel 和 POST /datasource/uploadExcel 直接把 multipart 的 file.filename 拼进落盘文件名,只按 / 做了一次切分,写入前也没有做 realpath / commonpath 归属校验:
# backend/apps/datasource/api/datasource.py parse_excel()
ALLOWED_EXTENSIONS = {".xlsx", ".xls", ".csv"}
if not file.filename.lower().endswith(tuple(ALLOWED_EXTENSIONS)):
raise HTTPException(400, trans('i18n_ds_unsupported_file_format'))
os.makedirs(path, exist_ok=True)
filename = f"{file.filename.split('.')[0].split('/')[-1]}_{hashlib.sha256(uuid.uuid4().bytes).hexdigest()[:10]}.{file.filename.split('.')[-1]}"
save_path = os.path.join(path, filename)
with open(save_path, "wb") as f:
f.write(await file.read())# backend/apps/datasource/api/datasource.py upload_excel() (标记 deprecated,但路由仍注册)
ALLOWED_EXTENSIONS = {"xlsx", "xls", "csv"}
if not file.filename.lower().endswith(tuple(ALLOWED_EXTENSIONS)):
raise HTTPException(400, trans('i18n_ds_unsupported_file_format'))
filename = f"{file.filename.split('.')[0]}_{hashlib.sha256(uuid.uuid4().bytes).hexdigest()[:10]}.{file.filename.split('.')[1]}"
save_path = os.path.join(path, filename)
with open(save_path, "wb") as f:
f.write(await file.read())path 取自 settings.EXCEL_PATH(同文件第 39 行)。相比 apps/terminology/api/terminology.py:190、apps/data_training/api/data_training.py:179 已经采用的 os.path.basename(...) + os.path.commonpath(...) 写法,这两个入口漏了同样的处理。
To Reproduce
构造 multipart 的文件名(name 部分即 file.filename):
..\..\evil.xlsx——split('/')不认 Windows 分隔符,剥不掉目录部分;split('.')[0]还会把..\..截成..\,最终文件名变成..\evil_<hash>.xlsx,在 Windows 主机上写到上传目录外。C:evil.xlsx—— Windows 驱动器相对路径,os.path.join(path, "C:evil_<hash>.xlsx")会整体替换上传目录。evil.xlsx之外的a.txlsx——uploadExcel的扩展名白名单是不带点的{"xlsx", "xls", "csv"},endswith会放过非 Excel 名称。- 不含点的名字(如
invoice)——upload_excel里的file.filename.split('.')[1]直接 IndexError,接口返回 500。
Expected behavior
- 落盘文件名只取客户端名称的最后一个路径分量,
/与\都要当作分隔符; - 拒绝空名、
.、..、含:(驱动器相对路径 / NTFS 数据流)或 NUL 的名称; - 写入前用
os.path.realpath+os.path.commonpath确认路径仍在settings.EXCEL_PATH内; - 扩展名白名单按带点的后缀匹配(与
parseExcel现在的写法一致); parseExcel返回的filePath只包含文件名本身,不含任何目录分量。
Additional context
importToDb的同类问题已在 importToDb accepts an arbitrary client-controlled filePath (path traversal / arbitrary file read) #1434 / fix: prevent path traversal in datasource importToDb (CWE-22) #1435 修复,这里补的是写入侧的两个入口;- POSIX 主机上
..\..\evil.xlsx只是一个普通文件名,因此实际越界写入发生在 Windows 主机(本仓库的离线安装脚本与开发环境都支持 Windows); - 影响范围:拥有
ws_admin权限的调用者可以在EXCEL_PATH之外写入带.xlsx/.xls/.csv后缀的文件。
Metadata
Metadata
Assignees
Labels
No labels
Activity
fix(datasource): store Excel uploads under a sanitised name dataease#…
fix(utils): reduce client upload names to a single path component #1448
fix(datasource): store Excel uploads under a sanitised name #1448