なぜPHPアプリにセキュリティホールが多いのか?

第37回 MOPS:PHPにおけるコード実行(1)

この記事を読むのに必要な時間:およそ 4 分

Perl互換正規表現

正規表現のコールバックを利用したコード実行は時折,脆弱性としてレポートされていました。時々アプリケーションの脆弱性としてレポートされているので,日本のブログでもこの正規表現のコールバックの問題を取り扱ってるブログを見かけたこともあります。本連載ではまだ取り扱っていなかった脆弱性なので,ほかの脆弱性より詳しく解説することにします。

PCRE(PERL互換正規表現)関数にはeモディファイアがあります。eモディファイアを利用すると正規表現にマッチした文字列にPHPの関数を適用することができます。

脆弱なPCREの利用

<?php
$var = '<tag>phpinfo()</tag>';
preg_replace("/<tag>(.*?)<\/tag>/e", 'addslashes(\\1)', $var);
?>

これを実行するとphpinfo関数が実行されてしまいます。これは<tag>(.*?)<\/tag>にマッチする文字列がPHPスクリプトとして評価されてしまうために起きる現象です。

正規表現

<tag>(.*)</tag>

が評価する文字列として

<tag>phpinfo()</tag>

を渡すと,

phpinfo()

がバックリファレンスの1番目にマッチした文字列になります。その結果,PHPがコードとして評価する文字列として

addslasshes(phpinfo())

が生成され,eval関数で実行した場合と同等に評価されます。この結果,意図しないphpinfo関数が実行されてしまいます。実はこのサンプルコードには間違いがあります。意図はどうあれ,addslashes関数のパラメータは文字列なので本来は

addslasshes('phpinfo()')

と処理されるよう

<?php
$var = '<tag>phpinfo()</tag>';
preg_replace("/<tag>(.*?)<\/tag>/e", "addslashes('\\1')", $var);
?>

と書くべきです。このコードを実行した場合はphpinfo()は文字列として扱われ関数呼び出しは発生しません。

ここで勘の良い読者は「ダブルクオートやシングルクォートがあるとどうなるのだろう?」と思ったと思います。実際に実行してみましょう。結果から分かるようにaddslashes関数をprintに書き換えます。

<?php
$var = "<tag>');phpinfo() //</tag>'";
preg_replace("/<tag>(.*?)<\/tag>/e", "print('\\1')", $var);
?>

これを実行すると

print('');phpinfo() //'

の文字列が生成されevalで評価されればphpinfo関数が実行されるはずですが,phpinfo()は実行されず。文字列が出力されます。

実行結果

');phpinfo() //

何故,コードとして評価されなかったかはPCREモジュールの中味を見れば分かります。

ext/pcre/php_pcre.c

                if (backref &lt; count) {
                    /* Find the corresponding string match and substitute it
                       in instead of the backref */
                    match = subject + offsets[backref<<1];
                    match_len = offsets[(backref<<1)+1] - offsets[backref<<1];
                    if (match_len) {
                        esc_match = php_addslashes_ex(match, match_len, &esc_match_len, 0, 1 TSRMLS_CC);
                    } else {
                        esc_match = match;
                        esc_match_len = 0;
                    }
                } else {

マッチした文字列に対してaddslashesの内部関数であるphp_addslashes_exを用いてエスケープ処理しています。このため,

print('');phpinfo() //')

とはならず,

print('\');phpinfo() //')

が評価されるのでphpinfo関数は実行されません。このコード実行は古くから知られているので知っている方も多いと思います。そもそも文字列を渡して処理した後の文字列を返すコールバック関数を定義する仕様であるため,この例のようなコードでは本来処理すべき文字列で構文エラーが発生するため,プログラムの開発途中でバグに気付くことが多いと思います。

もう少し現実的なコードは以下のようなコードでしょう。マッチした文字列をhtmlentities関数でHTMLエスケープしています。

<tag></tag>で囲まれた文字列をHTMLエスケープする

<?php
$var = '<tag><script>alert(1)</tag><script>alert(2)</script></tag>';
echo preg_replace("/<tag>(.*?)<\/tag>/e", "htmlentities('\\1', ENT_QUOTES, 'utf-8')", $var);
?>

出力結果

&lt;script&gt;alert(1)<script>alert(2)</script></tag>

正規表現に "?" がついており,(.*?) が最短一致になっているので上記のような結果になります。コード実行だけでなく,安易に正規表現を利用したセキュリティ処理を行うとセキュリティ問題の原因となるので注意が必要です。

eモディファイアが記載されていない場合でもコードが実行が可能な場合もあります。次のコードでは正規表現中の文字列に変数が利用されています。この変数に細工するとコード実行が可能になります。

<?php
$regexp = "<\/tag>/e\0";
$var = '<tag>phpinfo()</tag>';
preg_replace("/<tag>(.*?)$regexp<\/tag>/", '\\1', $var);
?>

preg_replaceの正規表現部分の文字列はバイナリセーフではありません。つまり,ヌル文字を挿入することにより文字列の終端となります。

$regexpを

$regexp = "<\/tag>/e\0";

と設定できれば,

preg_replace("/<tag>(.*?)$regexp<\/tag>/", '\\1', $var);

の正規表現

"/<tag>(.*?)$regexp<\/tag>/"

は$regexpの値で置き換えられ,

"/<tag>(.*?) <\/tag>/e\0<\/tag>/"

となり,ヌル文字で終了してしまい,

"/<tag>(.*?) <\/tag>/e"

と同じになります。このため,このコードを実行するphpinfo関数が実行されてしまいます。

元の文書では次のコードがPoCとして書かれています。

<?php
$regexp = $_GET['re'];
$var = '<tag>phpinfo()</tag>';
preg_replace("/<tag>(.*?)$regexp<\/tag>/", '\\1', $var);
?>

攻撃URLとして

http://www.example.com/index.php?re=<\/tag>/e%00

が紹介されています。magic_quotes_gpcが有効な場合,ヌル文字がエスケープされるためこの攻撃は成功しません。しかし,今時のアプリケーションでmagic_quotes_gpc=onを前提にアプリケーションを作ることは考えられません。

Gerkis氏は対策としてできる限り,ダブルクオートではなくシングルクォートを使うこと(特にバックリファレンスをシングルクォートで囲む)⁠コールバック関数が必要な場合はpreg_replace関数よりpreg_replace_callback関数を使うこと,正規表現をエスケープするpreg_quote関数の利用を勧めています。

まとめ

Gerkis氏の論文をすべては紹介しきれていませんが,今回はここまでとします。

次回もGerkis氏の論文の続きを紹介します。

著者プロフィール

大垣靖男(おおがきやすお)

University of Denver卒。同校にてコンピュータサイエンスとビジネスを学ぶ。株式会社シーエーシーを経て,エレクトロニック・サービス・イニシアチブ有限会社を設立。
オープンソース製品は比較的古くから利用し,Linuxは0.9xのころから利用している。オープンソースシステム開発への参加はエレクトロニック・サービス・イニシアチブ設立後から。PHPプロジェクトでは,PostgreSQLモジュールのメンテナンスを担当している。

URLhttp://blog.ohgaki.net/

著書

コメント

コメントの記入