Overview
The goal of this lab was to build something close to a real enterprise environment — not just a single Windows server running AD in a home lab. It spans three cloud providers, uses a proper WireGuard mesh to connect them all, has a real mail server, real hybrid cloud identity, and a realistic set of misconfigurations that mirror what you actually find in penetration tests.
Everything here was built manually, step by step, including all the errors that came up along the way. The domain is apex-corp.xyz and the internal forest root is hq.apex-corp.xyz. Characters are named after MCU, DCU, and The Boys cast to make the user accounts look like a real company.
This is a personal training lab and portfolio project. All systems are isolated and under personal control. The misconfigurations are intentional, documented, and meant to simulate realistic enterprise attack surfaces for learning purposes.
Tech Stack
Architecture
All machines connect through a WireGuard mesh VPN. The Domain Controller on Vultr (10.10.0.1) acts as the hub. Every machine uses it as its primary DNS, which means Active Directory resolution works across all three cloud providers.
Part 1: High-Level Infrastructure Overview
A conceptual mapping of how the cloud-hosted environments (Vultr, Oracle, AWS) interconnect via the WireGuard VPN mesh, and how they synchronize identity to the Azure cloud directory.
Part 2A: Vultr Core Internal Network
The Vultr datacenter hosts the primary domain controller alongside supporting servers. APEX-PORTAL and MAILSERVER depend on STARK-TOWER for DNS and directory authentication, while FLATIRON provides a domain-joined workstation for user simulation.
Root DC · AD CS · DNS
10.10.0.1"] EXCH01["📧 MAILSERVER (EXCH01)
Exchange Server 2019"] PORTAL["🌐 APEX-PORTAL (PORTAL01)
Flask Web App"] WS01["💻 FLATIRON (WS01)
Win10 Workstation
h.campbell session"] EXCH01 --- DC01 PORTAL --- DC01 WS01 -.->|"domain-joined"| DC01 style DC01 fill:#1a3a1a,stroke:#a4ff91,color:#fff style EXCH01 fill:#1a2a3a,stroke:#6bb5ff,color:#fff style PORTAL fill:#1a2a3a,stroke:#6bb5ff,color:#fff style WS01 fill:#1a2a2a,stroke:#44ffcc,color:#fff
Part 2B: Multi-Cloud VPN Mesh (WireGuard)
STARK-TOWER acts as the WireGuard hub, stitching remote cloud providers into a single private subnet (10.10.0.0/24). Each peer connects directly to the hub for AD resolution and inter-cloud communication.
WireGuard Hub · DNS
10.10.0.1"] P2["🖥️ VOUGHT-HQ (DC02)
Oracle Cloud · Child DC
10.10.0.2"] P3["🐧 BATCAVE (LINUX01)
Oracle Cloud · Ubuntu
10.10.0.3"] P5["🐧 WATCHTOWER (LINUX02)
AWS EC2 · Jenkins
10.10.0.5"] HUB <-->|"WireGuard"| P2 HUB <-->|"WireGuard"| P3 HUB <-->|"WireGuard"| P5 style HUB fill:#1a3a1a,stroke:#a4ff91,color:#fff style P2 fill:#3a2a1a,stroke:#ffaa44,color:#fff style P3 fill:#3a2a1a,stroke:#ffaa44,color:#fff style P5 fill:#3a1a1a,stroke:#ff6b6b,color:#fff
Part 3: Hybrid Cloud Identity & Auth Flow
Illustrating user credential and identity flows. Local AD credentials from the Vultr VPS sync up to Microsoft Entra ID. The web portal authenticates users externally against Entra ID and logs session history in Cosmos DB.
Domain Controller (hq.apex-corp.xyz)"] end subgraph AZURE ["☁️ Microsoft Azure"] ENTRA["☁️ Microsoft Entra ID
apex-corp.xyz Tenant"] COSMOS["🗄️ Cosmos DB
User Metadata Backend"] end subgraph WEBPORTAL ["External User Flow"] PORTAL["🌐 APEX-PORTAL (PORTAL01)
portal.apex-corp.xyz"] end DC01 -->|"Password Hash Sync (PHS)
via Microsoft Entra Connect"| ENTRA PORTAL -->|"OAuth2 User Login"| ENTRA PORTAL -->|"Read/Write Session Data"| COSMOS style DC01 fill:#1a3a1a,stroke:#a4ff91,color:#fff style ENTRA fill:#1a1a3a,stroke:#9988ff,color:#fff style COSMOS fill:#1a1a3a,stroke:#9988ff,color:#fff style PORTAL fill:#1a2a3a,stroke:#6bb5ff,color:#fff
Machine Inventory
| Hostname | OS | Role | WireGuard IP | Cloud | Status |
|---|---|---|---|---|---|
STARK-TOWER (DC01) |
Windows Server 2022 | Root DC, AD CS, DNS, WireGuard Hub, AD Connect | 10.10.0.1 |
Vultr | Active |
MAILSERVER (EXCH01) |
Windows Server 2022 | Exchange 2019 — OWA, SMTP, EWS, MAPI | via DC01 gateway | Vultr | Active |
APEX-PORTAL (PORTAL01) |
Ubuntu 24.04 | Flask OAuth Portal — Caddy, Gunicorn, Cosmos DB | via DC01 gateway | Vultr | Active |
FLATIRON (WS01) |
Windows 10 Pro | Domain workstation — user session h.campbell | 10.10.0.4 |
Vultr | Active |
WATCHTOWER (LINUX02) |
Ubuntu 24.04 | Jenkins :8080 — planted creds, vuln config | 10.10.0.5 |
AWS EC2 | Active |
VOUGHT-HQ (DC02) |
Windows Server 2022 | Child DC — vought.hq.apex-corp.xyz, MSSQL, IIS | 10.10.0.2 |
Oracle Cloud | Decommissioned |
BATCAVE (LINUX01) |
Ubuntu 24.04 | AD-joined Linux — Nginx, PostgreSQL, Docker | 10.10.0.3 |
Oracle Cloud | Decommissioned |
VOUGHT-HQ and BATCAVE run on Oracle Cloud free-tier instances and are fully operational within the WireGuard mesh. The child domain vought.hq.apex-corp.xyz is active and joined to the forest. All configurations are fully documented and reproducible.
Network Layout
apex-corp.xyz
DNS only — no proxy
on SMTP / mail records"] end subgraph VPN ["WireGuard VPN — 10.10.0.0/24"] HUB["DC01
10.10.0.1
Hub + DNS"] P2["DC02 10.10.0.2"] P3["LINUX01 10.10.0.3"] P4["WS01 10.10.0.4"] P5["LINUX02 10.10.0.5"] end CF -->|"A record → Vultr Public IP"| HUB HUB --- P2 HUB --- P3 HUB --- P4 HUB --- P5 style HUB fill:#1a3a1a,stroke:#a4ff91,color:#fff
| DNS Record | Type | Points To | Proxy |
|---|---|---|---|
apex-corp.xyz | A | Vultr Public IP (DC01) | DNS only |
mail.apex-corp.xyz | A | Vultr Public IP (EXCH01) | DNS only ⚠️ |
portal.apex-corp.xyz | A | Portal VPS Public IP | DNS only |
autodiscover.apex-corp.xyz | CNAME | mail.apex-corp.xyz | DNS only ⚠️ |
@ (MX) | MX | mail.apex-corp.xyz priority 10 | — |
@ (SPF) | TXT | v=spf1 a:mail.apex-corp.xyz ~all | — |
Users & Groups
The domain has around 50 named user accounts spread across realistic Organizational Units. Characters are pulled from MCU, DCU, and The Boys to make the environment look like an actual company. A selection of key accounts:
| Username | Name | OU | Privilege | Notes |
|---|---|---|---|---|
t.stark |
Tony Stark | Executives | Domain Admin | Main admin — Enterprise Admin, Schema Admin |
homelander |
John Gillman | Executives | Domain Admin | Intentional DA misconfiguration on child domain |
svc_alfred |
svc_alfred | Service Accounts | Service Acct | Kerberoastable — SPN set, GenericAll on Domain Admins |
svc_jarvis |
svc_jarvis | Service Accounts | Service Acct | Kerberoastable — HTTP SPN, creds in Jenkins |
svc_friday |
svc_friday | Service Accounts | Service Acct | Kerberoastable — creds in Jenkins workspace |
h.campbell |
Hughie Campbell | Operations | Standard User | Active workstation session on WS01 (FLATIRON) |
b.allen |
Barry Allen | IT Department | Standard User | Jenkins admin account — brute-forceable |
Service accounts have Kerberos Service Principal Names (SPNs) registered, making them targets for Kerberoasting. Several standard users have the "Do not require Kerberos preauthentication" box checked, making them AS-REPRoastable.
Attack Paths
The lab is designed so an attacker can move from a public foothold all the way to Domain Admin and cloud access. The full chain:
Jenkins :8080 on WATCHTOWER
No authentication on People API"] -->|"User enumeration
anonymous read"| B["👤 b.allen discovered
/people/api/json"] B -->|"Hydra brute force
rockyou.txt"| C["🔑 Jenkins login
b.allen / Flash4Ever!"] C -->|"Groovy Script Console
Read workspace files"| D["📄 svc_jarvis creds
J@rv1s2025! — in deploy.sh
svc_friday creds — in .env"] D -->|"Kerberoast SPNs
or use plaintext creds"| E["🎫 TGS tickets
offline crack OR
direct AD auth"] E -->|"Exchange WriteDACL
abuse via MAILSERVER$"| F["🏰 Domain Admin
hq.apex-corp.xyz"] F -->|"Password Hash Sync
Entra Connect"| G["☁️ Cloud Access
Entra ID tenant
portal.apex-corp.xyz
Azure services"] style A fill:#2a1a1a,stroke:#ff6b6b,color:#fff style F fill:#3a1a1a,stroke:#ff4444,color:#fff style G fill:#1a1a3a,stroke:#9988ff,color:#fff
Foothold — Jenkins
Anonymous read on /people/api/json exposes usernames. Brute force gives Jenkins access. Groovy console reads credential files.
AD Credential Abuse
Service account passwords found in Jenkins workspace files. Kerberoastable SPNs provide offline cracking targets.
AD CS — ESC1
ApexVPN certificate template allows Subject Alternative Name in request. Any domain user can enroll a cert for any identity, including Domain Admin.
Exchange WriteDACL
Exchange Windows Permissions group holds WriteDACL on the domain object. Compromising MAILSERVER$ allows privilege escalation to Domain Admin.
OWA Password Spray
Outlook Web Access on port 443 is publicly reachable. The Global Address List exposes every user email after minimal access is obtained.
Hybrid Cloud Pivot
Password Hash Sync means AD credential compromise = cloud access. Compromised accounts work on Azure services and the custom portal.
Technical Guides
Follow the numbered pathway below to see the full setup progression, complete with actual screenshots, configuration commands, and detailed error logs from the build.
Errors & What Fixed Them
These are the errors that actually happened during the build, in the order they came up, and what fixed each one. Useful to read before starting a similar build.
| Where | Error | Fix |
|---|---|---|
| Child DC join | Weak local admin password rejected during DC promotion wizard | net user administrator StrongPass123! before promoting |
| Linux domain join | realm join failing — "insufficient permissions" — actually an RDNS lookup failure |
Add rdns = false under [libdefaults] in /etc/krb5.conf |
| Exchange install | Installer freezes at 16% during file copy — CPU at 100% | Disable Defender real-time: Set-MpPreference -DisableRealtimeMonitoring $true |
| Exchange install | AutoReportProgress crash — CLI crashes mid-install | Never click or scroll the installer window. Use a second PowerShell: Get-Content C:\ExchangeSetupLogs\ExchangeSetup.log -Tail 10 -Wait |
| Exchange forest prep | /PrepareAllDomains fails — DomainNotReachableException on the torn-down child domain |
Use /PrepareDomain:hq.apex-corp.xyz instead |
| Exchange inbound mail | External emails not arriving in OWA | Set Receive Connector to AnonymousUsers, add Accepted Domain, update Email Address Policy |
| Exchange SSL | Empty thumbprint error when running Get-ExchangeCertificate by Subject |
Filter by Issuer -like "*Let's Encrypt*" instead of Subject |
| Exchange outbound mail | Outbound emails sitting in queue, never delivered | Open a Vultr support ticket to unblock outbound port 25 — it is blocked by default on all VPS providers |
| Entra ID sync | Synced users getting .onmicrosoft.com addresses instead of apex-corp.xyz |
Add Alternative UPN Suffix in AD Domains and Trusts, then bulk-update with Get-ADUser -Filter * | ForEach-Object { Set-ADUser -Identity $_ -UserPrincipalName "$($_.SamAccountName)@apex-corp.xyz" } |
| Jenkins install | Wrong key URL — apt-get rejects the GPG key from pkg.jenkins.io |
Use jenkins.io-2026.key instead of jenkins.io.key in the curl command |
| Windows features | Exchange prerequisite installer rejects Web-Asp-Net48 name |
Use the legacy name Web-Asp-Net45 — it installs .NET 4.8 on Server 2022, kept for backward compatibility |