MOPB-19-2007:PHP ext/filter Space Trimming Buffer Underflow Vulnerability

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

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

■クレジット
発見者:Stefan Esser
攻撃コード:Stefan Esser

■PoCまたは攻撃コード
MOPB-19-2007.php
http://www.php-security.org/MOPB/code/MOPB-19-2007.php

■リファレンス
なし

■サマリ
PHP 5.2.0から新しくFilter拡張モジュールが追加されました。このモジュールはアプリケーションからユーザ入力をフィルタしたり、サイト全体に対する入力フィルタ機能を実装するために利用されます。

全てのスペースの文字列をFILTER_VALIDATE_INT関数などのフィルタ関数に渡すと、少なくともMacOS X PPCなどのビッグエンディアンのシステムに対してリモートコード実行が可能なバッファアンダーフローが発生します。

■影響するバージョン
PHP 5.2.0

■詳細情報
PHP 5.2.0は簡単なmalloc()/free()関数ラッパーを利用しない、メモリプールを実装するために独自のヒープ管理を行う、新しいメモリマネージャを利用します。このヒープ管理マネージャは制御情報を含んでいる為、オーバーフロー攻撃に脆弱です。さらに、以前のメモリマネージャと異なり、1バイトのアンダーフローに脆弱です。

FILTER_VALIDATE_INT(この関数が最も頻繁にフィルタに利用されると考えられます)等の関数が利用されると、ユーザ入力に対して最初にホワイトスペース文字の除去を行います。これは以下のマクロによって実行されます。

#define PHP_FILTER_TRIM_DEFAULT(p, len, end) { \
        while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\v') { \
                p++; \
                len--; \
        } \
        start = p; \
        end = p + len - 1; \
        if (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\v') { \
                unsigned int i; \
                for (i = len - 1; i >= 0; i--) { \
                        if (!(p[i] == ' ' || p[i] == '\t' || p[i] == '\r' || p[i] == '\v')) { \
                                break; \
                        } \
                } \
                i++; \
                p[i] = '\0'; \
                end = p + i - 1; \
                len = (int) (end - p) + 1; \
        } \
}

このトリミングマクロはホワイトスペースがある限り先端ポインタを進め、同じ事を文字列の終端ポインタに対して逆方向に行います。そしてNULLバイトを付け加え文字列を終了させます。残念ながら、このトリミングマクロは終端ポインタが文字列の先端で終了しないので、全てのホワイトスペース文字に対して正しく動作しません。これにより、バッファの開始位置にホワイトスペースに文字が含まれていると、トリミング動作は確保したバッファの前にNULLバイトを書き込みます。

新しいメモリマネージャはバッファの前に、以前に利用したメモリブロックのサイズを保存します。この為、リトルエンディアンシステムでは、NULLバイトがホワイトスペース文字の前に(訳注:意味のある形で)書き込まれることは通常あり得えません。しかし、PPCの様なビッグエンディアンのシステムでは、リモートの攻撃者がバッファの前に(訳注:意味のある)NULL文字が含まれるヒープレイアウトを作る事ができます。(訳注:リトルエンディアンでは上位バイトが後ろに配置され、この脆弱性のサイズフィールドの場合、アンダーフローであるの為、普通は常に0でありNULLと同じです。ビッグエンディアンでは下位バイトが後ろに配置されるので、このアンダーフローによりアロケートしたメモリサイズの改ざんが可能になります)トリミング関数はNULLバイトで上書きするのです。これにより、制御情報が破壊されフリーブロックのリンクリストからアンリンク(削除)の際に利用する標準的な攻撃方法が利用可能になります。PoCで明らかなようにリモートコード実行が可能になります。

