One permission. Buried on one computer object in Active Directory.
That was the door.
A discretionary access control list (DACL) is attached to nearly every
object in Active Directory. It decides who can reset a password, modify a group,
edit a Group Policy Object (GPO), or take control of a computer object. Each
individual rule is an access control entry. In BloodHound, those rules become
relationships such as GenericAll, GenericWrite, WriteDacl, and
WriteOwner.
Because DACLs are everywhere, they become background noise. A delegated permission survives for years because nothing looks obviously broken and nobody wants to touch it. But are those old permissions really harmless?
During an authorised Active Directory assessment, I found a single DACL that opened the door from one member server to full domain compromise. The DACL was the key finding, but it did not work alone. Reaching it required relaying New Technology LAN Manager (NTLM) authentication, creating an attacker-controlled computer account, cracking a service credential, and carefully analysing the directory graph. After the server was compromised, a privileged session finished the job.
I rebuilt that attack path in GOAD-Light so I could explain it without using a single client hostname, credential, screenshot, or network detail.
By the end of this post, you will see how to:
- test the usual unauthenticated Active Directory entry points;
- poison Link-Local Multicast Name Resolution (LLMNR) and relay NTLM authentication to the Lightweight Directory Access Protocol (LDAP);
- turn the default MachineAccountQuota into an owned computer identity;
- request and crack an Advanced Encryption Standard (AES) encrypted service ticket for a service account with a predictable password;
- use BloodHound to find a dangerous
GenericAllDACL; - abuse
msDS-KeyCredentialLinkwith Shadow Credentials; - use Service for User to Self (S4U2Self) to impersonate a locally privileged user to the target host;
- inspect and extract Kerberos tickets with Rubeus; and
- convert a stolen Domain Admin Ticket Granting Ticket (TGT) into a directory replication attack commonly called DCSync.
Here is the full path:
NO CREDENTIALS
└─ NTLM relay → BlogPC$
└─ Kerberoast → svc_legacyapp
└─ GenericAll → control of CASTELBLACK$
└─ Shadow Credentials → CASTELBLACK$ identity
└─ S4U2Self → host administrator
└─ privileged session → Domain Admin TGT
└─ DCSync → full domain compromiseBefore the foothold: nothing #
I started with the same checks I use on most internal Active Directory assessments:
- null authentication against Server Message Block (SMB);
- readable SMB shares;
- username discovery with Kerbrute;
- Authentication Service Reply (AS-REP) roasting; and
- a careful password spray against the confirmed users.
Nothing worked. There were no useful anonymous shares, no AS-REP roastable users, and no password hit.
Responder changed the picture. LLMNR poisoning was possible, and systems were willing to send NTLM authentication to my host. Cracking the captured NetNTLMv2 challenge-response hashes went nowhere, but cracking was not the only option. If the authentication could not be recovered, perhaps it could be relayed.
The lab #
The environment is the Game of Active Directory GOAD-Light lab with a few synthetic objects added for this path.
| Object | Purpose |
|---|---|
WINTERFELL / 192.168.56.11 |
Domain controller and LDAP/Kerberos target |
CASTELBLACK |
Target member server |
BLOG.EMPLOYEE |
Low-privileged user whose NTLM authentication is relayed |
BlogPC$ |
Computer account created through the relay |
svc_legacyapp |
Kerberoastable service identity |
blog.targetadmin |
User with administrative access to CASTELBLACK |
blog.da |
Fictional Domain Admin with a disconnected session |
The exact values are disposable. The important part is how each identity leads to the next one.
1. Turning relayed authentication into a foothold #
I was still on the internal network with no domain credentials.
I started Responder on the attacker-controlled interface to listen for legacy name-resolution traffic:
sudo responder -I eth1
Responder’s SMB and HTTP servers are disabled here. Responder handles the
poisoning, while ntlmrelayx owns the relevant listener ports and forwards the
authentication instead of terminating it locally.
In this lab, a deterministic trigger makes BLOG.EMPLOYEE request
http://blog-trigger/, where blog-trigger is an unresolvable hostname. That
synthetic activity makes the test repeatable; it is not an attempt to pretend a
user happened to mistype a hostname at the perfect moment.
I launched ntlmrelayx against LDAP over Transport Layer Security, commonly
called LDAPS, on the domain controller and told it to create BlogPC$ when a
suitable authentication arrived:
impacket-ntlmrelayx -t ldaps://192.168.56.11 --add-computer 'BlogPC$'The relay succeeded, and Impacket added the new computer account:

