【実務・中級編】 パス・トラバーサル(ディレクトリトラバーサル)によるファイルシステムアクセス – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

パス・トラバーサルを甘く見るな:開発現場で「なぜか」抜かれるファイルシステムの裏側

現場でセキュリティ診断をしていると、いまだに遭遇するのが「パス・トラバーサル(ディレクトリトラバーサル)」だ。モダンなフレームワークを使っているから大丈夫? それは大きな勘違いだ。フレームワークが提供する機能の「使い方」を誤れば、たった一行のコードがサーバーの全ファイルを外部に晒すゲートウェイになる。

今日は、攻撃者がどのようにファイルシステムの壁を越え、我々エンジニアがどうやってその「抜け穴」を塞ぐべきか、泥臭い実務の視点で解説しよう。

—

1. 攻撃の解剖学:なぜ ../ は防げないのか

パス・トラバーサルの本質は、「ユーザーからの入力をファイルパスの一部として無防備に結合する」ことにある。

攻撃者は ../../etc/passwd のような文字列を送り込み、サーバー側のOSが解釈するパスの解決ルール(相対パスの遡及)を悪用する。多くのエンジニアは「ファイル名だけチェックすればいい」と考えがちだが、OSレベルではパスの正規化(canonicalization)が行われるため、中間のディレクトリ指定を無視することはできない。

攻撃のPoC(概念実証)

例えば、画像を表示するためのPHPコードがこうなっていたとしよう。

// 脆弱な例:ユーザー入力をそのまま結合している
$file = $_GET['file']; // 例: "../../etc/passwd"
readfile("/var/www/uploads/" . $file);

攻撃者はブラウザのアドレスバーに直接入力するだけで、Webルートの外側にあるシステム設定ファイルや、環境変数が含まれる .env ファイルを平然と読み取ってくる。これを放置することは、「家の鍵をかけずに玄関に金庫を置く」ようなものだ。

—

2. 実践的な防御策:コードレベルの「堅牢な実装」

防御の鉄則は、「ユーザー入力を絶対的に信用せず、ホワイトリストで管理する」ことだ。また、パスを結合する前に、必ず「そのパスが本来のディレクトリ配下にあるか」を判定しなければならない。

以下に、PHPで安全にファイルを読み込むための実装例を示す。

function getSecureFile($filename) {
    // 1. ベースとなるディレクトリを定義
    $baseDir = '/var/www/uploads/';
    
    // 2. パスを正規化(../ などを解決して絶対パスにする)
    $realBase = realpath($baseDir);
    $requestedPath = realpath($baseDir . basename($filename)); // basenameでディレクトリ指定を排除

    // 3. 正規化したパスがベースディレクトリ配下にあるかチェック
    if ($requestedPath === false || strpos($requestedPath, $realBase) !== 0) {
        throw new Exception("不正なアクセス試行です。");
    }

    return $requestedPath;
}

重要なポイント

  • basename(): ファイル名のパス部分(ディレクトリ名)を強制的に削除する。これにより、../../ といった構造そのものを無効化できる。
  • realpath(): シンボリックリンクなども含め、最終的な絶対パスに変換する。
  • strpos(...) !== 0: 解決されたパスが、許可されたベースディレクトリの先頭から始まっているかを厳格に確認する。

—

3. インフラで守る:NginxとDockerでの多層防御

アプリ側の修正は必須だが、インフラ側でも「もしアプリに穴があったら」という前提で対策を打つのがプロの仕事だ。

Nginxによるフィルタリング

WAFがない環境でも、Nginxの設定で露骨なトラバーサルパターンを拒否できる。

# nginx.conf の locationブロック内に記述
# ../ を含むリクエストを拒否する
if ($query_string ~ "\.\./") {
    return 403;
}

コンテナ(Docker)による分離

最善の手は、アプリケーションが実行されるプロセスを、必要なファイル以外にアクセスできない環境(chrootやDockerの非特権コンテナ)に閉じ込めることだ。

  • Read-onlyマウント: アプリケーションがファイルをアップロードする必要がないディレクトリは、--read-only フラグでマウントする。
  • 最小権限: アプリを実行するユーザー(www-data等)には、/etc/ やホームディレクトリへの読み取り権限を与えない。Linuxのパーミッション設定が最後の砦になる。

—

セキュリティチーフからのアドバイス

「動けばいい」というコードは、数ヶ月後の自分や運用チームにとっての爆弾になる。
パス・トラバーサルは、脆弱性としては「古典的」だが、だからこそ攻撃者は足元を見てくる。複雑なセキュリティ製品を導入する前に、まずは「ユーザー入力を結合する際は、一度止まって、そのパスが本当に安全な場所へ繋がっているかを確認する」というエンジニアとしての直感を研ぎ澄ませてほしい。

もし、今日書いたコードに $_GET がそのままパスとして使われている箇所を見つけたら、すぐに修正のチケットを切るんだ。それが、システムを、そして会社を守る第一歩になる。

コメント

タイトルとURLをコピーしました