ラック・セキュリティごった煮ブログ

セキュリティエンジニアがエンジニアの方に向けて、 セキュリティやIT技術に関する情報を発信していくアカウントです。

【お知らせ】2021年5月10日~リニューアルオープン!今後はこちらで新しい記事を公開します。

株式会社ラックのセキュリティエンジニアが、 エンジニアの方向けにセキュリティやIT技術に関する情報を発信するブログです。(編集:株式会社ラック・デジタルペンテスト部)
当ウェブサイトをご利用の際には、こちらの「サイトのご利用条件」をご確認ください。

デジタルペンテスト部提供サービス:ペネトレーションテスト

Token Stealing の仕組み

デジタルペンテスト部の北原です。 本日は、Windows OS への侵入に成功した攻撃者が管理者権限を奪取した後に悪用する、Token Stealing という手法について解説します。

要点

技術的な詳細に興味がない読者向けに、先に要点を書きます。 要点は以下の3点です:

  • ログオンしている Windows OS の管理者権限の奪取に成功した場合、Token Stealing の手法により他のプロセスの実行アカウントの権限を借用できます。

  • Active Directoryドメインに参加している Windows OS の場合、他のドメイン端末で使用するアカウントで起動しているプロセスが存在すると、Token Stealing によりそのプロセスのアカウントの権限が借用できるため、攻撃者の侵入範囲の拡大につながります。ドメインに参加している端末の管理のために各端末にプロセスを実行する場合は、対象の端末でのみ管理者権限を持つアカウントでのプロセス実行が好ましいです。

  • Token Stealing 自体を可能な限り防ぐには、日頃からローカル管理者グループに所属していないアカウントで端末を運用し、セキュリティ更新プログラムの早急な適用による権限昇格対策をすると良いでしょう。

Token Stealing の検知と対策の詳細については、MITRE ATT&CK の ID T1134 にまとめられています。

アクセストーク

Token Stealing は、ログオン中の Windows OS で起動しているプロセスからアクセストークンを借用する手法です。 一般的には、管理者権限を獲得した状態で実行可能です。 Token Stealing を使用すると、例えば admin という管理者ユーザアカウントで管理者権限が使える場合に、 winlogon.exe のような NT AUTHORITY\SYSTEM で稼働しているプロセスからアクセストークンを借用して、 NT AUTHORITY\SYSTEM としての動作が可能となります。

アクセストークンは Windows OS での権限情報を管理するために使用されるものです。 Windows OS のプロセスの権限はアクセストークンにより管理されています。 アクセストークン含まれている代表的な情報は以下の通りです:

  • 実行しているユーザアカウント
  • 所属しているグループアカウント
  • 整合性レベル
  • 特権
  • セッション情報

プロセスとスレッド

Token Stealing の原理を説明する前に、プロセスとスレッドについて簡単に解説します。 プロセスはスレッドの実行を管理するオブジェクトであり、プロセスでの処理はすべてスレッドにより実行されています。 簡易的な図にすると以下の通りです:

プロセスとスレッド

アクセストークンはプロセスとスレッドのそれぞれに割り当てられます。 スレッドのアクセストークンは、基本的にはスレッドを管理しているプロセスのアクセストークンが使用されますが、必要に応じて管理元プロセスとは異なるアクセストークンを割り当てることができます。 ただし、任意のアクセストークンをスレッドに割り当てたい場合は、 SeImpersonatePrivilege という特権が必要です。 プロセスに割り当てられるトークンをPrimaryトークン、借用によりスレッドに割り当てられるトークンをImpersonationトークンと呼び、Windows APIでは以下に示す TOKEN_TYPE 列挙型で指定されます:

typedef enum _TOKEN_TYPE {
  TokenPrimary = 1,
  TokenImpersonation
} TOKEN_TYPE;

メンバー名が示す通り、TokenPrimary はPrimaryトークンを、TokenImpersonation はImpersonationトークンを意味します。

