エラーメッセージをどう扱うか?仕事で使える魔法のLAMP(41)

今回は、スクリプト実行時にエラーが発生したことを知らせるメッセージの扱い方を説明します。開発者にとっては重要な情報ですが、攻撃者にとってもヒントとなってしまうので、場面に応じて扱いを切り替える必要があります(編集部)

» 2012年02月02日 00時00分 公開
[山口晴広株式会社イメージズ・アンド・ワーズ]

本番環境ではエラー表示は不要

 第37回から、PHPの実行時設定の中でもサーバ全体にかかわる設定、つまりphp.ini)で設定するものについて解説しています。特に、初期設定のままにしておかない方が良いものを選んで、変更すべき理由と、変更する方法を解説してきました。

 前回までの3回では、リクエストデータの取り扱いに関する設定を解説しました。この設定を誤ると、思わぬところに脆弱な点ができてしまい、セキュリティの面で問題になってしまいます。

 今回のテーマは、エラーログ関係の設定項目です。PHPスクリプトを実行したとき、エラーが発生すると、エラーの内容を説明するメッセージがWebブラウザ上に現れてしまいます(図1)。スクリプトの文法エラーだけでなく、存在しない変数や関数へのアクセスといったバグを原因とするエラーでも、発生するとその内容を説明するメッセージが出てきます。メモリ不足のような例外が発生したときも同じことです。

図1 メモリが足りなくなると、エラーが発生し、このメッセージがそのままWebブラウザに表示される。 図1 メモリが足りなくなると、エラーが発生し、このメッセージがそのままWebブラウザに表示される。クリックすると拡大

 エラー発生を知らせる警告は、アプリケーション開発中やテスト中には、アプリケーションの問題点を知り、バグを潰すために役立つ情報となりますが、実際の本番環境では表示すべきものではありません。まず、Webアプリケーションの利用者が戸惑います。そして、ファイルやディレクトリの構造、PHPアプリケーションの中身や、バグの存在など、攻撃者を喜ばせるようなヒントを与えることになります。つまり、セキュリティ上のリスクになり得るのです。

 では、エラーメッセージの出力を止める方法はあるのでしょうか。PHPでは、「display_errors」ディレクティブの設定を変更することで、エラー表示を出すか、あるいは止めるかを選択できます。開発中は設定値をOn(エラーを表示する)にしておいたとしても、本番環境では必ずOff(エラーを表示しない)に変えましょう。以降の説明では、本番環境を想定してdisplay_errorsディレクティブの設定をOffにしたものとして説明を続けます。

余計なヒントは与えない

 これまでも、第38回などで、サーバ上で動作しているのがPHPスクリプトであることを隠すための設定方法をいくつか紹介しています。さらに、display_errorsの設定をOffにすることで、Webブラウザなど、表に現れる情報を調べても、動作しているのがPHPスクリプトであるとは分からなくなります。

 PHPスクリプトを実行していることを隠す方法をまとめると、Apache HTTP Server(以下Apache)の設定を変更して、拡張子を「.php」ではなく「.html」などにしたPHPファイルを実行可能にすること。Apacheの設定ディレクティブ「ServerTokens」の設定を「Prod」にして、ApacheやPHPのバージョンを隠すこと。PHPの設定ではexpose_phpの設定をOffにして、ヘッダ情報に、PHPを使っているという事実を入れないこと。そして、先ほど説明した、display_errorsの設定をOffにして、余計なヒントになりかねないエラーメッセージの表示を止めることです。

 display_errorsににている部分を制御するディレクティブに、「display_startup_errors」があります。display_errorsはPHPスクリプト実行中のエラー表示の可否を決めますが、display_startup_errorsはスクリプト実行前の、初期化時に発生するエラーに関係するディレクティブです。この設定も、余計なヒントを与えないために、本番環境ではOffにしておくべきでしょう。ちなみに、初期化時に発生するエラーとは、前回に例として挙げたリクエストデータのサイズ超過などによって発生するエラーです。

 前回、POSTメソッドで受け取るリクエストデータの最大サイズを指定する「post_max_size」ディレクティブについて解説しました。設定値を超えたデータをサーバに送りつける例もお見せしました。しかし以下のように、エラーの発生を知らせるメッセージは何も出てきませんでした。

