ホームページ  >  記事  >  バックエンド開発  >  PHP カーネル層解析の逆シリアル化の脆弱性

PHP カーネル層解析の逆シリアル化の脆弱性

藏色散人
藏色散人転載
2019-04-02 12:00:003481ブラウズ

PHP カーネル層解析の逆シリアル化の脆弱性

まえがき

PHP を学習する過程で、次のようないくつかの PHP 機能を理解するのが難しいことがわかりました。 PHP 00 の切り捨て、MD5 の欠陥、逆シリアル化バイパス __wakeup など。表面的な理解に固執するのではなく、PHP コアがどのようにそれを行うのかを探っていきたいと思います。

以下は、CTF で一般的に使用される逆シリアル化の脆弱性、CVE-2016-7124 (マジック関数 __wakeup をバイパスする) であり、PHP カーネルのデバッグ プロセスを共有する例として示されています。カーネル ソース コードのデバッグ環境の構築、シリアル化および逆シリアル化カーネル ソース コードの分析から最終的な脆弱性分析までのすべての部分が含まれます。 (推奨: PHP チュートリアル )

1. 例によって引き起こされる考え

まず、私が書いた小さな例を見てみましょう。

PHP カーネル層解析の逆シリアル化の脆弱性

上の図に基づいて、まず PHP のマジック関数を紹介します。

まず、いくつかの関数の公式ドキュメントを見てみましょう。はじめに:

PHP カーネル層解析の逆シリアル化の脆弱性

これは、クラスがインスタンスとして初期化されるとき、__construct が呼び出されるときの簡単な概要です。破棄されると、__destruct が と呼ばれます。

クラスがシリアル化のために serialize を呼び出すと、__sleep 関数が自動的に呼び出されます。文字列が unserialize## を使用して逆シリアル化される場合、 # クラスに変換されると、__wakeup 関数が呼び出されます。上記のマジック関数が存在する場合は、自動的に呼び出されます。自分で手動でディスプレイ呼び出しを行う必要はありません。

コードの最初の部分を見てみましょう。

__destruct 関数では、ファイルの書き込みという機密性の高い操作が行われます。ここでは逆シリアル化を使用して危険な文字列を構築します。これにより、コード実行の脆弱性が発生する可能性があります。

対応する文字列を構築し、それを使用する準備をしたときに、その

__wakeup 関数にフィルタリング操作があり、それが構築の妨げになっていることがわかりました。逆シリアル化では最初に __wakeup 関数を呼び出す必要があることがわかっているためです。

ここでは、この PHP 逆シリアル化の脆弱性 CVE-2016-7124 (マジック関数 __wakeup のバイパス) を利用して、逆シリアル化中に自動的に呼び出されるマジック関数 ___wakeup を簡単にバイパスすることを考えざるを得ません。機密性の高い操作はファイルに書き込まれます。


もちろん、上記のコードは私が個人的に挙げた単純な例にすぎず、実際の状況でも上記と同様の状況がたくさんあります。しかし、このバイパス方法は私にとって非常に興味深いものです。 PHP の内部操作と処理が上位レベルのコード ロジックにどのような影響を与えて、このような魔法のような状況 (BUG) が発生するのか。次に、PHP カーネルの動的デバッグ分析を実行します。この質問について調べてみましょう。


この脆弱性 (CVE-2016-7124) は、PHP5 シリーズの 5.6.25 より前のバージョンと 7.x シリーズの 7.0.10 より前のバージョンに影響します。したがって、後で 2 つのバージョンをコンパイルします。1 つはこの脆弱性の影響を受けないバージョン 7.3.0 で、もう 1 つは脆弱性が存在するバージョン 5.6.10 です。違いについて詳しく知るには、2 つのバージョンを比較してください。


2. PHP ソース コードのデバッグ環境のセットアップ

PHP が C 言語で開発されていることは誰もが知っています。はWIN10ですので、主にWindowsでの環境構築を紹介します。次の資料が必要です:

PHP源码
PHP SDK工具包,用于构建PHP
调试所需要IDE

