flawopen.com/路径穿越/Python os.path.join()
Python 的 os.path.join() 能防止路径穿越吗?
CWE-22: Path Traversal参考页面
简要回答
不能。把基目录想成一间房:.. 会沿父级房间向外走;绝对路径是新地址,会让 os.path.join() 丢弃起始房间。打开文件前解析最终路径,并确认它仍在允许目录内。
VULNERABLE
import os base_dir = "/srv/uploads" user_filename = "../../outside/demo.txt" path = os.path.join(base_dir, user_filename) # Relative traversal can leave the base. path = os.path.join(base_dir, "/outside/demo.txt") # An absolute segment discards base_dir.
SECURE — resolve and check containment
import os
def resolve_under_base(base_dir, user_filename):
if not isinstance(user_filename, str) or not user_filename:
raise ValueError("Invalid filename")
base = os.path.realpath(base_dir)
candidate = os.path.realpath(
os.path.join(base, user_filename)
)
try:
inside = os.path.commonpath((base, candidate)) == base
except ValueError: # e.g. different Windows drives
inside = False
if not inside or candidate == base:
raise ValueError("Invalid filename")
return candidate
需要考虑的两种路径拼接行为
.. 可能使相对路径离开预期目录。Python 文档说明绝对路径组件会让 os.path.join() 丢弃前项;Windows 的盘符和根路径规则与 POSIX 不同。pathlib.Path(base) / user_input 也如此。拼接和规范化都不是授权检查。
为什么 commonpath() 比字符串前缀更安全
比较前解析可信基目录和候选路径。os.path.commonpath() 比较路径组件而非字符前缀;根或盘符不兼容时抛出 ValueError,应拒绝。realpath() 解析检查时存在的符号链接,但不能阻止其在打开前被修改。若他人能修改目录树,请采用适合平台的描述符访问和 no-follow 控制。
- ✓拒绝空路径及非字符串输入。
- ✓解析基目录和候选路径;要求
commonpath((base, candidate)) == base。拒绝ValueError;需要文件时也拒绝基目录本身。 - ✓将路径范围检查与授权分开,只允许端点需要的文件类型和操作。
- ✓若不可信用户可修改符号链接,使用平台适配的文件描述符 API 防止检查与使用之间的竞争。
常见问题
pathlib 的行为相同吗?
相同。Path 的 / 运算符遇到绝对路径会丢弃前项。解析结果并确认其位于允许的基目录内。
realpath() 能消除竞争条件吗?
不能。它只解析验证时存在的链接;其他进程可能在打开前修改链接。限制目录树写入,或使用平台支持的 no-follow 描述符相对操作。