2014年4月11日金曜日

PHP考古学: PHP4.2.xではmb_eregは複数行モードで動作していた

大垣さんのブログエントリに刺激を得て、古いmb_eregの挙動を調査しました。その結果、 PHP4.2.x上のmb_eregは複数行モードで動作していたことが分かりましたので報告します。

前回までのまとめ

正規表現によるバリデーションでは ^ と $ ではなく \A と \z を使おう」にて、Ruby、Perl、PHPの正規表現では、^ と $ は、「行」の先頭と末尾を示していて、文字列の先頭と末尾を指定するには、\A と \z を使うべきであることを説明しました。そして、Rubyの場合はデフォルトが複数行モードであるので、^○○○$ という形で全体一致検索を指定したつもりでも、簡単にチェックをすり抜け重大な脆弱性に直結します。一方、PerlやPHPの正規表現はデフォルトでは単一行モードであるので、文字列末尾の改行をチェックできないという問題はあるものの、重大な脆弱性に直結するケースはあまりないと考えられることを指摘しました。

古いバージョンのmb_eregはデフォルトが複数行モードだった?

ところが昨日大垣さんの書かれたエントリ「なぜRubyと違い、PHPの正規表現で^$の利用は致命的な問題ではないのか?」には以下のように書かれていました。
実は古いmbregex(忘れるくらい古い4.xの時代の話です)はRubyと全く同じ動作をしていました。この動作はセキュリティ上の問題であるとして、現在の^は\A(データの先頭)と同じ、$は\Z(改行を含む終端にマッチ)に変更されました。
私がPHPに親しむようになったのはPHP5.2以降のことなので、「忘れるくらい古い4.xの時代の話」は知らないのですが、どうしても知りたいと思い調べてみました。
といっても、私の手元にはphpallにて PHP4.4.9とPHP5.0.0以降のバイナリはすべてありますが、それより古いPHPはありません。そこで、この機会に、すべてのPHP4もビルドすることを決意いたしました。phpall完全版とでも言いましょうかw

phall完全版

phpall完全版と言っても、ここからPHPの古いソースをダウンロードしてビルドするだけです。@hnwさんが指摘するように、PHP5の古いバージョンをビルドするのはコツがいります。
実は、PHP 5.0.0-5.0.3はgcc4 でコンパイルできないという問題点があります。gcc4でコンパイルするためには、http://bugs.php.net/bug.php?id=32150 の通り、Zend/zend_modules.h を修正する必要があります。
こんなときgcc3でコンパイルするのも一つの手ですが、MacOSX10.4や10.5ではgcc3が提供されていません。今後このような環境が増えてくるのではないでしょうか。
また、PHP 5.2.0-5.2.3は./configureの途中で下記のように怒られてしまいます。
configure: error: installation or configuration problem: C++ compiler cannot create executables.
原因は追いかけていませんが、autoconfでconfigureスクリプトを作り直すと問題なくバイナリのビルドまで通ります。
phpallコマンドでPHPの全バージョンの挙動を試すより引用
後者のPHP 5.2.0-5.2.3の問題については、独自の調査により新たな知見を得ております。お恥ずかしいことに実験ノートをきちんとつけていなかったので、いつ・どのように発見したかを示す証拠はないのですが、以下のようにg++を導入することにより、エラーなくビルドできるようになります(Ubuntu 12.04LTSにて確認)。
$ sudo apt-get install g++
この方法で、私はこれまでに200回2回ほどPHP5.2.0-5.2.3のビルドに成功しています。まぁ、C++コンパイラが動かないよというメッセージに素直に従っただけということですがw
PHP4については、gccのバージョンが上がりチェックが厳しくなったためにエラーになる箇所が複数ありました。具体的には、static変数をextern宣言している等です。これらは、ソースの方を修正しました。
先のアーカイブにはPHP3.0.18のソースもあったので、ついでにこれもビルドしました。ということで、PHP3.0.18、PHP4全て、PHP5全てを含む「phpall完全版」の誕生です。

試してみる

以下のソースで試してみました。
<?php
  $a = "abc\n123\ndef";
  var_dump(mb_ereg('^[0-9]+$', $a));
実行結果は下記となります。mb_eregが実装されたのはPHP 4.2.0以降なので、該当バージョンのみ示します。
$ phpall mbereg.php
php-4.2.0: int(1)
php-4.2.1: int(1)
php-4.2.2: int(1)
php-4.2.3: int(1)
php-4.3.0: bool(false)
php-4.3.1: bool(false)
php-4.3.2: bool(false)
...
php-4.3.11: bool(false)
php-4.4.0: bool(false)
...
php-4.4.9: bool(false)
php-5.0.0: bool(false)
...
php-5.5.11: bool(false)
php-5.6.0beta1: bool(false)
なんということでしょう! PHP 4.2.xでは、大垣さんの指摘のように、mb_eregが複数行モードで動いていたようです。
PHP 4.3.0でこの仕様は単一行モードに変更されていますが、その理由は、大垣さんの言われる「セキュリティ上の問題」というよりは、eregと仕様を合わせたということではないでしょうか。mb_eregはeregからの移行を想定していると思われますし、mbstring.func_overload = 4 とすると、ereg関数の呼び出しがmb_eregにオーバーロードされるわけで、eregとの互換性は重要です。

この問題による影響と対策

PHP 4.2.xを使っている環境では、mb_eregの全体一致検索に ^ と $ を使っていると、前述のような脆弱性となる可能性があります。^ と $ ではなく、\A と \z を使って全体一致検索を指定するようにしてください…というより、PHP 4.2はとっくの昔のサポートが終了しているので、最新のPHPに移行するようにしましょう。
…と、ここで気になってPHP 4.2がリリースされた時期を調べてみたのですが、
  • PHP4.2.0     2002年4月22日 
  • Windows XP 2001年10月25日(OEM)
いまとなっては化石のようなPHP4.2ですが、リリースされた時期はWindows XPよりも *新しい* のですね。いかに、Windows XPが長くサポートされてきたかをあらためて感じました。

まとめ

大垣さんの指摘に刺激を受けて、古いmb_eregの挙動を調査しました。その結果、PHP4.2.xのmb_eregは複数行モードで動作することを確認しました。
この結果に関係なく、全体一致検索には、\A と \z を用いるようにしましょう。また、PHP4.xを使っているサイト(まだ結構あります)は早急にPHPの最新版に移行しましょう。

2014年4月1日火曜日

フォロワー

ブログ アーカイブ