$ curl -X POST --data 'foo=bar&baz=12345' http://www3026ub.sakura.ne.jp/print_post.php
Array
(
)

 これは、display_startup_errorsディレクティブの設定値がOffになっているからです。このディレクティブの設定値は初期状態がOffになっています。そこで、試しに設定をOnに変更して同じテストを実行してみましょう。

 リクエストデータの長さが制限を超えていることを知らせるエラーメッセージが返ってきます。先に説明したように、この設定は標準ではOffになっていますが、アプリケーション開発時やテスト時はOnに変えておくべきです。初期化時に問題が起きたとしても、手掛かりがないと、問題解決もままなりません。もちろん、この設定は本番環境ではOffにしましょう。

 直前にお見せした例では、コンソールからcurlコマンドを使って大きなリクエストデータを送りました。帰ってきたエラーメッセージをよく見ると、HTML形式になっています。ブラウザで表示することを想定しているのです。ただしこれは、ApacheにPHPを組み込んでいるときに見られるエラーメッセージです。コマンドライン版のPHPではHTMLではなくテキストのエラーメッセージになります。ちなみに、ApacheにPHPを組み込んだときでも「html_errors」ディレクティブの設定をOffにすると、エラーメッセージがテキスト形式になります。

エラー情報はログに蓄積

 エラーの情報は、Webブラウザに表示するだけでなく、ログに記録することもできます。Webブラウザへの表示はその場限りで、Webブラウザを閉じてしまえば情報は消えてしまいます。

 一方、ログであればすべての情報を蓄積しておいて、あとから確認することができます。開発環境だけでなく、本番環境でも、ログ記録の機能は利用すべきです。特に、本番環境ではdisplay_errorsをOffにしますので、エラーが発生したとしても、その事実をつかむにはログに情報を記録させるように設定するしかありません。

 ログ記録機能は「log_errors」ディレクティブをOnにすると動き出し、Apacheのエラーログファイルを記録するようになります。すなわちApacheの「ErrorLog」ディレクティブで指定しているファイルです。例えば図1で示したようなエラーなら、その情報は次のような形式でエラーログファイルに残ります。

[Thu Feb 02 00:30:42 2012] [error] [client 49.212.32.64] PHP Fatal error:  Allowed memory size of 33554432 bytes exhausted (tried to allocate 32 bytes) in /srv/httpd/www3026ub.sakura.ne.jp/webspace/memtest.php on line 2

 スクリプト実行前の初期化時に発生するエラーの情報は、前述の通り標準の状態ではWebブラウザには表示されませんが、エラーログには残ります。post_max_sizeの指定を超えたサイズのデータをサーバが受け取ると、次のようなログが残ります。

[Thu Feb 02 00:46:58 2012] [error] [client 49.212.32.64] PHP Warning:  Unknown: POST Content-Length of 17 bytes exceeds the limit of 16 bytes in Unknown on line 0

 Apacheのエラーログへの記録は、PHPではなく、PHPからログ内容を受け取ったApacheが処理しています。そのためログの記録先は、Apacheの設定によって決まっているファイルになりますが、他のファイルに記録することもできます。「error_log」ディレクティブにファイル名を指定すると、PHPによって指定ファイルにログが出力されます。

 ここで注意しなければならないのは、Apacheのエラーログファイルはroot権限で書き込むのに対し、error_logで指定したログファイルにはApacheの実行権限でアクセスするということです。ログファイルを書き込むディレクトリのオーナーやパーミッションを正しく設定しておかないと、ファイルにログを書き込めませんので、十分注意してください。

 筆者の個人的な意見を言うと、素直にApacheのエラーログを使う方が良いと思います。バーチャルホストごとに設定できることやファイルへのパーミッション設定の手間を考えると、標準設定で使う方が楽だと思います。

 ただし、Apacheのエラーログにあまりに多くのログが残るような場合は、PHPのログは分離したいと考える方もいるでしょう。そのときはphp.iniで設定するのではなく、各バーチャルホストごとにApacheのディレクティブ「PHP_Value」(第36回参照)を利用して、バーチャルホストごとにログファイルを設定するとよいでしょう。ディレクトリごとにログファイルを分けることもできます。

