なぜPHPアプリにセキュリティホールが多いのか?

第41回 PHP 5.3.4におけるセキュリティ上重要な仕様変更

この記事を読むのに必要な時間:およそ 3 分

PHP 5.3.4のリリースは2010年12月にリリースされました。このリリースにはセキュリティ上重要な変更が追加されています。

  • Paths with NULL in them (foo\0bar.txt) are now considered as invalid. (Rasmus)

パスにfoo\0bar.txtなどのようにNULLが含まれる場合は無効として処理される,とPHP 5.3.4のリリースノートには記載されています。PHP開発者の間でもあまり大きなニュースとして取り上げられていないので,この仕様変更をご存知でない方も多いと思います。2011年4月現在でもこの仕様変更はマニュアルには記載されていません。しかし,この修正はセキュリティ上非常に重要な意味を持っているので解説します。

仕様変更の必要性

PHP本体はC言語で記述されているため,ファイルを開く場合,最終的にはC言語のライブラリにファイル名が渡されます。C言語の文字列はバイナリセーフではなく,NULL文字(\0)は文字列の終端を表す文字になっています。一方,PHPの文字列はバイナリセーフであるためNULL文字は特別な意味を持ちません。

このPHP言語とC言語の文字列型変数の仕様の違いにより,脆弱なスクリプトが存在するとNULL文字を利用した強制ブラウズが行えるようになっていました。

サンプルコード1

<?php
// 拡張子をチェックしてpngファイルを送信する
if (preg_match('|\.png$|', $_GET['png_file'])) {
  echo readfile($_GET['png_file']);
}
?>

サンプルコード1には2つの問題があります。一つは..や最初の文字の/をチェックしていないため,システム上の任意のディレクトリにアクセスできる問題です。もう一つはNULL文字を利用した任意ファイルへのアクセス脆弱性です。この二つの脆弱性によりリモートユーザーはPHPがアクセスできるシステム上の任意ファイルにアクセスできてしまいます。

少なくとも.pngファイルだけにアクセスは制限されるのでは? と考える方も居るかも知れません。

  • preg_match('|\.png$|', $_GET['png_file']))

この行で拡張子をチェックしようとしていますが,このチェックは意味をなしません。preg_match関数はバイナリセーフであるためNULL文字を特別な文字列として取り扱いません。このため,最後の文字列が.pngであれば途中にNULL文字が含まれていても終端の.pngにマッチしてしまいます。

<?php
var_dump( preg_match('|\.png$|', "../../../etc/passwd\0.png"));
?>

このコードはint(1)を返し,.pngで終わる文字列として評価します。このため,

  • http://example.com/get_image.php?..%2F..%2F..%2Fetc%2Fpasswd%00.png

のようにアクセスすると,/etc/passwdファイルを読み取ることができました。サンプルコードの場合はフルパスでのアクセスもチェックしていないのでもっと簡単に,

  • http://example.com/get_image.php?%2Fetc%2Fpasswd%00.png

としてUNIX系OSのパスワードファイルを取得できてしまいます。

今までのPHPの防御

PHP 5.3.4以前のPHPでも,上記のようにセキュリティ上問題があるスクリプトがあっても問題の影響範囲を小さくする仕組みが用意されており,次の2つのphp.ini設定で利用できるようになっています。

  • safe_mode
  • open_basedir

セーフモード(Safe Mode)はphp.ini(sefe_mode=on)で設定し,PHPスクリプトファイルのユーザーID・グループIDと開こうとするファイルのユーザーID・グループIDが一致しない場合にファイルの利用を制限する機能です。フェイルセーフ対策としては有効な機能ですが,安全性を維持する機能として誤用されることが多かったため,次のメジャーリリース(PHP 5.4以降)では削除される機能になっています。

オープンベースディレクトリ(open_basedir)はPHPスクリプトファイルが開くことが可能なトップレベルのディレクトリを指定する機能です。例えば,サンプルコード1のように脆弱なスクリプトがあっても

  • open_basedir=/var/www/php-application

のように設定していれば,/var/www/php-application以外の/etc/passsなどのファイルには「PHPのファイル関数」を利用してアクセスできなくなります。

注意

PHPのファイル関数からアクセスできなくするだけで,データベースなどからシステム上のファイルにアクセスすることは可能です。open_basedirもsafe_modeと同様,問題が発生した場合の影響範囲を制限するフェイルセーフ対策です。

著者プロフィール

大垣靖男(おおがきやすお)

University of Denver卒。同校にてコンピュータサイエンスとビジネスを学ぶ。株式会社シーエーシーを経て,エレクトロニック・サービス・イニシアチブ有限会社を設立。
オープンソース製品は比較的古くから利用し,Linuxは0.9xのころから利用している。オープンソースシステム開発への参加はエレクトロニック・サービス・イニシアチブ設立後から。PHPプロジェクトでは,PostgreSQLモジュールのメンテナンスを担当している。

URLhttp://blog.ohgaki.net/

著書

コメント

コメントの記入