$ gdb ./php
(gdb) run MOPB-19-2007.php
Starting program: /Users/Benutzer/php-5.2.0/sapi/cli/php MOPB-19-2007.php
Reading symbols for shared libraries . done
Using offsets 55555555 and 66666666

Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_INVALID_ADDRESS at address: 0x55555561
_zend_mm_free_int (heap=0x2000400, p=0xcbad90) at /Users/Benutzer/php-5.2.0/Zend/zend_alloc.c:480
480             prev->next_free_block = next;

このgdbセッションはフリーリストのアンリンクによる典型的なクラッシュを示しています。有効なオフセットを利用すると任意のコードが実行可能となります。(訳注:これはlibcのフリーリストではなくPHPのメモリマネージャのフリーリストなのでlibcのバージョンに関わらず攻撃可能です)デモ用の攻撃コードはローカルで動作するので必要なオフセット情報をsubstr_compare()関数の情報暴露脆弱性から自動的に取得して実行します。以下のように動作しています。

$ gdb ./php
(gdb) run MOPB-19-2007.php
Starting program: /Users/Benutzer/php-5.2.0/sapi/cli/php MOPB-19-2007.php
Reading symbols for shared libraries . done
Using offsets 00cd46e0 and 00cd16a0
^C
Program received signal SIGINT, Interrupt.
0x00cd47a4 in ?? ()
(gdb) x/20x $pc
0xcd47a4:       0x44000002      0x7c000278      0x7c7e1b78      0x38a00002
0xcd47b4:       0x3800005a      0x7fc3f378      0x7ca42b78      0x44000002
0xcd47c4:       0x7c000278      0x38a5ffff      0x2c05ffff      0x4082ffe5
0xcd47d4:       0x38000042      0x44000002      0x7c000278      0x7ca52a79
0xcd47e4:       0x4082fffd      0x7c6802a6      0x38630028      0x9061fff8
(gdb) x/5i $pc
0xcd47a4:       sc
0xcd47a8:       xor     r0,r0,r0
0xcd47ac:       mr      r30,r3
0xcd47b0:       li      r5,2
0xcd47b4:       li      r0,90
(gdb) bt
#0  0x00cd47a4 in ?? ()
#1  0x00cd475c in ?? ()
warning: Previous frame identical to this frame (corrupt stack?)
#2  0x0020ffd8 in _zval_dtor_func (zvalue=0xb) at /Users/.../.../Zend/zend_variables.c:43
#3  0x00204144 in _zval_ptr_dtor (zval_ptr=0xcbb72c) at /Users/.../.../Zend/zend_variables.h:35
...
(gdb) continue

この例はコード実行攻撃に成功し、誰かが4444ポートに接続するのを待っている事を示しています。

■PoC、攻撃コードまたは再現手順
添付の攻撃コードはローカルでバッファアンダーフローがPPCのMaxOS X上で攻撃可能である事を証明します。

攻撃コードはどのオフセットに上書きするか決める為にsubstr_compare()関数の情報暴露脆弱性を利用しています。実際のリモートからの攻撃では、オフセットはフルートフォースか他の手法(例えば、リモートからの情報暴露脆弱性)使って見つけ出す必要があります。

添付のコードが攻撃に成功すると、4444ポートに接続するとシェルが利用できるようになります。シェルコードはMetasploitシェルコードジェネレータから取得しました。

■備考
この新しく導入されたセキュリティ機能のリモート脆弱性は、私たちがベンダー(訳注:PHPセキュリティレスポンスチーム)に情報公開して、PHP 5.2.1で修正されました。

この非常に危険な脆弱性は、他のベンダーもよく行っているように「Filter拡張モジュール内部にある幾つかの入力処理バグの修正」とリリースノートに書くのみで、PHPユーザに多かれ少なかれ隠しています。私たちは責任あるベンダーは別の行動を取らなければならないと強く信じています。
(訳注:「別の行動」(must act differently)とは、「すべての脆弱性をタイムリーに修正・公開する事」だと思われます。私も隠ぺいによりセキュリティが維持できる、とは考えません。)

投稿者: yohgaki