2013年5月22日水曜日

phpMyAdmin3.5.8以前に任意のスクリプト実行を許す脆弱性(CVE-2013-3238)

phpMyAdmin3.5.8以前にはpreg_replace関数の呼び出し方法に不備があり、ログインしたユーザが指定した任意のスクリプトを実行できる脆弱性CVE-2013-3238があります。

概要

phpMyAdminの「テーブル名の接頭辞を付け替える」および「接頭辞を付け替えてテーブルをコピーする」操作の際に、付け替え元および付け替え先のパラメータを操作することにより、任意のPHPスクリプトを実行できます。

影響

phpMyAdminのログインユーザが任意のPHPスクリプトを実行できます。phpMyAdminのユーザは通常サーバー管理者と想定されますが、データベース管理者という権限を越えて、任意のPHPスクリプトをphpMyAdminの動作する権限(apache等に与えられた権限)で実行できることが問題です。

影響を受けるバージョン

影響を受けるのは、phpMyAdmin3.5.8以前です。
NVDの情報では3.5.8には脆弱性がないように読めますが、ソースを確認したところ、3.5.8にも脆弱性があります。
phpMyAdmin3.5.8.1以降、およびphpMyAdmin4.0.0(正式版)以降で対処されています。

ただし、後述するように、PHP5.4.7以降を用いている場合は、この脆弱性の影響を受けません。

再現方法

phpMyAdminにログインして適当なデータベースを選択し、テーブル一覧を表示します。

テーブルを1つ選択し、チェックしたものを:のプルダウンから「テーブル名の接頭辞を付け替える」を選択します。以下の画面が表示されます。

「付け替え元」に「/e」、「付け替え先」に「phpinfo();」を入力します。このまま実行せずに、Proxyツールなどで、/eの後に%00を付加して下さい(下図オレンジ色の部分)。


実行後の表示は下記となり、phpinfo()関数が実行されていることが分かります。(PHPのバージョンが古いのは検証環境なので気にしないで下さい。)


脆弱性の発生するメカニズム

脆弱性の原因はlibraries/mult_submits.inc.php の以下の部分です。
case 'replace_prefix_tbl':
  $current = $selected[$i];
  $newtablename = preg_replace("/^" . $from_prefix . "/", $to_prefix, $current);
上記再現手順の場合、preg_replace関数の呼び出しは以下の引数となります。
preg_replace("/^/e\0/", "phpinfo();", "test");
preg_replace関数(PHP5.4.6以前)の第1引数はバイナリセーフでないため、NULLバイト以降が無視され、結局以下の引数で呼び出されたのと同じになります。
preg_replace("/^/e", "phpinfo();", "test");
ここで、preg_replace関数の第1引数に注目ください。/eは、e修飾子というもので、これが指定された場合preg_replace関数の第2引数をPHPのスクリプトと見なし、解釈実行します。すなわち、eval("phpinfo();")としたのと同じです。このため、phpinfo()関数が実行されることになります。

対策

phpMyAdmin3.5.8.1以降、またはphpMyAdmin4.0.0(正式版)以降にバージョンアップすることで対策となります。当該箇所の差分を確認したところ、preg_replaceをやめ、substrで接頭辞の確認を行い、文字列連結で新しい接頭辞をつけるという手堅い方式に変更されています。賢明な対処と考えます(参照)。


また、回避策としてPHP5.4.7以降の導入が挙げられます。特別な理由がない限りPHPの最新版(現在PHPの最新版は5.4.15)を導入することをお勧めします。ただし、PHP5.3の最新版(5.3.25)では効果がありません。

phpMyAdminの利用者に悪意がある状況を想定しなくてよいケース(phpMyAdminの利用者全員がPHPスクリプト実行と同等以上の権限を元々持っている場合等)では、この脆弱性を許容するという判断もあり得るかと思いますが、できるだけバージョンアップをお勧めします。

脆弱性の根本原因

