PR

ディレクトリトラバーサル攻撃の仕組みと防止策

この記事は学習用です。ChatGPT と GitHub Copilot を使っています。

1. 概要:ディレクトリトラバーサルとは何か

ディレクトリトラバーサル(Directory Traversal)とは、Webアプリケーションが扱うファイルパスの検証が不十分な場合に、攻撃者が意図しないディレクトリやファイルへアクセスできてしまう脆弱性である。
別名「パストラバーサル(Path Traversal)」とも呼ばれ、主に相対パス(例:../)を悪用してサーバ上の任意ファイルを参照・取得する。

例:

http://example.com/view?file=../../etc/passwd

上記のような入力を受け取ると、アプリケーションが /etc/passwd を読み込み、機密情報が漏洩する可能性がある。

2. 発生する原因

2.1 不適切なパス検証

サーバ側でユーザー入力値をそのままファイルパスに結合している場合、../ によって上位ディレクトリへ遡れる。

2.2 ブラックリスト方式の限界

拡張子やキーワードを除外するブラックリスト方式では、エンコード(例:..%2F)などにより回避される可能性がある。

2.3 開発時の典型的な誤り

  • open("uploads/" + filename) のように無検証で結合してしまう。
  • ファイル名のみチェックし、ディレクトリ遡り文字を想定していない。

3. 攻撃の具体例

3.1 シンプルな例

GET /download?file=../../../../etc/passwd HTTP/1.1

これにより Linux サーバ上のパスワードファイルが取得されてしまう可能性がある。

3.2 Webアプリ設定ファイルの漏洩

例:config.php.env ファイルが閲覧されることで、データベース認証情報などが流出する。

3.3 エンコードを用いた回避

GET /download?file=..%2F..%2F..%2Fetc/passwd

URL エンコードによりフィルタを回避できる場合がある。

4. 対策方針

4.1 絶対パス固定

安全なディレクトリを固定し、その外へのアクセスを禁止する。

import os

BASE_DIR = os.path.realpath("/var/www/app/uploads")
candidate = os.path.realpath(os.path.join(BASE_DIR, filename))

if os.path.commonpath([BASE_DIR, candidate]) != BASE_DIR:
    raise ValueError("Invalid file path")

startswith による先頭一致判定は誤検知の恐れがあるため、os.path.commonpath などでディレクトリ階層を厳密に比較する。

4.2 ホワイトリスト方式

許可されたファイル名や拡張子の一覧を保持し、それ以外は拒否する。

4.3 入力値の正規化と検証

  • os.path.normpath() などを利用してパスを正規化する。
  • 許可する文字種(例:^[a-zA-Z0-9._-]+$)をホワイトリストで厳格に制限する。
  • ディレクトリ遡りやパス区切り(../, ..\\, %2e%2e/ など)を拒否する。
  • 複数回のエンコード(ダブルエンコード等)を想定して検証する。
  • Windows では \\ もパス区切りである点に注意する。

4.4 Webサーバ側の制限

  • Apache: Options -Indexes
  • Nginx: autoindex off;
  • 公開ディレクトリを \var\www\html\public に限定するなど、ドキュメントルートの分離・権限最小化を徹底する。

これらはアプリケーションの不備を直接修正するものではないが、被害範囲を限定するための防御層として有効である。

5. 検知とテスト

5.1 セキュリティ診断ツール

  • OWASP ZAP
  • Burp Suite
  • Nikto

5.2 テスト観点の例

観点テスト内容期待結果
../ の挿入ファイル外参照拒否される
%2e%2e/URL エンコード利用拒否される
/etc/passwd 直指定システムファイル参照拒否される

6. 関連する脆弱性との比較

攻撃主な目的手口
ディレクトリトラバーサル任意ファイル参照相対パスの遡り
コードインジェクションコード実行外部入力を評価
LFI/RFI(ローカル/リモートファイルインクルード)ファイル取り込みinclude() 関数経由

これらは類似しており、外部入力をそのままファイル操作に使用する点が共通している。

7. 実装例:安全なファイルダウンロード

Python(Flask)での安全実装例:

from flask import Flask, send_from_directory, abort
import os

app = Flask(__name__)
BASE_DIR = os.path.realpath("/var/www/app/files")

@app.route("/download/<path:filename>")
def download(filename):
    safe_abs = os.path.realpath(os.path.join(BASE_DIR, filename))
    if os.path.commonpath([BASE_DIR, safe_abs]) != BASE_DIR:
        abort(403)
    # send_from_directory は内部で安全な結合を行う
    return send_from_directory(BASE_DIR, filename)

このように、ディレクトリ階層を越えるリクエストを拒否できる。

8. まとめ

ディレクトリトラバーサルは、開発側の「ファイル名を信用する」ことによって発生する典型的な脆弱性である。
防止の基本は「固定パス」「正規化」「ホワイトリスト」の3原則にある。
特に外部入力をファイルパスに結合する際は、常に検証を行い、サーバ構成でも不要なアクセス権を与えないことが重要である。

A. 参考サイト

OWASP: Path Traversal
IPA 安全なウェブサイトの作り方 第3版
NIST Vulnerability Database (CWE-22)
PortSwigger: Directory Traversal
Flask Documentation - send_from_directory

B. 関連書籍

コメント