MOPB-25-2007:PHP header() Space Trimming Buffer Underflow Vulnerability

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

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

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

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

■リファレンス
MOPB-19-2007

■サマリ
PHP 5.2.0以降のメモリマネージャーは1バイトのアンダーフロー脆弱性の場合も攻撃を許してしまいます。

header()関数に全て空白の文字列が渡された場合、少なくともPPC MacOS Xのようなビッグエンディアンシステムの場合、コード実行が可能となります。

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

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

header()関数が呼ばれると、空白文字のトリムを行います。これは以下のコードで実行されます。

/* cut of trailing spaces, linefeeds and carriage-returns */
while(isspace(header_line[header_line_len-1]))
header_line[–header_line_len]=’\0′;

このコードは後ろの空白文字列をポインタを前に移動させ文字列の終端にNULLバイトを書き込みトリムを行います。残念ながら、すべて空白文字の場合、前へのポインタ移動が文字列の先端で終了しないため、このトリムは正しく動作しません。割り当てられたメモリアドレスの1バイト前の値がASCIIの空白文字である場合、このトリムコードは割り当てられたメモリアドレスの1バイト前にNULLバイトを書き込みます。

新しいメモリマネージャはバッファの先頭にメモリブロックのサイズを保存しています。リトルエンディアンのシステムでは、メモリブロックの1バイト前にASCIIの空白文字となる値が書き込まれるのは非現実的です。しかし、PPCのようなビッグエンディアンシステムの場合、リモートの攻撃者がバッファの直前の1バイトがASCIIの空白文字となるようなヒープレイアウトを作ることが可能です。トリムコードはそのバイトをNULLバイトで上書きする為、制御情報を破壊し、フリーメモリブロックのメモリリストのアンリンク時に行える標準的な攻撃が可能となり、POCで示すようにリモートからコード実行が可能となります。

$ gdb ./php
(gdb) run MOPB-25-2007.php
Starting program: /Users/Benutzer/php-5.2.0/sapi/cli/php MOPB-25-2007.php
Reading symbols for shared libraries . done

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

これはリンクリストのフリーリスト内部での典型的なクラッシュを示しています。有効なオフセットを使用することにより任意のコードを実行できます。デモ攻撃はローカルで動作するので、substr_compare()関数の情報漏えいぜい弱性を利用して有効なオフセットを自動的に決定します。これは以下のようになります。

$ gdb ./php
(gdb) run MOPB-25-2007.php
Starting program: /Users/Benutzer/php-5.2.0/sapi/cli/php MOPB-25-2007.php
Reading symbols for shared libraries . done
^C
Program received signal SIGINT, Interrupt.
0x00cd4544 in ?? ()
(gdb) x/20x $pc
0xcd4544: 0x44000002 0x7c000278 0x7c7e1b78 0x38a00002
0xcd4554: 0x3800005a 0x7fc3f378 0x7ca42b78 0x44000002
0xcd4564: 0x7c000278 0x38a5ffff 0x2c05ffff 0x4082ffe5
0xcd4574: 0x38000042 0x44000002 0x7c000278 0x7ca52a79
0xcd4584: 0x4082fffd 0x7c6802a6 0x38630028 0x9061fff8
(gdb) x/5i $pc
0xcd4594: stw r5,-4(r1)
0xcd4598: addi r4,r1,-8
0xcd459c: li r0,59
0xcd45a0: sync
0xcd45a4: sc
(gdb) bt
#0 0x00cd4544 in ?? ()
#1 0x00cd44fc in ?? ()
warning: Previous frame identical to this frame (corrupt stack?)
#2 0x0020ffd8 in _zval_dtor_func (zvalue=0xb) at /…/php-5.2.0/Zend/zend_variables.c:43
#3 0x00204144 in _zval_ptr_dtor (zval_ptr=0xcbb4bc) at /…/php-5.2.0/Zend/zend_variables.h:35

(gdb) continue

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

■PoC、攻撃コードまたは再現手順
添付の攻撃コードはこのバッファアンダーフローが攻撃可能で、PPC MacOS Xプラットフォームでコード実行を許してしまうことをデモンストレーションしています。この攻撃は、攻撃に利用する関数が異なることをheader()関数が失敗するため出力が省略されている点を除けば、MOPB-19-2007と全く同じです。

この攻撃コードは有効なオフセットを決定するためにsubstr_compare()関数の情報漏えい脆弱性を利用しています。

攻撃が成功すると、攻撃コードは4444ポートに接続するとシェルを起動します。シェルコードはMetasploitシェルコードジェネレータから借用しています。

■備考
論理的には、この脆弱性はリモートから攻撃可能です。まだ多くのPHPアプリケーションはheaderインジェクションに脆弱です。しかし、リモートからのインジェクション攻撃が成功するためには文字列の最初からインジェクション可能でなければなりません。(\0によるトランケート動作)
(訳注:攻撃なコードはheader($_GET[‘location’]) のようなコードでなければならない。原理的には”Location: http://example.com/some/where/ “といった文字列を$_GET[‘location’]に設定すれば、正しいHTTPヘッダになりますが、この様なコードは別のセキュリティ問題含むため元々間違っています。注意: HTTP Response Splittingに脆弱でないheader()でheader(‘Location: ‘.$_GET[‘location’])としてもセキュリティ上問題です)

さらに、この攻撃コードは類似している脆弱性性に対して攻撃コード全体がほとんど変更なしに再利用であるか示しています。

投稿者: yohgaki