← 中文资源

简体中文版本

防止目录穿越的安全路径拼接

把用户提供的相对路径解析到固定根目录下,拒绝绝对路径、盘符、NUL 与 .. 穿越,并再次确认最终路径仍在根目录内。

阅读英文原文

功能说明

safe_join 把用户提供的相对路径拼到固定根目录下,并阻止目录穿越。它先拒绝空值、NUL 字节、绝对路径和盘符,再对根目录和目标路径调用 resolve() 展开 .. 与符号链接,最后用 relative_to 确认结果仍位于根目录内。

关键代码

base = Path(root).resolve()
target = (base / candidate).resolve()
try:
    target.relative_to(base)
except ValueError as error:
    raise UnsafePathError(f"path escapes root: {user_path!r}") from error
return target

先拼接再解析,而不是只检查字符串里有没有 ..,这样才能覆盖符号链接和混合分隔符的情况。

使用注意

  • 校验与真正打开文件之间存在时间窗口,攻击者可能替换符号链接;高安全场景应使用 os.open 配合 O_NOFOLLOW 或目录文件描述符。
  • 允许目标等于根目录本身;如果业务只允许文件,需要额外检查 target != base。
  • Windows 上盘符和 UNC 路径由 candidate.drive 拦截,POSIX 上反斜杠只是普通字符。
  • resolve() 默认不要求路径存在,适合在创建新文件前做校验。
  • 返回的是绝对路径,调用方不要再自行拼接用户输入。

验证方式

示例中 reports/2026/q1.csv 会被接受,../secrets.env、/etc/passwd 和 a/../../b 都会被拒绝并打印原因。可以在根目录下创建指向外部的符号链接,确认解析后仍会被拦截。