また、ImpersonationトークンにはImpersonation Levelという情報を指定する必要があります。 Impersonation Levelは、Windows APIでは以下に示す SECURITY_IMPERSONATION_LEVEL 列挙型で指定されます:

typedef enum _SECURITY_IMPERSONATION_LEVEL {
  SecurityAnonymous,
  SecurityIdentification,
  SecurityImpersonation,
  SecurityDelegation
} SECURITY_IMPERSONATION_LEVEL, *PSECURITY_IMPERSONATION_LEVEL;

SECURITY_IMPERSONATION_LEVEL 列挙型の4つのImpersonation Levelは、それぞれ以下の用途で使用します:

  • SecurityAnonymous -- このImpersonation LevelはImpersonationトークンでは用いられません。PrimaryトークンもImpersonationトークンも、作成する際に同じWindows APIを使用するため、Primaryトークンを作成する際にImpersonation Levelを指定するパラメータとしてこのメンバーを使用します(PrimaryトークンにはImpersonation Levelが存在しないため)。

  • SecurityIdentification -- このImpersonation Levelが設定されたImpersonationトークンでは、アクセストークンに関連づけられているアカウント名と基本的な情報の確認のみが可能であり、アクセストークンに設定された権限は行使できません。SeImpersonatePrivilege の使用ができないなど、十分な権限がない状態でスレッドにImpersonationトークンを割り当てた場合は、強制的にこのImpersonation Levelが設定された状態でImpersonationトークンが割り当てられます。

  • SecurityImpersonation -- このImpersonation Levelが設定されたImpersonationトークンでは、プロセスが稼働しているマシンの内部でのみアクセストークンに設定されている権限を行使できます。

  • SecurityDelegation -- このImpersonation Levelはサーバ/クライアント型のソフトウェアで使用されます。サーバのプロセスがクライアントのアクセストークンを借用してこのImpersonation Levelが指定されたImpersonationトークンを使用すると、サーバのプロセスが稼働しているマシンの内部だけではなく、クライアントのプロセスが稼働しているマシンでもアクセストークンに設定された権限を行使できます。

プロセスが使用可能な特権は、Sysinternals Suite の Process Explorer で確認できます。 以下の図は Process Explorerwinlogon.exe の特権を表示しています:

Process Explorer による winlogon.exe に割り当てられた特権の確認

cmd.exepowershell.exe であれば、 whoami /priv と実行すると以下のように特権情報が列挙できます:

C:\Windows\system32>whoami /priv

PRIVILEGES INFORMATION
----------------------

Privilege Name                            Description                                                        State
========================================= ================================================================== ========
SeIncreaseQuotaPrivilege                  Adjust memory quotas for a process                                 Disabled
SeSecurityPrivilege                       Manage auditing and security log                                   Disabled
SeTakeOwnershipPrivilege                  Take ownership of files or other objects                           Disabled
SeLoadDriverPrivilege                     Load and unload device drivers                                     Disabled
SeSystemProfilePrivilege                  Profile system performance                                         Disabled
SeSystemtimePrivilege                     Change the system time                                             Disabled
SeProfileSingleProcessPrivilege           Profile single process                                             Disabled
SeIncreaseBasePriorityPrivilege           Increase scheduling priority                                       Disabled
SeCreatePagefilePrivilege                 Create a pagefile                                                  Disabled
SeBackupPrivilege                         Back up files and directories                                      Disabled
SeRestorePrivilege                        Restore files and directories                                      Disabled
SeShutdownPrivilege                       Shut down the system                                               Disabled
SeDebugPrivilege                          Debug programs                                                     Disabled
SeSystemEnvironmentPrivilege              Modify firmware environment values                                 Disabled
SeChangeNotifyPrivilege                   Bypass traverse checking                                           Enabled
SeRemoteShutdownPrivilege                 Force shutdown from a remote system                                Disabled
SeUndockPrivilege                         Remove computer from docking station                               Disabled
SeManageVolumePrivilege                   Perform volume maintenance tasks                                   Disabled
SeImpersonatePrivilege                    Impersonate a client after authentication                          Enabled
SeCreateGlobalPrivilege                   Create global objects                                              Enabled
SeIncreaseWorkingSetPrivilege             Increase a process working set                                     Disabled
SeTimeZonePrivilege                       Change the time zone                                               Disabled
SeCreateSymbolicLinkPrivilege             Create symbolic links                                              Disabled
SeDelegateSessionUserImpersonatePrivilege Obtain an impersonation token for another user in the same session Disabled