This distinction matters: I did not recover BLOG.EMPLOYEE’s password. I
used the live authentication once and converted it into a new credential that I
controlled.
Two directory conditions made that possible:
- The LDAP path accepted the relayed authentication.
- The relayed user could create a computer object. This is controlled by
ms-DS-MachineAccountQuotaor equivalent delegated rights.
The lab’s MachineAccountQuota, usually shortened to MAQ, was set to 10.
That is the traditional default and allows an ordinary domain user to create up
to ten computer accounts. MAQ is not a privilege escalation on its own. In this
case, however, it gave the relayed authentication somewhere durable to land.
ntlmrelayx generated and printed the password for BlogPC$ during creation.
I used that password to validate the new account immediately:
nxc smb 192.168.56.11 -u 'BlogPC$' -p 'w<1m4FbKg#a0}T5'
The foothold was unusual but perfectly usable. BlogPC$ was not a member of
Domain Users; it was a computer object in Domain Computers. It could not
directly use the Domain Users DACL I would find later. First, I needed to turn
machine access into user access.
2. Kerberoasting from a computer identity #
A computer account is still an authenticated Kerberos principal. It can query LDAP, discover accounts with a service principal name (SPN), and request service tickets.
Using BlogPC$, I enumerated the domain’s SPN-bearing users:
impacket-GetUserSPNs -outputfile kerberoastables.txt -dc-ip 192.168.56.11 'north.sevenkingdoms.local/BlogPC$:w<1m4FbKg#a0}T5'
The synthetic target is svc_legacyapp, registered with:
HTTP/legacyapp.north.sevenkingdoms.localKerberoasting does not attack the service host. It requests a legitimate Ticket-Granting Service (TGS) ticket, usually just called a service ticket. Its encrypted portion is protected with key material derived from the service account’s password. That ticket can be tested offline without generating password failures against the domain.
The output contained more than one encryption type, so Hashcat first identifies the formats:
hashcat --identify kerberoastables.txt
The target ticket used AES etype 18, the stronger key type in the collected
set. That increased the cost of each password guess, but it could not rescue a
predictable password. Hashcat mode 19700 still recovered the deliberately
planted service credential:
hashcat -m 19700 -a 0 -w 3 -O kerberoastables.txt ~/rockyou.txt
The password was fairly long, but it followed a service-name plus season and year pattern. Strong encryption protects the ticket. It cannot make a weak password strong.
I verified the result before trusting it:
nxc smb 192.168.56.11 -u 'svc_legacyapp' -p 'LegacyApp-Autumn-2026!'
That crossed the boundary I needed: from a Domain Computers foothold to a
normal user in Domain Users.
3. BloodHound finds the dangerous DACL #
With authenticated user access, I ran BloodHound collection as
svc_legacyapp:
bloodhound-ce-python -d north.sevenkingdoms.local -ns 192.168.56.11 -u svc_legacyapp -p 'LegacyApp-Autumn-2026!' -c All --zip
BloodHound made the dangerous relationship obvious:

