安全な方法で2つのパスを連結する必要があるnode.js webappがあります。最初の1つ(左端)は定数で、2番目の1つ(右端)は最初の1つを基準にしており、信頼できないユーザー入力からのものです。結果のパスは、最初のパスの下にあるものでなければなりません。だから状況はこれです:
_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;
}
_
これで十分ですか?これを行う標準的な方法はありますか?助言がありますか?
この状況で私が使用したアプローチの1つを次に示します。
path.normalize()
はすべての_.
_と_..
_を処理するので、どちらかが存在する場合、それがパスの先頭にあることを確認できます。../../
_を削除します。そう:
_var safeSuffix = path.normalize(unsafeSuffix).replace(/^(\.\.(\/|\\|$))+/, '');
var safeJoin = path.join(basePath, safeSuffix);
_
あなたのアプローチについて:プレフィックスをチェックすることは私にはかなり良い考えのようです。私はあなたの実装で見られるいくつかの問題があります:
../html-other
_は_public/html-other
_に解決されますが、これはあなたが望むものではないと思います。.normalize()
が_/
_を_\
_に変換します。つまり、noパスが機能します。(わずかに異なる状況で)接頭辞チェックを実行すると、次のようになります。
_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);