This post is also available in: English (英語)

概要

Windowsコンテナのリバース エンジニアリングに関する前回の投稿ではWindows Serverコンテナの内部実装について概説しました。Windows Serverコンテナについて詳しく調査した後、これらのコンテナ内でコードを実行することは、ホスト上で管理コードを実行するくらいに危険だと考える必要があることがわかりました。これらのコンテナはサンドボックス用に設計されておらず、簡単にエスケープできることが判明したからです。Microsoftは弊社と共同で調査にあたり、コンテナセキュリティの限界について完全に把握しました。この投稿の目的は、このコンテナの実行による危険性に対する認識を高めることにあります。

この問題を具体的に示すため、筆者が最近発見したWindowsコンテナにおけるコンテナ エスケープ方法を提示します。この方法を使うと、コンテナ内部で実行されているプロセスからホストにファイルを書き込むことができるようになります。この方法はホスト上でRCE(リモート コード実行)を実現する目的で利用されうります。Kubernetes環境では、このエクスプロイトを利用して簡単にノード間の拡散が行われます。言い換えれば、Windows Serverコンテナ内部で実行される単一のアプリケーション インスタンスの侵害に成功した攻撃者は、コンテナの境界を難なく突き破り、同じマシン上の他のアプリケーションにアクセスできるというわけです。Kubernetesの場合、攻撃者は他のマシンにもアクセスできる可能性があります。つまり、たった1つのエンドポイント インスタンスを突破した攻撃者が、本番環境のワークロード全体にアクセスできてしまうということです。

この問題は、Windows Serverコンテナの使用を許可しているクラウド プロバイダのユーザーに影響すると考えられ、そこにはWindowsを使用するMicrosoft AKS (Azure Kubernetes Service) のすべてのユーザーが含まれます。パロアルトネットワークスのお客様は、Prisma™ Cloudによってこの脅威から保護されます。

Windows Serverコンテナ

前回の投稿で詳しくお伝えしたように、MicrosoftはWindowsベースのコンテナを実行するために2つのソリューションを開発しました。1つ目のソリューションは、Hyper-Vテクノロジに基づいて各コンテナを仮想マシン(VM)の内側で実行するものです。2つ目のオプションはWindows Serverコンテナで、サイロ オブジェクトなどのWindowsカーネル機能を信頼してコンテナを設定するものです。後者のソリューションは、従来のLinuxのコンテナ実装と似ています。つまり、相互を隔離する論理メカニズムによって同じカーネル上で実行されるプロセスです。

一部のユーザーはHyper-VコンテナではなくWindows Serverコンテナを利用しています。そうする理由は、1つのVM内部で各コンテナを実行するとパフォーマンス上のコストがかかるためです。Microsoftもこれについて次のようにドキュメントに記載しています。

The additional isolation provided by Hyper-V containers is achieved in large part by a Hypver-Visor layer of isolation between the container and the container host. This affects container density as, unlike Windows Server Containers, less sharing of system files and binaries can occur, resulting in an overall larger storage and memory footprint. In addition there is the expected additional overhead in some network, storage io, and CPU paths.

HYPER-V コンテナーによって提供される追加の分離は、主にコンテナーとコンテナー ホストの間の分離のハイパーバイザー レイヤーによって実現されます。 これは、Windows Server コンテナーとは異なり、システム ファイルとバイナリの共有が少なくなる可能性があるため、コンテナー密度に影響し、ストレージとメモリの使用量が全体的に大きくなります。 さらに、一部のネットワーク、ストレージ IO、 CPU のパスでは、追加のオーバーヘッドが予想されます。※ 出典 https://docs.microsoft.com/ja-jp/windows-server/administration/performance-tuning/role/windows-server-container/

 

