PHP ファイル包含脆弱性の仕組みと対策および実証実験

ファイル包含脆弱性の概要

Web 開発において、共通の処理をまとめたファイルを複数の脚本から呼び出すことは一般的です。この仕組みを「ファイル包含」と呼びます。しかし、包含するファイル名をユーザー入力に基づいて決定し、かつ適切な検証を行わない場合、攻撃者が任意のファイルを読み込んだり、悪意のあるコードを実行したりする脆弱性が発生します。

PHP においてファイル包含に使用される主な関数は以下の通りです。これらはエラー処理や再包含の挙動に違いがあります。

  • include(): 実行時にファイルを読み込みます。エラー発生時は警告を出し、スクリプトは続行されます。
  • include_once(): include と同様ですが、同一ファイルが複数回読み込まれるのを防ぎます。
  • require(): 実行時にファイルを読み込みます。エラー発生時は致命的なエラーとなり、スクリプトは停止します。
  • require_once(): require と同様ですが、同一ファイルの重複読み込みを防ぎます。

ファイル包含脆弱性は、対象ファイルがサーバー内にある「ローカルファイル包含(LFI)」と、外部 URL を指定できる「リモートファイル包含(RFI)」に分類されます。RFI を実行可能にするには、php.ini にて allow_url_fopen が有効になっている必要があります。

脆弱性レベル別の検証

ここでは、セキュリティ学習用プラットフォームの構成を模して、異なるセキュリティレベルにおけるコードとその bypass 手法について解説します。

レベル 1:無検証状態

最も基本的な脆弱性パターンです。ユーザーからの入力をそのままファイルパスとして使用しています。

<?php
// ユーザーから取得したパラメータ
$target_path = $_REQUEST['doc'];

// 検証なしで包含
include($target_path);
?>

この場合、URL パラメータに任意のパスを指定することで、サーバー上の設定ファイル(例:php.ini)やソースコードを読み取ることが可能です。また、絶対パスを指定することでシステムファイルへのアクセスも試みられます。

レベル 2:単純なフィルタリング

次のレベルでは、特定の文字列を削除するフィルタが実装されています。

<?php
$target_path = $_REQUEST['doc'];

// 危険なプロトコルやディレクトリ移動を削除
$target_path = str_ireplace(array('http://', 'https://'), '', $target_path);
$target_path = str_ireplace(array('../', '..\\'), '', $target_path);

include($target_path);
?>

この対策は、同じ文字列を二重に記述することで迂回可能です。例えば、htthttp://p:// と入力すると、中央の http:// が削除され、結果として http:// が残ります。同様に、....// などとすることで、フィルタを回避しディレクトリトラバーサルを実行できます。

レベル 3:名前による制限

さらに高度なレベルでは、ファイル名のプレフィックスチェックが行われます。

<?php
$target_path = $_REQUEST['doc'];

// 'file' で始まるか、特定のファイル名であることを確認
if( !preg_match( '/^file/', $target_path ) && $target_path != 'include.php' ) {
    echo 'ERROR: 指定されたファイルは存在しません';
    exit;
}

include($target_path);
?>

この制限は、PHP のラッパープロトコルを使用することで bypass できる場合があります。例えば、file:///etc/passwd のように指定すると、文字列チェックをパスしつつローカルファイルシステムにアクセスすることが可能です。

レベル 4:ホワイトリスト方式

最も_secure_な実装は、許可されたファイル名を明示的にリスト化する方法です。

<?php
$target_path = $_REQUEST['doc'];

// 許可リストの定義
$allowed_files = array('include.php', 'file1.php', 'file2.php');

if( !in_array($target_path, $allowed_files) ) {
    echo 'ERROR: 指定されたファイルは存在しません';
    exit;
}

include($target_path);
?>

この実装であれば、リストにないファイルは包含されないため、ファイル包含脆弱性は発生しません。

ログファイルを利用した攻撃

ローカルファイル包含脆弱性が見つかった場合、直接悪意のあるファイルをアップロードできなくても、サーバーのログファイルを悪用できる可能性があります。Web サーバー(例:Apache)は、アクセスログやエラーログにリクエスト内容を記録します。

攻撃者は、ユーザーエージェントや URL パラメータに PHP のコードを埋め込み、エラーを発生させてログに記録させます。その後、ファイル包含脆弱性を用いてそのログファイルを読み込ませることで、埋め込んだコードを実行させます。一般的なログのパスには以下のようなものがあります。

  • /var/log/apache2/access.log
  • /var/log/httpd/error_log

ログに記録されたコードが含まれることで、攻撃者はサーバー上で任意のコードを実行し、制御権を取得できます。

PHP ラッパープロトコル

PHP には、ファイルシステム操作関数で使用可能な様々なラッパープロトコルが用意されています。ファイル包含脆弱性の文脈では、以下のようなプロトコルが悪用されることがあります。

  • file://: ローカルファイルシステムへのアクセス
  • php://: 入力/output ストリームへのアクセス(例:php://input
  • data://: データストリームの直接指定(RFC2397)
  • expect://: コマンド実行プロセスとの相互作用

これらのプロトコルを理解することは、脆弱性の診断および対策において重要です。

実証実験:フレームワークのログ機能悪用

特定の CMS やフレームワークでは、エラー処理機構がログファイルに詳細な情報を記録する設定になっています。この挙動を利用した攻撃シナリオについて説明します。

対象環境として、ロギング機能を持つ Web アプリケーションを想定します。まず、存在しないリソースへアクセスするなどして、アプリケーションにエラーを発生させます。この際、リクエストパラメータに悪意のあるコードを含めておきます。

例えば、テンプレートエンジン構文を利用して、以下のようなペイロードを送信します。

{@eval($_REQUEST['cmd'])}

アプリケーションがこの入力内容をエラーメッセージとしてログファイルに書き込んだ場合、ファイル包含脆弱性を通じてそのログファイルを読み込むことで、送信したコードがサーバー上で実行されます。

ログファイルのパスが特定できれば、Web シェル管理ツールなどを用いて接続し、サーバーの制御権を取得することが可能です。この手法は、ファイルアップロード機能がない環境でもリモートコード実行を実現する有効な手段となります。

タグ: php-security file-inclusion lfi rfi log-poisoning

7月27日 18:06 投稿