一線を越えてしまうと、ホストの安全性をかき乱すことに
エグゼクティブサマリー
- Akamai の研究者の Ben Barnea は、Microsoft Windows RPC サービスの重要な脆弱性を発見しました。この脆弱性は、 CVE-2022-37998 および CVE-2022-37973 に指定され、ベーススコアは 7.7 です。
- これらの脆弱性は、Local Session Manager RPC インターフェースのいくつかのバグを利用するのもです。
- これらの脆弱性は、コンテナおよびセッションサービス(Microsoft Defender Application Guard、Sandbox、Docker、Windows Terminal Server など)の動作を妨げるサービス妨害攻撃につながります。
- この脆弱性は、パッチが適用されていない Windows 10、Windows 11、Windows Server 2022 のマシンに存在します。
- これらの脆弱性は、Microsoft に確実に開示され、2022 年 10 月の Patch Tuesday で修正されました。
- Akamai では、脆弱性の概念実証を リサーチリポジトリで公開しています。
概要
Akamai Security Intelligence Group はこの 1 年間、MS-RPC について深く調査しました。さまざまな操作を実行するプロトコルについて、MS-RPC は大部分が調査が不十分であり、現実的な影響を及ぼす可能性があります。その影響の 1 つは、 RPC インターフェース が脆弱性にさらされることです。このブログ投稿では、Local Session Manager(LSM)RPC インターフェース内の脆弱性に焦点を当てています。
LSM は、Session Manager Subsystem の一部であるサービスです。LSM は、Windows マシン上のターミナル・サーバー・セッションに関連するローカルセッションを管理し、Winlogon や Csrss など、その他の関連する Windows コンポーネントと通信します。
LSM は Lsm.dll に実装され、クライアントとサーバーの両方のロジックが含まれます。LSM では複数の RPC インターフェースが公開されており、その 1 つは、Hyper-V 仮想マシン内で実行されるコンテナのセッション管理に関連する興味深いインターフェースです。これらの脆弱性は、このインターフェース内に存在します。
このインターフェースとは何ですか?
新しい RPC インターフェースには、UUID c938b419-5092-4385-8360-7cdc9625976a が指定されています。このインターフェースでは、 ContainerCOM_AskForSession および ContainerCom_SessionLoggedOff の 2 つの関数が公開されています。このインターフェースは、常に RPC_S_OK を返すセキュリティコールバックにも登録されているため、すべてのユーザがアクセスできます。LSM サーバーでは、Hyper-V コンテナのみからアクセス可能な Hyper-V ソケット(hvsocket)エンドポイントが登録されます。
図 1:hvsocket 経由の RPC 接続のクライアント設定と hvsocket エンドポイントのサーバー設定
セッションがコンテナ内部に作成されると(RDP 接続などのため)、LSM クライアントは、コンテナの LSM の内部の RpcGetRequestForWinlogon を最初にコールします。この関数は、セッションの作成を判別し、コンテナ内部で実行中に、最初にホストに許可を要求します。これは、hvsocket を使用するホストに対する RPC コール ContainerCOM_AskForSession を親に対して呼び出すことで実行されます。RPC インターフェースでは、セッション数をコンテナに制限します。これは、新しく作成したセッションを追跡することで実行されます。
LSM はどのようにセッションを追跡しますか?
答えは簡単です。ContainerSessionServer という名前のグローバルオブジェクトがあり、セッションを追跡するための次の 2 つの変数が保持されます。
- 作成されたセッションの合計数のカウンター。 これは 1 つに制限されています。つまり、任意の時点で許可されるセッションは 1 つだけです。
- コンテナの GUID とコンテナのセッション数の間のマップ。 これは、各コンテナで 2 つに制限されています。
コンテナがセッションを要求するたびに、 ContainerSessionServer::AskForSession が、セッションの合計数のカウンターが 1 未満かどうかを最初にチェックします。1 未満であれば、セッションの合計数が増分し、マップ内のコンテナのセッションカウンターも増分します
。 ContainerSessionServer::OnSessionLoggedOff がコールされると(コンテナが終了したとき、または RPC コールとして直接コールされたとき)、この関数はセッションの合計数とコンテナのセッション数の両方を 1 つずつ減分します。
RPC 関数の脆弱性の監査
このインターフェースは単純で実装しやすいように思えますが、4 つのバグが見つかっており、Akamai では、これらのバグを 2 つの脆弱性のチェーンに指定しました。
チェーン #1 - クリティカルセクションを介した DoS - CVE-2022-37998
バグ #1 - クリティカルセクションを終了できない
ContainerSessionServer::AskForSession では、クリティカルセクションを使用して、グローバルオブジェクト ContainerSessionServer へのアクセスを同期します。
図 2:脆弱性の逆コンパイルコード。この関数は、クリティカルセクションを解放せずに終了する
前述のように、クリティカルセクションは行 112 に入力されます。その後、114~116 のすべての行で、コンテナのセッションカウンターが制限値(2)に達しているかどうかをチェックします。その場合、LSM はこのセッションを追跡せず、すぐに関数を終了します(行 125)。残念ながら、このコードでは、入力されたクリティカルセクションを終了しません。このため、このインターフェースをさらにコールしても、このクリティカルセクションが解放されるのを待機している間に停止します。
ただし、前述のように、セッションの合計数のカウンターは 1 に制限されているため、コンテナのセッションカウンターが 2 つのポイントに到達するためにはどうすればよいでしょうか?論理的には不可能ですが、ここで、2 つ目バグが発生します。
バグ #2 - カウンターが正しく追跡されない
コンテナへのセッションがログオフされると、ホストの ContainerSessionServer::OnSessionLoggedOff に RPC コールが実行されます。この関数は、 DecreaseTotalSessionCount をコールすることで、セッションの合計数のカウンターを最初に減分します。これは、コンテナが追跡されているかどうかに関係なく実行されます。コンテナが追跡されていないことが検出された場合は、セッションの合計数のカウンターが増分せずに終了します。
これにより、セッションの合計数のカウンターの値が負の数になる場合があります(符号付き整数であるため)。多くの OnSessionLoggedOff 要求を送信してから、 AskForSession 要求を送信するだけです。これにより、セッションの合計数のカウンターを任意の負の数に減分できます。
1 つ目と 2 つ目のバグをチェーンに指定する
バグ #2 を使用すると、負の数になるまで、セッションの合計数のカウンターを数回減分できます。これにより、2 つの要求を AskForSessionに送信すれば、バグ #1 を悪用できます。この関数が 2 回コールされると、セッションの合計数のカウンターが 1 未満であることがチェックされます。2 つ目のバグのため、このようになります。次に、コンテナのセッションカウンターが 2 であることが確認され、クリティカルセクションを終了せずに戻ります。
図 3:悪用プロセスの概要
DoS は、クリティカルセクションのデッドロックを生成したのと同じスレッドに新しい着信 RPC コールがディスパッチされるかどうかによって異なります。RPC ランタイムが新しいコールを同じスレッドにディスパッチすると、DoS は発生しません。これは、ネストされた所有権が EnterCriticalSection で許可され、同じスレッドで EnterCriticalSection を 2 回コールできるからです。クリティカルセクションを保持しているスレッド以外のスレッドに RPC コールがディスパッチされた場合は、永久に待機します。
チェーン #2:メモリーリークによる DoS:CVE-2022-37973
バグ #3 - メモリーリーク
ContainerSessionServer::AskForSession は、コンテナの終了/一時停止/再開などのコンテナのイベントも追跡します。これは、コンテナの GUID で HcsOpenComputeSystem をコールしてから、 HcsRegisterComputeSystemCallback をコールバックに登録することで実行されます。
登録されたコールバックは、コンテキストオブジェクトを受け取ります。コンテキストは ContainerSessionServer::AskForSession内部に割り当てられます。残念ながら、エラーが多く発生する場合は、コンテキストに割り当てられたメモリーを解放せずに関数が終了します。これにより、メモリーリークが発生し、攻撃者が何度もトリガーできるようになります。コールが十分に実行されると、LSM プロセスのメモリーが枯渇し、プロセスがクラッシュします。
テストでは、無限ループで RPC 要求を送信すると、毎秒約 3 MB の割り当てが得られました。この場合、24 GB のメモリーが割り当てられた後、LSM サービスがクラッシュしました。24 GB を消費するのにかかる時間は約 2 時間です。サービスは自動的に再生成されません。
バグ #4 - リモートアクセス
MS-RPC のエンドポイントは多重化されます。サーバーが複数のインターフェースと複数のエンドポイントを登録した場合、各インターフェースは各エンドポイントからアクセスできます。エンドポイントとインターフェースは 互いにバインドされていません。
このインターフェースには、コンテナへの hvsocket 経由でのみアクセスできるようになっています。この場合、LSM は名前付きパイプエンドポイント “\pipe\LSM_API_service” を登録しリモートアクセス可能です。エンドポイントが多重化されているため、リモートの攻撃者は名前付きパイプエンドポイントに接続して、コンテナのインターフェースに要求を送信できます。修正は簡単です。クライアントが使用しているエンドポイントをセキュリティコールバックでチェックし、それが hvsocket でない場合はアクセスを拒否するようにします。
3 つ目と 4 つ目のバグをチェーンに指定する
コンテナの追跡機能は、クライアント識別子プロパティに基づいています。つまり、hvsocket の場合、クライアント識別子はコンテナの GUID になります。名前付きパイプの場合、これはクライアントのマシン名になります。
最初の悪用をトリガーするためには、実行中のコンテナの実際の GUID であるクライアント識別子がクライアントに必要になります。そのため、実行中のコンテナの GUID を正常に確認し、マシン名を変更することができなければ、これらのバグをリモートクライアントでトリガーすることができません(起こりにくいシナリオですが)。
残念ながら、3 つ目のバグ(メモリーリーク)では、要求されたコンテナでチェックを実行する前にオブジェクトを割り当てます。これは、リモートの攻撃者(バグ #4 を使用)がメモリーリークをリモートでトリガーできることを意味します。攻撃者が何度もコールすると、メモリーの枯渇やプロセスのクラッシュを引き起こす可能性があります。
影響
これらの脆弱性はサービス妨害(DoS)の脆弱性に分類されますが、攻撃者がセキュリティ機能を回避できるようにするため、セキュリティ上の影響があります。
最初の悪用(クリティカルセクション)では、特定の新しいインターフェースに対する DoS が発生します。この問題により、新しいサンドボックスインスタンスの作成が妨げられます。
2 つ目の悪用では、リモートとコンテナの両方でトリガーできるため、プロセス全体がクラッシュします。このため、LSM への依存関係はすべて機能しません。Microsoft Defender Application Guard や Sandbox などのセキュリティ機能も動作しなくなります。さらに、RDP と Docker も動作しなくなります。
図 4:Microsoft Edge に表示される MDAG エラー
まとめ
これらの脆弱性は、単純なものや重大でないと思われるものでも、実際に悪影響を及ぼす可能性があることを示す貴重な例です。このインターフェースは、トリガーが簡単で、注意を引く影響を及ぼすバグも少なく、大したことのないように思えるかもしれませんが、
実際には、このように野放しにされた攻撃チェーンを攻撃者が利用しているのです。重要でないと思われれば、無視されがちですが、これこそ恰好の標的として悪用されます。
Akamai では、RPC での継続的な取り組みと併せて、他の RPC インターフェースにも同様のバグがないかを探すように、他の研究者に勧めています。
このような RPC 調査のトピックに興味がある方は、 RPC ツールキット でその他記事やツールをチェックしてください。また、これに関するリアルタイムの最新情報や、Akamai が現在取り組んでいるその他の調査については、 X(旧 Twitter) でフォローすることもできます。