【テクニカル・上級編】 パス・トラバーサル(ディレクトリ・トラバーサル)による機密ファイル読み取り – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

はじめに:なぜ「たかがパス・トラバーサル」でシステムが沈むのか

ペネトレーションテストの現場において、未だに最も高確率でクリティカルなヒットを生むプリミティブ(基本攻撃プリミティブ)は何かと問われれば、私は迷わず「パス・トラバーサル(ディレクトリ・トラバーサル)」を挙げる。

「URLに ../ を挟むだけの古典的な脆弱性だろう」と侮る開発者やセキュリティエンジニアが後を絶たないが、それは表面的な理解に過ぎない。現代のモダンなWebアプリケーション、マイクロサービス、そしてコンテナ化されたインフラストラクチャにおいて、この「ファイルパスの解決における論理的矛盾」は、単なる機密ファイルの読み取りに留まらず、リモートコード実行(RCE)、暗号鍵の窃取、そしてサプライチェーン全体の崩壊へと直結するキルチェーンの起点となる。

本稿では、ファイルシステムの低レイヤにおけるパス解決の挙動から、HTTPサーバーとアプリケーションフレームワークの解釈の差異が生む脆弱性の本質、そして現代のアーキテクチャにおける堅牢な防御設計まで、実戦の知見を交えて徹底的に解説する。

—

1. 脆弱性の根源:OSのファイルシステムとWebサーバーの「解釈の乖離」

パス・トラバーサルの本質は、ユーザー入力がシステムコールへ渡される過程における「信頼境界の崩壊」と「正規化(Normalization)の不備」にある。

攻撃者が狙うのは、Webアプリケーションが静的ファイルを配信したり、動的にファイルを読み込んだりする際の処理ロジックだ。例えば、以下のような脆弱なPHPのコード片を考えてみる。

<?php
// 脆弱なファイル読み取りスクリプトの例
// パラメータ 'file' の入力値が検証なしにファイルパスとして結合されている
$base_dir = "/var/www/html/uploads/";
$filename = $_GET['file'];

// 入力値をそのままベースディレクトリと結合してファイルパスを生成
$filepath = $base_dir . $filename;

// ファイルが存在すれば内容を出力する
if (file_exists($filepath)) {
    // 任意のファイルを読み取れてしまう
    readfile($filepath);
} else {
    echo "File not found.";
}
?>