ソース コードは GITHUB でダウンロードできます (リンク: https://github.com/php/php-src)。ダウンロードする必要なバージョンを選択できます。

PHPSDK ツールキットのダウンロード アドレス: https://github.com/Microsoft/php-sdk-binary-tools このアドレスからダウンロードされたツールキットは、VC14 と VC15 のみをサポートします。もちろん、https://windows.php.net/downloads/ から PHP の下位バージョンをサポートする VC11、VC12 などを見つけることもできます。PHP SDK を使用する前に、対応するバージョンの VS がインストールされていることを確認する必要があります。 Windows SDK コンポーネントのバージョン。


以下の記事では PHP7.3.0 と 5.6.10 を使用します。これら 2 つのバージョンのソース コードのコンパイルを以下に紹介します。他のバージョンでも同様の方法です。


2.1 Windows PHP 7.3.0 のコンパイル

ネイティブ環境は WIN10 X64 で、PHP SDK は上記の github リンクからダウンロードします。 SDK ディレクトリに入り、4 つのバッチ ファイルを見つけます。ここで

phpsdk-vc15-x64 をダブルクリックします。

PHP カーネル層解析の逆シリアル化の脆弱性

次に、このシェルに

phpsdk_buildtreephp7 と入力すると、php7 フォルダーが同じディレクトリに表示され、シェル ディレクトリも変更されていることがわかります。

PHP カーネル層解析の逆シリアル化の脆弱性

#

次に、解凍​​したソース コードを \php7\vc15\x64 の下に置きます。シェルはこのフォルダーに入り、phpsdk_deps–update–branchmaster コマンドを使用して、関連する依存コンポーネントを更新およびダウンロードします。

完了を待った後、ソース コード ディレクトリに入り、buildconf.bat バッチ ファイルをダブルクリックします。configure.batconfigure がリリースされます。 .js 2 つのファイル、シェルで configure–disable-all–enable-cli–enable-debug–enable-phar を実行して、対応するコンパイル オプションを設定します。他に必要な場合は、configure –help を実行して表示できます

PHP カーネル層解析の逆シリアル化の脆弱性

プロンプトに従って、nmake を使用して直接コンパイルします。

PHP カーネル層解析の逆シリアル化の脆弱性

コンパイルが完了し、実行可能ファイルのディレクトリが php7\vc15\x64\php-src\x64\Debug_TS フォルダーに作成されます。 php -v と入力すると、関連情報が表示されます。

PHP カーネル層解析の逆シリアル化の脆弱性

2.2 Windows PHP 5.6.10のコンパイル

方法は7.3.0と同じですが、使用方法に注意してください。 PHP5.6 の WindowsSDK コンポーネントのバージョンは VC11 で、VS2012 をダウンロードする必要があり、github からダウンロードした PHP SDK をコンパイルに使用することはできません。https://windows.php で VC11 PHP SDK と関連する依存コンポーネントを選択する必要がありますコンパイル用の .net/downloads/ 残りは上記とまったく同じなので、ここでは繰り返しません。

PHP カーネル層解析の逆シリアル化の脆弱性

2.3 デバッグ構成

上記で PHP インタープリターをコンパイルしたため、デバッグには VSCODE を直接使用します。

ダウンロード後、C/C デバッグ拡張機能をインストールします。

PHP カーネル層解析の逆シリアル化の脆弱性

次に、ソース コード ディレクトリを開き、[デバッグ] -> [構成を開く] をクリックすると、launch.json ファイルが開きます。

PHP カーネル層解析の逆シリアル化の脆弱性

上の図によると、これら 3 つのパラメータを設定した後、現在のディレクトリの 1.php に PHP コードを記述し、PHP ソース コードにブレークポイントを設定できます。直接デバッグします。

デバッグ環境がセットアップされました。

3. PHP デシリアライズのソース コード分析

一般的に PHP デシリアライズと言えば、通常、serialize と unserialize という 2 つの関数がペアで表示されますが、もちろんこれらはペアではありません。 __sleep() と __wakeup() という 2 つのマジック メソッドもあります。ご存知のとおり、シリアル化は単にオブジェクトがファイルに保存されることを意味しますが、逆シリアル化はその逆で、オブジェクトがファイルから読み取られてインスタンス化されます。

次に、上記で設定したデバッグ環境に基づいて、動的デバッグを使用して、PHP (バージョン 7.3.0) で行われるシリアル化と逆シリアル化の内容を直感的に反映します。

3.1 ソース コード分析のシリアル化

まず、__sleep マジック関数を含まない簡単なデモを作成しましょう:

PHP カーネル層解析の逆シリアル化の脆弱性

次に、ソース コード内で serialize 関数をグローバルに検索し、var.c ファイル内でこの関数を見つけます。関数ヘッダーの直下にブレークポイントを配置し、デバッグを開始します。

PHP カーネル層解析の逆シリアル化の脆弱性

いくつかの準備作業を行った後、シリアル化処理関数の入力を開始し、php_var_serialize 関数をフォローアップしていることがわかります。 。

PHP カーネル層解析の逆シリアル化の脆弱性

引き続き php_var_serialize_intern 関数を追っていきます。主な処理関数は次のとおりです。関数コードが多いため、重要なのは、この関数が var.c ファイルにも含まれていることです。

PHP カーネル層解析の逆シリアル化の脆弱性

関数全体の構造は switch case であり、struc バリアントの型はマクロ Z_TYPE_P によって解析されます (このマクロは struc-> に展開されます)。 ;u1.v.type) を使用してシリアル化する型を決定し、対応する CASE 部分を入力して操作します。次の図は型定義を示しています。