どのエラー情報が必要なのか?

 これまでに例として挙げてきたエラーを知らせるログをよく見ると、冒頭が「PHP Fatal error」となっているものと「PHP Warning」になっているものの2種類があることが分かります。UNIX/Linuxのsyslogにはerror、warning、noticeなど、エラーの深刻さを示すレベルがあることをご存じの読者も多いと思いますが、PHPのログにも、同じようなレベル分けがあるのです。

 とはいえ、PHPの場合はレベルというよりも、種類といった方が適切です。syslogのレベルは上下関係が成立していて、例えば「warning以上のエラー」というような表現で設定したり、技術者同士コミュニケーションしたりできますが、PHPではそうは行きません。それぞれのログの種類に上下関係はないのです。

 PHPがどのような種類のログを残すのかということは、PHPのマニュアルで確認できます。「エラー処理」の中の「定義済み定数」の部分をご覧ください(図2)。それぞれのエラーの種類は、定義済みの定数として表すようになっています。例えば「E_ERROR」は1、「E_WARNING」は2、「E_PARSE」は4、という具合です。この値を指定して、どの種類のログを出力させるかを設定できます。

図2 PHPがログに残すメッセージには4種類ある。 図2 PHPがログに残すメッセージには4種類ある。クリックすると拡大

 定数の値が、1、2ときて次が3ではなく4になっていますが、これはビットフィールドで保持する値であるからです。1、2、4のそれぞれを2進数にすると、1、10、100となります。1になっている部分(けた)が、ログの種類を表しているのです。例えば、「E_PARSE」と「E_ERROR」を選択したと考えてください。それぞれを指し示す2進数のけたを1にすると、101になります。これは、十進数にすると5です。5を設定すると、この2つを指定したことになります。こういった値の使い方を、ビットフィールドと呼びます。

 この2進数の計算は、ビット演算子で表現できます。「E_PARSE」と「E_ERROR」であれば「|」というビット演算子で結合し、「E_PARSE | E_ERROR」のようにします。すべての種類のログを出力したいときは、定数をすべて「|」で連結すれば良いのですが、PHPはかなり多くの種類のログを出力するので、ビット演算ですべてを表現するのは面倒です。そこで別途、「E_ALL」という、すべての種類のログを表す定数が用意されています。

 PHP 5.3系では、正確には「E_ALL」はすべてのログではなく「E_STRICT」が除いたものを指します。すべてのログを指定するには「E_ALL | E_STRICT」としなければなりません。5.4系になると「E_ALL」だけですべてのログになります。

 開発中はすべてのログを表示するようにしておき、ささいな警告も見逃さない環境を作るのが理想です。最終的には警告が出なくなるように開発することで、バグをつぶせます。しかし、既存のアプリケーションを利用するときなどは、警告が大量に出ることがあります。このような場合は、ログ出力を抑止したいと考えることもあるでしょう。

 そういったときは「E_ALL」からいくつかの種類のエラーを除いて、エラーを出力させるようにするのです。この指定もビット演算子でできます。「E_ALL & ~E_NOTICE」とすると「E_ALL」から「E_NOTICE」を取り除くという意味になります。なお、「E_ALL | E_STRICT」から取り除きたい場合は、「(E_ALL | E_STRICT) & ~E_NOTICE」とします。「|」と「&」では、「&」の方が優先順位が高いので、カッコで優先順位を変えなくてはならないためです。

 E_NOTICEと指定すると、エラーとは言えないが、「バグにつながる可能性がある」という程度の警告も出します。出力が大量になるようなら、上記の手法で絞り込むと良いでしょう。

まとめ

 ここまで説明してきた内容を反映させると、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
post_max_size = 64K
max_input_vars = 100
upload_tmp_dir = /home/uploads
upload_max_filesize = 1
display_errors = Off
display_startup_errors = Off
log_errors = On
error_reporting = E_ALL | E_STRICT

 次回は、ファイルの取り扱いを決める設定について解説します。

著者紹介

株式会社イメージズ・アンド・ワーズ
代表取締役
山口晴広(やまぐち はるひろ)



「仕事で使える魔法のLAMP」バックナンバー

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。