PHPの正しいエラー処理

エラーを無視しがちなPHPで安心ガードする、または「require strict;」 - uzullaがブログという記事を見ていて、エラーが発生した時に必ずエラーを表示する次のようなコードを見かけた。

<?php
// strict error bailout
function strict_error_handler($errno, $errstr, $errfile, $errline)
{
    die ("STRICT: {$errno} {$errstr} {$errfile} {$errline} ".PHP_EOL);
}
set_error_handler("strict_error_handler");

これだと、スクリプト内で発生したエラーがdieで本番でも必ず表示される。開発環境でエラーを表示するのはいいが、これでは本番環境でもPHPのエラーが直接表示されてしまう。これは、XSSの可能性を残すのでセキュアではない。元記事だとCLIに限定するということは特に書いてなかったので一応指摘する。

なぜセキュアではないのかは、PHPのdisplay_errorsが有効だとカジュアルにXSS脆弱性が入り込む | 徳丸浩の日記という記事に書いてあるとおりだが、上のようなエラー処理を行っている場合には例えば次のようなコードのクエリにx=<script>alert(1)</script>を指定した場合、XSSが発生する。

<?php

$a = array();            
$index = $_GET['x'];
$b = $a[$index];         

PHPで書いたコードを本番で運用する際には、display_errorsをoffにするのが定石なのにはこういう理由がある。しかし、冒頭で紹介したエラーハンドラを設定すると、display_errorsの値にかかわらず、必ずエラーが表示されてしまうため、セキュリティ上よろしくない。

まともなフレームワークを使っていれば、このへんは勝手にやってくれるはずだが、フレームワークなどを使わずにエラー処理する場合には次のようなコードを記述するのが望ましい。

<?php

set_error_handler(function($errno, $errstr, $errfile, $errline) {
    // エラーを例外に変換する
    throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
});

set_exception_handler(function($e) {
    // display_errorsの値によって処理を変更する
    if (ini_get('display_errors')) {
        echo '<pre>' . $e . '</pre>';
    } else {
        // エラーログに保存なりなんなりしてエラー画面表示
        // ...
        readfile('path/to/error.html');
    }
});

set_error_handlerの中では、エラーをErrorExceptionに変換して投げている。これをやると、エラーメッセージが表示されるだけではなくスタックトレースも表示されるため開発時に役に立つ。set_exception_handlerでは、display_errorsディレクティブがonになっていれば普通に例外を表示するし、そうでない場合には固定のエラー画面を表示するようになっている。これであれば、display_errorsがoffになっている本番環境でも問題なく運用できる。

まとめ

  • PHPのエラーをそのまま表示するのはセキュアではない