PHP カーネル層解析の逆シリアル化の脆弱性

上の赤いボックス内の数字 8 によると、この時点でオブジェクトにシリアル化する必要があることがわかります IS_OBJECT 、対応する CASE ブランチを入力します:

PHP カーネル層解析の逆シリアル化の脆弱性

上図ではマジック関数 __sleep の呼び出しタイミングが確認できますが、この関数は今回作成したデモには存在しないため、処理は入りません。この枝。分岐ごとに処理の流れが異なるので、マジック関数 __sleep を使った処理については後ほど見ていきます。

PHP カーネル層解析の逆シリアル化の脆弱性

上記の case IS_OBJECT ブランチにはプロセス ヒットがなく、case に Break ステートメントもないため、実行は IS_ARRAY ブランチに継続されます。 struc構造体名からclassを抽出し、その長さを計算してbuf構造体に代入し、クラス内でシリアル化する構造体を抽出してハッシュ配列に格納します。

PHP カーネル層解析の逆シリアル化の脆弱性

次のステップは、php_var_serialize_intern 関数を使用してハッシュ配列全体を再帰的に解析し、そこから変数名と値を抽出することです。フォーマット分析を実行し、解析された文字列を buf 構造に結合します。最後に、プロセス全体が完了すると、文字列全体が柔軟な配列構造 buf に完全に格納されます。

PHP カーネル層解析の逆シリアル化の脆弱性

#上の図の赤いボックスから、最終結果と一致していることがわかります。デモを少し変更して、マジック関数 __sleep を追加しましょう。公式ドキュメントによると、__sleep 関数は配列を返す必要があります。この関数ではクラス メンバー関数も呼び出しています。その特定の動作を観察してください。

PHP カーネル層解析の逆シリアル化の脆弱性

#前のプロセスはまったく同じなのでここでは繰り返しませんが、分岐点から始めましょう。

PHP カーネル層解析の逆シリアル化の脆弱性

php_var_serialize_call_sleep 関数を直接フォローアップします。

PHP カーネル層解析の逆シリアル化の脆弱性

ここで引き続きフォローアップします。call_user_function。マクロ定義によれば、実際には _call_user_function_ex 関数が呼び出されます。 , いくつかのコピー操作がここで実行されたため、スクリーンショットは取得されません。その後、プロセスは zend_call_function 関数の呼び出しに進みます。

PHP カーネル層解析の逆シリアル化の脆弱性

関数 zend_call_function では、実際の状況では、__sleep で独自の処理のいくつかを実行する必要があります。ここで、PHP は実行される操作を PHP 自身の zend_vm エンジン スタックにプッシュし、後で 1 つずつ解析します (つまり、対応する OPCODE を解析します)。

PHP カーネル層解析の逆シリアル化の脆弱性

ここでのプロセスはこの分岐に到達し、zend_execute_ex 関数をフォローアップします。

PHP カーネル層解析の逆シリアル化の脆弱性

ZEND_VM では、全体的な処理フローが while(1) ループであり、ZEND_VM スタック内の操作を継続的に解析していることがわかります。上図の赤いボックス内の ZEND_VM エンジンは、ZEND_FASTCALL メソッドを使用して、対応する処理関数にディスパッチします。

PHP カーネル層解析の逆シリアル化の脆弱性

PHP カーネル層解析の逆シリアル化の脆弱性

__sleep でメンバー関数 show を呼び出したので、ここでは最初に show を見つけます。その後、オペレーション全体が解析されるまで、次のオペレーションは次のラウンドの新しい解析のために ZEND_VM スタックにプッシュされ続けます (ここでは、表示されているオペレーションが処理されます)。ここではこれ以上のフォローアップはしません。

PHP カーネル層解析の逆シリアル化の脆弱性

__sleep の戻り値である上記の送信パラメータ retval をまだ覚えていますか? 上の図は、返された配列の最初の要素 x を示しています。変数を直接チェックインすることもできます。

