在 node.js 中加入路径时如何防止目录遍历?

信息安全 Web应用程序 节点.js 目录遍历
2021-08-09 18:50:11

我有一个 node.js webapp,我需要在其中以安全的方式连接两条路径。第一个(最左边)是一个常数,第二个(最右边)与第一个相关,来自不受信任的用户输入。生成的路径应该低于第一条路径。所以情况是这样的:

path1 = "public/html";                // Hardcoded path.
path2 = req.query.path;               // Untrusted user input.
result = safePathJoin(path1, path2);  // Result can be e.g. public/html/index.htm,
                                      // but never private/config.xml

我需要的是safePathJoin()对目录遍历攻击安全的功能。我的第一个天真的方法是这样的:

safePathJoin = function(path1, path2) {
    path1 = path.normalize(path1);
    var result = path.join(path1, path2);
    return result.startsWith(path1) ? result : undefined;
}

这够好吗?有没有标准的方法来做到这一点?有什么建议?

2个回答

这是我在这种情况下使用的一种方法:

  1. path.normalize()处理所有.and ..,因此您可以确定如果其中任何一个存在,它将位于路径的前面。
  2. ../../从路径的前面删除任何内容。

所以:

var safeSuffix = path.normalize(unsafeSuffix).replace(/^(\.\.(\/|\\|$))+/, '');
var safeJoin = path.join(basePath, safeSuffix);

关于您的方法:检查前缀对我来说似乎是个好主意。我在您的实施中看到了几个问题:

  • 您已经检查了没有尾部斜杠的前缀:../html-other将解析为public/html-other,我猜这不是您想要的。
  • 你会在 Windows 系统上遇到麻烦,其中.normalize()会转换/\,这意味着没有路径可以工作。

当我完成前缀检查(对于稍微不同的情况),这就是我最终得到的结果:

function checkPrefix(prefix, candidate) {
    // .resolve() removes trailing slashes
    var absPrefix = path.resolve(prefix) + path.sep;
    var absCandidate = path.resolve(candidate) + path.sep;
    return absCandidate.substring(0, absPrefix.length) === absPrefix;
}

(是的,我添加path.sep了两者,因此前缀 dir 本身通过了测试。)

鉴于需要接收path作为已定义root目录的子目录的用户输入并允许用户访问(以任何方式)组合root+path目录,对我来说最安全的选择是只允许绝对路径作为输入并检查是否已清理任何特殊目录名称( ., ..) 的输入路径等于原始输入:

// Input

const path1 = "public/html";
const path2 = req.query.path;

// Checkout

const isNotSpecialDirName = part => !(['', '.', '..'].includes(part));

const path2Clean = path2.split(path.sep).filter(isNotSpecialDirName).join(path.sep);

// Output

if (path2Clean !== path2) {
  // Obfuscated with Not Found
  throw new Error('Not Found');
}

const result = path.join(path1, path2Clean);