flawopen.com/path-traversal/Javascript

● CWE-918 · 高
セキュリティ研究 · FlawOpen

脆弱性の解説:Path Traversal in JavaScript

How naive path.join allows directory traversal in Express/Node.js, and how to enforce directory boundary checks with path.resolve and path.sep.

💡 わかりやすい解説 (ELI5)

ホテルの客室カードリーダーが2階の部屋のみを開けるよう制限されている場面を例えに考えてみてください。もし宿泊客がドアのキーパッドに「../../master-safe」と入力すると、不備のある鍵が廊下を抜け出して支配人の金庫を直接解錠してしまいます。

主要な概念と専門用語

Web Application Security
主要概念 (Web Application Security):Core architecture component affected by CWE-918.
CWE-918
主要概念 (CWE-918):Standard Common Weakness Enumeration classification for path-traversal-javascript.
Defense-in-Depth
主要概念 (Defense-in-Depth):Multi-layered engineering verification and runtime boundary isolation.

ステップ・バイ・ステップの攻撃フロー

Step 1

ファイルパス入力の受信

エンドポイントがHTTPリクエストパラメータからユーザー指定のファイル名やパスを受け取ります。

Step 2

ディレクトリトラバーサル記号の挿入

攻撃者が '../' や '..%2f' などの相対パス記号や絶対パス指定を挿入します。

Step 3

ベースディレクトリ境界の突破

バックエンドが正規化パスの検証を行わずに文字列を連結し、公開フォルダ外へのアクセスを許容します。

Step 4

機密ファイルの漏洩または上書き

OSが /etc/passwd や設定ファイルなどの重要ファイルを読み取り、攻撃者へ返却します。

ソースコード比較:脆弱 vs 堅牢化

✕ 脆弱な実装
// VULNERABLE: path.join allows directory breakout
const express = require('express');
const path = require('path');
const fs = require('fs');

const app = express();
const PUBLIC_DIR = path.join(__dirname, 'public');

app.get('/download', (req, res) => {
  // Attacker input: "../../etc/passwd"
  const targetFile = path.join(PUBLIC_DIR, req.query.file);
  
  // Directly pipes arbitrary system file to response!
  res.sendFile(targetFile);
});
✓ 堅牢化されたセキュアパッチ
// HARDENED: Canonicalize path and assert prefix with directory separator
const express = require('express');
const path = require('path');
const fs = require('fs');

const app = express();
const PUBLIC_DIR = path.resolve(__dirname, 'public');

app.get('/download', (req, res) => {
  const userInput = req.query.file;
  if (!userInput || typeof userInput !== 'string') {
    return res.status(400).send('Invalid file parameter');
  }

  // 1. Resolve to absolute path
  const safePath = path.resolve(PUBLIC_DIR, userInput);

  // 2. Strict boundary check: must start with base folder + separator
  if (!safePath.startsWith(PUBLIC_DIR + path.sep)) {
    return res.status(403).send('Forbidden: Path Traversal detected');
  }

  // 3. Verify file exists and is a regular file
  fs.stat(safePath, (err, stats) => {
    if (err || !stats.isFile()) return res.status(404).send('File not found');
    res.sendFile(safePath);
  });
});

エンジニアリング&システム堅牢化チェックリスト

References