Enumerates Active Directory domains and maps attack surface for penetration testing.
You are helping a penetration tester enumerate an Active Directory domain and identify attack paths. All testing is under explicit written authorization.
This skill works at three access levels:
Check for ./engagement/ directory. If absent, proceed without logging.
When an engagement directory exists:
[ad-discovery] Activated → <target> to the screen on activation.engagement/evidence/ with
descriptive filenames (e.g., sqli-users-dump.txt, ssrf-aws-creds.json).This skill covers Active Directory discovery — enumerating domain objects, identifying misconfigurations, and reporting findings to the orchestrator. When you confirm an exploitable finding — STOP.
Do not load or execute another skill. Do not continue past your scope boundary. Instead, return to the orchestrator with:
The orchestrator decides what runs next. Your job is to execute this skill thoroughly and return clean findings.
Stay in methodology. Only use techniques documented in this skill. If you encounter a scenario not covered here, note it and return — do not improvise attacks, write custom exploit code, or apply techniques from other domains. The orchestrator will provide specific guidance or route to a different skill.
You MUST NOT:
When you find exploitable attack paths, present routing recommendations in your return summary. Do not continue past enumeration.
Call get_state_summary() from the state MCP server to read current
engagement state. Use it to:
Write actionable findings immediately via state so the orchestrator can react in real time (via event watcher) instead of waiting for your full return summary. Use these tools as you discover findings:
add_credential() — valid credentials (pre-created computer accounts, gMSA readable, cleartext in descriptions/GPP/shares)add_vuln() — ADCS misconfigs (ESC1-ESC8), Kerberoastable accounts, coercion vectors, SMB signing disabled, LDAP signing not requiredadd_pivot() — delegation paths, ACL abuse chains, trust relationships, new subnets from AD Sitesadd_blocked() — techniques attempted and failed (so orchestrator doesn't re-route)
Your return summary must include:netexec (nxc), bloodhound-python or rusthound-ce, certipy,
bloodyAD, kerbrute, Impacket suite (GetUserSPNs.py, GetNPUsers.py,
lookupsid.py)Kerberos-first authentication (when credentials are available):
This skill may start unauthenticated. Once credentials are obtained, switch to Kerberos authentication for all subsequent enumeration:
# Get a TGT (password, hash, or AES key)
# Use getTGT.py or impacket-getTGT — both are the same tool (see Troubleshooting)
getTGT.py DOMAIN/user:'Password123'@dc.domain.local
# or with NTLM hash
getTGT.py DOMAIN/[email protected] -hashes :NTHASH
export KRB5CCNAME=user.ccache
# Then use -k -no-pass on all Impacket tools
# Use --use-kcache on NetExec
# Use -k on Certipy and bloodyAD
Identify domain controllers and assess the network posture.
# DNS SRV records
nslookup -type=srv _ldap._tcp.dc._msdcs.DOMAIN.LOCAL
nslookup -type=srv _kerberos._tcp.DOMAIN.LOCAL
# NetExec SMB scan — shows OS, signing, SMBv1
nxc smb 10.10.10.0/24
# NetExec generate /etc/hosts entries
nxc smb 10.10.10.0/24 --generate-hosts-file hosts
# SMB signing — signing:False = relay target
nxc smb 10.10.10.0/24 | grep -i "signing:False"
# LDAP signing — signing:None = relay to LDAP viable
nxc ldap DC01.DOMAIN.LOCAL
# Determine if LDAPS is available
nxc ldap DC01.DOMAIN.LOCAL --port 636
Findings:
State writes:
add_vuln(title="SMB signing disabled on <host>", host="<host>", vuln_type="smb-signing", severity="medium")add_vuln(title="LDAP signing not required on <host>", host="<host>", vuln_type="ldap-signing", severity="medium")add_pivot(source="AD discovery", destination="<domain>/<hostname>", method="DNS/SMB enumeration")Use when no valid credentials are available yet.
# SMB null session
nxc smb DC01.DOMAIN.LOCAL -u '' -p ''
nxc smb DC01.DOMAIN.LOCAL -u 'guest' -p ''
# enum4linux
enum4linux -a -u "" -p "" DC01.DOMAIN.LOCAL
# rpcclient
rpcclient -U "" -N DC01.DOMAIN.LOCAL -c "enumdomusers;enumdomgroups;querydominfo"
# NetExec — enumerate users via RID brute force
nxc smb DC01.DOMAIN.LOCAL -u 'guest' -p '' --rid-brute 10000
# Impacket
lookupsid.py -no-pass '[email protected]' 20000
# Extract just usernames
nxc smb DC01.DOMAIN.LOCAL -u '' -p '' --rid-brute \
| awk -F'\\\\| ' '/SidTypeUser/ {print $3}' > users.txt
# kerbrute — validates usernames via Kerberos pre-auth responses
# Generates Event 4771, NOT 4625 (often less monitored)
kerbrute userenum -d DOMAIN.LOCAL --dc DC01.DOMAIN.LOCAL usernames.txt
Use output as username list for password spraying and AS-REP roasting checks.
# Enumerate Certificate Authorities via RPC — no credentials needed
nxc smb DC01.DOMAIN.LOCAL -M enum_ca
If CAs found, note for authenticated ADCS enumeration in Step 3 (certipy).
If network position allows, note LLMNR/NBT-NS traffic for Responder-based hash capture. → STOP. Return to orchestrator with: DC IP, domain name, network position, LLMNR/NBT-NS traffic details. Do not execute poisoning or relay commands inline.
The single highest-value enumeration step. Requires valid domain credentials.
# bloodhound-python — LDAP-based, remote
bloodhound-python -d DOMAIN.LOCAL -u 'user' -p 'Password123' \
-gc DC01.DOMAIN.LOCAL -c all -ns DC_IP
# rusthound-ce — faster, includes ADCS data
rusthound-ce -d DOMAIN.LOCAL -u '[email protected]' -p 'Password123' \
-o /tmp/bloodhound -z --adcs
# With Kerberos auth
export KRB5CCNAME=user.ccache
bloodhound-python -d DOMAIN.LOCAL -u 'user' -k -no-pass \
-gc DC01.DOMAIN.LOCAL -c all
# SharpHound — full collection
.\SharpHound.exe -c all -d DOMAIN.LOCAL --searchforest
# Stealthier — DC-only mode (no host enumeration)
.\SharpHound.exe --CollectionMethod DCOnly
# OPSEC — throttle and randomize
.\SharpHound.exe -c all,GPOLocalGroup --throttle 10000 --jitter 23
# SOAPHound — uses ADWS instead of LDAP (avoids LDAP monitoring)
SOAPHound.exe --buildcache -c c:\temp\cache.txt
SOAPHound.exe -c c:\temp\cache.txt --bhdump -o c:\temp\bh-output
SOAPHound.exe -c c:\temp\cache.txt --certdump -o c:\temp\bh-output
# Certipy — full certificate template enumeration
certipy find 'DOMAIN/user:[email protected]' \
-output engagement/evidence/certipy-full-DOMAIN
# Find vulnerable templates only
certipy find 'DOMAIN/user:[email protected]' -vulnerable -hide-admins \
-output engagement/evidence/certipy-vulnerable-DOMAIN
# With Kerberos
certipy find 'DOMAIN/[email protected]' -k \
-output engagement/evidence/certipy-full-DOMAIN
State writes: Vulnerable ADCS templates found →
add_vuln(title="ADCS <ESC_type> on <template>", host="<CA_host>", vuln_type="adcs", severity="high").
Certipy output: Always use -output engagement/evidence/certipy-<label>
to write results to the evidence directory. Without -output, certipy writes
{timestamp}_Certipy.{json,txt} to CWD, polluting the working directory.
Certipy version notes:
-bloodhound flag. Run certipy find without it
— v5 outputs JSON by default. Import the JSON into BloodHound CE manually.-vulnerable returns 0 results, re-run without it to get the full
template list. Some ESC variants (ESC9-15) require manual analysis of the
full output rather than certipy's built-in vulnerability checks.After importing data, run these queries first:
Deeper enumeration beyond BloodHound. Run these based on what BloodHound reveals.
# NetExec
nxc smb DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' --pass-pol
# enum4linux
enum4linux -u 'user' -p 'Password123' -P DC01.DOMAIN.LOCAL
# PowerView
(Get-DomainPolicy)."SystemAccess"
Record lockout threshold, observation window, complexity requirements. Report for password spraying decisions.
# Fine-grained password policies — different groups may have different lockout rules
nxc ldap DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M pso
PSOs override default domain policy for specific groups. A service account group may have no lockout while user accounts lock at 5 attempts. Report any PSOs found — they affect password spraying decisions.
High-value quick win. Pre-created computer accounts (created via "Pre-Windows
2000 Compatible" checkbox in ADUC or New-ADComputer) often have their password
set to the sAMAccountName in lowercase, minus the trailing $. For example,
MS01$ has password ms01. This is a common administrative oversight that
yields machine account credentials with zero noise.
# Identify pre-created computer accounts and test default passwords
# The module checks for accounts and attempts TGT with default password
nxc ldap DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M pre2k
What pre2k checks:
userAccountControl flags indicating pre-creationsAMAccountName.rstrip('$').lower() as the passwordWhy this matters:
If pre2k finds valid machine credentials → write immediately:
add_credential(username="MACHINE$", secret="machine", source="pre2k module on <DC>").
Also record in your return summary: account name, confirmed password, and any
group memberships or privileges visible from LDAP.
Run these modules as a batch during authenticated enumeration. They are all non-destructive and low-privilege — any domain user can run them.
# User descriptions — frequently contain cleartext passwords
nxc ldap DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M get-desc-users
# GPP cpassword in SYSVOL (MS14-025)
nxc smb DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M gpp_password
# Autologon credentials from registry.xml in SYSVOL
nxc smb DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M gpp_autologin
User descriptions are a common source of cleartext passwords — admins often
document initial passwords or service account passwords in the AD description
field. Any result containing password-like strings is a credential finding.
Write immediately: add_credential(username=..., secret=..., source="AD description field").
# WebClient service (WebDAV) — enables HTTP-based NTLM coercion
# Critical: if WebDAV is running, coercion can use HTTP instead of SMB
# which bypasses SMB signing and enables relay to LDAP/ADCS
nxc smb DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M webdav
# Print Spooler — enables PrinterBug/SpoolSample coercion
nxc smb DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M spooler
# Broad coercion vulnerability check (check-only mode — no LISTENER set)
# Tests for PetitPotam, PrinterBug, DFSCoerce, ShadowCoerce, MSEven, etc.
nxc smb DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M coerce_plus
Note all coercion-eligible hosts. WebDAV is especially valuable — it enables HTTP-based coercion that works even when SMB signing is enforced.
State writes: Coercion-eligible hosts →
add_vuln(title="Coercion: <type> on <host>", host="<host>", vuln_type="coercion", severity="medium").
# AV/EDR detection — identifies endpoint security products
nxc smb DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M enum_av
# MachineAccountQuota — can current user add computer accounts?
# MAQ > 0 enables resource-based constrained delegation (RBCD) attacks
nxc ldap DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M maq
# Obsolete/EOL operating systems — unpatched, vulnerable to known CVEs
nxc ldap DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M obsolete
# BadSuccessor (dMSA) — CVE-2025-21293, privilege escalation via
# delegated Managed Service Accounts
nxc ldap DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M badsuccessor
# DNS records from AD — all A/AAAA/CNAME/SRV records in the domain
nxc ldap DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M get-network
# AD Sites and Subnets — reveals internal network segmentation
nxc ldap DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M subnets
# Additional network interfaces on hosts (multi-homed pivot targets)
nxc smb DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M ioxidresolver
# SCCM discovery via LDAP
nxc ldap DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M sccm
# Entra ID (Azure AD Connect) sync server
nxc ldap DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M entra-id
# DNS zones allowing nonsecure dynamic updates (ADIDNS poisoning)
nxc ldap DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M dns-nonsecure
If SCCM found → note for SCCM exploitation. If Entra ID Connect server found → high-value target (stores cleartext AD sync credentials). If nonsecure DNS updates allowed → note for ADIDNS poisoning (enables MITM/coercion without LLMNR).
Standard in most AD environments. Authenticated Users have CreateChild on AD-Integrated DNS zones by default, allowing any domain user to create new A records. This enables ADIDNS poisoning: redirect hostnames that don't have DNS records (or that you can overwrite) to the attackbox for credential capture.
# Check zone ACL — look for Authenticated Users with CreateChild
# Use dacledit to read the ACL on the DNS zone object
dacledit.py -action read -target-dn \
'DC=DOMAIN.LOCAL,CN=MicrosoftDNS,DC=DomainDnsZones,DC=DOMAIN,DC=LOCAL' \
'DOMAIN/user:[email protected]'
# List existing DNS records (look for gaps — hostnames referenced but missing)
# dnstool.py is from krbrelayx toolkit
python3 /opt/krbrelayx/dnstool.py -u 'DOMAIN\user' -p 'Password123' \
-r '*' --action query DC01.DOMAIN.LOCAL
Cross-reference with engagement state: Check for unreachable hostnames in the pivot map or blocked items — linked server data sources, SPN hostnames, or service references that resolved to nothing. If an expected hostname has no DNS A record and Authenticated Users can create records, this is a high-value