Skip to main content
  1. Write-ups/

Responder, Kerberos, and One Bad DACL: Inside an Active Directory Attack Path

vikious
Author
vikious
I break into things (with permission) and write up how it’s done. Web, network, and physical penetration testing.

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 GenericAll DACL;
  • abuse msDS-KeyCredentialLink with 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 compromise
Scope and authorisation: every identity, credential, host, hash, ticket, and screenshot in this article comes from my isolated GOAD-Light lab. The path was deliberately configured for education and tested only against systems I own. The original assessment was authorised, but no client data or production material appears here. Do not reproduce these actions outside a lab or a system you are explicitly authorised in writing to test.

Before 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 listening with its SMB and HTTP servers disabled
Responder is poisoning name-resolution requests while ntlmrelayx owns the relay listeners

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:

ntlmrelayx successfully relaying BLOG.EMPLOYEE and creating BlogPC
The relayed user creates an attacker-controlled computer account without revealing the user’s password

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:

  1. The LDAP path accepted the relayed authentication.
  2. The relayed user could create a computer object. This is controlled by ms-DS-MachineAccountQuota or 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'
NetExec confirming that the new computer credentials authenticate
BlogPC is now a valid domain security principal controlled by the attacker

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'
GetUserSPNs listing the domain service accounts, including svc_legacyapp
An owned machine account is sufficient to enumerate and request service tickets

The synthetic target is svc_legacyapp, registered with:

HTTP/legacyapp.north.sevenkingdoms.local

Kerberoasting 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
Hashcat identifying AES and RC4 Kerberos service-ticket formats
The collected file contains multiple Kerberos TGS hash modes

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
Hashcat recovering the svc_legacyapp password
The service ticket is cracked offline using a deterministic lab wordlist

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!'
NetExec validating the cracked service-account password
The cracked credential authenticates as a normal Domain Users member

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 CE Python collecting directory and computer data
The service account has enough directory access to map the domain

BloodHound made the dangerous relationship obvious:

BloodHound showing svc_legacyapp in Domain Users and Domain Users with GenericAll over CASTELBLACK
Every Domain Users member has full control over the CASTELBLACK computer object

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
Certipy adding a temporary Shadow Credential and recovering the CASTELBLACK machine hash
GenericAll over the computer object becomes control of the computer identity

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$'
Impacket obtaining an S4U2Self HTTP service ticket for blog.targetadmin
The ticket impersonates a locally privileged user only to the selected service on 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
Evil-WinRM confirming blog.targetadmin access and showing a disconnected blog.da session
Host compromise exposes a disconnected privileged session

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
Rubeus listing Kerberos tickets for all sessions, including blog.da
The Domain Admin logon session retains a krbtgt ticket

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 /nowrap

The 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
Converting the exported Rubeus ticket into a ccache file
The stolen Windows ticket is prepared for use by Impacket

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)'
secretsdump using DRSUAPI to retrieve the synthetic domain credentials
The Domain Admin TGT completes the path with a DCSync of the lab directory

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-MachineAccountQuota to 0 after 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 Users from computer-object write permissions. In this path, removing that one GenericAll entry 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 to msDS-KeyCredentialLink after 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 5136 when msDS-KeyCredentialLink changes unexpectedly.
  • Baseline principals that legitimately use Windows Hello for Business or Key Trust. Investigate certificate-based event 4768 activity 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 replication

The biggest lesson is not the NTDS output. It is how ordinary configuration choices supported one another:

  • Review DACLs. GenericAll over 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 10 gave 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.

Related

Starting This Blog

·251 words·2 mins
An intro to the blog: offensive security write-ups across network and physical penetration testing, and why I’m sharing what I learn.