MOPB-38-2007:PHP printf() Family 64 Bit Casting Vulnerabilities

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

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

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

■PoCまたは攻撃コード
MOPB-37-2007.phpの予定

■リファレンス
なし

■サマリ
printf()関数と類似の関数に利用されるヘルパー関数は符号なしの63ビット整数を返します。しかし、内部的には32ビット整数の結果が保存されています。32ビットへのトランケートにより、結果の整数は、異なる実行パスによって呼び出されると、検出されずに負の値になることがあります。この動作により2種類の攻撃可能なメモリ破壊が可能となります。

■影響するバージョン
PHP 4.4.5未満、PHP 5.2.1未満

■詳細情報
printf()と類似の関数は内部的にフォーマット文字列をパースし、フォーマット既述子を処理する、php_formatted_print()関数を利用しています。この関数は通常のフォーマット文字列、$修飾子により既述子の番号を選択可能とし幅と精度を指定できます。この関数は正の値、幅、精度のみ受け付けます。これらの3つの値はphp_sprintf_getnumber()関数によって取り出されます。

inline static long
php_sprintf_getnumber(char *buffer, int *pos)
{
    char *endptr;
    register long num = strtol(&buffer[*pos], &endptr, 10);
    register int i = 0;

    if (endptr != NULL) {
        i = (endptr - &buffer[*pos]);
    }
    PRINTF_DEBUG(("sprintf_getnumber: number was %d bytes long\n", i));
    *pos += i;
    return num;
}

残念ながら、内部的にはこの関数は64ビットシステム上では64bit幅のlong型として動作します。呼び出し側のコードは番号は数字で始まりマイナス符号で始まらず、結果が常に正のlong数値になるようにしています。しかし、既述子の数、幅と精度はintとして保存され、64ビットから32ビットへの変換は負の値になる場合があります。

これは2つの問題を発生させます。はじめに、記述子の数は最大値のチェックだけ行い、負の値はありえないと仮定し、正しく処理していません。これにより64ビットシステムでは任意のメモリアドレスを参照可能としています。この問題は任意のメモリアドレス情報の漏えいか攻撃可能なメモリ破壊をもたらします。

2つ目の問題はこのキャスト脆弱性によって”s”フォーマット記述子php_sprintf_appendstring()を利用する関数に発生します。幅と-1の精度が指定されるとバッファ位置から1つ前の位置となります。このパターンを繰り返すことにより、ポインタの位置を実際のヒープのバッファより前の設定できます。内部のバッファのポインタがヒープの制御構造に位置するとフォーマット文字列の文字で書きかえることができます。

両方の攻撃経路はコード実行を可能にしますが、2つめの経路は簡単かつ確実に、リモートから、攻撃できます。

C言語のフォーマット文字列脆弱性と異なりPHPのフォーマット文字列脆弱性は64bitシステムでのみ攻撃でき、少なくともフォーマット文字列の後の引数1つがprintf()か類似の関数であることです。

■PoC、攻撃コードまたは再現手順
POC will be released during the next week

■備考
解説した問題は我々が問題を報告した後、PHP開発者によってPHP 4.4.5とPHP 5.2.1で修正され、新たに生まれたPHPアプリケーションのフォーマット文字列脆弱性は既に消滅したはずです。

投稿者: yohgaki