FlawOpen

flawopen.com/路径穿越/Node.js path.join()

Node.js path.join() 能阻止路径穿越吗?

参考页面
简答

不能。path.join() 会规范化路径段,但不会阻止 .. 走出预期目录。应基于基础目录解析候选路径,并验证相对路径仍位于基础目录内。该词法检查本身无法限制文件系统符号链接。

仍然存在漏洞
const filePath = path.join(
  BASE_DIR, userFilename
);
// userFilename = "../../etc/passwd"
// still escapes BASE_DIR
已修复 — 词法路径包含检查
const base = path.resolve(BASE_DIR);
const resolved = path.resolve(base, userFilename);
const relative = path.relative(base, resolved);

if (
  relative === '' ||
  relative === '..' ||
  relative.startsWith('..' + path.sep) ||
  path.isAbsolute(relative)
) {
  throw new Error("Invalid path");
}

为什么单独使用 path.join() 不够

path.join() 使用平台分隔符组合路径段并规范化结果。规范化不会拒绝 ..,因为它在许多路径中都合法。将 ../../etc/passwd 与基础目录拼接后仍可能得到目录外路径;join() 并不检查路径包含关系。

常见问题

path.normalize() 有帮助吗?

path.normalize() 会处理字符串中的 .. 和 . 路径段,但不会验证结果是否仍位于允许的基础目录中。比较 path.relative(base, resolved);若必须是文件则拒绝空结果,同时拒绝 ..、以 .. 加 path.sep 开头的结果以及绝对相对结果。

该检查能阻止符号链接逃逸吗?

不能。path.resolve() 和 path.relative() 比较的是路径字符串,不会跟随文件系统符号链接。如果攻击者能在基础目录下创建或替换符号链接,应验证实际解析目标,并使用可避免检查与使用之间竞态的文件访问方式。尽可能限制该目录树的写权限。

参考资料

另请参阅 路径遍历(CWE-22):原理及在各种语言中的防范 — flawopen.com PHP include() 与用户控制的路径 修复 Python os.path.join() 路径穿越 什么是 Zip Slip 漏洞?