Prancer Blog / GOAD Challenge: Swarmhack AD pentesting
Anonymous Bind to Domain Admin — One Command Against GOAD
One swarmhack spawn against GOAD — anonymous LDAP bind, kerberoasting, ADCS ESC1 certificate enrollment, Domain Admin in 9–10 minutes. No credentials supplied.
SwarmHack Team · 2026-05-12 · 12 min
TL;DR
- One
swarmhack spawn --target 192.168.56.10runs the entire AD kill chain — no operator credentials supplied, no--agentsfilter - 22 native AD agents orchestrated by a GOAP planner detect AD services automatically and route to LDAP / Kerberos / SMB / ADCS
- Validated numbers across four GOAD variants — GOAD-Mini 15/11 in 10m 10s, GOAD-Light 15/11 in 9m 9s, including autonomous ESC1 certificate enrollment as
[email protected]
This is Part 2 of 3. In Part 1 we built a real Active Directory lab using GOAD. Now we attack it.
Series:
- Part 1 — Build the GOAD lab
- Part 2 — Run the engagement (you are here)
- Part 3 — Under the hood
1. The command
swarmhack spawn \
--target 192.168.56.10 \
--token $PRANCER_TOKEN \
--customer $PRANCER_CUSTOMER \
--exec-mode kill-chain
That's it. Note what's not in that command:
- No
--agentsfilter. The orchestrator detects LDAP (389), Kerberos (88), SMB (445), Global Catalog (3268), and ADCS Web Enrollment (80/443) on first port sweep and routes to the AD agent suite automatically. - No
--username/--password. The engagement starts with zero credentials.vagrant:vagrantgets discovered by the SMB enumerator and bridged into the AD context by the cross-agent intelligence layer. - No
--allow-write-operations. Default is read-only. Destructive operations (DACL grants, owner changes, forced password resets, Shadow Credentials, RBCD) require explicit opt-in and back up the original DACL before writing.
2. The validated numbers
Real GOAD labs, 2026-05-09 hands-on re-validation, single command per row:
| Lab | Domain | Findings | Crown Jewels | Time | Validator |
| ----- | -------- | ---------- | -------------- | ------ | ----------- |
| Synthetic LDAP fixture | corp.local | 36 | 30 | 5m 48s | — |
| GOAD-Mini | sevenkingdoms.local | 15 | 11 | 10m 10s | PASS — variant=goad-mini matched=2/2 |
| MINILAB | mini.lab | 14 | 10 | — | PASS — variant=minilab matched=2/2 |
| GOAD-Light | sevenkingdoms.local + essos.local | 15 | 11 | 9m 9s | PASS — variant=goad-light matched=3/3 |
| DRACARYS | dracarys.lab | 8 | 3 | 7m 44s | PASS — variant=dracarys matched=2/2 |
Every lab captured vagrant:vagrant over SMB. Every lab surfaced anonymous LDAP bind. Every lab walked the seven Domain-Admins / Enterprise-Admins WriteDACL paths to the Domain Controllers container. Mini and Light autonomously enrolled an ESC1 certificate for [email protected] against SEVENKINGDOMS-CA and saved it as administrator.pfx.
3. The kill chain — phase by phase
The GOAP planner sequences agents by world-state preconditions. Here's what fires when:
<diagram title="AD kill chain — output of phase N becomes input of phase N+1">
PHASE 1 (T+0..T+45s) PHASE 2 (T+45..T+120s) PHASE 3 (T+120..T+180s)
Discovery LDAP enumeration SMB sweep
───────── ────────────── ─────────
nmap top-1k + AD ports Anonymous bind → rootDSE smb_enum default-cred
LDAP/Kerberos/SMB/ADCS 149 user accounts vagrant:vagrant ✓
detected, GOAP routes Kerberoastable SPNs 6 shares enumerated
to AD agent suite AS-REP-roastable users → bridge to AD ctx
adminCount=1 principals
nTSecurityDescriptor blobs
│ │ │
▼ ▼ ▼
PHASE 4 (T+180..T+240s) PHASE 5 (T+240..T+300s) PHASE 6 (T+300..T+340s)
Kerberos roasting ACL graph walk ADCS ESC1
───────────────── ────────────── ─────────
TGS hashes (-m 13100) Native nTSecurityDescriptor adcs_exploit detects
AS-REP hashes (-m 18200) parser walks 7 ACE edges EnrolleeSuppliesSubject
queued to hash_crack finance → erin (DA) + low enrollment ACL
shadow_creds primed for → autonomous enrollment
DA-tier compromise → administrator.pfx ✓
│
▼
PHASE 7 (T+340..T+360s)
LAPS / gMSA / delegation
─────────────────────────
ms-Mcs-AdmPwd cleartext
msDS-ManagedPassword blob
TRUSTED_FOR_DELEGATION UAC
</diagram>
Each phase produces intelligence the next phase consumes — automatically, through a shared-memory bus, with no human gluing things together.
4. Phase 1 — Service discovery (T+0 → T+45s)
A focused port sweep against the target. AD has a tell-tale fingerprint: 389/636 (LDAP), 88 (Kerberos), 445 (SMB), 3268 (GC), 80/443 (ADCS Web Enrollment). When that pattern matches, the GOAP planner routes to the 22-agent AD suite instead of (or alongside) the web suite.
No human says "this is an AD target." The agents agree among themselves based on what they see.
5. Phase 2 — LDAP enumeration via anonymous bind
ldap_enum opens a TCP socket to 389, sends an LDAP bindRequest with empty DN and empty password, and gets a bindResponse: success. That alone is a medium-severity finding in any modern AD audit.
Then it pivots into discovery, using extensible-match LDAP filters that hard-code the canonical Microsoft attribute OIDs:
| Query | Filter | Returns |
| ------- | -------- | --------- |
| Kerberoastable SPNs | (servicePrincipalName=*) (excluding krbtgt) | svc_sql, svc_web, svc_iis, svc_backup, … |
| AS-REP-roastable users | (userAccountControl:1.2.840.113556.1.4.803:=4194304) | every principal with DONT_REQ_PREAUTH |
| DA-tier principals | (adminCount=1) | erin, administrator, … |
| Computers | (objectClass=computer) | WS01$, WS02$, FILE01$, DC01$ |
| Trusts | (objectClass=trustedDomain) | parent/child / cross-forest relationships |
| LAPS-enabled hosts | (ms-Mcs-AdmPwdExpirationTime=*) | every host with LAPS deployed |
| Description-field secrets | (description=*) | the classic password-in-description leak |
Critically, every principal's nTSecurityDescriptor binary blob is pulled and parked in shared memory. That's the raw material the ACL graph walker consumes in Phase 5.
6. Phase 3 — SMB credential sweep
smb_enum runs a default-credential sweep using data/wordlists/smb.yaml. Every GOAD VM ships with the Vagrant baseline vagrant:vagrant account. The sweep lands on the first try.
That match is enriched, not just logged:
- The credential gets cross-published into the AD namespace (
creds_ad_*) so cred-consuming agents (DCSync, WinRM, MSSQL, ADCS RPC) can use it - All accessible SMB shares are enumerated (typically
ADMIN$,C$,IPC$,NETLOGON,SYSVOL, plus any GOAD-specific shares) - SYSVOL gets a sweep for GPP
cpasswordXML files — the classic legacy AD vulnerability that still shows up in real audits
For DRACARYS (the post-install vagrant-disabled variant), the --no-vagrant-default flag suppresses the baseline credential from the sweep so the run reflects a hardened baseline.
7. Phase 4 — Kerberoast + AS-REP-roast
kerberos_attack consumes the SPN list from Phase 2 and the AD credential from Phase 3.
Kerberoast issues a TGS-REQ for each kerberoastable SPN, captures the encrypted portion of the response (encrypted under the service-account's long-term key), and formats it as hashcat mode -m 13100:
$krb5tgs$23$*alice$CORP.LOCAL$HTTP/dc01.corp.local*lt;checksum_hex>lt;encrypted_hex>
AS-REP roast issues an AS-REQ *without* PA-ENC-TIMESTAMP padata for every DONT_REQ_PREAUTH principal. The KDC's response exposes the encrypted timestamp section under the user's long-term key — formatted as hashcat mode -m 18200:
[email protected]:<checksum_hex>lt;encrypted_hex>
Both hash sets are queued straight into the hash_crack agent's shared-memory queue. No copy-paste, no manual --format=krb5tgs flag. Cracked plaintexts come back into the AD credential namespace and can rotate into the next agent's authentication.
There's a deeper primitive here too: targeted Kerberoast. When the chain has LDAP write rights against a principal, the agent writes a temporary SPN onto that principal, issues a TGS-REQ against the temp SPN, captures the hash, removes the SPN, and feeds the cracked plaintext back into the credential pool. It's the native equivalent of targetedKerberoast.py.
8. Phase 5 — The ACL graph walk
This is the part wrapper-based stacks usually outsource to BloodHound + a human staring at a graph. SwarmHack does it natively.
The ACL agent walks every nTSecurityDescriptor blob captured in Phase 2, parsing it per MS-DTYP §2.4.6. Every ACE that grants a non-trivial access mask becomes an edge in an in-memory directed graph of principals.
A shortest-path search with weighted edges (ForceChangePassword and GenericAll cheapest, WriteOwner most expensive) finds the most efficient route to a DA-tier principal.
The synthetic fixture's seven-step chain looks like this:
alice ─[ForceChangePassword]→ bob
bob ─[GenericWrite]→ carol
carol ─[WriteDACL]→ dave
dave ─[Self/WriteSelf]→ engineering OU
engineering ─[WriteSelf]→ operations OU
operations ─[WriteOwner]→ finance OU
finance ─[GenericAll]→ erin (adminCount=1, DA-tier) ✓
On the real GOAD variants, the corresponding chains terminate at the Domain Controllers container via Domain Admins / Enterprise Admins WriteDACL ACEs — the canonical seven paths every red-team report ends with.
Every edge in the chain becomes a structured crown jewel with source SID, target DN, access mask bitmap, and exploitation downstream pointer. Exactly what a SOC ticket needs.
9. Phase 6 — ADCS ESC1: autonomous certificate enrollment
adcs_exploit walks the certificate templates published in CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=… and inspects each for the ESC1 fingerprint:
pKIExtendedKeyUsageincludes Client AuthenticationmsPKI-Certificate-Name-Flaghas theENROLLEE_SUPPLIES_SUBJECTbit- The discretionary ACL grants enrollment rights to a low-privilege principal
When all three line up, the agent calls the ADCS web-enrollment endpoint (/certsrv/certfnsh.asp) — or, on hardened deployments, the MS-WCCE RPC opnum 0 via the native DCERPC stack — with a CSR whose subject alternative name says [email protected].
The CA happily issues the certificate. SwarmHack saves it as administrator.pfx and emits a critical finding with the certificate fingerprint as the crown jewel.
That's a paper-clean DA-tier impersonation primitive, autonomously, without an operator at the keyboard.
10. Phase 7 — LAPS, gMSA, delegation, coercion
These run in parallel because they don't depend on each other:
laps_readerqueries each computer object forms-Mcs-AdmPwdand emits cleartext local admin passwords — three of them on the synthetic fixture (LAPS!Workstation01@2026,LAPS!Workstation02#2026,LAPS!FileServer01$2026).gmsa_readerreadsmsDS-ManagedPasswordfrom gMSA accounts and decodes theMSDS-MANAGEDPASSWORD_BLOBstructure (Version, Reserved, Length, CurrentPassword, PreviousPassword, intervals).delegation_exploitscansuserAccountControlfor theTRUSTED_FOR_DELEGATIONbit (0x80000) —DC01$lights up.auth_coercionrunsMS-RPRN PrinterBug(opnum 65),MS-EFSR PetitPotam(opnum 0),MS-DFSNM DFSCoerce(opnum 13) when DCERPC is reachable.gpo_abuseenumeratesgroupPolicyContainerobjects.trust_exploitreadstrustedDomainrecords and emits trust-relationship findings (parent/child for Light, partner forest for the synthetic fixture).
11. The OCSF report
When the orchestrator finishes, you get a single OCSF 1.1.0 JSON report at reports/mission-<timestamp>-<uuid>.json. Each finding includes:
cwe.uid— e.g.CWE-284for the ACL findings,CWE-522for cleartext LAPSattacks— MITRE ATT&CK technique IDs (T1098,T1208,T1558.003,T1003.006, …)severity_id,risk_score,confidencecrown_jewels[]— actual extracted impact, with structuredcontextblobs
Sample (the capstone ACL finding):
{
"title": "AD ACL abuse: finance → erin via GenericAll",
"severity_id": 6,
"cwe": { "uid": "CWE-284" },
"attacks": ["T1098"],
"evidence": "Native nTSecurityDescriptor walk: finance --[GenericAll]--> erin (DA-tier; adminCount=1).",
"crown_jewels": [{
"category": "acl_attack_path",
"value": "finance --[GenericAll]--> erin",
"context": {
"agent": "acl_abuse",
"ace_kind": "ACCESS_ALLOWED_ACE",
"access_mask": "0x10000000",
"trustee_sid": "S-1-5-21-1234567890-2345678901-3456789012-1107",
"target_dn": "cn=erin,dc=corp,dc=local",
"da_tier": true,
"exploitation": "Path identified; downstream shadow_creds primitive applied"
}
}]
}
That's a SOC-ready crown jewel: source SID, target DN, access-mask bitmap, downstream chain pointer. No human transcription. No CSV wrangling.
12. Run it yourself
If you finished Part 1, you're one command away:
# Make sure GOAD is up
cd ~/GOAD && ./goad.sh
goad> set_lab GOAD-Mini && set_provider virtualbox && status
# Fire SwarmHack — read-only by default
swarmhack spawn \
--target 192.168.56.10 \
--token $PRANCER_TOKEN \
--customer $PRANCER_CUSTOMER \
--exec-mode kill-chain
# Output
ls -la reports/mission-*.json
Expected on GOAD-Mini:
- ~10 minutes wall-clock
- 15 findings, 11 crown jewels
vagrant:vagrantcaptured over SMB- Anonymous LDAP bind flagged
- Kerberoastable + AS-REP-roastable hashes queued
- The seven Domain-Admins / Enterprise-Admins
WriteDACLpaths walked administrator.pfxsitting in your output directory, ready for a PKINIT TGT
If you turn on --allow-write-operations, the destructive primitives become available — with automatic backup of every original nTSecurityDescriptor blob before modification, so post-engagement cleanup can restore the original DACL exactly.
What's next
In Part 3 — Under the Hood we'll open up the engine: the 22 native AD agents, the GOAP planner that sequences them, and the native Rust protocol stack — LDAP, Kerberos, DCERPC (PrinterBug, PetitPotam, DFSCoerce, DCSync, ADCS-RPC, LSARPC), NTLMv2, and BloodHound — that replaces impacket, certipy, BloodHound.py, pywhisker, and Responder with one compact binary and a sub-second cold start.
See you in Part 3.