Token Stealing の動作原理

Token Stealing の動作をまとめると、以下の手順になります:

  1. 借用したいアクセストークンで動作しているプロセスのアクセス権を取得

  2. アクセス権を取得したプロセスのアクセストークンへのアクセス権を取得

  3. アクセス権を取得したアクセストークンを複製

  4. 複製したアクセストークンを使用

それでは、Token Stealing がどのように実装されているかについて解説しましょう。

プロセスのアクセス権を取得

まずは、借用したいアクセストークンで動作しているプロセスのアクセス権を取得する必要があります。 アクセス権の取得に使用する代表的な Windows APIOpenProcess APIです。 関数シグネチャを以下に示します:

HANDLE OpenProcess(
  [in] DWORD dwDesiredAccess,
  [in] BOOL  bInheritHandle,
  [in] DWORD dwProcessId
);

このWindows APIは、パラメータ dwProcessId に指定したPIDを持つプロセスにアクセスするためのオブジェクト(ハンドル)を取得するためのものです。 OpenProcess APIのパラメータの中で特に重要なパラメータは、アクセス権限を指定する dwDesiredAccess です。 Token Stealing に必要な最低限のアクセス権限は、簡易的な情報の取得に必要な権限である PROCESS_QUERY_LIMITED_INFORMATION です。

プロセスへのアクセス権の取得に成功すると、戻り値として、プロセスにアクセスするためのハンドルがゼロでは無い値で返されます。

アクセストークンのアクセス権を取得

続けて、アクセス権を取得したプロセスから、アクセストークンのアクセス権を取得します。 アクセストークンのアクセス権を取得するための基本的な Windows APIOpenProcessToken APIです。 関数シグネチャを以下に示します:

BOOL OpenProcessToken(
  [in]  HANDLE  ProcessHandle,
  [in]  DWORD   DesiredAccess,
  [out] PHANDLE TokenHandle
);

OpenProcessToken APIでは、 OpenProcess APIで取得したプロセスのハンドルと アクセス権 を設定する必要があります。 Token Stealingでは、アクセス権の取得に成功したアクセストークンを複製したいので、複製に必要な権限である TOKEN_DUPLICATE を設定します。

アクセストークンへのアクセス権の取得に成功すると、戻り値として TRUE が返されると共に、パラメータ TokenHandle に設定したメモリ領域にアクセストークンへのハンドルが出力されます。

アクセストークンを複製

アクセス権を獲得したアクセストークンを複製するには、DuplicateTokenEx APIに代表されるような Windows API を使用します。 関数シグネチャを以下に示します:

BOOL DuplicateTokenEx(
  [in]           HANDLE                       hExistingToken,
  [in]           DWORD                        dwDesiredAccess,
  [in, optional] LPSECURITY_ATTRIBUTES        lpTokenAttributes,
  [in]           SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
  [in]           TOKEN_TYPE                   TokenType,
  [out]          PHANDLE                      phNewToken
);

DuplicateTokenEx APIには、アクセス権を取得したアクセストークンへのハンドルを指定すると共に、上述のToken TypeとImpersonation Levelを指定します。 新しく作成するプロセスに割り当てるアクセストークン(Primaryトークン)として複製したい場合は、TokenType には TokenPrimary を設定します。 PrimaryトークンにはImpersonation Levelが存在しないため、ImpersonationLevel には SecurityAnonymous を設定します。 スレッドに割り当てるアクセストークン(Impersonationトークン)として複製したい場合は、TokenType には TokenImpersonation を設定します。 借用するアクセストークンの権限を行使するには、ImpersonationLevelSecurityImpersonation または SecurityDelegation のいずれかを設定する必要があります。