このような大きな循環の後、異なるパスが同じ目標につながります。_sleep 関数で一連の操作を処理した後、php_var_serialize_class 関数を使用してクラス名をシリアル化し、その戻り値の構造を再帰的にシリアル化します。 _スリープ機能。最後に、結果は buf 構造体に保存されます。この時点で、シリアル化のプロセス全体が完了します。

3.1.1 シリアル化処理の概要

シリアル化処理をまとめます。

マジック関数がない場合は、クラス名をシリアル化します。再帰を使用して残りの構造をシリアル化します。

マジック関数がある場合は、マジック関数 __sleep を呼び出します。>ZEND_VM エンジンを使用して PHP 操作を解析します。>変換する必要がある構造体の配列を返します。シリアル化 –> シリアル化クラス名 –> __sleep の戻り値構造の再帰的シリアル化を利用します。

3.2 アンシリアル化ソース コードの分析

シリアル化プロセスを読んだ後、次に、最も単純なデモから unserialize プロセスを見ていきます。この例にはマジック関数は含まれていません。

PHP カーネル層解析の逆シリアル化の脆弱性

方法は上記と同じです。unserializeソース コードも var.c ファイルにあります。

PHP カーネル層解析の逆シリアル化の脆弱性

PHP カーネル層解析の逆シリアル化の脆弱性

上の図には、allowed_classes によると、PHP7 の新機能、フィルタリングによる逆シリアル化が含まれています。不正なデータの挿入を防ぐために、対応する PHP オブジェクトをフィルタリングする設定。フィルターされたオブジェクトは __PHP_Incomplete_Class オブジェクトに変換され、直接使用することはできませんが、逆シリアル化プロセスには影響しないため、ここでは詳しく説明しません。続いて php_var_unserialize 関数を使用します。

PHP カーネル層解析の逆シリアル化の脆弱性

ここでは引き続き

php_var_unserialize_internal 関数に従います。

PHP カーネル層解析の逆シリアル化の脆弱性

#この関数の主な内部操作プロセスは、文字列を解析し、対応する処理プロセスにジャンプすることです。上の図では最初の文字 0 が解析されており、オブジェクトへの逆シリアル化を表しています。


PHP カーネル層解析の逆シリアル化の脆弱性

最初にオブジェクト名が解析され、オブジェクトが存在することを確認するためにテーブル検索操作が実行されます。引き続き下を見てみましょう。


PHP カーネル層解析の逆シリアル化の脆弱性

上記の操作を完了した後、オブジェクト名 new に基づいて独自の新しいオブジェクトを作成し、初期化しましたが、逆シリアル化操作はまだ機能しませんでした。では、

object_common2 関数について説明します。

ここではマジック関数の判断と検出が見られますが、呼び出し部分はここにはありません。 process_nested_data 関数を続けて見てみましょう。


PHP カーネル層解析の逆シリアル化の脆弱性

PHP カーネル層解析の逆シリアル化の脆弱性

この関数は WHILE ループを使用して、2 つの php_var_unserialize_internal 関数を含む解析の残りの部分をネストしているようです。最初のものは名前を解析し、2 つ目は名前に対応する値を解析します。 process_nested_data 関数の実行が終了すると、文字列の解析が完了し、逆シリアル化操作の主要な内容が完了し、プロセスが終了しようとしています。


PHP カーネル層解析の逆シリアル化の脆弱性

レイヤーごとに元の関数

PHP_FUNCTION に戻ります。仕上げ作業があることがわかり、適用されたスペースを解放して、元の関数に戻ります。シーケンスが完了しました。マジック関数 __wakeup はここでは呼び出されません。 __wakeup の呼び出しタイミングを調べるために、ここで Demo を修正します。

PHP カーネル層解析の逆シリアル化の脆弱性

#ここから、新しいラウンドのデバッグが始まります。シリアル化が完了すると、確認したい呼び出しが

PHP_VAR_UNSERIALIZE_DESTROY リリース スペースに表示されることがわかります。

PHP カーネル層解析の逆シリアル化の脆弱性

逆シリアル化プロセスで __wakeup が見つかったときの VAR_WAKEUP_FLAG フラグをまだ覚えていますか? ここで、bar_dtor_hash 配列をトラバースしてこのフラグに遭遇すると、__wakeup の呼び出しが正式に開始されます。後の呼び出し方法は前の呼び出し方法と同じです。紹介されている __sleep 呼び出しメソッドはまったく同じであるため、ここでは繰り返しません。この時点で、すべての逆シリアル化プロセスが完了します。

