まえがき
PHP を学習する過程で、次のようないくつかの PHP 機能を理解するのが難しいことがわかりました。 PHP 00 の切り捨て、MD5 の欠陥、逆シリアル化バイパス __wakeup
など。表面的な理解に固執するのではなく、PHP コアがどのようにそれを行うのかを探っていきたいと思います。
以下は、CTF で一般的に使用される逆シリアル化の脆弱性、CVE-2016-7124 (マジック関数 __wakeup をバイパスする) であり、PHP カーネルのデバッグ プロセスを共有する例として示されています。カーネル ソース コードのデバッグ環境の構築、シリアル化および逆シリアル化カーネル ソース コードの分析から最終的な脆弱性分析までのすべての部分が含まれます。 (推奨: PHP チュートリアル )
1. 例によって引き起こされる考え
まず、私が書いた小さな例を見てみましょう。
上の図に基づいて、まず PHP のマジック関数を紹介します。
まず、いくつかの関数の公式ドキュメントを見てみましょう。はじめに:
これは、クラスがインスタンスとして初期化されるとき、__construct
が呼び出されるときの簡単な概要です。破棄されると、__destruct が
と呼ばれます。
クラスがシリアル化のために serialize
を呼び出すと、__sleep
関数が自動的に呼び出されます。文字列が unserialize## を使用して逆シリアル化される場合、 # クラスに変換されると、
__wakeup 関数が呼び出されます。上記のマジック関数が存在する場合は、自動的に呼び出されます。自分で手動でディスプレイ呼び出しを行う必要はありません。
__destruct 関数では、ファイルの書き込みという機密性の高い操作が行われます。ここでは逆シリアル化を使用して危険な文字列を構築します。これにより、コード実行の脆弱性が発生する可能性があります。
__wakeup 関数にフィルタリング操作があり、それが構築の妨げになっていることがわかりました。逆シリアル化では最初に
__wakeup 関数を呼び出す必要があることがわかっているためです。
2. PHP ソース コードのデバッグ環境のセットアップ
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 コンポーネントのバージョン。
2.1 Windows PHP 7.3.0 のコンパイル
phpsdk-vc15-x64 をダブルクリックします。
phpsdk_buildtreephp7 と入力すると、php7 フォルダーが同じディレクトリに表示され、シェル ディレクトリも変更されていることがわかります。
#
次に、解凍したソース コードを \php7\vc15\x64 の下に置きます。シェルはこのフォルダーに入り、phpsdk_deps–update–branchmaster
コマンドを使用して、関連する依存コンポーネントを更新およびダウンロードします。
完了を待った後、ソース コード ディレクトリに入り、buildconf.bat
バッチ ファイルをダブルクリックします。configure.bat
と configure がリリースされます。 .js
2 つのファイル、シェルで configure–disable-all–enable-cli–enable-debug–enable-phar を実行して、対応するコンパイル オプションを設定します。他に必要な場合は、configure –help を実行して表示できます
プロンプトに従って、nmake を使用して直接コンパイルします。
コンパイルが完了し、実行可能ファイルのディレクトリが php7\vc15\x64\php-src\x64\Debug_TS フォルダーに作成されます。 php -v と入力すると、関連情報が表示されます。
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/ 残りは上記とまったく同じなので、ここでは繰り返しません。
2.3 デバッグ構成
上記で PHP インタープリターをコンパイルしたため、デバッグには VSCODE を直接使用します。
ダウンロード後、C/C デバッグ拡張機能をインストールします。
次に、ソース コード ディレクトリを開き、[デバッグ] -> [構成を開く] をクリックすると、launch.json ファイルが開きます。
上の図によると、これら 3 つのパラメータを設定した後、現在のディレクトリの 1.php に PHP コードを記述し、PHP ソース コードにブレークポイントを設定できます。直接デバッグします。
デバッグ環境がセットアップされました。
3. PHP デシリアライズのソース コード分析
一般的に PHP デシリアライズと言えば、通常、serialize と unserialize という 2 つの関数がペアで表示されますが、もちろんこれらはペアではありません。 __sleep() と __wakeup() という 2 つのマジック メソッドもあります。ご存知のとおり、シリアル化は単にオブジェクトがファイルに保存されることを意味しますが、逆シリアル化はその逆で、オブジェクトがファイルから読み取られてインスタンス化されます。
次に、上記で設定したデバッグ環境に基づいて、動的デバッグを使用して、PHP (バージョン 7.3.0) で行われるシリアル化と逆シリアル化の内容を直感的に反映します。
3.1 ソース コード分析のシリアル化
まず、__sleep
マジック関数を含まない簡単なデモを作成しましょう:
次に、ソース コード内で serialize
関数をグローバルに検索し、var.c ファイル内でこの関数を見つけます。関数ヘッダーの直下にブレークポイントを配置し、デバッグを開始します。
いくつかの準備作業を行った後、シリアル化処理関数の入力を開始し、php_var_serialize
関数をフォローアップしていることがわかります。 。
引き続き php_var_serialize_intern
関数を追っていきます。主な処理関数は次のとおりです。関数コードが多いため、重要なのは、この関数が var.c ファイルにも含まれていることです。
関数全体の構造は switch case であり、struc バリアントの型はマクロ Z_TYPE_P によって解析されます (このマクロは struc-> に展開されます)。 ;u1.v.type) を使用してシリアル化する型を決定し、対応する CASE 部分を入力して操作します。次の図は型定義を示しています。
上の赤いボックス内の数字 8 によると、この時点でオブジェクトにシリアル化する必要があることがわかります IS_OBJECT
、対応する CASE ブランチを入力します:
上図ではマジック関数 __sleep
の呼び出しタイミングが確認できますが、この関数は今回作成したデモには存在しないため、処理は入りません。この枝。分岐ごとに処理の流れが異なるので、マジック関数 __sleep を使った処理については後ほど見ていきます。
上記の case IS_OBJECT ブランチにはプロセス ヒットがなく、case に Break ステートメントもないため、実行は IS_ARRAY ブランチに継続されます。 struc構造体名からclassを抽出し、その長さを計算してbuf構造体に代入し、クラス内でシリアル化する構造体を抽出してハッシュ配列に格納します。
次のステップは、php_var_serialize_intern
関数を使用してハッシュ配列全体を再帰的に解析し、そこから変数名と値を抽出することです。フォーマット分析を実行し、解析された文字列を buf 構造に結合します。最後に、プロセス全体が完了すると、文字列全体が柔軟な配列構造 buf に完全に格納されます。
#上の図の赤いボックスから、最終結果と一致していることがわかります。デモを少し変更して、マジック関数 __sleep
を追加しましょう。公式ドキュメントによると、__sleep
関数は配列を返す必要があります。この関数ではクラス メンバー関数も呼び出しています。その特定の動作を観察してください。
#前のプロセスはまったく同じなのでここでは繰り返しませんが、分岐点から始めましょう。
php_var_serialize_call_sleep
関数を直接フォローアップします。
ここで引き続きフォローアップします。call_user_function
。マクロ定義によれば、実際には _call_user_function_ex
関数が呼び出されます。 , いくつかのコピー操作がここで実行されたため、スクリーンショットは取得されません。その後、プロセスは zend_call_function
関数の呼び出しに進みます。
関数 zend_call_function
では、実際の状況では、__sleep
で独自の処理のいくつかを実行する必要があります。ここで、PHP は実行される操作を PHP 自身の zend_vm
エンジン スタックにプッシュし、後で 1 つずつ解析します (つまり、対応する OPCODE を解析します)。
ここでのプロセスはこの分岐に到達し、zend_execute_ex
関数をフォローアップします。
ZEND_VM では、全体的な処理フローが while(1) ループであり、ZEND_VM スタック内の操作を継続的に解析していることがわかります。上図の赤いボックス内の ZEND_VM エンジンは、ZEND_FASTCALL
メソッドを使用して、対応する処理関数にディスパッチします。
__sleep
でメンバー関数 show を呼び出したので、ここでは最初に show を見つけます。その後、オペレーション全体が解析されるまで、次のオペレーションは次のラウンドの新しい解析のために ZEND_VM スタックにプッシュされ続けます (ここでは、表示されているオペレーションが処理されます)。ここではこれ以上のフォローアップはしません。
__sleep の戻り値である上記の送信パラメータ retval をまだ覚えていますか? 上の図は、返された配列の最初の要素 x を示しています。変数を直接チェックインすることもできます。
このような大きな循環の後、異なるパスが同じ目標につながります。_sleep 関数で一連の操作を処理した後、php_var_serialize_class 関数を使用してクラス名をシリアル化し、その戻り値の構造を再帰的にシリアル化します。 _スリープ機能。最後に、結果は buf 構造体に保存されます。この時点で、シリアル化のプロセス全体が完了します。
3.1.1 シリアル化処理の概要
シリアル化処理をまとめます。
マジック関数がない場合は、クラス名をシリアル化します。再帰を使用して残りの構造をシリアル化します。
マジック関数がある場合は、マジック関数 __sleep を呼び出します。>ZEND_VM エンジンを使用して PHP 操作を解析します。>変換する必要がある構造体の配列を返します。シリアル化 –> シリアル化クラス名 –> __sleep の戻り値構造の再帰的シリアル化を利用します。
3.2 アンシリアル化ソース コードの分析
シリアル化プロセスを読んだ後、次に、最も単純なデモから unserialize
プロセスを見ていきます。この例にはマジック関数は含まれていません。
方法は上記と同じです。unserialize
ソース コードも var.c ファイルにあります。
上の図には、allowed_classes
によると、PHP7 の新機能、フィルタリングによる逆シリアル化が含まれています。不正なデータの挿入を防ぐために、対応する PHP オブジェクトをフィルタリングする設定。フィルターされたオブジェクトは __PHP_Incomplete_Class オブジェクトに変換され、直接使用することはできませんが、逆シリアル化プロセスには影響しないため、ここでは詳しく説明しません。続いて php_var_unserialize 関数を使用します。
php_var_unserialize_internal 関数に従います。
object_common2 関数について説明します。
PHP_FUNCTION に戻ります。仕上げ作業があることがわかり、適用されたスペースを解放して、元の関数に戻ります。シーケンスが完了しました。マジック関数
__wakeup はここでは呼び出されません。
__wakeup の呼び出しタイミングを調べるために、ここで Demo を修正します。
PHP_VAR_UNSERIALIZE_DESTROY リリース スペースに表示されることがわかります。
逆シリアル化プロセスで __wakeup が見つかったときの VAR_WAKEUP_FLAG フラグをまだ覚えていますか? ここで、bar_dtor_hash 配列をトラバースしてこのフラグに遭遇すると、__wakeup の呼び出しが正式に開始されます。後の呼び出し方法は前の呼び出し方法と同じです。紹介されている __sleep 呼び出しメソッドはまったく同じであるため、ここでは繰り返しません。この時点で、すべての逆シリアル化プロセスが完了します。
3.2.1 シリアル化プロセスの概要
上記のことから、逆シリアル化プロセスは、シリアル化プロセスと比較して、マジック関数が出現するかどうかに依存しないことがわかります。 . プロセスに違いを生み出すため。アンシリアライズ プロセスは次のとおりです:
デシリアライズされた文字列を取得します –> 型に従ってデシリアライズします –> テーブルを検索して、対応するデシリアライズ クラスを見つけます –> 文字列に基づいて要素の数を決定します–> new 新しいインスタンスを作成します -> 残りの文字列を繰り返し解析します -> マジック関数 __wakeup があるかどうかを判断し、マークします -> スペースを解放し、マークがあるかどうかを判断します -> 呼び出しをオンにします。
4. PHP デシリアライゼーションの脆弱性
上記のソース コードの基礎を使用して、脆弱性 CVE-2016-7124 (__wakeup のバイパス) マジック関数を調べてみましょう。
したがって、この脆弱性には特定のバージョン要件があるため、上記でコンパイルした別の PHP バージョン (5.6.10) を使用してこの脆弱性を再現し、デバッグします。
最初に脆弱性を再現します:
ここで、TEST クラスには要素 $a が 1 つだけ含まれていることがわかります。変更するときに、ここでそれを逆配列しています。要素文字列内の要素の数を表す値を指定すると、この脆弱性がトリガーされ、このクラスはマジック関数 __wakeup
の呼び出しを回避します。
もちろん、脆弱性を引き起こす過程で興味深い現象も発見されましたが、これが唯一の引き起こし方法ではありません。
#上の図の 4 つのペイロードに対応する逆シリアル化操作により、この脆弱性が引き起こされます。以下の 4 つは脆弱性を引き起こしますが、いくつかの小さな違いがあります。ここで、コードを少し変更します。
上の図から、解析クラスの要素が出現する限り、逆シリアル化された文字列内にこの脆弱性があることがわかります。エラーが発生するたびにトリガーされます。ただし、クラス要素の内部操作を変更すると (上図の文字列長やクラス変数の型の変更など)、クラス メンバー変数の割り当てが失敗します。クラス メンバーの数が変更された場合 (元のメンバー数よりも大きい場合) のみ、クラス メンバーの割り当ての成功が保証されます。
デバッグを通じて問題を見てみましょう:
3 番目のパートでの逆シリアル化ソース コードの分析によると、この問題は最終的に解析されたコードで発生する可能性があると推測されます。変数の問題。ここでは、動的デバッグのためにデバッガーに直接移動します。
バージョン 7.3.0 のソース コードと比較すると、このバージョンにはフィルター パラメーターがないことがわかります。反復のバージョンが非常に多いため、下位バージョンの処理プロセスは比較的単純に見えます。ただし、全体の調和ロジックは変更されていません。ここでは php_var_unserialize 関数を直接に従います。同じロジックは再度繰り返されません。クラス内のメンバー変数を処理するコードである差分 (object_common2 関数) を直接に従います。
関数 object_common2 には、2 つの主な操作があります。 process_nested_data はクラス内のデータを繰り返し解析し、マジック関数 __wakeup を呼び出します。関数は解析に失敗し、値 0 を直接返します。後続の __wakeup 関数を呼び出す機会はありません。
これは、脆弱性を引き起こすペイロードが複数ある理由を説明しています。
クラス メンバーの数のみを変更する場合は、while ループを 1 回完了するだけで、クラス内のメンバー変数を完全に割り当てることができます。内部メンバー変数を変更すると、pap_var_unserialize 関数の呼び出しが失敗し、その後 zval_dtor 関数と FREE_ZVAL 関数が呼び出されて現在のキー (変数) スペースが解放され、クラスでの変数の割り当てが失敗します。
一方、PHP7.3.0版では、ここに呼び出し処理はなく、単純にマークされているだけで、マジック関数の呼び出し処理全体のタイミングが、データが公開されます。これにより、このバイパスの問題が回避されます。この脆弱性は論理的な欠陥によって引き起こされるはずです。
以上がPHP カーネル層解析の逆シリアル化の脆弱性の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。