MOPB-24-2007:PHP array_user_key_compare() Double DTOR Vulnerability

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

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

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

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

■リファレンス
なし

■サマリ
array_user_key_compare()は、例えばuksort()など、利用される内部関数(訳注:PHPコードから利用できる関数です)です。この関数はキーの値を比較するユーザが定義した配列比較関数として呼び出すことが目的です。この関数が復帰する際にパラメータがリファレンスで渡されていたとしても両方のパラメータは開放されてしまいます。このため既に開放済みのZVALがシンボルテーブルに残り、任意コード実行攻撃に利用可能な二重のDOTRが可能な状態になります。

■影響するバージョン
PHP 4.4.6以下、PHP 5.2.1以下

■詳細情報
例えばuksort()関数がユーザスペースでキーを比較するためにarray_user_key_compare()関数を内部的に呼び出されると、array_user_key_compare()関数はa,bの2つのハッシュバケットからkey1,key2のZVALを取得します。これらのZVALはユーザスペースの関数に渡され、zval_dtor()(訳注:PHP内部のZVALデストラクタ関数)によって開放されます。

static int array_user_key_compare(const void *a, const void *b TSRMLS_DC)
{

zval key1, key2;
zval *args[2];

args[0] = &key1;
args[1] = &key2;

status = call_user_function(EG(function_table), NULL, *BG(user_compare_func_name), &retval, 2, args TSRMLS_CC);

zval_dtor(&key1);
zval_dtor(&key2);

}

このコードの問題点はユーザスペースの関数がリファレンスのキーを渡すかもしれない点を考慮していないことです。単純にzval_dtor()によって開放すると既に開放済みのメモリを参照してしまいます。これによりあらゆるメモリ破壊が発生し添付の攻撃コードで示すように任意コード実行攻撃にも利用できます。

この問題を解決する正しい方法はzval_dtor()を変数の解放前にリファレンスを考慮するzval_ptr_dtor()に置き換えることです。

■PoC、攻撃コードまたは再現手順
添付の攻撃コードはMOPBの最初に紹介したバグをトリックに利用しています。文字列をハッシュテーブルの同じ場所に配置し、ハッシュテーブルの初めに配置されたエントリのアドレスを読み出し、シェルコードへのオフセットを割り出し、同じ文字列を使ってハッシュテーブルのデストラクタフィールドを上書きします。ハッシュテーブルが開放される際にコードが実行されます。

■備考
この脆弱性はローカルからのみ攻撃可能です。しかし、攻撃コードはPHP4とPHP5でいかに簡単に任意のマシン語実行できるか示し、PHPに組み込まれたdisable_functions, open_basedir, safe_modeが完全に無意味であることを明らかにしています。

投稿者: yohgaki