3.2.1 シリアル化プロセスの概要

上記のことから、逆シリアル化プロセスは、シリアル化プロセスと比較して、マジック関数が出現するかどうかに依存しないことがわかります。 . プロセスに違いを生み出すため。アンシリアライズ プロセスは次のとおりです:

デシリアライズされた文字列を取得します –> 型に従ってデシリアライズします –> テーブルを検索して、対応するデシリアライズ クラスを見つけます –> 文字列に基づいて要素の数を決定します–> new 新しいインスタンスを作成します -> 残りの文字列を繰り返し解析します -> マジック関数 __wakeup があるかどうかを判断し、マークします -> スペースを解放し、マークがあるかどうかを判断します -> 呼び出しをオンにします。

4. PHP デシリアライゼーションの脆弱性

上記のソース コードの基礎を使用して、脆弱性 CVE-2016-7124 (__wakeup のバイパス) マジック関数を調べてみましょう。

したがって、この脆弱性には特定のバージョン要件があるため、上記でコンパイルした別の PHP バージョン (5.6.10) を使用してこの脆弱性を再現し、デバッグします。

最初に脆弱性を再現します:

PHP カーネル層解析の逆シリアル化の脆弱性

ここで、TEST クラスには要素 $a が 1 つだけ含まれていることがわかります。変更するときに、ここでそれを逆配列しています。要素文字列内の要素の数を表す値を指定すると、この脆弱性がトリガーされ、このクラスはマジック関数 __wakeup の呼び出しを回避します。

もちろん、脆弱性を引き起こす過程で興味深い現象も発見されましたが、これが唯一の引き起こし方法ではありません。

PHP カーネル層解析の逆シリアル化の脆弱性

#上の図の 4 つのペイロードに対応する逆シリアル化操作により、この脆弱性が引き起こされます。以下の 4 つは脆弱性を引き起こしますが、いくつかの小さな違いがあります。ここで、コードを少し変更します。

PHP カーネル層解析の逆シリアル化の脆弱性

上の図から、解析クラスの要素が出現する限り、逆シリアル化された文字列内にこの脆弱性があることがわかります。エラーが発生するたびにトリガーされます。ただし、クラス要素の内部操作を変更すると (上図の文字列長やクラス変数の型の変更など)、クラス メンバー変数の割り当てが失敗します。クラス メンバーの数が変更された場合 (元のメンバー数よりも大きい場合) のみ、クラス メンバーの割り当ての成功が保証されます。

デバッグを通じて問題を見てみましょう:

3 番目のパートでの逆シリアル化ソース コードの分析によると、この問題は最終的に解析されたコードで発生する可能性があると推測されます。変数の問題。ここでは、動的デバッグのためにデバッガーに直接移動します。

PHP カーネル層解析の逆シリアル化の脆弱性

バージョン 7.3.0 のソース コードと比較すると、このバージョンにはフィルター パラメーターがないことがわかります。反復のバージョンが非常に多いため、下位バージョンの処理プロセスは比較的単純に見えます。ただし、全体の調和ロジックは変更されていません。ここでは php_var_unserialize 関数を直接に従います。同じロジックは再度繰り返されません。クラス内のメンバー変数を処理するコードである差分 (object_common2 関数) を直接に従います。

PHP カーネル層解析の逆シリアル化の脆弱性

関数 object_common2 には、2 つの主な操作があります。 process_nested_data はクラス内のデータを繰り返し解析し、マジック関数 __wakeup を呼び出します。関数は解析に失敗し、値 0 を直接返します。後続の __wakeup 関数を呼び出す機会はありません。

これは、脆弱性を引き起こすペイロードが複数ある理由を説明しています。

PHP カーネル層解析の逆シリアル化の脆弱性

クラス メンバーの数のみを変更する場合は、while ループを 1 回完了するだけで、クラス内のメンバー変数を完全に割り当てることができます。内部メンバー変数を変更すると、pap_var_unserialize 関数の呼び出しが失敗し、その後 zval_dtor 関数と FREE_ZVAL 関数が呼び出されて現在のキー (変数) スペースが解放され、クラスでの変数の割り当てが失敗します。

一方、PHP7.3.0版では、ここに呼び出し処理はなく、単純にマークされているだけで、マジック関数の呼び出し処理全体のタイミングが、データが公開されます。これにより、このバイパスの問題が回避されます。この脆弱性は論理的な欠陥によって引き起こされるはずです。

以上がPHP カーネル層解析の逆シリアル化の脆弱性の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はfreebuf.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。