MOPB-01-2007:PHP 4 Userland ZVAL Reference Counter Overflow Vulnerability

(Last Updated On: 2018年8月16日)

“the Month of PHP Bugs”をできるだけ多くの方が読めるように、Stefanさんの承諾を得て日本語訳を公開しています。このブログの「the Month of PHP Bugs」カテゴリでMOPBの翻訳ページを一覧できます。分かりやすいように意訳できる部分は意訳します。厳密に原文の通り訳していないので正確性を重視される方は原文をご覧ください。

■クレジット
発見者:不明
実証コード:Stefan Esser

■POC/攻撃コード
MOPB-01-2007.php
http://www.php-security.org/MOPB/code/MOPB-01-2007.php

■リファレンス
なし

■サマリ
「The Month of PHP Bugs」は長年の間PHP開発者の間では知られていたPHP4のセキュリティ脆弱性から始まります。

PHPアプリケーションがPHP4で実行されている場合、変数の参照カウンタが16bitであるためにオーバーフローを起こしてしまいます。このオーバーフローが発生するたびに変数の2重のメモリ解放(訳注:一般にダブルフリーと呼ばれ、任意コードの実行も可能な場合があるバグです。通常はC言語のfree()システム関数を2重に呼び出す事を意味しますが、ここではPHPのメモリ管理機構の解放機能を2重に呼び出す事を意味しています。)が発生します。ローカルコンピュータからの攻撃者はこの2重のメモリ解放を利用してPHPを実行しているプロセス(たとえばWebサーバプロセス)内で任意コード実行を行うPHPコードを簡単に記述可能です。この脆弱性はdisable_functions、open_basedir、SAFE_MODEのバイパスを可能にしたり、対象のローカルルート攻撃を発動できます。

■影響するバージョン
すべてのPHP4

■詳細情報
PHP4の変数を定義するZVAL構造体は内部的には以下のように定義されています。

struct _zval_struct {
        /* Variable information */
        zvalue_value value;     /* value */
        zend_uchar type;        /* active type */
        zend_uchar is_ref;
        zend_ushort refcount;
};

この構造体は構造体全体が(32bit システム上で)8バイトになるようrefcouuntは16ビット幅で設計されました。16ビットでは簡単にオーバーフローし、PHPはリファレンスカウンタのオーバーフローに対して内部で保護する機構を持たないため、PHP5では32ビット幅に拡張されました。このことは残念ながらPHP4にとって以下のようなコードはリファレンスカウンタをオーバーフローさせ2重のメモリ解放をスクリプトの最後で発生させます。

  $var = "POC";
  for ($i = 0; $i < 0x10001; $i++) {
    $arr[] = &$var;
  }

攻撃者はリファレンスカウンタをオーバフローさせ利用できます。そして、攻撃者は攻撃のために削除する値のリファレンスを一つ削除します。PHPは下位16ビットの値だけしか保持していないのでこの場合のリファレンスカウントは1になります。この変数(訳注:$var)が解放されるとき、攻撃者は他の変数が同じメモリを割り当てられるように細工した変数を作成します。その後、すべてのrefcountが1の変数が再度解放されるようすべてのリファレンスを解放します。

私たちのPoC(Proof of comcept)コードは攻撃者がどのようにPHP配列の内部ヘッダと同じ場所に文字列を配置するか説明しています。このコードは攻撃者が文字列のオフセットを利用して内部ヘッダの読み書きを可能します。コードの実行は内部配列ヘッダのデストラクタフィールドを任意のポインタ(訳注:もちろん攻撃用のコードへのポインタ)に書き換え、後で配列変数を削除するだけです。

■PoC、攻撃または再現実験の手順

添付された攻撃コードは直接実行できません。最初に外部から読み込みを防ぐためのdie()関数を削除します。この攻撃コードは実際のシェルコード(訳注:bash, shなどのシェルを起動するコード)も含んでいません。このコードはデバッガを起動するだけです。シェルコードを入れたければどのようなものでも入れられます。十分に長くすることにだけ注意してください。(たとえば500バイト)

攻撃コードは32ビットシステム用でリトルエンディアンとビックエンディアンシステムの両方でそのまま実行可能なはずです。(訳注:gdbを起動するのでgdbが実行できるシステム(Linuxなど)でそのまま実行できる)シェルコードアドレスにリークしているので、攻撃コードにはオフセットは必要ありません。(訳注:通常この手の攻撃コードを機能させるにはメモリオフセットが必要なことが多い)

$ gdb ./php-4.4.4
(...)

(gdb) run exploit.php
Starting program: /home/code/MOPB/php-4.4.4 exploit.php

Program received signal SIGTRAP, Trace/breakpoint trap.
0x08573e95 in ?? ()
(gdb) x/5i $eip
0x8573e95:      int3
0x8573e96:      int3
0x8573e97:      int3
0x8573e98:      int3
0x8573e99:      int3
(gdb)

■備考

PHP開発者はこの問題を修正するつもりがありません。唯一の対処策はPHPリファレンスカウンタのサイズを変更(16ビットから32ビット)することですが、ソースを公開していないPHP拡張モジュールを作成している会社に問題を発生させるためです。リファレンスカウンタのサイズを変更をするとすべてのPHPモジュールをコンパイルし直しが必要で、ソースを公開していないPHPモジュールは使えなくなります。

投稿者: yohgaki