この脆弱性の根本原因は、preg_replace関数の第1引数の呼び出し方(下図の色をつけたところ)にあります。
preg_replace("/^" . $from_prefix . "/", $to_prefix, $current);
PCRE系関数では正規表現のデリミタ(スラッシュ「/」等)を指定しますが、正規表現の中にデリミタがある場合、デリミタをエスケープする必要があります。例えば、スラッシュ一文字を検索する場合は以下のようにします。
/\//
これを怠っていることが根本原因です。脆弱性のメカニズムとしてはSQLインジェクションのシングルクォートのエスケープ漏れと同じです。
攻撃者の視点で考えると、$from_prefixを単に「/e」とすると、preg_replaceの第1引数は「/^/e/」となり、最後の「/」が余計です。このため、その前にNULL文字を入れて、余計な「/」が見えないようにしています。

また、$from_prefixに [a-z]と言う文字列があれば、任意の英小文字にマッチするという結果になります。応用によっては「それは仕様です」となるでしょうが、phpMyAdminの場合は、そうではなさそうです。
したがって、preg_replaceを活かしたままで脆弱性対処するには、正規表現の特殊文字とデリミタの両方をエスケープする必要がありますが、PHPには、このための関数preg_quoteが用意されています。従って、以下のようにすれば問題ないはずです。
preg_replace("/^" . preg_quote($from_prefix, '/') . "/", $to_prefix, $current);
実際にはpreg_replace自体を避ける形で変更されているわけですが、その理由は以下のようなものではないかと推測します。
  • preg_replaceの仕様が危なっかしいものであり、過去にも脆弱性の原因になってきた
  • preg_quoteの信頼感があまりなかった(憶測)
  • 元々正規表現を必要としない処理である

このような理由から、先に「賢明な対処」と評したわけです。

preg_replaceの代替処理

先に述べたように、preg_replaceには潜在的に危険性があります。この問題について詳しく知りたい方は、寺田さんの素晴らしい記事「preg_replaceによるコード実行」を参照下さい。
preg_replaceの危険性を避けるためには、以下を検討するとよいでしょう。
  • 正規表現は原則として固定とする
  • preg_replace関数のe修飾子の代わりに、preg_replace_callbackを使用する
  • preg_replaceの代わりに、mb_ereg_replaceまたはmb_ereg_replace_callback(PHP5.4.1以降)の使用を検討する

PHP5.4.7におけるPCRE関数の変更

PHP5.4.7以降の環境ではこの問題の影響を受けないことは、以下の記事から知りました。
なお、システムで PHP 5.4.6 以前のバージョンを利用する環境のみが、この脆弱性の影響を受けます。
phpMyAdmin の preg_replace() 関数の不適切な利用に起因する任意コード実行の脆弱性(Scan Tech Report)
PHP5.4.6以前のみで影響を受けるという意味が最初分からなかったので、調べたところPHP5.4.7以降ではPCRE関数(preg_replace等)の正規表現中のNULLバイトをチェックして、もしあればエラーにしています。以下は、preg_replace関数の第1引数にNULLバイトを含む文字列を指定した場合のエラーメッセージの例です。
PHP Warning:  preg_replace(): Null byte in regex in /home/…/preg-nullbyte.php on line 2
PHPのChnage Log等を見てもこの変更は載っていないので、Scanのレポートを書いた方はすごいと思いました。

まとめ

phpMyAdmin3.5.8以前に含まれる、任意のスクリプト実行を許す脆弱性について説明しました。前述のように、この脆弱性の危険度の評価は微妙ですが、極力バージョンアップすることをお勧め致します。
また、preg_replaceの呼び出し方によっては、スクリプト実行の脆弱性を含みやすいという問題があります。phpMyAdminは管理者向けのシステムなので、管理者がPHPスクリプトを実行できても大きな問題ではない環境もあり得ますが、一般利用者が使うアプリケーションで任意スクリプト実行を許す脆弱性が入ると大問題です。この機会に、preg_replace関数の使い方を見直すことを推奨します。
また、PHP5.4.7以降で、PCRE関数の安全性が強化されています。この問題に限らず、極力PHPの新しいバージョンの使用を推奨します。

0 件のコメント:

コメントを投稿

フォロワー

ブログ アーカイブ