一見すると、/var/www/html/uploads/ という安全なベースディレクトリが指定されているため、安全に見えるかもしれない。しかし、LinuxのカーネルやファイルシステムAPI(あるいはC言語の fopen やPHPの readfile)は、パスに含まれる ../(親ディレクトリへの移動)やヌルバイト(%00)、連続するスラッシュ(//)を以下のように解釈して解決する。

1. 入力値 $filename に ../../../../etc/passwd が渡される。
2. 結合されたパスは /var/www/html/uploads/../../../../etc/passwd となる。
3. OSのファイルシステムはこれを正規化し、実際には /etc/passwd を指し示すものとして処理する。

リバースエンジニアリング視点:APIごとの挙動の違い

攻撃の精度を高めるためには、使用されている言語やフレームワークの内部実装、ひいてはC言語レベルのシステムコール(open, stat 等)の挙動を理解していなければならない。

例えば、Windows環境におけるファイルパス処理では、スラッシュ(/)とバックスラッシュ(\)の混在、ドライブ文字(C:)、さらにはNTFSのオルタネーターデータストリーム(ADS)が絡むことで、フィルタリング機構をいとも簡単にバイパスすることが可能になる。Javaの java.io.File や Node.js の fs モジュールでも、プラットフォーム依存のパス区切り文字の解釈の違いを利用したエクスプロイトが数多く報告されてきた。

—

2. 攻撃ベクトルの進化:単なる /etc/passwd 読み取りからの脱却

レッドチームのエンゲージメントにおいて、/etc/passwd や C:\boot.ini を読み取ること自体は、あくまでシステム内部の構造を把握するための「偵察(Reconnaissance)」に過ぎない真の目的は、ここから得た情報を次のフェーズ(権限昇格やRCE)へ繋げることだ。

実戦で狙うべきターゲットファイル群

1. 環境変数・設定ファイル

  • .env ファイル(データベースの認証情報、APIシークレット、暗号化キー)
  • docker-compose.yml や Kubernetes のマニフェストファイル
  • データベースの接続設定ファイル(database.yml, settings.py)

2. セッション・ログファイル

  • セッションストアファイル(ファイルベースのセッション管理の場合、他ユーザーのセッションハイジャックが可能)
  • アプリケーションログ(平文のパスワードや個人情報、内部IPアドレスの露出)

3. SSH秘密鍵・認証情報

  • /home/user/.ssh/id_rsa(水平展開・横展開のための鍵の窃取)

特に、モダンなクラウドネイティブアプリケーションでは、設定情報の集中管理が行われておらず、各コンテナ内の .env ファイルに強力な権限を持つAWSの IAM アクセスキーやクラウドストレージのマスターキーがハードコードされているケースが散見される。パス・トラバーサルによる一撃が、そのままクラウド環境全体のコンプロマイズ(乗っ取り)に繋がる所以がここにある。

—

3. 防衛アーキテクチャ:なぜ従来の対策は破られるのか

多くの開発者が実装する「不十分な対策」の典型例を見てみよう。そして、それがなぜ攻撃者に突破されるのかを解説する。

誤った対策例:ブラックリスト方式や単純な文字列置換

<?php
// 非常に脆弱なブラックリスト方式の例
$filename = $_GET['file'];

// "../" という文字列を単に削除または空文字に置き換える
$filename = str_replace("../", "", $filename);

$filepath = "/var/www/html/uploads/" . $filename;
readfile($filepath);
?>

この実装は、セキュリティの専門家から見れば「ザル」の一言に尽きる。攻撃者は以下のような手法で容易にバイパスする。

  • 入れ子のパストラバーサル: ....// や ..././ のような文字列を入力すると、置換された結果として ../ が残る(例: ....//....//etc/passwd -> ../../etc/passwd)。
  • URLエンコード・二重エンコード: Webサーバーやフレームワークが自動デコードするタイミングと、アプリケーションが検証を行うタイミングのズレを突く(例: %2e%2e%2f)。

—

4. 究極の防御とセキュアコーディングの実装例

パス・トラバーサルを完全に根絶するための防御層(ガードレイル)は、単一のバリデーションに依存するのではなく、「正規化とベースディレクトリの厳密な比較(Canonicalization)」によって構築されなければならない。

以下に、PHPおよびセキュアな設計思想に基づいた実装例を示す。

<?php
/**
 * セキュアなファイル読み取り関数の実装例
 * 
 * @param string $base_dir 許可されたベースディレクトリの絶対パス
 * @param string $user_input ユーザーからの入力値
 * @return string 安全に解決された絶対パス
 * @throws Exception 不正なパスが検出された場合
 */
function getSecureFilePath(string $base_dir, string $user_input): string {
    // 1. ベースディレクトリの絶対パスを取得し、末尾のスラッシュを正規化
    $real_base = realpath($base_dir);
    if ($real_base === false) {
        throw new Exception("ベースディレクトリが存在しません。");
    }
    $real_base = rtrim($real_base, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;

    // 2. ユーザー入力を結合してパスを生成(この時点ではまだ検証前)
    // basename() を使用してディレクトリ区切り文字を除去するアプローチも有効
    $target_path = $real_base . ltrim($user_input, DIRECTORY_SEPARATOR);

    // 3. realpath() を用いてシンボリックリンクや相対パスを解決した「実際の絶対パス」を取得
    // ※ 注意: 対象ファイルが存在しない場合、realpath() は false を返すため、
    // 存在しないファイルを扱う場合はパスの論理的正規化ロジックを自前で実装する必要がある。
    $real_target = realpath($target_path);

    if ($real_target === false) {
        throw new Exception("指定されたファイルは存在しません。");
    }

    // 4. 【最重要】解決されたパスが、ベースディレクトリの配下から始まっているかを厳密に比較する
    // これにより、任意のディレクトリへの脱出(トラバーサル)を完全に防ぐ
    if (strpos($real_target, $real_base) !== 0) {
        throw new Exception("セキュリティ警告: 不正なパスアクセスが検知されました。");
    }

    return $real_target;
}

// --- 実行例 ---
try {
    $base = "/var/www/html/uploads";
    // 攻撃的な入力をシミュレート
    $input = "../../../etc/passwd"; 
    
    $safe_path = getSecureFilePath($base, $input);
    // readfile($safe_path);
} catch (Exception $e) {
    // ログに攻撃試行の詳細を記録し、ユーザーには汎用的なエラーメッセージを返す
    error_log($e->getMessage());
    echo "エラーが発生しました。";
}
?>

アーキテクチャレベルでの推奨事項

1. 設計の分離(Principle of Least Privilege):
静的ファイルやアップロードされたファイルの配信は、アプリケーションサーバー(PHP, Node.js, Python等)に直接処理させるべきではない。NginxやApacheなどのWebサーバー、あるいは専用のオブジェクトストレージ(AWS S3等)にルーティングし、アプリケーション層を経由しないアーキテクチャを構築する。
2. chroot環境およびコンテナの隔離:
アプリケーションが実行されるプロセスを chroot 環境に閉じ込めるか、Docker等のコンテナを用いてファイルシステムへのアクセスを厳しく制限する。万が一アプリケーションにパス・トラバーサルが存在したとしても、コンテナ外部のシステムファイル(/etc/passwd 等)へのアクセスを物理的に遮断できる。
3. Web Application Firewall (WAF) およびRASPによる多層防御:
シグネチャベースのWAFだけに頼るのではなく、ランタイム・アプリケーション・セルフ・プロテクション(RASP)を導入し、ファイルシステムAPIへの不正な引数の渡され方をリアルタイムで検知・ブロックする体制を整える。

—

おわりに

パス・トラバーサルは、サイバーセキュリティの歴史において最も古く、そして最も普遍的な脆弱性のひとつである。しかし、この脆弱性が今なお数多くのシステムで猛威を振るっている理由は、開発者が「ファイルシステムがどのようにパスを解釈しているか」という低レイヤのメカニズムを無視し、表面的な文字列処理だけでセキュリティを担保しようとする怠慢にある。

真のセキュリティエンジニア、そしてテックリードたる者よ。フレームワークやライブラリの便利さに隠された下位レイヤの挙動に目を向け、コードの1行1行に「攻撃者の視点」を宿らせよ。脆弱性は、常に設計の甘さと人間の油断の隙間に巣食うのだから。

コメント

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