svc_legacyapp inherited GenericAll over CASTELBLACK$ through its ordinary
membership in Domain Users.
GenericAll means full control of the directory object, not automatically
administrative access to the operating system. The distinction is easy to miss.
The computer object lives in Active Directory; the server is the machine using
that identity.
This was the golden door. Control of the object exposed powerful takeover
primitives.
BloodHound documents
both Resource-Based Constrained Delegation and writing
msDS-KeyCredentialLink. I chose the second route: Shadow Credentials.
4. Shadow Credentials: becoming the server #
Key Trust lets an account authenticate with an asymmetric key whose public
material is stored in msDS-KeyCredentialLink. If an attacker can write that
attribute, they can add their own key and ask the Kerberos Key Distribution
Center (KDC) to authenticate them as the target account.
That is the heart of the Shadow Credentials technique. It requires Public Key Cryptography for Initial Authentication in Kerberos (PKINIT) support and a suitable certificate on the domain controller, both intentionally available in this lab.
The easiest way to picture the attack is:
flowchart TD
A["Generate a public and private key pair"]
A --> B["Write the public key to CASTELBLACK$
msDS-KeyCredentialLink"]
A --> C["Keep the private key"]
B --> D["The KDC trusts the added key"]
C --> E["Authenticate through PKINIT"]
D --> E
E --> F["Receive a TGT as CASTELBLACK$"]
No password reset is required. The server continues to work, its existing machine password remains unchanged, and my added key behaves like another valid way to authenticate as the computer.
Certipy automates the full sequence. It adds the temporary KeyCredential, authenticates through PKINIT, obtains a TGT, recovers the computer’s NT hash through the Kerberos credential exchange, and finally restores the original KeyCredential state.
certipy-ad shadow auto -u 'svc_legacyapp@north.sevenkingdoms.local' -p 'LegacyApp-Autumn-2026!' -account 'CASTELBLACK$' -dc-ip 192.168.56.11
I still did not possess the local Administrator password. What I had was the
Kerberos identity backing services registered to CASTELBLACK$.
5. S4U2Self turns the machine identity into host access #
S4U2Self is a Kerberos extension built for services. It allows a service to ask the KDC for a ticket to itself on behalf of a user, even when that user never presented a Kerberos ticket to the service.
That behavior has a legitimate purpose. A web application might authenticate a user through another mechanism but still need a Kerberos representation of that user for local authorization. The service proves its own identity to the KDC, names the user, and receives a service ticket back to itself.
After the Shadow Credentials step, I controlled the identity of
CASTELBLACK$. In Kerberos terms, I could act as the service. I asked for a
ticket to the server on behalf of blog.targetadmin, a user that was locally
privileged on CASTELBLACK, and changed the service class to HTTP for Windows
Remote Management (WinRM):
impacket-getST -self -impersonate blog.targetadmin -altservice HTTP/castelblack.north.sevenkingdoms.local -hashes ":9403a8c25905d2597029067c7b8ebc5a" -dc-ip 192.168.56.11 'north.sevenkingdoms.local/CASTELBLACK$'
This was not a Domain Admin ticket and it was not valid everywhere. It
represented blog.targetadmin only to the selected service on the server whose
identity I controlled. With KRB5CCNAME already pointing to the ccache produced
by getST, that was enough for Kerberos-authenticated WinRM:
evil-winrm -i castelblack.north.sevenkingdoms.local -r NORTH.SEVENKINGDOMS.LOCAL -K $KRB5CCNAME
whoami confirmed the impersonated identity. I then checked the logged-on
users. query user showed blog.da, a Domain Admin, in a disconnected session.
Bingo.
6. The session that changes the blast radius #
A Domain Admin logging on to an ordinary member server can collapse the boundary between host compromise and domain compromise.
A session does not guarantee recoverable credentials. Ticket lifetime, logon
type, Credential Guard, Remote Credential Guard, protection settings, and user
activity all matter. BloodHound’s
HasSession documentation
correctly describes it as an opportunity rather than proof.
In this lab, the disconnected interactive session still had a valid TGT. From the elevated WinRM context, I used Rubeus to inspect ticket caches belonging to all logon sessions:
./Rubeus.exe triage /nowrap
The blog.da locally unique identifier (LUID) 0xd0acbf contained a
krbtgt/NORTH.SEVENKINGDOMS.LOCAL ticket. A LUID distinguishes one Windows
logon session from another, which let me select the right user’s ticket cache.
I exported that ticket with:
./Rubeus.exe dump /luid:0xd0acbf /service:krbtgt /nowrapThe server compromise had now yielded a reusable Domain Admin TGT.
7. From a .kirbi ticket to DCSync
#
Rubeus prints the ticket as Base64. Back on Kali, I removed the line endings,
decoded it, and converted the Windows .kirbi ticket into an MIT Kerberos
credential cache:
tr -d '\r\n ' < ticket.b64 | base64 -d > blog.da.kirbi
impacket-ticketConverter blog.da.kirbi blog.da.ccache
export KRB5CCNAME=/home/viktor/goad/blog.da.ccache
For the final step, I used the Domain Admin ticket to perform DCSync. This
technique makes a system ask Active Directory for credential data as if it were
another domain controller performing normal replication. Impacket’s
secretsdump sends that request through the Directory Replication Service (DRS)
Remote Protocol:
impacket-secretsdump -k -no-pass -dc-ip 192.168.56.11 -just-dc 'north.sevenkingdoms.local/blog.da@winterfell.north.sevenkingdoms.local' -ldapfilter '(objectClass=user)'
Despite the usual shorthand of “dumping NTDS.dit”, the Active Directory
database, this execution does not copy that file from disk. secretsdump uses
replication operations to retrieve the directory secrets remotely.
That completed the path from no credentials to replication-capable domain access.
What actually failed? #
The final dump is dramatic, but it is not the useful finding. The useful part is understanding how many controls had to fail before it became possible.
| Link | Weakness | Why it mattered |
|---|---|---|
| No credentials → relay | Legacy name resolution plus relayable NTLM | Authentication could be redirected and reused |
Relay → BlogPC$ |
LDAP relay exposure and machine-account creation rights | A one-time authentication became a persistent credential |
BlogPC$ → service user |
Predictable service-account password | A legitimate TGS could be cracked offline |
| Service user → computer control | Domain Users had GenericAll over a computer |
Any ordinary user could modify a sensitive machine identity |
| Computer control → machine identity | Writable msDS-KeyCredentialLink plus PKINIT |
Shadow Credentials enabled certificate-based takeover |
| Machine identity → host admin | S4U2Self and a locally privileged target identity | Control of the object became control of the host |
| Host admin → Domain Admin | A Domain Admin logged on to a lower-tier server | The server’s ticket cache contained domain-wide privilege |
| Domain Admin ticket → directory secrets | Replication rights inherent to the privileged identity | The stolen TGT authorized DCSync |
Removing any major link would have broken this exact path.
So how do we break the chain? #
The remediation list is long, but it should not become one giant project that never starts. I would work through it in this order:
| Rank | Time horizon | Highest-value actions |
|---|---|---|
| 1 | Quickest wins | Set MAQ to 0, disable legacy name resolution, and remove the exposed GenericAll permission |
| 2 | Near-term hardening | Enforce LDAP protections, reduce NTLM, and replace predictable service credentials |
| 3 | Continuous control | Review DACLs, SPNs, Key Trust changes, and the events that expose abuse |
| 4 | Long-term project | Separate privileged administration from ordinary member servers |
1. Quickest wins #
- Set
ms-DS-MachineAccountQuotato0after confirming that no legitimate provisioning workflow depends on it. Delegate domain joins to a narrow identity and a controlled organizational unit instead. - Disable LLMNR and NetBIOS Name Service (NBT-NS) where they are not required, and remove legacy Web Proxy Auto-Discovery (WPAD) behavior.
- Remove broad principals such as
Domain Usersfrom computer-object write permissions. In this path, removing that oneGenericAllentry would have stopped the takeover at its most important junction. - Rotate the exposed service credential immediately. Do not wait for a wider identity project before closing a known path.
2. Near-term protocol and credential hardening #
- Require LDAP signing and enforce appropriate LDAP channel binding. Microsoft’s LDAP signing guidance explains how these controls reject unsigned or improperly bound sessions.
- Reduce NTLM usage and identify applications that still depend on it before moving them to Kerberos.
- Use group Managed Service Accounts (gMSAs) for supported services. Where a conventional service account is unavoidable, give it a long, random password that is not based on the service, company, season, or year.
- Remove stale SPNs and unused service accounts. Every forgotten identity is one more offline password target.
3. Continuous identity review and detection #
- Collect BloodHound data defensively. Review
GenericAll,GenericWrite,WriteDacl,WriteOwner, and explicit writes tomsDS-KeyCredentialLinkafter identity, delegation, or organizational unit changes. - Investigate unexpected computer-account creation through Security event
4741, including who created the object and where it was placed. - Monitor unusual volumes of event
4769, especially service-ticket requests for accounts that rarely receive them from workstations or computer accounts. - Enable Directory Service Changes auditing and alert on event
5136whenmsDS-KeyCredentialLinkchanges unexpectedly. - Baseline principals that legitimately use Windows Hello for Business or Key
Trust. Investigate certificate-based event
4768activity for accounts that do not normally use PKINIT.
4. Long-term privilege isolation #
- Apply an administrative tiering model and use dedicated privileged access workstations.
- Deny Domain Admin logon to member servers through policy.
- Use Credential Guard, Remote Credential Guard, Protected Users, and authentication policies where compatible.
- Treat a privileged session on a compromised host as a credential-compromise incident, even when no plaintext password was recovered.
Wrapping up #
There was no zero-day in this chain.
Responder did not produce Domain Admin. Kerberoasting did not produce Domain
Admin. GenericAll, Shadow Credentials, and S4U2Self did not independently
produce Domain Admin either.
Each technique changed the attacker’s identity or position just enough to reach the next mistake:
anonymous
→ computer
→ service user
→ server identity
→ local administrator
→ Domain Admin
→ directory replicationThe biggest lesson is not the NTDS output. It is how ordinary configuration choices supported one another:
- Review DACLs.
GenericAllover a computer object is not harmless delegation. It can become control of the machine identity and then the host. - Review MAQ. The default value of
10gave the relayed authentication a persistent foothold that I fully controlled. - Review service credentials. AES made each Kerberoast guess more expensive, but a predictable password still cracked.
- Remove LLMNR where possible. If systems willingly send NTLM authentication to an attacker, failure to crack the response does not make it safe. Relay may still be available.
- Keep Domain Admins off member servers. The dangerous DACL opened the host. The privileged session turned that host compromise into domain compromise.
Small permissions and legacy defaults are easy to dismiss during a review. In this case, one DACL was the open door, and the surrounding weaknesses formed the path leading straight through it.