今回は、リクエストデータを受け取る変数の扱いについて注意すべきポイントを挙げて説明します。扱いを誤ると、脆弱性を作ることになりかねないので注意してください(編集部)
第37回から、PHPの実行時設定の中でも、デフォルトのままにしておかない方が良い項目を紹介し、どのように設定を変更すべきか解説しています。前回に引き続き、WebブラウザなどのHTTPクライアントが送信してくるデータ(以下リクエストデータ)の取り扱いに関係する設定について解説します。
Webアプリケーションを作るときに、リクエストデータの扱いを誤ると、攻撃を許すような欠陥、つまり脆弱性を作ってしまいます。本来ならその扱いは、アプリケーション開発者が制御するべきものですが、Webアプリケーションに特化しているPHPでは、言語としてリクエストデータの扱いをサポートしています。これにより開発者は楽をすることができるのですが、その方法が必ずしも正しいものではなかったため、脆弱性を生み出す要因になってしまっています。
前回解説したマジッククオート機能も、開発者を助けるためのものでした。この機能はリクエストデータのクオート文字を自動的にエスケープするものです。SQLインジェクションという典型的な攻撃をPHP側で防ぐことを目的としています。
しかし、安直にすべてのリクエストデータをエスケープしてしまうため、SQLとは無関係なデータがエスケープされてしまったり、互換性の問題などを引き起こします。また、すべてのSQLインジェクションを完全に防げるものでもありませんでした。
つまり、SQLインジェクションへの対処法としては間違ったものだったと言えます。そのため、今後この機能は削除されることになっています。開発者を助けるために考え出されたものの、正しい方法でなかったがために、バグや脆弱性の温床になってしまったということです。
同じような経緯をたどっているのが、リクエストデータのグローバル変数への格納機能です。マジッククオートと違い、現在ではデフォルトで無効になっているので、設定を気にする必要はありません。しかし、互換性確保などの理由で、設定を有効にしなければならないこともあるかもしれません。「こういう機能があって、今は無効になっている」ということを頭の隅に入れておいた方がよいでしょう。
もともと、PHPではリクエストデータはグローバルな変数に格納するようになっていました。例えば「foo=bar」というリクエストであれば、「$foo」という変数に「bar」という文字列を格納します。
ただし、これは過去の仕様です。前回、curlコマンドを使って配列変数「$_GET」に文字列を格納する例をお見せしました。今はこのように、あらかじめ決まっている配列変数にリクエストデータを格納するようになっています。
この動作の違いは「register_globals」ディレクティブで制御できます。前述の通り、今ではデフォルトはOffになっていますが、動作を確認するためにphp.iniを開いて、Onに設定してみましょう。
register_globals = On
この状態で、次のスクリプトをreg_globals.phpというファイル名で、PHPが実行できる公開ディレクトリに配置します。
<?php if ($foo == '123') { $check = True; } if ($check) { echo "foo is 123.\n"; } ?>
reg_globals.phpに記述した処理は、非常に単純なもので、$fooの内容が123ならば表示するというだけのものです。前回同様にcurlコマンドで確認してみます。
$ curl "http://www3026ub.sakura.ne.jp/reg_globals.php?foo=123" foo is 123. $ curl "http://www3026ub.sakura.ne.jp/reg_globals.php?foo=abc" (何も表示せず)
このように、fooとして送信したデータの内容によって、サーバ側の振る舞いが変わることが確認できます。自動的に変数に値を格納してくれることは一見便利です。しかし、落とし穴があります。例えば、次のようなリクエストをサーバが受け取ると、開発者が意図しない動作を見せます。
$ curl "http://www3026ub.sakura.ne.jp/reg_globals.php?check=1" foo is 123.
このリクエストを送ると、$checkという変数に「1」という文字列が設定されます。その一方で、$fooの値を送信していないため、最初のif文のブロックは実行されません。そのため、次のif文ではそのままリクエストデータの「1」が評価されます。PHPでは空でない文字列は真(True)なので、「foo is 123.」と表示されることになります。
つまり、最初のif文によって$checkが初期化されないケースがあるというのが、このスクリプトの問題点です。変数の初期化を怠ると、変数と同じ名前のリクエストデータが来たときに、そのまま使われてしまうため、意図しない動作をします。もちろんこれはregister_globalsがオンの場合にのみ発生しうる問題です。
リクエストデータは、通常はHTMLフォームから生成するものですが、curlコマンドで実際にやってみたように、フォームとは無関係に意図的に送信できるものです。初期化しない変数は、外部から自由に値を設定できるということになります。プログラムの内容によっては、重大な脆弱性を作ってしまう恐れがあるということが、お分かりいただけると思います。
register_globalsがオンの状態であるときにこういった脆弱性を排除するには、プログラムで使う「すべての」変数を明示的に初期化しなくてはなりません。1つでも初期化を忘れるとそこが穴になるため、register_globalsは脆弱性の温床を作り出しているといえます。現在では、グローバル変数への登録は標準でオフになり、リクエストデータは特定の変数の配列として格納するようになっています。
以上の設定の確認が済んだら、register_globalsディレクティブの設定をOffにすることを忘れないようにしてください。
「register_globals」のほかに、その動作を制御するディレクティブ「variables_order」にも注意しましょう。グローバル変数に値を設定するとき、どの値を優先するかを決めるディレクティブです。
グローバル変数に設定できるデータは、GETメソッドによるリクエストデータだけではありません。環境変数(E)、GETメソッドによるリクエストデータ(G)、POSTメソッドによるリクエストデータ(P)、クッキーで送信されたデータ(C)、サーバ変数(S)の5種類があります。サーバ変数はApache HTTP Server(以下Apache)から渡される情報です。
variables_orderは、この5種類のデータの優先順位を決めるディレクティブです。前述のカッコ内の文字を使って、順位を指定します。例えば、デフォルト値である「EGPCS」では、すべてのデータを変数に格納します。また、先に挙げた5種類のデータの間で名前が重複した場合も、ここで設定する優先順位の通りに値を設定します。左から右へ、順番にデータを上書きすると考えれば良いでしょう。つまり右側にあるものが優先となります。
次のようなスクリプトをhostname.phpという名前で保存し、PHPスクリプトが実行可能なディレクトリに配置してください。動作を確認してみましょう。
<?php echo $HOSTNAME; ?>
$HOSTNAMEという変数の値を返すだけのスクリプトです。このプログラムで設定した変数のほかに、環境変数に「HOSTNAME」という項目があります。リクエストデータがなければこのデータが返ってくるはずです。
$ curl http://www3026ub.sakura.ne.jp/hostname.php www3026ub.sakura.ne.jp $ curl "http://www3026ub.sakura.ne.jp/hostname.php?HOSTNAME=foo" foo
GETメソッドによるリクエストデータが変数の値を上書きしていることが分かります。「variables_order」からEを取り除いて、「GPCS」にすると次のようになります。
$ curl http://www3026ub.sakura.ne.jp/hostname.php $ curl "http://www3026ub.sakura.ne.jp/hostname.php?HOSTNAME=foo" foo
スクリプトの変数に環境変数の値を登録することがなくなったと確認できます。なお、POSTメソッドを使ったデータ送信もcurlコマンドから実行できます。GETメソッドでは、URLに文字列を埋め込んで値を渡しますが、POSTメソッドを使うときは、設定する値はURLとは別に送信します。そして、デフォルトでは、GETメソッドで設定した値よりも、POSTメソッドで設定した値が優先します。
$ curl -X --data HOSTNAME=bar "http://www3026ub.sakura.ne.jp/hostname.php?HOSTNAME=foo" bar
variables_orderディレクティブの設定によって、スクリプトがどのように動作するかお見せしました。これまでは、register_globalsディレクティブが有効になっているという前提で説明してきましたが、このディレクティブが無効になっているときも、variables_orderディレクティブの設定内容はスクリプトに動作に影響を与えます。ここから先はregister_globalsをオフにして解説を進めます。
前回説明したように、HTTPクライアントがGETメソッドで送信してきたデータは、$_GETという特殊な配列変数に入ります。そして、register_globalsの設定を有効にすると、GETメソッドを使って、好きな変数に好きな値を設定できることは今回解説しました。
先ほど、スクリプトの外部から来るデータとして、環境変数、GETメソッドによるリクエストデータ、POSTメソッドによるリクエストデータ、クッキーで送信されたデータ、サーバ変数の5種類があると紹介しました。そして、PHPには、これら5種類のデータを自動的に格納するスーパーグローバル変数という変数があります。環境変数は$_ENV、GETメソッドによるリクエストデータは$_GET、POSTメソッドによるリクエストデータは$_POST、クッキーで送信されたデータは$_COOKIE、サーバ変数は$_SERVERに入ります。
register_globalsが無効であるとき、variables_orderの設定値はスーパーグローバル変数に値を格納するかどうかを決めるものとなります。register_globalsが有効であるときのように順番が関係することはありませんが、例えば「GPCS」であれば、$_ENVという変数は存在しないことになります。
存在しなくなるだけならvariables_orderを積極的に使う意味はないと思う方もいるかもしれません。しかし、不要な変数を登録しないようにすると、少しでも処理性能を高めることができます。環境変数は「getenv()」という手段でも取得できますので、Eを取り除いて「GPCS」としておきましょう。
環境変数を変数に登録するときは、それなりにコンピュータリソースを消費するようです。そこで、「auto_globals_jit」というディレクティブで消費を抑えられるようにもなっています。これは、スクリプトの実行開始時には$_ENVと$_SERVERは作成せず、必要になったときに(Just In Time:JIT)作成するという設定です。この機能は標準でオンになっています。
また、デフォルトでは、スーパーグローバル変数のほかにGETメソッドのリクエストデータを格納する変数として、$HTTP_GET_VARSという変数も利用できます。これを制御するのが「register_long_arrays」ディレクティブです。ただし、この変数はすでに非推奨となっており、次のバージョンであるPHP 5.4で削除される予定です。互換性確保などの事情でもない限り、使わないようにしましょう。
コマンドライン版のPHPにも、標準で作成されるグローバル変数があります。$argc、$argvの2種類です。これはコマンドライン引数の値を参照するために使うものです。Apacheに組み込んだPHPでも、GETメソッドの内容を格納しますが、必要ありませんので、この変数の登録もオフにします。これはディレクティブ「register_argc_argv」で設定します。
ここまでの内容をまとめると、php.iniには次の設定を記述すべきと言えます。
memory_limit = 32M max_execution_time = 30 expose_php = Off magic_quotes_gpc = Off variables_order = GPCS register_long_arrays = Off register_argc_argv = Off
次回も引き続き、リクエストデータに関する設定について説明していきます。
Copyright © ITmedia, Inc. All Rights Reserved.