2016年12月16日金曜日

PHPの全バージョンの挙動をApacheモジュールとして試す

この投稿は PHP Advent Calendar 2016 の16日目の記事です。

エグゼクティブサマリ

PHPのバージョン間の挙動の違いを調査するツールとして、@hnwによるphpallや、それを改造したphpcgiallがあったが、現実のPHPの利用環境とは違いがあり、検証の妨げになる場合があった。このため、PHPのバージョン毎にApacheを異なるポートで動かすことにより、全てのバージョン(229種)のPHPをApacheモジュールとして動作させることに成功し、modphpallと命名した。modphpallはPHPの検証に有効であることを、キャリッジリターンのみで起こるPHPヘッダインジェクションを用いて確認した。

はじめに

昨日の日記では、PHPの全バージョンをCGIモードで試す phpcgiall について紹介しました。HTTPヘッダインジェクションやセッションの挙動について確認するためには、CLI版のPHPでは限界があり、ウェブスクリプトとして全バージョンのPHPを試すことができる phpcgiall は有効であるものの、ヘッダの微妙な挙動については、Apacheモジュール版と差異があることがわかりました。

それに、やはりPHPの本流はCGI版ではなく、Apacheモジュール版であろという思いから、なんとかphpallのApacheモジュール版はできないだろうかと考えるようになりました。
基本的にアイデアは3つありました。
  • Apacheに同時に複数バージョンのPHPを組み込めるようにする
  • Apacheに組み込むPHPを次々に切り替える
  • Apacheのインスタンスを複数起動して、それぞれ異なるバージョンのPHPを動かす
上の2つは、そもそも実現が難しかったり、性能が悪かったり等の予想がありました。そこで、PHPのバージョン毎に異なるポート番号でApacheを起動することで、Apacheモジュール版PHPの全バージョンを一つのサーバーで動かすことを考えました。modphpallと命名しました。

PHPのビルド

既にCLI版とCGI版のPHP版があるので、Apacheモジュール版のPHPをビルドすることには、さほど困難はありませんでした。ただ、時間はそれなりにかかりますね。
PHP 3.0.18(PHP 3の最終バージョン)から最新のPHP 7.1.0 まで、229バージョンのPHPバイナリができあがりました。

Apacheのバージョン

使用するApacheバージョンは、あまり良く考えずに、PHP 3とPHP 4はApache1.3.42(1.3系の最終バージョン)、PHP 5以降はApache2.2.31(2.2系の最新)を用いましたが、これで特に問題は出ていません。

ディレクトリ構成

ディレクトリ構成は下記のとおりです。

~/apache1.3PHP 3, 4向け。Apache 1.3.42のバイナリ、コンフィグレーション
~/apache2.2PHP 5, 7向け。Apache 2.2.31のバイナリ、コンフィグレーション
~/phplibPHP 3, 4, 5, 7のApacheモジュール版バイナリ
~/Dropbox/wwwドキュメントルート。PHPスクリプトを配置

設定ファイル

PHPのバージョンの数だけ、ポート番号を変えてApacheを起動することになるため、設定ファイル(httpd.conに相当するもの)は、簡単なスクリプトでジェネレートしています。
以下のような設定フアイルのテンプレートをPHPのバージョン毎に用意しています。
Include "conf/httpd_header.conf"

Listen %PORT%
LoadModule php5_module        /home/ockeghem/phplib/%PHPVER%.so
ServerName      phpcgiall:%PORT%
PidFile "logs/httpd_%PHPVER%.pid"

Include "conf/httpd_tail.conf"
そして、スクリプトで%PORT%と%PHPVER%を書き換えて、以下のような設定ファイルを生成します。
Include "conf/httpd_header.conf"

Listen 49364
LoadModule php5_module        /home/ockeghem/phplib/php-5.6.29.so
ServerName      phpcgiall:49364
PidFile "logs/httpd_php-5.6.29.pid"

Include "conf/httpd_tail.conf"
そして、同じスクリプトで、以下のような形で起動します。
apache2.2/bin/httpd -f conf/httpd_php-5.6.29.conf
起動後、phpinfo()の表示例を示します。


使用メモリ量

同一サーバー内で229ものApacheインスタンスを起動するので、メモリ使用量が心配になります。手元の環境は、Ubuntu 12.04LTS (32ビット)ですが、VMに割り当てるメモリを当初21Gバイトとしていました。現実には2Gバイト程度でも一応動くようです。現在、暫定的に8Gバイトのメモリを割り当てていますが、起動当初の状態は下記となります。
$ free
             total       used       free     shared    buffers     cached
Mem:       8266464    3794592    4471872          0      48444    1938060
-/+ buffers/cache:    1808088    6458376
Swap:      2093052          0    2093052
$

使ってみる

それでは、modphpallを試してみましょう。PoCは、昨日の日記で示したキャリッジリターンのみを用いたHTTPヘッダインジェクションです。
<?php
  header("Location: http://example.jp/\rSet-Cookie: a=b;");
PHP 5.3.0による実行結果は下記のとおりです。昨日同様、キャリッジリターン(0x0D)をsedにより[CR]と変換して表示しています。
$ curl --dump-header - http://localhost:49220/headerinj-cr.php | sed 's/\r/[CR]/'
HTTP/1.1 302 Found[CR]
Date: Thu, 15 Dec 2016 10:20:07 GMT[CR]
Server: Apache/2.2.31 (Unix) PHP/5.3.0[CR]
X-Powered-By: PHP/5.3.0[CR]
Location: http://example.jp/[CR]Set-Cookie: a=b;
Content-Length: 0[CR]
Content-Type: text/html[CR]
[CR]
めでたく(?)Set-Cookiヘッダの前にキャリッジリターンが出力されており、一部のブラウザ(IE、Google Chrome、Safari等)ではHTTPヘッダインジェクションが起こる結果が出ました。
他のバージョンも含めると、問題が発生するバージョンは下記となります(見やすくなるように表示の順序を入れ替えています)。
$ grep Set-Cookie *.log | sed 's/\r/[CR]/'
php-3.0.18.log:Location: http://example.jp/[CR]Set-Cookie: a=b;
php-4.0.0.log:Location: http://example.jp/[CR]Set-Cookie: a=b;
php-4.0.1.log:Location: http://example.jp/[CR]Set-Cookie: a=b;
...
php-4.4.9.log:Location: http://example.jp/[CR]Set-Cookie: a=b;
php-5.0.0.log:Location: http://example.jp/[CR]Set-Cookie: a=b;
php-5.0.1.log:Location: http://example.jp/[CR]Set-Cookie: a=b;
...
php-5.3.9.log:Location: http://example.jp/[CR]Set-Cookie: a=b;
php-5.3.10.log:Location: http://example.jp/[CR]Set-Cookie: a=b;
$
キャリッジリターンのみでHTTPヘッダインジェクションが起こるのは、PHP 5.3.10までであることがわかります。

まとめ

PHPの全バージョンの挙動をApacheモジュールとして試すことのできる modphpall を作成し、その成果として、キャリッジリターンのみでHTTPヘッダインジェクションを起こすPHPバージョンを確認しました。CGIモードとApacheモジュールの挙動の差は小さいものですが、その小さい差が問題に成る場合もあるので、今後のPHPの検証に役立てたいと思います。

0 件のコメント:

コメントを投稿

フォロワー

ブログ アーカイブ