アクセストークンへのアクセス権の取得に成功すると、戻り値として TRUE が返されると共に、パラメータ phNewToken に設定したメモリ領域に複製したアクセストークンへのハンドルが出力されます。

複製したアクセストークンを使用

複製したアクセストークンを使用する際には、新しいプロセスを起動するか Token Stealing を実行しているスレッドに割り当てるかによって異なります。

新しいプロセスを作成する

複製したアクセストークンを適用した新しいプロセスを作成する場合は、CreateProcessAsUser APIという Windows API が一般的に使用されています。 アクセストークンを適用した新しいプロセスの作成には SeAssignPrimaryTokenPrivilegeSeIncreaseQuotaPrivilege が特権として必要になります。 デフォルトの Windows OS の設定では、SeAssignPrimaryTokenPrivilege は Administrators グループのアカウントに与えられていないため、Administrators グループのアカウントでは直接的にToken Stealingで新しいプロセスを生成できません。 SeAssignPrimaryTokenPrivilege を持つプロセスから取得したアクセストークンを Token Stealing を実行しているスレッドに割り当てるか、何かしらの手法で SeAssignPrimaryTokenPrivilege を持つ高権限アカウントが使用できる状態である必要があります。

補足 SeAssignPrimaryTokenPrivilege が使えなくても、SeImpersonatePrivilege が使える場合は CreateProcessAsUser API の代わりに CreateProcessWithToken API を使用すると、複製したトークンを割り当てた新しいプロセスが起動できます。しかし、CreateProcessWithToken API を使用するには、対象のWindows OS で Secondary Logon というサービスが稼働している必要があります。

Token Stealing しているスレッドに割り当てる

複製したアクセストークンを実行中のスレッドに割り当てるWindows APIはいくつか存在しますが、代表的なものは ImpersonateLoggedOnUser APIです。 関数シグネチャを以下に示します:

BOOL ImpersonateLoggedOnUser(
  [in] HANDLE hToken
);

パラメータに複製したアクセストークンへのハンドルを設定するのみです。 特殊な場合を除き、スレッドにアクセストークンを適用するには SeImpersonatePrivilege が特権として必要です。 しかし ImpersonateLoggedOnUser APIは基本的には TRUE を返しエラーコードを出力しないため、単に戻り値を確認しただけではアクセストークンの適用に成功したかどうかが確認できません。 アクセストークンの適用に成功したかどうかを確認する手法としては、スレッドに割り当てられたアクセストークンのImpersonation Levelを確認する手法があります。 十分な権限を持たない状態でスレッドにアクセストークンを設定しようとすると、前述の通りImpersonation Levelは Identification に設定されます。

スレッドに割り当てられたアクセストークンの状態を確認する場合は、GetTokenInformation APIという Windows API を使用するか、Google Project Zero の James Forshaw 氏が開発した TokenViewer を使用すると良いでしょう。 TokenViewer は事前にビルドされたバージョンが公式には配布されていないため、自身でビルドする必要があります。

以下の図は、独自に開発した検証コードを使用して、SeImpersonatePrivilege が無効な状態で winlogon.exe から Token Stealing を試行し、ImpersonateLoggedOnUser API の実行後に TokenViewer でスレッドに割り当てられたアクセストークンを確認した様子を示しています。 ImpersonateLoggedOnUser API の実行結果は TRUE を返しておりエラーコードは 0(エラーは無い)ですが、TokenViewer でImpersonation Levelが Identification に設定されていることが確認できます。 スレッドには winlogon.exe から複製したアクセストークンが割り当てられていますが、Impersonation Levelが Identification に設定されているので、実際には winlogon.exe から複製したアクセストークンの権限を行使できません。

