PHPでのFile Inclusion


今日の日記では、以下のようなPHPコードへの攻撃について書きます。

<?php
include "/path/to/". $_GET['file'];

セキュリティ本でよく紹介されるのは、file=../../etc/passwd のようなクエリストリングを与える攻撃です(合せて、末尾にNUL文字を付ける攻撃方法もよく紹介されます)。

ただし、当然のことながら、/etc/passwd の中身を外部の攻撃者が制御することはできないため、攻撃者が任意のコマンドを実行することはできません。

/etc/passwd だけでなく、多くのサーバ上のファイルは外部から内容を制御できません。しかし、いくつかのファイルは、外部から制御することができます。攻撃者は、このようなファイルにPHPコマンドを埋め込み、それを include する可能性があります。

外部から制御可能なファイル

外部から内容を制御できるファイルには、ログファイル、セッションファイル、ファイルアップロード一時ファイルがあるようです。

参考:Open_basedir confusion - PHP Security Blog

この辺り、一般的には良く知られているのかもしれませんが、私は詳しく知らなかったので、少し調べてみました。

以下では、PHP5、ApacheLinux の環境を前提としています。

ログファイル

ログファイル(アクセスログ)にPHPコマンドを仕込んだ例です。

$ tail /var/log/httpd/access_log
・・・
192.168.1.2 - - [15/Feb/2007:23:15:57 +0900] "GET <?php echo 'foo'; ?>" 400 226
・・・

ファイルパスはhttpdの設定によって変わりますが、概ね想像がつくでしょう。telnetで80番portを叩くことで、概ね任意のコマンド文字列をログ行に含めることができます。

ただし、デフォルトではPHPの実行ユーザには、アクセスログを参照(includeなど)する権限はありません。その場合、攻撃者は、アプリケーションログなど参照権限があるファイルのincludeを試みるかもしれません。

セッションファイル

セッションファイルにPHPコマンドを仕込んだ例です。

$ cat /tmp/sess_セッションID
hoge|s:19:"<?php echo 'foo';?>";

セッションをデフォルトの「ファイル保存」にしており、外部入力変数をそのままセッション領域に保存するようなプログラムが存在する場合は、この種の攻撃が成功する可能性があります。

セッションファイルでは、パーミッションの問題はありません。

PHPの設定(session.save_path)によってディレクトリが変わりますが、/tmp であることが多いと思われます。なお、ファイルパスのうち「セッションID」の部分は、攻撃者にとっては既知です。

ファイルの内容はシリアライズされた値ですが、ほぼ「そのまま」の内容なので、PHPコマンドの挿入に不都合はありません。

ファイルアップロード一時ファイル

一時ファイルにPHPコマンドを仕込んだ例です。

$ cat /tmp/phpbKubfg
<?php echo 'foo';?>

ファイルの内容は、アップロードを試みたファイルの内容そのままです。

この攻撃の場合、攻撃対象のサイト上に、ファイルアップロード機能が存在する必要はありません。ファイルがアップロードされると、そのプログラムがファイルアップロード処理を行なうものでなくても、自動的に一時ファイルが作成されるからです。

一時ファイルの場合も、パーミッションの問題はありません。

攻撃者にとっての問題は、ファイルパスの推測です。また一時ファイルは、スクリプト終了までのわずかな時間しか存在しないため、攻撃のタイミングも問題になります。

ファイルの置かれるディレクトリは、PHPの設定(upload_tmp_dir)もしくは環境変数(TMPDIR)によって決まります。ファイルパス全体は、mkstemp関数によって生成されます。上記で「bKubfg」の部分の推測は困難だと思われます。

ただし、PHP4の場合、/tmp/phpXXX.tmp のようなファイルパスとなり、推測はPHP5より容易なようです(参考)。

対策

いちおう対策についても書いておきます。includeするファイルパスが攻撃者により制御される時点でアウトですので、それを防ぐのが対策の基本です。

  • パス値のvalidation
    include(やrequire)に与える値を検証し、不正な値ならエラーとする。極力、外部入力変数をincludeなどに与えない。
  • open_basedir関連
    保険的な対策。open_basedirでインクルードするファイルパスを制限する(関連:allow_url_fopen allow_url_includeなど)。
  • register_globals関連
    1/16の日記を参照

最後にPHP言語についても、少し書いておきます。

ログ、セッション、アップロード一時ファイルのうち現実的なのは、セッションファイルを利用したものでしょう。

まず言えるのは、外部からセッションファイルのパスが比較的容易に推測できるのは、望ましくない仕様だと言えると思います。

また、PHPは、ASPなどと同じく、HTML中にコマンドを埋め込めるタイプのスクリプト言語であるため、この種の問題が発生しやすいと思われます。実際、以下の内容のセッションファイルを include した場合、<?php 〜 ?> の部分のみがコマンドとして解釈され、実行されます。

hoge|s:19:"<?php echo 'foo';?>";

手軽さは失われますが、ファイルの先頭行がある一定の書式の場合のみスクリプトとして解釈する、などの制約を言語に持たせれば、攻撃者が付け入る隙は減るでしょう。