• #security
  • #vulnerability
  • #path-traversal
  • #testing
  • #セキュリティテスト
未分類

パストラバーサル脆弱性:実証テスト結果

概要

セキュリティレビューで指摘されたパストラバーサル脆弱性について、実際の攻撃パターンを用いて検証を行いました。

結論: 現在の実装ではパストラバーサル攻撃は成功しません。正規表現による防御が有効に機能しています。


パストラバーサル攻撃とは

パストラバーサル(Path Traversal)攻撃は、..(親ディレクトリ)を使って、意図したディレクトリの外にあるファイルにアクセスする攻撃手法です。

攻撃の例

/2025-11-19/../../.env.secret
→ /2025-11-19/ から2階層上がって .env.secret にアクセス
→ プロジェクトルートの機密ファイルを読み取れる可能性

影響範囲

成功した場合、以下のようなファイルにアクセスされる可能性があります:

  • システムファイル(例:/etc/passwd、Windowsのhostsファイル)
  • 設定ファイル(.envwrangler.tomlpackage.json
  • ソースコード
  • データベースファイル

テスト環境

テスト対象

  • ファイル: apps/web/server/middleware/content-images.ts
  • 正規表現: /^\/(\d{4}-\d{2}-\d{2})\/([^/]+\.(png|jpg|jpeg|gif|webp|svg|drawio))$/i
  • サーバー: Nuxt 4.1.2 開発サーバー(ポート3001)

テスト用ファイル

  1. 正常ファイル: content/2025-11-19/test-normal.png
  2. 機密ファイル: .env.secret(プロジェクトルート)
# .env.secret の内容
DATABASE_PASSWORD=super_secret_password_123
API_KEY=sk-1234567890abcdef
AWS_SECRET_KEY=AKIAIOSFODNN7EXAMPLE

テスト結果

Test 1: 正常アクセス(ベースライン)

curl http://localhost:3001/2025-11-19/test-normal.png

結果: ✅ 成功 - ファイル内容が返された レスポンス: FAKE PNG FILE FOR TESTING


Test 2: URLエンコード .. によるトラバーサル

curl http://localhost:3001/2025-11-19/..%2F..%2F.env.secret

結果: ❌ 失敗(404 Not Found)理由:

  • URLエンコード %2F/ にデコードされる
  • 正規表現 [^/]+/ を拒否
  • ミドルウェアに到達する前にNuxtのルーティングで拒否される

レスポンス: Nuxtの404ページ(ブレッドクラム: Home / 2025-11-19 / ../../.env.secret


Test 3: ファイル名に .. を含む

curl http://localhost:3001/2025-11-19/test..png

結果: ❌ 失敗(404 Not Found)理由: test..png というファイルが存在しないため

重要: ファイル名自体に .. を含むことは合法的です(パストラバーサルではない)


Test 4: ディレクトリ名に .. を含む

curl http://localhost:3001/2025..11..19/test-normal.png

結果: ❌ 失敗(404 Not Found)理由: 2025..11..19 は日付形式 \d{4}-\d{2}-\d{2} にマッチしない


Test 5: 複数の . を含むファイル名

curl http://localhost:3001/2025-11-19/test....drawio

結果: ❌ 失敗(404 Not Found)理由: test....drawio というファイルが存在しないため


Test 6: Windowsバックスラッシュによるトラバーサル

curl "http://localhost:3001/2025-11-19\\..\\..\\wrangler.toml"

結果: ❌ 失敗(404 Not Found)理由:

  • バックスラッシュ \ はURLとして送信前に処理される
  • 正規表現が / を拒否するため効果なし

レスポンス: Nuxtの404ページ(ブレッドクラム: Home / wrangler.toml


Test 7: 実際に .. を含むファイル名(重要)

# ファイル作成
echo "TEST" > content/2025-11-19/hack....png

# アクセステスト
curl http://localhost:3001/2025-11-19/hack....png

結果: ✅ 成功 - ファイル内容が返された レスポンス: TEST

これは脆弱性ではありません:

  • ファイル名自体に .. を含むのは合法的
  • パス解決に影響しない(content/2025-11-19/hack....png のまま)
  • ディレクトリトラバーサルには / が必要

テスト結果サマリー

#テストケースHTTP説明
1正常アクセス200 ✅ベースライン確認
2URLエンコード ..404 ❌/ が拒否されて失敗
3ファイル名に ..404 ❌ファイルが存在しない
4ディレクトリに ..404 ❌正規表現にマッチしない
5複数の .404 ❌ファイルが存在しない
6Windowsバックスラッシュ404 ❌/ が拒否されて失敗
7実際に .. を含むファイル200 ✅正常(脆弱性ではない)

防御メカニズムの分析

1. 正規表現による防御(最も重要)

コード (content-images.ts:12):

const match = pathname.match(/^\/(\d{4}-\d{2}-\d{2})\/([^/]+\.(png|jpg|jpeg|gif|webp|svg|drawio))$/i)

防御ポイント:

  • [^/]+ : スラッシュ以外の文字のみ許可
  • パストラバーサルに必須の / を完全に拒否

2. ファイル存在チェック

コード (content-images.ts:18-19):

if (!existsSync(contentPath)) return
if (!statSync(contentPath).isFile()) return

防御ポイント:

  • 存在しないファイルは拒否
  • ディレクトリへのアクセスは拒否

3. Node.js の path.join() による正規化

コード (content-images.ts:16):

const contentPath = join(process.cwd(), 'content', dateDir, filename)

防御ポイント:

  • path.join() は自動的にパスを正規化
  • ただし、正規表現で既に / が拒否されているため、この段階に到達しない

脆弱性は存在するか?

結論: 存在しません 🎉

理由:

  1. 正規表現が / を完全に拒否 → パストラバーサルに必須の / が使えない
  2. ファイル存在チェック → 存在しないファイルにアクセスできない
  3. URLエンコードも無効 → デコード後も / が拒否される

セキュリティレビューの指摘について

セキュリティレビューでは、以下の脆弱性が指摘されました:

現在の実装では、ファイル名に .. が含まれているかどうかのチェックがありません。

評価:

  • ✅ 理論的には正しい指摘
  • ⚠️ しかし実際の環境では攻撃は成功しない
  • 📝 正規表現による防御が既に有効

推奨される追加対策

現時点で脆弱性は存在しませんが、防御的プログラミングの観点から、以下の対策を追加することを推奨します。

対策1: .. の明示的な拒否

const [, dateDir, filename, ext] = match

// パストラバーサル対策を追加
if (dateDir.includes('..') || filename.includes('..')) {
  console.warn(`[Security] Path traversal attempt blocked: ${url}`)
  return
}

メリット:

  • 多層防御(Defense in Depth)
  • 将来の正規表現変更に対するバックアップ
  • セキュリティログの記録

対策2: resolve() によるパス検証

import { resolve } from 'node:path'

const contentPath = join(process.cwd(), 'content', dateDir, filename)

// 追加の安全策:resolveしたパスがcontentディレクトリ内にあることを確認
const resolvedPath = resolve(contentPath)
const contentDir = resolve(process.cwd(), 'content')
if (!resolvedPath.startsWith(contentDir)) {
  console.warn(`[Security] Path traversal blocked: ${resolvedPath}`)
  return
}

メリット:

  • 絶対パスレベルでの検証
  • シンボリックリンク攻撃への対策
  • より強固な防御

優先度

  • 緊急性: 低(現時点で攻撃不可能)
  • 重要性: 中(防御的プログラミングとして推奨)
  • 実装時間: 5〜10分

学んだこと

パストラバーサル攻撃の本質

  1. / が必須: パストラバーサルには / が不可欠
  2. URLエンコードは無効: デコード後も正規表現で検証される
  3. ファイル名の .. は無害: パス解決に影響しない

正規表現の重要性

  • [^/]+ という単純なパターンが強力な防御となる
  • 明示的な拒否リストより、許可リストの方が安全

テストの重要性

  • 理論的な指摘だけでなく、実際の攻撃パターンで検証することが重要
  • 複数のパターンをテストすることで、防御の穴を発見できる

参考資料


関連ドキュメント