調査を進めるなかで筆者は「Windows Serverコンテナのセキュリティについてはもっとうまくドキュメント化できるはずだ」と考えました。Hyper-Vコンテナを使用する方が安全であることを示す参考資料はありますが、Windowsコンテナがブレイクアウトに陥りやすいことを明確に示したドキュメントの記述は見つけられませんでした。そこでMicrosoftに問い合わせたところ、同社のガイダンスでは、ホスト上で管理者として実行したくないものはWindows Serverコンテナ上で実行しないようユーザーに推奨していました。また同社は次のようにも指摘していました。

Windows Server Containers are meant for enterprise multi-tenancy. They provide a high degree of isolation between workloads, but are not meant to protect against hostile workloads. Hyper-V containers are our solution for hostile multi-tenancy.

Windows Serverコンテナはエンタープライズ マルチテナンシーでの利用が想定されています。ワークロード間で高度な隔離を実現しますが、敵対的なワークロードに対する防御は想定されていません。敵対的なマルチテナンシーに対する当社のソリューションはHyper-Vコンテナとなります。

 

そこで以下のセクションでは、Windowsシンボリック リンクのカーネル インターナルなど、この問題の詳細について説明します。なお、以前の投稿で説明したサイロなどのWindowsコンテナ インターナルの一部の知識があれば、ここで提示する手法が理解しやすいでしょう。

コンテナエスケープ

コンテナ内部からWindowsシンボリック リンクを解決するには、説明している文書のないフラグを使用します。このフラグでシンボリック リンクをホスト マシンのルート ディレクトリに解決するとうい行為は、要するにコンテナのファイル システムの外側にアクセスするという行為です。このフラグをコンテナ プロセスで有効にするには特別な権限が必要ですが、筆者は、デフォルトのコンテナ プロセスからこの権限を昇格させる方法を見つけました。これがコンテナエスケープにつながる可能性があります。

以下のセクションでは、筆者がその方法を発見したいきさつを振り返り、なぜエスケープが可能になるのかについて詳細に説明します。

シンボリック リンク

Windowsのシンボリック リンクについてきちんと説明したドキュメントは多くはありませんがWindows NT以降は多少存在します。まずWindows NTが「オブジェクト マネージャー シンボリック リンク」、「レジストリ キー シンボリック リンク」という2種類のシンボリック リンクとともに登場しました。ただしこれらはファイル関連のシンボリック リンクではなく、Microsoftがオペレーティング システムの内部をたまたまそのように実装することにした、というにすぎませんでした。Windows 2000になってようやくファイル システム シンボリック リンクが登場しましたが、それもファイル レベルのシンボリック リンクではなく、ディレクトリ リダイレクトとしてのみ機能するものでした。完全にファイル レベルのシンボリック リンクが登場したのはWindows Vistaが初めてでした。

本稿では、オブジェクト マネージャー シンボリック リンクのみを取り上げます。他のものは範疇外ととなりますので本稿では割愛します。

オブジェクト マネージャー シンボリック リンク

Windowsを使用していれば、気付かないうちに使用しているのがこのシンボリック リンクでしょう。Cドライブ文字などのようなものは、実はオブジェクト マネージャー シンボリック リンクを使用して実装されています。中身を見てみると、誰かがC:\にアクセスすると、オブジェクト マネージャーがその呼び出しを実際にマウントされているデバイスにリダイレクトします。

図1: C:が単なるシンボリック リンクであることを示すWinObj。C:はシンボリック リンクのリストに含まれている
図1: C:が単なるシンボリック リンクであることを示すWinObj

オブジェクト マネージャーはファイルだけでなく、レジストリやセマフォをはじめとする多くの名前付きオブジェクトも処理します。ユーザーがC:\secret.txtにアクセスしようとすると、その呼び出しは\??\C:\secret.txtというパスを使用してカーネル関数NtCreateFileに到達します。このパスは、カーネルが操作方法を認知しているNTパスです。実際のシステム コールが実行される前に、ユーザーモードのWindows APIによってこのパスが変換されます。このパス変換が行われる理由は\??\部分にあります。これが、ルート ディレクトリ マネージャーでカーネルを正しいディレクトリに向かわせます。たとえば、ディレクトリはC:シンボリック リンクのターゲットを保持します。