TokenViewer でスレッドに割り当てられたアクセストークンを確認した様子(失敗時)

SeImpersonatePrivilege が有効な状態で、同様の手順を実行して TokenViewer を確認すると、以下の図のようにImpersonation Levelが Impersonation に設定されていることが確認できます。

TokenViewer でスレッドに割り当てられたアクセストークンを確認した様子(成功時)

補足 Token Stealing を実行するスレッドのアクセストークンを変更する場合は ImpersonateLoggedOnUser API を使用しますが、同一プロセスの別のスレッドのアクセストークンを変更する場合は SetThreadToken API を使用します。

アクセストークンを変更したスレッドのアクセストークンを元の状態に戻したい場合は、RevToSelf API を使用します。

Token Stealing の実際

攻撃検知のガイドラインとして世界的に活用されている MITRE ATT&CK では、ID T1134のページ に、Token Stealing の検知や軽減策に関する情報がまとめられています。 代表的なツールとしては、C2 フレームワークである Empire などで使用されている Invoke-TokenManipulationTokenVator などが存在します。 また、世界で最も著名であろう攻撃検証ツールであるMetasploitには、incognito というプラグインとして実装されています。

現実的な悪用手法としてまず考えられるのは、管理者権限アカウントから NTAUTHORITY\SYSTEM の権限を使用したい場合でしょう。 NTAUTHORITY\SYSTEM の権限を使えれば、通常の管理者権限ではアクセスできない Windows OS 上に保存された機密情報(認証情報など)にアクセスできます。

注意する必要があるのは、Windows OS の端末が組織の Active Directoryドメインに参加している場合です。 組織によっては、管理の都合上、Active Directoryドメインを管理する Domain Admins グループに相当する権限を持つグループで、ドメインに参加している端末を管理するための動作(バッチ処理などによる保守作業)をしている場合があります。 もし、 Domain Admins グループに相当する権限を持つグループで、攻撃者が管理者権限の奪取に成功している端末を操作してしまった場合は、Token Stealing により操作元アカウントの権限が奪取され、攻撃者の侵入範囲が大幅に拡大してしまう危険性が考えられます。

以下の図は、試験的に作成した Active Directoryドメイン環境で、Token Stealing によるドメインコントローラへの侵入の様子を示しています。 クライアント端末 cl01.contoso.localドメイン管理者アカウント contoso\administrator が、Sysinternals Suite の PsExec のような遠隔管理ツールを用いて cmd.exe を起動して、何かしらの保守作業をしている状況を想定しています。 この場合、管理者権限の奪取に成功すれば、Token Stealing によりドメイン管理者アカウント contoso\administrator からアクセストークンの借用し、contoso\administrator の権限でドメインコントローラ dc01.contoso.local の管理共有の閲覧や遠隔コード実行が可能となります。

Token Stealing による Active Directory 環境での侵入範囲の拡大の例

Token Stealing による侵入範囲の拡大には、借用するアクセストークンにネットワーク認証情報が使用されているという前提条件が必要ですが、不測の事態に備えてドメインアカウントでの遠隔操作は避けたほうが安全です。 ドメインに参加している端末に対してネットワーク越しに保守作業をしたい場合は、保守対象端末でのみ管理者権限を持つドメインに所属していないアカウントで実施すると良いでしょう。

まとめ

Windows OS への侵入に成功した攻撃者が管理者権限の奪取に成功してしまった場合は、Token Stealing により OS で稼働しているプロセスのアカウントの権限を使用されてしまいます。 Active Directoryドメインに参加している端末である場合は、他の端末で使用しているドメインアカウントのプロセスが稼働していると、侵入範囲の拡大につながる可能性があります。 権限昇格への対策はもちろんですが、権限昇格されてしまった場合に備えて、Active Directoryドメインに参加している端末のネットワーク越しの保守には、保守対象の端末でのみ管理者権限が行使可能なローカルアカウントを使用すると、攻撃者の更なる侵入範囲の拡大への危険性を低減できます。