FlawOpen

flawopen.com/Path Traversal/Node.js path.join()

Node.js path.join() で Path Traversal を防げますか?

リファレンスページ
結論

いいえ。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 脆弱性とは?