最終的には、ObpLookupObjectNameが呼び出されます。ObpLookupObjectNameの仕事は、名前から実際のオブジェクトに解決することです。この関数は別のカーネル関数ObpParseSymbolicLinkExを使用して、そのターゲットへのシンボリック リンクとなるパス部分を解析します。

パスのすべての部分がシンボリック リンクになるかどうかチェックされます。このチェックはObpParseSymbolicLinkExによって実行されます。オブジェクト マネージャーはリーフ ノード(末端のノード)が見つかるまでこの処理を繰り返します。リーフ ノードとは、オブジェクト マネージャーではそれ以上解析できなくなったものを指します。パスの一部がシンボリック リンクである場合、この関数はSTATUS_REPARSEまたはSTATUS_REPARSE_OBJECTを返し、パスの関連部分をシンボリック リンクのターゲットに変更します。

図2: CreateFile APIのコール スタックを示すWinDbg
図2: CreateFile APIのコール スタックを示すWinDbg
このすべての完了後、C:\secret.txtが実際のパスに解析され、\Device\HarddiskVolume3\secret.txtのようになります。\Device\HarddiskVolume3というパスが、ルート ディレクトリ オブジェクト(ObpRootDirectoryObject)の下にオープンされます。

ルート ディレクトリ オブジェクトの詳細

オブジェクト マネージャーのルート ディレクトリ オブジェクトは、すべてのアプリケーション可視性のある名前付きオブジェクト(ファイルやレジストリ キーなど)を含むフォルダのようなものです。このメカニズムにより、アプリケーションはそれぞれの間でこれらのオブジェクトを作成したりアクセスしたりできます。

重要な部分

コンテナ内部からファイルにアクセスするときには、カスタム ルート ディレクトリ オブジェクトの下ですべてが解析されます。C:が解析される場合には、クローンのC:シンボリック リンクに対して解析されます。このためホストのファイル システムではなく、仮想マウントされたデバイスを指すことになります。

シンボリック リンクとコンテナ

筆者は、コンテナ内部からシンボリック リンクのルックアップ プロセスを追うことにしました。コンテナ内部のプロセスが、C:\secret.txtというターゲット ファイルを使用してCreateFileを呼び出します。先に説明したように、このパスはカーネルに到着する前に\??\C:\secret.txtに変換されます。コンテナのカスタム ルート ディレクトリ オブジェクトの下で、システムは??にアクセスします。これはGLOBAL??を参照するものです。システムはGLOBAL??ディレクトリの下でシンボリック リンクC:を検索し、実際にそうしたシンボリック リンクを見つけます。この時点で、このパスは上記のシンボリック リンクのターゲットに解析されます。この場合は\Device\VhdHardDisk{a36fab63-7f29-4e03-897e-62a6f003674f}\secret.txtです。次にカーネルが当該のVhdHardDisk{…}デバイスを開くわけですが、ホストのルート ディレクトリ オブジェクト内のDeviceフォルダの下でこのデバイスを検索するのではなく、コンテナのカスタム ルート ディレクトリ オブジェクトの下でこのデバイスを検索し、コンテナのファイル システムの仮想デバイスを見つけます。

図3: ルート ディレクトリ オブジェクトでパスが解析される方法を示すWinObj
図3: ルート ディレクトリ オブジェクトでパスが解析される方法を示すWinObj

しかし何か間違っています。\Silos\1588\の下でDeviceを参照したときに、筆者は実際のデバイスを指すVhdHardDisk{…}という名前のオブジェクトが見つかることを期待していましたが、見つかったのは\Device\VhdHardDisk{…}を指す同じ名前のシンボリック リンクでした。何が起こったのでしょうか。どうすればWindowsは実際のデバイスに到達するのでしょうか。この時点で筆者は、シンボリック リンク ルックアップ サブジェクトの調査を開始し、セキュリティ研究者のAlex Ionescu (CrowdStrike)とJames Forshaw (Google Project Zero)がRecon 2018で述べた「グローバル」シンボリック リンクを示すフラグの存在を、彼らのスライドのある1行に見つけました。さらに、このフラグがチェックされる場所を見つけるため、関連する関数をさかのぼって確認していきました。

最終的にObpLookupObjectName内で有望そうな分岐を見つけました。

図4:有望そうなIDA内の分岐
図4:有望そうなIDA内の分岐

レジスタediObpParseSymbolicLinkExの戻り値を保持するので、筆者はこの値である368hを検索し、これがSTATUS_REPARSE_GLOBALを表していることをつきとめました。つまり、ObpParseSymbolicLinkExSTATUS_REPARSE_GLOBALを返すと、オブジェクト マネージャーはサイロのルート ディレクトリには向かわず、通常のルート ディレクトリ オブジェクトであるObpRootDirectoryObjectの下のファイルをオープンするのです。

問題点

この時点で、筆者はこの動作を理解したと確信しました。グローバル シンボリック リンクを作成するには、システム プロセスのみが持つ何らかの特別な権限が必要なのだと考えました。コンテナの作成時に、作成プロセスはこれらの特別な権限を持っているので、コンテナが使用するためのグローバル シンボリック リンクを作成できるものの、コンテナ内部のプロセスはこれを実行できません。この作成プロセスはグローバル シンボリック リンクのポイント先を制御し、それはVhdHardDiskのような一部の特別なデバイスにアクセスするためだけに使用されるので、実際には問題が発生しないのです。ところが、これは部分的にしか正しくないことがわかりました。

本当の問題

筆者は、カーネル コードでSTATUS_REPARSE_GLOBALを表す368hという値を検索し始めました。IDAおよびWinDbgを少し操作した後、最終的に筆者は関数ObpParseSymbolicLinkExに行き着き、それによって、シンボリック リンク オブジェクト内の関連フラグがオフセット28h (Object + 0x28)にあることを見つけました。筆者は、新しいシンボリック リンクを作成する関数であるNtCreateSymbolicLinkObjectにブレークポイントを設置し、次にDockerを使用して新しいコンテナを作成しました。これによって、コンテナに新しいシンボリック リンクを作成するたびに、多くのブレークが発生するようになりました。これにより実際に\Silos\1588\Device\VhdHardDisk{a36fab63-7f29-4e03-897e-62a6f003674f}オブジェクトを作成することができました。

注意: これは、グローバル シンボリック リンクのように振る舞うシンボリック リンク オブジェクトでした。最終的には、シンボリック リンク オブジェクト上のアクセス ブレークポイントはオフセット28hに設置されました。成功です! シンボリック リンクの作成後すぐに、筆者がブレークポイントを設置した場所で別の関数がメモリを変更しようとしました。それはNtSetInformationSymbolicLinkという関数でした。この関数はシンボリック リンクを処理して、関連オブジェクトを開き、内部の要素を変更しているようでした。

幸運にも、これはntdll内で同じ名前のラッパー関数も取得していたため、ユーザー モードから簡単に呼び出すことができました。筆者はこの関数のリバース エンジニアリング(コード分析)を行い、その中でTcb権限をチェックするコード部分を見つけました。TcbはTrusted Computing Baseのことで、その権限説明は「オペレーティング システムの一部として機能する」となっています。

筆者は、どういう条件下でSTATUS_REPARSE_GLOBALが返されるのかと、シンボリック リンクをグローバルに変更するためにNtSetInformationSymbolicLinkが必要とする正確なパラメータを把握できるところまで、ObpParseSymbolicLinkExをさかのぼりました。攻撃者がエクスプロイトを作成しにくくなるように、この投稿ではこれらのパラメータをあえて省略します。

エクスプロイト プラン

Tcb権限によってこのグローバル フラグを有効にできる可能性と、それによるコンテナ エスケープの可能性がわかったところで、コンテナのファイル システムをエスケープするために次のようなプランを考えてみました。

  1. ホストのC:ドライブに対するシンボリック リンクを作成します。
  2. Tcb権限を取得します。
  3. 上記のシンボリック リンクをグローバルにします。
  4. ホストのファイル システム上のファイルにアクセスします。

唯一このプランに欠けているのがステップ2の実行方法です。そう、コンテナ内にはTcb権限がないのです。つまり、筆者のコンテナ プロセスはデフォルトではTcb権限を持っていません。ただし、WindowsコンテナにはCExecSvcという名前の特殊なプロセスがあります。このプロセスは、ホストとコンテナ間の通信をはじめとするコンテナ実行の多くの側面を担っているものです。これはTcb権限も持っているので、CExecSvcを介してコンテナ プロセスがコードを実行できれば、それはTcb権限による実行ということになり、上記プランの展開が可能になります。

図5: CExecSvcがSeTcbPrivilegeを持つことを示すProcessHacker
図5: CExecSvcがSeTcbPrivilegeを持つことを示すProcessHacker

実行

筆者はCExecSvcに対してシンプルなDLLインジェクションを行うことにしました。これは攻撃ロジックに含まれているものです。これはうまく機能して、筆者はすぐにホストのファイル システムへのアクセス権を得ることができました。CExecSvcはシステム プロセスなので、他のシステム プロセスが持つのとまったく同じように、ホスト ファイル システム全体への完全な無制限のアクセス権を得たのです。

Azure Kubernetes Service (AKS)

Azure Kubernetes Service (AKS)はマネージド コンテナ オーケストレーション サービスで、オープン ソースのKubernetesシステムに基づいており、Microsoft Azure Public Cloudで使用可能です。AKSを使用すると、Dockerコンテナやコンテナベースのアプリケーションを、コンテナ ホストのクラスタ全体にデプロイし、スケーリングして管理できるようになります。

AKSはポッドごとにWindows Serverコンテナを使用しているので、Windowsノードを持つKubernetesクラスタの1つ1つがこのエスケープに対して脆弱であると言えます。

それだけでなく、攻撃者がWindowsノードのいずれかへのアクセス権を得てしまえば、残りのクラスタへの拡散は容易です。

次の画像は、残りのクラスタを制御するために必要なものすべてを含むWindowsノードを示しています。ここに表示されているのは、コンテナ(この場合にはポッド)からホスト(この場合はノード)へのアクセスを何とか行った後の状況です。

表示されたWindowsノードには、攻撃者が残りのKubernetesクラスタを制御するために必要なすべてのものが含まれています。
図6: Windowsノード内部で必要なものすべて

ここからは、kubectlを使用するだけで残りのクラスタを制御できます。

ノード内部からkubectlを使用
図7: ノード内部からkubectlを使用

結論

本稿では、権限を昇格してWindows Serverコンテナをエスケープするための方法をひととおり俯瞰してきました。ユーザーは、セキュリティ境界をコンテナ化に依存するような内容についてはWindows ServerコンテナのかわりにHyper-Vコンテナの使用を推奨するMicrosoftのガイダンスに従う必要があります。Windows Serverコンテナで実行されるプロセスはどれも、ホスト上の管理者と同じ権限を備えていると想定しておく必要があります。セキュリティ保護の必要なアプリケーションをWindows Serverコンテナで実行するのであれば、Hyper-Vコンテナに移動させることをお勧めします。

この調査にあたって貴重なアドバイスをくださったAlex Ionescu氏とJames Forshaw氏に感謝します。ありがとうございました。

パロアルトネットワークスのPrisma™ Cloudは、お客様のコンテナを侵害から守ります。Prisma Cloudコンピューティングは、ユーザーが既知の署名済みイメージのみを実行するように制限できるTrusted Imagesという名前のコンプライアンス機能も提供しています。この機能を使用することで、悪意のあるイメージの実行が阻止され、お客様は攻撃対象領域をさらに縮小することができます。

Enlarged Image